Merge "Trace coroutine launch "listenForQsExpandedChange"" into main
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c091062..6a96a54 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,7 +60,8 @@
     @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
             BRIGHTNESS_MAX_REASON_NONE,
             BRIGHTNESS_MAX_REASON_THERMAL,
-            BRIGHTNESS_MAX_REASON_POWER_IC
+            BRIGHTNESS_MAX_REASON_POWER_IC,
+            BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BrightnessMaxReason {}
@@ -157,6 +158,8 @@
                 return "thermal";
             case BRIGHTNESS_MAX_REASON_POWER_IC:
                 return "power IC";
+            case BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE:
+                return "wear bedtime";
         }
         return "invalid";
     }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 23e262c..d7952eb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1251,8 +1251,22 @@
      * canceled. Only the pilfering window will continue to receive events for the affected pointers
      * until the pointer is lifted.
      *
-     * This method should be used with caution as unexpected pilfering can break fundamental user
-     * interactions.
+     * Furthermore, if any new pointers go down within the touchable region of the pilfering window
+     * and are part of the same gesture, those new pointers will be pilfered as well, and will not
+     * be sent to any other windows.
+     *
+     * Pilfering is designed to be used only once per gesture. Once the gesture is complete
+     * (i.e. on {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_CANCEL},
+     * or {@link MotionEvent#ACTION_HOVER_EXIT}), the system will resume dispatching pointers
+     * to the appropriately touched windows.
+     *
+     * NOTE: This method should be used with caution as unexpected pilfering can break fundamental
+     * user interactions.
+     *
+     * NOTE: Since this method pilfers pointers based on gesture stream that is
+     * currently active for the window, the behavior will depend on the state of the system, and
+     * is inherently racy. For example, a pilfer request on a quick tap may not be successful if
+     * the tap is already complete by the time the pilfer request is received by the system.
      *
      * @see android.os.InputConfig#SPY
      * @hide
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0714285..d8a88b8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -138,12 +138,6 @@
             "settings_show_stylus_preferences";
 
     /**
-     * Flag to enable/disable biometrics enrollment v2
-     * @hide
-     */
-    public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
-
-    /**
      * Flag to enable/disable FingerprintSettings v2
      * @hide
      */
@@ -223,7 +217,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
-        DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
         DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
         DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
         DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 2302dc7..ea4abc1 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -50,13 +50,12 @@
     private final SurfaceControl mSurface;
 
     /**
-     * Takes all of the current pointer events streams that are currently being sent to this
-     * monitor and generates appropriate cancellations for the windows that would normally get
-     * them.
+     * Pilfer pointers from this input monitor.
      *
-     * This method should be used with caution as unexpected pilfering can break fundamental user
-     * interactions.
+     * @see android.hardware.input.InputManager#pilferPointers(IBinder)
+     * @deprecated
      */
+    @Deprecated
     public void pilferPointers() {
         try {
             mHost.pilferPointers();
@@ -197,10 +196,10 @@
     };
 
     @DataClass.Generated(
-            time = 1679692514588L,
+            time = 1720819824835L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
-            inputSignatures = "private static final  java.lang.String TAG\nprivate static final  boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic  void pilferPointers()\npublic  void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+            inputSignatures = "private static final  java.lang.String TAG\nprivate static final  boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic @java.lang.Deprecated void pilferPointers()\npublic  void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java
index 946bb82..71c500c 100644
--- a/core/java/android/window/ActivityWindowInfo.java
+++ b/core/java/android/window/ActivityWindowInfo.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -144,4 +146,15 @@
                 + ", taskFragmentBounds=" + mTaskFragmentBounds
                 + "}";
     }
+
+    /** Gets the {@link ActivityWindowInfo} of the given activity. */
+    @Nullable
+    public static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+        if (activity.isFinishing()) {
+            return null;
+        }
+        final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
+                .getActivityClient(activity.getActivityToken());
+        return record != null ? record.getActivityWindowInfo() : null;
+    }
 }
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 2c64b8e..ac57c00 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,12 +39,6 @@
     void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
 
     /**
-     * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
-     * only occupies a portion of Task bounds.
-     */
-    boolean isActivityEmbedded(in IBinder activityToken);
-
-    /**
      * Notifies the server that the organizer has finished handling the given transaction. The
      * server should apply the given {@link WindowContainerTransaction} for the necessary changes.
      */
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 15f1258..8e429cb 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
 
 import android.annotation.CallSuper;
 import android.annotation.FlaggedApi;
@@ -29,6 +30,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.app.Activity;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -38,6 +40,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -324,16 +327,15 @@
     }
 
     /**
-     * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+     * Checks if an activity is organized by a {@link android.window.TaskFragmentOrganizer} and
      * only occupies a portion of Task bounds.
+     *
+     * @see ActivityWindowInfo for additional window info.
      * @hide
      */
-    // TODO(b/287582673): cleanup
-    public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
-        try {
-            return getController().isActivityEmbedded(activityToken);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+    public static boolean isActivityEmbedded(@NonNull Activity activity) {
+        Objects.requireNonNull(activity);
+        final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+        return activityWindowInfo != null && activityWindowInfo.isEmbedded();
     }
 }
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 982189e..1a1d83c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1063,8 +1063,8 @@
     }
     env->ReleaseStringUTFChars(file, file8);
 
-    // Most proc files we read are small, so we only go through the
-    // loop once and use the stack buffer.  We allocate a buffer big
+    // Most proc files we read are small, so we go through the loop
+    // with the stack buffer firstly. We allocate a buffer big
     // enough for the whole file.
 
     char readBufferStack[kProcReadStackBufferSize];
@@ -1072,37 +1072,47 @@
     char* readBuffer = &readBufferStack[0];
     ssize_t readBufferSize = kProcReadStackBufferSize;
     ssize_t numberBytesRead;
+    off_t offset = 0;
     for (;;) {
+        ssize_t requestedBufferSize = readBufferSize - offset;
         // By using pread, we can avoid an lseek to rewind the FD
         // before retry, saving a system call.
-        numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
-        if (numberBytesRead < 0 && errno == EINTR) {
-            continue;
-        }
+        numberBytesRead =
+                TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
         if (numberBytesRead < 0) {
             if (kDebugProc) {
-                ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
+                ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
+                      strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
+                      fd.get());
             }
             return JNI_FALSE;
         }
-        if (numberBytesRead < readBufferSize) {
+        if (numberBytesRead == 0) {
+            // End of file.
+            numberBytesRead = offset;
             break;
         }
-        if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
-            if (kDebugProc) {
-                ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+        if (numberBytesRead < requestedBufferSize) {
+            // Read less bytes than requested, it's not an error per pread(2).
+            offset += numberBytesRead;
+        } else {
+            // Buffer is fully used, try to grow it.
+            if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+                if (kDebugProc) {
+                    ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+                }
+                return JNI_FALSE;
             }
-            return JNI_FALSE;
+            readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
+            readBufferHeap.reset(); // Free address space before getting more.
+            readBufferHeap = std::make_unique<char[]>(readBufferSize);
+            if (!readBufferHeap) {
+                jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+                return JNI_FALSE;
+            }
+            readBuffer = readBufferHeap.get();
+            offset = 0;
         }
-        readBufferSize = std::max(readBufferSize * 2,
-                                  kProcReadMinHeapBufferSize);
-        readBufferHeap.reset();  // Free address space before getting more.
-        readBufferHeap = std::make_unique<char[]>(readBufferSize);
-        if (!readBufferHeap) {
-            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
-            return JNI_FALSE;
-        }
-        readBuffer = readBufferHeap.get();
     }
 
     // parseProcLineArray below modifies the buffer while parsing!
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index cdd8557..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -393,12 +393,6 @@
     <bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
     <java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
 
-    <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if
-     existing cellular networks already supports IWLAN.
-     -->
-    <bool name="force_iwlan_mms_feature_enabled">false</bool>
-    <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" />
-
     <!-- The time duration in millis after which Telephony will abort the last message datagram
      sending requests. Telephony starts a timer when receiving a last message datagram sending
      request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b120723..8e1fde0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -80,6 +81,7 @@
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizer;
 import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
@@ -2553,9 +2555,9 @@
         return ActivityThread.currentActivityThread().getActivity(activityToken);
     }
 
-    @VisibleForTesting
     @Nullable
-    ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+    private ActivityThread.ActivityClientRecord getActivityClientRecord(
+            @NonNull Activity activity) {
         return ActivityThread.currentActivityThread()
                 .getActivityClient(activity.getActivityToken());
     }
@@ -3092,10 +3094,8 @@
      */
     @Override
     public boolean isActivityEmbedded(@NonNull Activity activity) {
-        Objects.requireNonNull(activity);
         synchronized (mLock) {
-            final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
-            return activityWindowInfo != null && activityWindowInfo.isEmbedded();
+            return TaskFragmentOrganizer.isActivityEmbedded(activity);
         }
     }
 
@@ -3165,15 +3165,6 @@
         }
     }
 
-    @Nullable
-    private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
-        if (activity.isFinishing()) {
-            return null;
-        }
-        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
-        return record != null ? record.getActivityWindowInfo() : null;
-    }
-
     @NonNull
     private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
             @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 99c0ee2..d852204 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -165,6 +165,7 @@
     private Consumer<List<SplitInfo>> mEmbeddingCallback;
     private List<SplitInfo> mSplitInfos;
     private TransactionManager mTransactionManager;
+    private ActivityThread mCurrentActivityThread;
 
     @Before
     public void setUp() {
@@ -181,10 +182,12 @@
         };
         mSplitController.setSplitInfoCallback(mEmbeddingCallback);
         mTransactionManager = mSplitController.mTransactionManager;
+        mCurrentActivityThread = ActivityThread.currentActivityThread();
         spyOn(mSplitController);
         spyOn(mSplitPresenter);
         spyOn(mEmbeddingCallback);
         spyOn(mTransactionManager);
+        spyOn(mCurrentActivityThread);
         doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
         final Configuration activityConfig = new Configuration();
         activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -1668,7 +1671,8 @@
         final IBinder activityToken = new Binder();
         doReturn(activityToken).when(activity).getActivityToken();
         doReturn(activity).when(mSplitController).getActivity(activityToken);
-        doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
+        doReturn(activityClientRecord).when(mCurrentActivityThread).getActivityClient(
+                activityToken);
         doReturn(taskId).when(activity).getTaskId();
         doReturn(new ActivityInfo()).when(activity).getActivityInfo();
         doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..1a9c304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -206,13 +206,12 @@
     @WMSingleton
     @Provides
     static PipMotionHelper providePipMotionHelper(Context context,
-            @ShellMainThread ShellExecutor mainExecutor,
             PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
             PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
             PipTransitionController pipTransitionController,
             FloatingContentCoordinator floatingContentCoordinator,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
-        return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
+        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
                 menuController, pipSnapAlgorithm, pipTransitionController,
                 floatingContentCoordinator, pipPerfHintControllerOptional);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,12 +16,10 @@
 
 package com.android.wm.shell.pip;
 
-import android.annotation.NonNull;
 import android.graphics.Rect;
 
 import com.android.wm.shell.shared.annotations.ExternalThread;
 
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -71,10 +69,9 @@
     default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
 
     /**
-     * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
-     * started / finished callbacks.
+     * @return {@link PipTransitionController} instance.
      */
-    default void registerPipTransitionCallback(
-            @NonNull PipTransitionController.PipTransitionCallback callback,
-            @NonNull Executor executor) { }
+    default PipTransitionController getPipTransitionController() {
+        return null;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6e1e44b..ff40d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,8 +423,7 @@
             });
             mPipTransitionController.setPipOrganizer(this);
             displayController.addDisplayWindowListener(this);
-            pipTransitionController.registerPipTransitionCallback(
-                    mPipTransitionCallback, mMainExecutor);
+            pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
         }
     }
 
@@ -496,9 +495,7 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
         mPipTransitionState.setInSwipePipToHomeTransition(true);
-        if (!ENABLE_SHELL_TRANSITIONS) {
-            sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
-        }
+        sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
         setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
         return mPipBoundsAlgorithm.getEntryDestinationBounds();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87692ac..e5633de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1174,7 +1174,6 @@
         }
 
         final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
-        sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
                         destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 8d36db9..b1dd4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,9 +53,8 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Responsible supplying PiP Transitions.
@@ -67,7 +66,7 @@
     protected final ShellTaskOrganizer mShellTaskOrganizer;
     protected final PipMenuController mPipMenuController;
     protected final Transitions mTransitions;
-    private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
+    private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
     protected PipTaskOrganizer mPipOrganizer;
     protected DefaultMixedHandler mMixedHandler;
 
@@ -184,18 +183,16 @@
     /**
      * Registers {@link PipTransitionCallback} to receive transition callbacks.
      */
-    public void registerPipTransitionCallback(
-            @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
-        mPipTransitionCallbacks.put(callback, executor);
+    public void registerPipTransitionCallback(PipTransitionCallback callback) {
+        mPipTransitionCallbacks.add(callback);
     }
 
     protected void sendOnPipTransitionStarted(
             @PipAnimationController.TransitionDirection int direction) {
         final Rect pipBounds = mPipBoundsState.getBounds();
-        for (Map.Entry<PipTransitionCallback, Executor> entry
-                : mPipTransitionCallbacks.entrySet()) {
-            entry.getValue().execute(
-                    () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionStarted(direction, pipBounds);
         }
         if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
             try {
@@ -212,10 +209,9 @@
 
     protected void sendOnPipTransitionFinished(
             @PipAnimationController.TransitionDirection int direction) {
-        for (Map.Entry<PipTransitionCallback, Executor> entry
-                : mPipTransitionCallbacks.entrySet()) {
-            entry.getValue().execute(
-                    () -> entry.getKey().onPipTransitionFinished(direction));
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionFinished(direction);
         }
         if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
             try {
@@ -232,10 +228,9 @@
 
     protected void sendOnPipTransitionCancelled(
             @PipAnimationController.TransitionDirection int direction) {
-        for (Map.Entry<PipTransitionCallback, Executor> entry
-                : mPipTransitionCallbacks.entrySet()) {
-            entry.getValue().execute(
-                    () -> entry.getKey().onPipTransitionCanceled(direction));
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionCanceled(direction);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 0cb7e17..26b7e58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,7 +106,6 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -479,7 +478,7 @@
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
                 INPUT_CONSUMER_PIP, mMainExecutor);
-        mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+        mPipTransitionController.registerPipTransitionCallback(this);
         mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
             mPipDisplayLayoutState.setDisplayId(displayId);
             onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1221,11 +1220,8 @@
         }
 
         @Override
-        public void registerPipTransitionCallback(
-                PipTransitionController.PipTransitionCallback callback,
-                Executor executor) {
-            mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
-                    callback, executor));
+        public PipTransitionController getPipTransitionController() {
+            return mPipTransitionController;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..e8d6576 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,7 +38,6 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,7 +47,6 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
@@ -173,9 +171,7 @@
         public void onPipTransitionCanceled(int direction) {}
     };
 
-    public PipMotionHelper(Context context,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @NonNull PipBoundsState pipBoundsState,
+    public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
             PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -187,7 +183,7 @@
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
-        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
+        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
         mResizePipUpdateListener = (target, values) -> {
             if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079..62c0944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
     }
 
     private void onInit() {
-        mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+        mPipTransitionController.registerPipTransitionCallback(this);
 
         reloadResources();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index c12219c..b939b16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -64,7 +64,6 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -413,11 +412,6 @@
 
         @Override
         public void showPictureInPictureMenu() {}
-
-        @Override
-        public void registerPipTransitionCallback(
-                PipTransitionController.PipTransitionCallback callback,
-                Executor executor) {}
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 75d2145..6888de5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@
 
     @Test
     public void instantiatePipController_registersPipTransitionCallback() {
-        verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
+        verify(mMockPipTransitionController).registerPipTransitionCallback(any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 66f8c0b..ace09a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
         final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
                 mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
                 mSizeSpecSource);
-        final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
-                mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+        final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+                mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator,
                 Optional.empty() /* pipPerfHintControllerOptional */);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 6d18e36..92762fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
                 new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
-        PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
-                mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+        PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+                mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator,
                 Optional.empty() /* pipPerfHintControllerOptional */);
         mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index b949cd5..ac0b9b4 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -15,6 +15,7 @@
  */
 package com.android.settingslib.drawer;
 
+import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -25,7 +26,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -36,6 +39,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -102,6 +107,9 @@
     /** The key used to get the package name of the icon resource for the preference. */
     static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
 
+    /** The key used for the raw byte data of the icon for the preference. */
+    static final String EXTRA_PREFERENCE_ICON_RAW = "com.android.settings.icon_raw";
+
     /**
      * Name of the meta-data item that should be set in the AndroidManifest.xml
      * to specify the key that should be used for the preference.
@@ -518,6 +526,24 @@
     }
 
     /**
+     * Retrieves an icon stored in the Bundle as a Parcel with key EXTRA_PREFERENCE_ICON_RAW
+     */
+    @TargetApi(Build.VERSION_CODES.TIRAMISU)
+    @Nullable
+    public static Icon getRawIconFromUri(@NonNull Context context, @Nullable Uri uri,
+            @NonNull Map<String, IContentProvider> providerMap) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            return null;
+        }
+        final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        if (bundle == null) {
+            return null;
+        }
+
+        return bundle.getParcelable(EXTRA_PREFERENCE_ICON_RAW, Icon.class);
+    }
+
+    /**
      * Gets text associated with the input key from the content provider.
      *
      * @param context context
@@ -564,8 +590,9 @@
         return getBundleFromUri(context, uri, providerMap, bundle);
     }
 
-    private static Bundle getBundleFromUri(Context context, Uri uri,
-            Map<String, IContentProvider> providerMap, Bundle bundle) {
+    @Nullable
+    private static Bundle getBundleFromUri(@NonNull Context context, @Nullable Uri uri,
+            @NonNull Map<String, IContentProvider> providerMap, @Nullable Bundle bundle) {
         final Pair<String, String> args = getMethodAndKey(uri);
         if (args == null) {
             return null;
@@ -593,8 +620,9 @@
         }
     }
 
-    private static IContentProvider getProviderFromUri(Context context, Uri uri,
-            Map<String, IContentProvider> providerMap) {
+    @Nullable
+    private static IContentProvider getProviderFromUri(@NonNull Context context, @Nullable Uri uri,
+            @NonNull Map<String, IContentProvider> providerMap) {
         if (uri == null) {
             return null;
         }
@@ -609,7 +637,8 @@
     }
 
     /** Returns method and key of the complete uri. */
-    private static Pair<String, String> getMethodAndKey(Uri uri) {
+    @Nullable
+    private static Pair<String, String> getMethodAndKey(@Nullable Uri uri) {
         if (uri == null) {
             return null;
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12ca997..a184cf3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -337,7 +337,7 @@
     // expanded, reset scrim offset.
     LaunchedEffect(stackHeight, scrimOffset) {
         snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
-            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
+            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.animateTo(0f, tween()) }
     }
 
     // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index d95b388..20b1303 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -184,31 +184,33 @@
     ): Swipes {
         val fromSource =
             startedPosition?.let { position ->
-                layoutImpl.swipeSourceDetector.source(
-                    fromScene.targetSize,
-                    position.round(),
-                    layoutImpl.density,
-                    orientation,
-                )
+                layoutImpl.swipeSourceDetector
+                    .source(
+                        fromScene.targetSize,
+                        position.round(),
+                        layoutImpl.density,
+                        orientation,
+                    )
+                    ?.resolve(layoutImpl.layoutDirection)
             }
 
         val upOrLeft =
-            Swipe(
+            Swipe.Resolved(
                 direction =
                     when (orientation) {
-                        Orientation.Horizontal -> SwipeDirection.Left
-                        Orientation.Vertical -> SwipeDirection.Up
+                        Orientation.Horizontal -> SwipeDirection.Resolved.Left
+                        Orientation.Vertical -> SwipeDirection.Resolved.Up
                     },
                 pointerCount = pointersDown,
                 fromSource = fromSource,
             )
 
         val downOrRight =
-            Swipe(
+            Swipe.Resolved(
                 direction =
                     when (orientation) {
-                        Orientation.Horizontal -> SwipeDirection.Right
-                        Orientation.Vertical -> SwipeDirection.Down
+                        Orientation.Horizontal -> SwipeDirection.Resolved.Right
+                        Orientation.Vertical -> SwipeDirection.Resolved.Down
                     },
                 pointerCount = pointersDown,
                 fromSource = fromSource,
@@ -833,10 +835,10 @@
 
 /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
 private class Swipes(
-    val upOrLeft: Swipe?,
-    val downOrRight: Swipe?,
-    val upOrLeftNoSource: Swipe?,
-    val downOrRightNoSource: Swipe?,
+    val upOrLeft: Swipe.Resolved?,
+    val downOrRight: Swipe.Resolved?,
+    val upOrLeftNoSource: Swipe.Resolved?,
+    val downOrRightNoSource: Swipe.Resolved?,
 ) {
     /** The [UserActionResult] associated to up and down swipes. */
     var upOrLeftResult: UserActionResult? = null
@@ -844,7 +846,7 @@
 
     fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
         val userActions = fromScene.userActions
-        fun result(swipe: Swipe?): UserActionResult? {
+        fun result(swipe: Swipe.Resolved?): UserActionResult? {
             return userActions[swipe ?: return null]
         }
 
@@ -940,25 +942,27 @@
                 when {
                     amount < 0f -> {
                         val actionUpOrLeft =
-                            Swipe(
+                            Swipe.Resolved(
                                 direction =
                                     when (orientation) {
-                                        Orientation.Horizontal -> SwipeDirection.Left
-                                        Orientation.Vertical -> SwipeDirection.Up
+                                        Orientation.Horizontal -> SwipeDirection.Resolved.Left
+                                        Orientation.Vertical -> SwipeDirection.Resolved.Up
                                     },
                                 pointerCount = pointersInfo().pointersDown,
+                                fromSource = null,
                             )
                         fromScene.userActions[actionUpOrLeft]
                     }
                     amount > 0f -> {
                         val actionDownOrRight =
-                            Swipe(
+                            Swipe.Resolved(
                                 direction =
                                     when (orientation) {
-                                        Orientation.Horizontal -> SwipeDirection.Right
-                                        Orientation.Vertical -> SwipeDirection.Down
+                                        Orientation.Horizontal -> SwipeDirection.Resolved.Right
+                                        Orientation.Vertical -> SwipeDirection.Resolved.Down
                                     },
                                 pointerCount = pointersInfo().pointersDown,
+                                fromSource = null,
                             )
                         fromScene.userActions[actionDownOrRight]
                     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index b0dc3a1..97c0cef 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -21,14 +21,28 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 
 /** The edge of a [SceneTransitionLayout]. */
-enum class Edge : SwipeSource {
-    Left,
-    Right,
-    Top,
-    Bottom,
+enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource {
+    Top(resolveEdge = { Resolved.Top }),
+    Bottom(resolveEdge = { Resolved.Bottom }),
+    Left(resolveEdge = { Resolved.Left }),
+    Right(resolveEdge = { Resolved.Right }),
+    Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+    End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+    override fun resolve(layoutDirection: LayoutDirection): Resolved {
+        return resolveEdge(layoutDirection)
+    }
+
+    enum class Resolved : SwipeSource.Resolved {
+        Left,
+        Right,
+        Top,
+        Bottom,
+    }
 }
 
 val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 936f4ba..a49f1af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -37,7 +37,7 @@
     val key: SceneKey,
     layoutImpl: SceneTransitionLayoutImpl,
     content: @Composable SceneScope.() -> Unit,
-    actions: Map<UserAction, UserActionResult>,
+    actions: Map<UserAction.Resolved, UserActionResult>,
     zIndex: Float,
 ) {
     internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -54,8 +54,8 @@
         }
 
     private fun checkValid(
-        userActions: Map<UserAction, UserActionResult>
-    ): Map<UserAction, UserActionResult> {
+        userActions: Map<UserAction.Resolved, UserActionResult>
+    ): Map<UserAction.Resolved, UserActionResult> {
         userActions.forEach { (action, result) ->
             if (key == result.toScene) {
                 error(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 45758c5..0c467b1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -28,10 +28,13 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.UserAction.Resolved
 
 /**
  * [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -344,34 +347,71 @@
 @Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
 
 /** An action performed by the user. */
-sealed interface UserAction {
+sealed class UserAction {
     infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
         return this to UserActionResult(toScene = scene)
     }
+
+    /** Resolve this into a [Resolved] user action given [layoutDirection]. */
+    internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved
+
+    /** A resolved [UserAction] that does not depend on the layout direction. */
+    internal sealed class Resolved
 }
 
 /** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-data object Back : UserAction
+data object Back : UserAction() {
+    override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved
+
+    internal object Resolved : UserAction.Resolved()
+}
 
 /** The user swiped on the container. */
 data class Swipe(
     val direction: SwipeDirection,
     val pointerCount: Int = 1,
     val fromSource: SwipeSource? = null,
-) : UserAction {
+) : UserAction() {
     companion object {
         val Left = Swipe(SwipeDirection.Left)
         val Up = Swipe(SwipeDirection.Up)
         val Right = Swipe(SwipeDirection.Right)
         val Down = Swipe(SwipeDirection.Down)
+        val Start = Swipe(SwipeDirection.Start)
+        val End = Swipe(SwipeDirection.End)
     }
+
+    override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
+        return Resolved(
+            direction = direction.resolve(layoutDirection),
+            pointerCount = pointerCount,
+            fromSource = fromSource?.resolve(layoutDirection),
+        )
+    }
+
+    /** A resolved [Swipe] that does not depend on the layout direction. */
+    internal data class Resolved(
+        val direction: SwipeDirection.Resolved,
+        val pointerCount: Int,
+        val fromSource: SwipeSource.Resolved?,
+    ) : UserAction.Resolved()
 }
 
-enum class SwipeDirection(val orientation: Orientation) {
-    Up(Orientation.Vertical),
-    Down(Orientation.Vertical),
-    Left(Orientation.Horizontal),
-    Right(Orientation.Horizontal),
+enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) {
+    Up(resolve = { Resolved.Up }),
+    Down(resolve = { Resolved.Down }),
+    Left(resolve = { Resolved.Left }),
+    Right(resolve = { Resolved.Right }),
+    Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+    End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+    /** A resolved [SwipeDirection] that does not depend on the layout direction. */
+    internal enum class Resolved(val orientation: Orientation) {
+        Up(Orientation.Vertical),
+        Down(Orientation.Vertical),
+        Left(Orientation.Horizontal),
+        Right(Orientation.Horizontal),
+    }
 }
 
 /**
@@ -386,6 +426,16 @@
     override fun equals(other: Any?): Boolean
 
     override fun hashCode(): Int
+
+    /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */
+    fun resolve(layoutDirection: LayoutDirection): Resolved
+
+    /** A resolved [SwipeSource] that does not depend on the layout direction. */
+    interface Resolved {
+        override fun equals(other: Any?): Boolean
+
+        override fun hashCode(): Int
+    }
 }
 
 interface SwipeSourceDetector {
@@ -460,11 +510,13 @@
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
     val density = LocalDensity.current
+    val layoutDirection = LocalLayoutDirection.current
     val coroutineScope = rememberCoroutineScope()
     val layoutImpl = remember {
         SceneTransitionLayoutImpl(
                 state = state as BaseSceneTransitionLayoutState,
                 density = density,
+                layoutDirection = layoutDirection,
                 swipeSourceDetector = swipeSourceDetector,
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
                 builder = scenes,
@@ -475,7 +527,7 @@
 
     // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
     // SnapshotStateMap anymore.
-    layoutImpl.updateScenes(scenes)
+    layoutImpl.updateScenes(scenes, layoutDirection)
 
     SideEffect {
         if (state != layoutImpl.state) {
@@ -486,6 +538,7 @@
         }
 
         layoutImpl.density = density
+        layoutImpl.layoutDirection = layoutDirection
         layoutImpl.swipeSourceDetector = swipeSourceDetector
         layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 6095419..3e48c42 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachReversed
 import com.android.compose.ui.util.lerp
@@ -45,6 +46,7 @@
 internal class SceneTransitionLayoutImpl(
     internal val state: BaseSceneTransitionLayoutState,
     internal var density: Density,
+    internal var layoutDirection: LayoutDirection,
     internal var swipeSourceDetector: SwipeSourceDetector,
     internal var transitionInterceptionThreshold: Float,
     builder: SceneTransitionLayoutScope.() -> Unit,
@@ -114,7 +116,7 @@
         private set
 
     init {
-        updateScenes(builder)
+        updateScenes(builder, layoutDirection)
 
         // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
         // current scene (required for SwipeTransition).
@@ -147,7 +149,10 @@
         return scenes[key] ?: error("Scene $key is not configured")
     }
 
-    internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+    internal fun updateScenes(
+        builder: SceneTransitionLayoutScope.() -> Unit,
+        layoutDirection: LayoutDirection,
+    ) {
         // Keep a reference of the current scenes. After processing [builder], the scenes that were
         // not configured will be removed.
         val scenesToRemove = scenes.keys.toMutableSet()
@@ -163,11 +168,13 @@
                 ) {
                     scenesToRemove.remove(key)
 
+                    val resolvedUserActions =
+                        userActions.mapKeys { it.key.resolve(layoutDirection) }
                     val scene = scenes[key]
                     if (scene != null) {
                         // Update an existing scene.
                         scene.content = content
-                        scene.userActions = userActions
+                        scene.userActions = resolvedUserActions
                         scene.zIndex = zIndex
                     } else {
                         // New scene.
@@ -176,7 +183,7 @@
                                 key,
                                 this@SceneTransitionLayoutImpl,
                                 content,
-                                userActions,
+                                resolvedUserActions,
                                 zIndex,
                             )
                     }
@@ -213,7 +220,7 @@
     @Composable
     private fun BackHandler() {
         val targetSceneForBack =
-            scene(state.transitionState.currentScene).userActions[Back]?.toScene
+            scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
         PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
     }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 171e243..aeb6262 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -98,7 +98,9 @@
 
     /** Whether swipe should be enabled in the given [orientation]. */
     private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
-        return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+        return userActions.keys.any {
+            it is Swipe.Resolved && it.direction.orientation == orientation
+        }
     }
 
     private fun startDragImmediately(startedPosition: Offset): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index aa8dc38..7daefd0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -44,26 +44,26 @@
             return value
         }
 
-        return when (edge) {
-            Edge.Top ->
+        return when (edge.resolve(layoutImpl.layoutDirection)) {
+            Edge.Resolved.Top ->
                 if (startsOutsideLayoutBounds) {
                     Offset(value.x, -elementSize.height.toFloat())
                 } else {
                     Offset(value.x, 0f)
                 }
-            Edge.Left ->
+            Edge.Resolved.Left ->
                 if (startsOutsideLayoutBounds) {
                     Offset(-elementSize.width.toFloat(), value.y)
                 } else {
                     Offset(0f, value.y)
                 }
-            Edge.Bottom ->
+            Edge.Resolved.Bottom ->
                 if (startsOutsideLayoutBounds) {
                     Offset(value.x, sceneSize.height.toFloat())
                 } else {
                     Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
                 }
-            Edge.Right ->
+            Edge.Resolved.Right ->
                 if (startsOutsideLayoutBounds) {
                     Offset(sceneSize.width.toFloat(), value.y)
                 } else {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ff83d4b..7a5a84e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
@@ -61,8 +62,24 @@
                 canChangeScene = { canChangeScene(it) },
             )
 
-        val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
-        val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+        var layoutDirection = LayoutDirection.Rtl
+            set(value) {
+                field = value
+                layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+            }
+
+        var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+            set(value) {
+                field = value
+                layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+            }
+
+        var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+            set(value) {
+                field = value
+                layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+            }
+
         private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
             scene(
                 key = SceneA,
@@ -94,6 +111,7 @@
             SceneTransitionLayoutImpl(
                     state = layoutState,
                     density = Density(1f),
+                    layoutDirection = LayoutDirection.Ltr,
                     swipeSourceDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
                     builder = scenesBuilder,
@@ -466,10 +484,8 @@
         dragController1.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
-        mutableUserActionsA.remove(Swipe.Up)
-        mutableUserActionsA.remove(Swipe.Down)
-        mutableUserActionsB.remove(Swipe.Up)
-        mutableUserActionsB.remove(Swipe.Down)
+        mutableUserActionsA = emptyMap()
+        mutableUserActionsB = emptyMap()
 
         // start accelaratedScroll and scroll over to B -> null
         val dragController2 = onDragStartedImmediately()
@@ -495,7 +511,7 @@
         val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
-        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+        mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
         dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
         // target stays B even though UserActions changed
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -512,7 +528,7 @@
         val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
-        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+        mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
         dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
         dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
 
@@ -1149,8 +1165,7 @@
             overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        mutableUserActionsA.clear()
-        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB)
+        mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
 
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
         val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
@@ -1178,8 +1193,7 @@
             overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        mutableUserActionsA.clear()
-        mutableUserActionsA[Swipe.Down] = UserActionResult(SceneC)
+        mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
 
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
         val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
@@ -1220,7 +1234,8 @@
 
     @Test
     fun requireFullDistanceSwipe() = runGestureTest {
-        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true)
+        mutableUserActionsA +=
+            Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
 
         val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 25ea2ee..0766e00 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -23,11 +23,13 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -37,10 +39,12 @@
 import androidx.compose.ui.test.swipeWithVelocity
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -634,4 +638,152 @@
         // Foo should be translated by (20dp, 30dp).
         rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
     }
+
+    @Test
+    fun startEnd_ltrLayout() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    initialScene = SceneA,
+                    transitions =
+                        transitions {
+                            from(SceneA, to = SceneB) {
+                                // We go to B by swiping to the start (left in LTR), so we make
+                                // scene B appear from the end (right) edge.
+                                translate(SceneB.rootElementKey, Edge.End)
+                            }
+
+                            from(SceneA, to = SceneC) {
+                                // We go to C by swiping to the end (right in LTR), so we make
+                                // scene C appear from the start (left) edge.
+                                translate(SceneC.rootElementKey, Edge.Start)
+                            }
+                        },
+                )
+            }
+
+        val layoutSize = 200.dp
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+                scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+                    Box(Modifier.fillMaxSize())
+                }
+                scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+                scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+            }
+        }
+
+        // Swipe to the left (start).
+        rule.onRoot().performTouchInput {
+            val middle = (layoutSize / 2).toPx()
+            down(Offset(middle, middle))
+            moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+        }
+
+        // Scene B should come from the right (end) edge.
+        var transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        rule
+            .onNode(isElement(SceneB.rootElementKey))
+            .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+        // Release to go back to A.
+        rule.onRoot().performTouchInput { up() }
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+        // Swipe to the right (end).
+        rule.onRoot().performTouchInput {
+            val middle = (layoutSize / 2).toPx()
+            down(Offset(middle, middle))
+            moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+        }
+
+        // Scene C should come from the left (start) edge.
+        transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneC)
+        rule
+            .onNode(isElement(SceneC.rootElementKey))
+            .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+    }
+
+    @Test
+    fun startEnd_rtlLayout() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    initialScene = SceneA,
+                    transitions =
+                        transitions {
+                            from(SceneA, to = SceneB) {
+                                // We go to B by swiping to the start (right in RTL), so we make
+                                // scene B appear from the end (left) edge.
+                                translate(SceneB.rootElementKey, Edge.End)
+                            }
+
+                            from(SceneA, to = SceneC) {
+                                // We go to C by swiping to the end (left in RTL), so we make
+                                // scene C appear from the start (right) edge.
+                                translate(SceneC.rootElementKey, Edge.Start)
+                            }
+                        },
+                )
+            }
+
+        val layoutSize = 200.dp
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+                    scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+                        Box(Modifier.fillMaxSize())
+                    }
+                    scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+                    scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+                }
+            }
+        }
+
+        // Swipe to the left (end).
+        rule.onRoot().performTouchInput {
+            val middle = (layoutSize / 2).toPx()
+            down(Offset(middle, middle))
+            moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+        }
+
+        // Scene C should come from the right (start) edge.
+        var transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneC)
+        rule
+            .onNode(isElement(SceneC.rootElementKey))
+            .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+        // Release to go back to A.
+        rule.onRoot().performTouchInput { up() }
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+        // Swipe to the right (start).
+        rule.onRoot().performTouchInput {
+            val middle = (layoutSize / 2).toPx()
+            down(Offset(middle, middle))
+            moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+        }
+
+        // Scene C should come from the left (end) edge.
+        transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        rule
+            .onNode(isElement(SceneB.rootElementKey))
+            .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 51c008a..9b725eb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -17,7 +17,6 @@
 import android.app.UserSwitchObserver
 import android.content.Context
 import android.database.ContentObserver
-import android.graphics.drawable.Drawable
 import android.net.Uri
 import android.os.UserHandle
 import android.provider.Settings
@@ -33,6 +32,7 @@
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
 import com.android.systemui.plugins.clocks.ClockProvider
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
 import com.android.systemui.plugins.clocks.ClockSettings
@@ -341,6 +341,7 @@
     }
 
     private var isClockChanged = AtomicBoolean(false)
+
     private fun triggerOnCurrentClockChanged() {
         val shouldSchedule = isClockChanged.compareAndSet(false, true)
         if (!shouldSchedule) {
@@ -355,6 +356,7 @@
     }
 
     private var isClockListChanged = AtomicBoolean(false)
+
     private fun triggerOnAvailableClocksChanged() {
         val shouldSchedule = isClockListChanged.compareAndSet(false, true)
         if (!shouldSchedule) {
@@ -458,6 +460,7 @@
     }
 
     private var isQueued = AtomicBoolean(false)
+
     fun verifyLoadedProviders() {
         val shouldSchedule = isQueued.compareAndSet(false, true)
         if (!shouldSchedule) {
@@ -565,8 +568,8 @@
         return availableClocks.map { (_, clock) -> clock.metadata }
     }
 
-    fun getClockThumbnail(clockId: ClockId): Drawable? =
-        availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+    fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
+        availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
 
     fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 9e0af97..4802e34 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,13 +15,13 @@
 
 import android.content.Context
 import android.content.res.Resources
-import android.graphics.drawable.Drawable
 import android.view.LayoutInflater
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
 import com.android.systemui.plugins.clocks.ClockProvider
 import com.android.systemui.plugins.clocks.ClockSettings
 
@@ -60,12 +60,17 @@
         )
     }
 
-    override fun getClockThumbnail(id: ClockId): Drawable? {
+    override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
         if (id != DEFAULT_CLOCK_ID) {
             throw IllegalArgumentException("$id is unsupported by $TAG")
         }
 
-        // TODO(b/352049256): Update placeholder to actual resource
-        return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+        return ClockPickerConfig(
+            DEFAULT_CLOCK_ID,
+            resources.getString(R.string.clock_default_name),
+            resources.getString(R.string.clock_default_description),
+            // TODO(b/352049256): Update placeholder to actual resource
+            resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+        )
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index c7998f0..4812ff0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -50,8 +50,8 @@
     /** Initializes and returns the target clock design */
     fun createClock(settings: ClockSettings): ClockController
 
-    /** A static thumbnail for rendering in some examples */
-    fun getClockThumbnail(id: ClockId): Drawable?
+    /** Settings configuration parameters for the clock */
+    fun getClockPickerConfig(id: ClockId): ClockPickerConfig
 }
 
 /** Interface for controlling an active clock */
@@ -133,6 +133,7 @@
     // both small and large clock should have a container (RelativeLayout in
     // SimpleClockFaceController)
     override val views = listOf(view)
+
     override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
         if (views.size != 1) {
             throw IllegalArgumentException(
@@ -267,6 +268,25 @@
     val clockId: ClockId,
 )
 
+data class ClockPickerConfig(
+    val id: String,
+
+    /** Localized name of the clock */
+    val name: String,
+
+    /** Localized accessibility description for the clock */
+    val description: String,
+
+    /* Static & lightweight thumbnail version of the clock */
+    val thumbnail: Drawable,
+
+    /** True if the clock will react to tone changes in the seed color */
+    val isReactiveToTone: Boolean = true,
+
+    /** True if the clock is capable of chagning style in reaction to touches */
+    val isReactiveToTouch: Boolean = false,
+)
+
 /** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
 data class ClockConfig(
     val id: String,
@@ -280,7 +300,7 @@
     /** Transition to AOD should move smartspace like large clock instead of small clock */
     val useAlternateSmartspaceAODTransition: Boolean = false,
 
-    /** True if the clock will react to tone changes in the seed color. */
+    @Deprecated("TODO(b/352049256): Remove")
     val isReactiveToTone: Boolean = true,
 
     /** True if the clock is large frame clock, which will use weather in compose. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..484e758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,8 +124,6 @@
     public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
     // Touchpad gestures are disabled
     public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
-    // PiP animation is running
-    public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
     // Communal hub is showing
     public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
 
@@ -177,7 +175,6 @@
             SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
             SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
             SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
-            SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
             SYSUI_STATE_COMMUNAL_HUB_SHOWING,
     })
     public @interface SystemUiStateFlags {}
@@ -283,9 +280,6 @@
         if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
             str.add("touchpad_gestures_disabled");
         }
-        if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
-            str.add("disable_gesture_pip_animating");
-        }
         if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
             str.add("communal_hub_showing");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2f872b6..e46a7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -78,7 +78,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -151,6 +150,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.settings.UserTracker;
@@ -360,6 +360,7 @@
     private final SecureSettings mSecureSettings;
     private final SystemSettings mSystemSettings;
     private final SystemClock mSystemClock;
+    private final ProcessWrapper mProcessWrapper;
     private final SystemPropertiesHelper mSystemPropertiesHelper;
 
     /**
@@ -1459,10 +1460,12 @@
             Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
             Lazy<ScrimController> scrimControllerLazy,
             IActivityTaskManager activityTaskManagerService,
+            IStatusBarService statusBarService,
             FeatureFlags featureFlags,
             SecureSettings secureSettings,
             SystemSettings systemSettings,
             SystemClock systemClock,
+            ProcessWrapper processWrapper,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamViewModel> dreamViewModel,
             Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -1487,9 +1490,9 @@
         mSecureSettings = secureSettings;
         mSystemSettings = systemSettings;
         mSystemClock = systemClock;
+        mProcessWrapper = processWrapper;
         mSystemPropertiesHelper = systemPropertiesHelper;
-        mStatusBarService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mStatusBarService = statusBarService;
         mKeyguardDisplayManager = keyguardDisplayManager;
         mShadeController = shadeControllerLazy;
         dumpManager.registerDumpable(this);
@@ -3511,12 +3514,20 @@
             // TODO (b/155663717) After restart, status bar will not properly hide home button
             //  unless disable is called to show un-hide it once first
             if (forceClearFlags) {
-                try {
-                    mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
-                            mContext.getPackageName(),
-                            mSelectedUserInteractor.getSelectedUserId(true));
-                } catch (RemoteException e) {
-                    Log.d(TAG, "Failed to force clear flags", e);
+                if (UserManager.isVisibleBackgroundUsersEnabled()
+                        && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+                    // TODO: b/341604160 - Support visible background users properly.
+                    if (DEBUG) {
+                        Log.d(TAG, "Status bar manager is disabled for visible background users");
+                    }
+                } else {
+                    try {
+                        mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+                                mContext.getPackageName(),
+                                mSelectedUserInteractor.getSelectedUserId(true));
+                    } catch (RemoteException e) {
+                        Log.d(TAG, "Failed to force clear flags", e);
+                    }
                 }
             }
 
@@ -3540,6 +3551,14 @@
             }
 
             if (!SceneContainerFlag.isEnabled()) {
+                if (UserManager.isVisibleBackgroundUsersEnabled()
+                        && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+                    // TODO: b/341604160 - Support visible background users properly.
+                    if (DEBUG) {
+                        Log.d(TAG, "Status bar manager is disabled for visible background users");
+                    }
+                    return;
+                }
                 try {
                     mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
                             mContext.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 15dac09..a43bfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -64,6 +65,7 @@
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -158,10 +160,12 @@
             Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
             Lazy<ScrimController> scrimControllerLazy,
             IActivityTaskManager activityTaskManagerService,
+            IStatusBarService statusBarService,
             FeatureFlags featureFlags,
             SecureSettings secureSettings,
             SystemSettings systemSettings,
             SystemClock systemClock,
+            ProcessWrapper processWrapper,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamViewModel> dreamViewModel,
             Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -206,10 +210,12 @@
                 activityTransitionAnimator,
                 scrimControllerLazy,
                 activityTaskManagerService,
+                statusBarService,
                 featureFlags,
                 secureSettings,
                 systemSettings,
                 systemClock,
+                processWrapper,
                 mainDispatcher,
                 dreamViewModel,
                 communalTransitionViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index d848b43..e8ded03 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -22,6 +22,7 @@
 
 import android.annotation.Nullable;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -36,6 +37,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
@@ -63,6 +65,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardStateController mKeyguardStateController;
     private final UiEventLogger mUiEventLogger;
+    private final ProcessWrapper mProcessWrapper;
     private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
 
     private boolean mKeyguardSessionStarted;
@@ -73,13 +76,15 @@
             AuthController authController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardStateController keyguardStateController,
-            UiEventLogger uiEventLogger
+            UiEventLogger uiEventLogger,
+            ProcessWrapper processWrapper
     ) {
         mStatusBarManagerService = statusBarService;
         mAuthController = authController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardStateController = keyguardStateController;
         mUiEventLogger = uiEventLogger;
+        mProcessWrapper = processWrapper;
     }
 
     @Override
@@ -109,6 +114,16 @@
 
         final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
         mSessionToInstanceId.put(type, instanceId);
+
+        if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+                && !mProcessWrapper.isForegroundUser()) {
+            // TODO: b/341604160 - Support visible background users properly.
+            if (DEBUG) {
+                Log.d(TAG, "Status bar manager is disabled for visible background users");
+            }
+            return;
+        }
+
         try {
             if (DEBUG) {
                 Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
@@ -139,6 +154,14 @@
             if (endSessionUiEvent != null) {
                 mUiEventLogger.log(endSessionUiEvent, instanceId);
             }
+            if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+                    && !mProcessWrapper.isForegroundUser()) {
+                // TODO: b/341604160 - Support visible background users properly.
+                if (DEBUG) {
+                    Log.d(TAG, "Status bar manager is disabled for visible background users");
+                }
+                return;
+            }
             mStatusBarManagerService.onSessionEnded(type, instanceId);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to send onSessionEnded for session="
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index b4cc196..294d0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.process;
 
+import android.app.ActivityManager;
 import android.os.Process;
 import android.os.UserHandle;
 
@@ -37,6 +38,13 @@
     }
 
     /**
+     * Returns {@code true} if the foreground user is running the current process.
+     */
+    public boolean isForegroundUser() {
+        return ActivityManager.getCurrentUser() == myUserHandle().getIdentifier();
+    }
+
+    /**
      * Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
      *
      * This should not be used to get the "current" user. This information only applies to the
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3f1ec85..ec9b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,9 +20,8 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -67,7 +66,6 @@
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.sysui.ShellInterface;
@@ -251,25 +249,7 @@
                 pip.showPictureInPictureMenu();
             }
         });
-        pip.registerPipTransitionCallback(
-                new PipTransitionController.PipTransitionCallback() {
-                    @Override
-                    public void onPipTransitionStarted(int direction, Rect pipBounds) {
-                        mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
-                                .commitUpdate(mDisplayTracker.getDefaultDisplayId());
-                    }
 
-                    @Override
-                    public void onPipTransitionFinished(int direction) {
-                        mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
-                                .commitUpdate(mDisplayTracker.getDefaultDisplayId());
-                    }
-
-                    @Override
-                    public void onPipTransitionCanceled(int direction) {
-                        // No op.
-                    }
-                }, mSysUiMainExecutor);
         mSysUiState.addCallback(sysUiStateFlag -> {
             mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
             pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 03afcb7..e68a4a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -77,6 +77,7 @@
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardSecurityView;
@@ -101,6 +102,7 @@
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
 import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.settings.UserTracker;
@@ -188,6 +190,7 @@
     private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
     private @Mock ScrimController mScrimController;
     private @Mock IActivityTaskManager mActivityTaskManagerService;
+    private @Mock IStatusBarService mStatusBarService;
     private @Mock SysuiColorExtractor mColorExtractor;
     private @Mock AuthController mAuthController;
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -211,6 +214,7 @@
     private @Mock SystemSettings mSystemSettings;
     private @Mock SecureSettings mSecureSettings;
     private @Mock AlarmManager mAlarmManager;
+    private @Mock ProcessWrapper mProcessWrapper;
     private FakeSystemClock mSystemClock;
     private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
 
@@ -247,6 +251,7 @@
                 .thenReturn(mock(Flow.class));
         when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
         when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
+        when(mProcessWrapper.isSystemUser()).thenReturn(true);
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
                 new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -1225,10 +1230,12 @@
                 () -> mActivityTransitionAnimator,
                 () -> mScrimController,
                 mActivityTaskManagerService,
+                mStatusBarService,
                 mFeatureFlags,
                 mSecureSettings,
                 mSystemSettings,
                 mSystemClock,
+                mProcessWrapper,
                 mDispatcher,
                 () -> mDreamViewModel,
                 () -> mCommunalTransitionViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index fbeb6d8..732bef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -44,6 +44,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -68,6 +69,8 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private ProcessWrapper mProcessWrapper;
 
     @Captor
     ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -86,13 +89,15 @@
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
+        when(mProcessWrapper.isSystemUser()).thenReturn(true);
 
         mSessionTracker = new SessionTracker(
                 mStatusBarService,
                 mAuthController,
                 mKeyguardUpdateMonitor,
                 mKeyguardStateController,
-                mUiEventLogger
+                mUiEventLogger,
+                mProcessWrapper
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 2f52248..150f53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
 import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.plugins.PluginLifecycleManager
@@ -74,6 +75,7 @@
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
+    private lateinit var pickerConfig: ClockPickerConfig
     private val featureFlags = FakeFeatureFlags()
 
     companion object {
@@ -82,9 +84,9 @@
             return null!!
         }
 
-        private fun failThumbnail(clockId: ClockId): Drawable? {
-            fail("Unexpected call to getThumbnail: $clockId")
-            return null
+        private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
+            fail("Unexpected call to getClockPickerConfig: $clockId")
+            return null!!
         }
     }
 
@@ -123,22 +125,31 @@
     private class FakeClockPlugin : ClockProviderPlugin {
         private val metadata = mutableListOf<ClockMetadata>()
         private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
-        private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
+        private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
 
         override fun getClocks() = metadata
-        override fun createClock(settings: ClockSettings): ClockController =
-            createCallbacks[settings.clockId!!]!!(settings.clockId!!)
-        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+
+        override fun createClock(settings: ClockSettings): ClockController {
+            val clockId = settings.clockId ?: throw IllegalArgumentException("No clockId specified")
+            return createCallbacks[clockId]?.invoke(clockId)
+                ?: throw NotImplementedError("No callback for '$clockId'")
+        }
+
+        override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
+            return pickerConfigs[clockId]?.invoke(clockId)
+                ?: throw NotImplementedError("No picker config for '$clockId'")
+        }
+
         override fun initialize(buffers: ClockMessageBuffers?) { }
 
         fun addClock(
             id: ClockId,
             create: (ClockId) -> ClockController = ::failFactory,
-            getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
+            getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
         ): FakeClockPlugin {
             metadata.add(ClockMetadata(id))
             createCallbacks[id] = create
-            thumbnailCallbacks[id] = getThumbnail
+            pickerConfigs[id] = getPickerConfig
             return this
         }
     }
@@ -148,9 +159,10 @@
         scheduler = TestCoroutineScheduler()
         dispatcher = StandardTestDispatcher(scheduler)
         scope = TestScope(dispatcher)
+        pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
 
         fakeDefaultProvider = FakeClockPlugin()
-            .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { mockThumbnail })
+            .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
         whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
 
         val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
@@ -215,8 +227,8 @@
     @Test
     fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
         val plugin1 = FakeClockPlugin()
-            .addClock("clock_1", { mockClock }, { mockThumbnail })
-            .addClock("clock_2", { mockClock }, { mockThumbnail })
+            .addClock("clock_1", { mockClock }, { pickerConfig })
+            .addClock("clock_2", { mockClock }, { pickerConfig })
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
         val plugin2 = FakeClockPlugin()
@@ -238,8 +250,8 @@
 
         assertEquals(registry.createExampleClock("clock_1"), mockClock)
         assertEquals(registry.createExampleClock("clock_2"), mockClock)
-        assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
-        assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+        assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
+        assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
         verify(lifecycle1, never()).unloadPlugin()
         verify(lifecycle2, times(2)).unloadPlugin()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2522ed7..bbe03f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -72,6 +72,10 @@
             .thenReturn(mockSmallClockView)
         whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean()))
             .thenReturn(mockLargeClockView)
+        whenever(resources.getString(R.string.clock_default_name))
+            .thenReturn("DEFAULT_CLOCK_NAME")
+        whenever(resources.getString(R.string.clock_default_description))
+            .thenReturn("DEFAULT_CLOCK_DESC")
         whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
             .thenReturn(mockClockThumbnail)
         whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
@@ -85,7 +89,7 @@
         // All providers need to provide clocks & thumbnails for exposed clocks
         for (metadata in provider.getClocks()) {
             assertNotNull(provider.createClock(metadata.clockId))
-            assertNotNull(provider.getClockThumbnail(metadata.clockId))
+            assertNotNull(provider.getClockPickerConfig(metadata.clockId))
         }
     }
 
diff --git a/services/Android.bp b/services/Android.bp
index cd974c5..dce6aa7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -186,7 +186,15 @@
 
 // merge all required services into one jar
 // ============================================================
-java_library {
+soong_config_module_type {
+    name: "system_java_library",
+    module_type: "java_library",
+    config_namespace: "system_services",
+    bool_variables: ["without_vibrator"],
+    properties: ["vintf_fragments"],
+}
+
+system_java_library {
     name: "services",
     defaults: [
         "services_java_defaults",
@@ -248,9 +256,19 @@
         "service-sdksandbox.stubs.system_server",
     ],
 
-    vintf_fragments: [
-        "manifest_services.xml",
-    ],
+    soong_config_variables: {
+        without_vibrator: {
+            vintf_fragments: [
+                "manifest_services.xml",
+            ],
+            conditions_default: {
+                vintf_fragments: [
+                    "manifest_services.xml",
+                    "manifest_services_android.frameworks.vibrator.xml",
+                ],
+            },
+        },
+    },
 
     required: [
         "libukey2_jni_shared",
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 00183ac..67985ef 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -76,6 +76,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.os.WakeLockStats;
 import android.os.WorkSource;
@@ -158,6 +159,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -1107,6 +1109,13 @@
                 FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
                 null, // use default PullAtomMetadata values
                 DIRECT_EXECUTOR, pullAtomCallback);
+        if (Flags.addBatteryUsageStatsSliceAtom()) {
+            statsManager.setPullAtomCallback(
+                    FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+                    null, // use default PullAtomMetadata values
+                    DIRECT_EXECUTOR,
+                    pullAtomCallback);
+        }
     }
 
     /** StatsPullAtomCallback for pulling BatteryUsageStats data. */
@@ -1115,7 +1124,7 @@
         public int onPullAtom(int atomTag, List<StatsEvent> data) {
             final BatteryUsageStats bus;
             switch (atomTag) {
-                case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+                case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
                     @SuppressLint("MissingPermission")
                     final double minConsumedPowerThreshold =
                             DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1130,6 +1139,7 @@
                                     .build();
                     bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
                     break;
+                }
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
                     final BatteryUsageStatsQuery queryPowerProfile =
                             new BatteryUsageStatsQuery.Builder()
@@ -1141,7 +1151,7 @@
                                     .build();
                     bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
                     break;
-                case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
+                case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
                     final long sessionStart =
                             getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
                     final long sessionEnd;
@@ -1158,6 +1168,31 @@
                     bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
                     setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
                     break;
+                }
+                case FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID: {
+                    if (!Flags.addBatteryUsageStatsSliceAtom()) {
+                        return StatsManager.PULL_SKIP;
+                    }
+
+                    @SuppressLint("MissingPermission")
+                    final double minConsumedPowerThreshold =
+                            DeviceConfig.getFloat(
+                                    DEVICE_CONFIG_NAMESPACE,
+                                    MIN_CONSUMED_POWER_THRESHOLD_KEY,
+                                    0);
+                    final long sessionStart = 0;
+                    final long sessionEnd = System.currentTimeMillis();
+                    final BatteryUsageStatsQuery query =
+                            new BatteryUsageStatsQuery.Builder()
+                                    .setMaxStatsAgeMs(0)
+                                    .includeProcessStateData()
+                                    .includeVirtualUids()
+                                    .aggregateSnapshots(sessionStart, sessionEnd)
+                                    .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
+                                    .build();
+                    bus = getBatteryUsageStats(List.of(query)).get(0);
+                    return StatsPerUidLogger.logStats(bus, data);
+                }
                 default:
                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
             }
@@ -1169,6 +1204,262 @@
         }
     }
 
+    private static class StatsPerUidLogger {
+
+        private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000;
+
+        private static final int[] UID_PROCESS_STATES = {
+            BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+            BatteryConsumer.PROCESS_STATE_FOREGROUND,
+            BatteryConsumer.PROCESS_STATE_BACKGROUND,
+            BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+            BatteryConsumer.PROCESS_STATE_CACHED
+        };
+
+        public record SessionInfo(
+                long startTs,
+                long endTs,
+                long duration,
+                int dischargePercentage,
+                long dischargeDuration) {}
+        ;
+
+        static int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
+            final SessionInfo sessionInfo =
+                    new SessionInfo(
+                            bus.getStatsStartTimestamp(),
+                            bus.getStatsEndTimestamp(),
+                            bus.getStatsDuration(),
+                            bus.getDischargePercentage(),
+                            bus.getDischargeDurationMs());
+
+            if (DBG) {
+                Slog.d(TAG, "BatteryUsageStats dump = " + bus);
+            }
+            final BatteryConsumer deviceConsumer =
+                    bus.getAggregateBatteryConsumer(
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+            final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+
+            for (@BatteryConsumer.PowerComponent int componentId = 0;
+                    componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+                    componentId++) {
+
+                for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+                    if (!addStatsForPredefinedComponent(
+                            data,
+                            sessionInfo,
+                            Process.INVALID_UID,
+                            processState,
+                            totalDeviceConsumedPowerMah,
+                            deviceConsumer,
+                            componentId)) {
+                        return StatsManager.PULL_SUCCESS;
+                    }
+                }
+            }
+
+            final int customPowerComponentCount = deviceConsumer.getCustomPowerComponentCount();
+            for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+                    componentId
+                            < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+                                    + customPowerComponentCount;
+                    componentId++) {
+
+                if (!addStatsForCustomComponent(
+                        data,
+                        sessionInfo,
+                        Process.INVALID_UID,
+                        BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+                        0,
+                        totalDeviceConsumedPowerMah,
+                        deviceConsumer,
+                        componentId)) {
+                    return StatsManager.PULL_SUCCESS;
+                }
+            }
+
+            final List<UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
+            uidConsumers.sort(
+                    Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
+                            .reversed());
+
+            // Log single atom for BatteryUsageStats per uid/process_state/component/etc.
+            for (UidBatteryConsumer uidConsumer : uidConsumers) {
+                final int uid = uidConsumer.getUid();
+                final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
+
+                for (@BatteryConsumer.PowerComponent int componentId = 0;
+                        componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+                        componentId++) {
+
+                    for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+                        if (!addStatsForPredefinedComponent(
+                                data,
+                                sessionInfo,
+                                uid,
+                                processState,
+                                totalConsumedPowerMah,
+                                uidConsumer,
+                                componentId)) {
+                            return StatsManager.PULL_SUCCESS;
+                        }
+                    }
+                }
+
+                // looping over custom components
+                for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+                        componentId
+                                < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+                                        + customPowerComponentCount;
+                        componentId++) {
+                    for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+                        final long timeInStateMillis =
+                                uidConsumer.getTimeInProcessStateMs(processState);
+                        if (timeInStateMillis <= 0) {
+                            continue;
+                        }
+
+                        if (!addStatsForCustomComponent(
+                                data,
+                                sessionInfo,
+                                uid,
+                                processState,
+                                timeInStateMillis,
+                                totalConsumedPowerMah,
+                                uidConsumer,
+                                componentId)) {
+                            return StatsManager.PULL_SUCCESS;
+                        }
+                    }
+                }
+            }
+            return StatsManager.PULL_SUCCESS;
+        }
+
+        private static boolean addStatsForPredefinedComponent(
+                List<StatsEvent> data,
+                SessionInfo sessionInfo,
+                int uid,
+                @BatteryConsumer.ProcessState int processState,
+                float totalConsumedPowerMah,
+                BatteryConsumer batteryConsumer,
+                @BatteryConsumer.PowerComponent int componentId) {
+            final BatteryConsumer.Key key = batteryConsumer.getKey(componentId, processState);
+            if (key == null) {
+                return true;
+            }
+
+            final String powerComponentName = BatteryConsumer.powerComponentIdToString(componentId);
+            final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+            final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
+
+            if (powerMah == 0 && powerComponentDurationMillis == 0) {
+                return true;
+            }
+
+            long timeInState = 0;
+            if (batteryConsumer instanceof UidBatteryConsumer) {
+                timeInState =
+                        ((UidBatteryConsumer) batteryConsumer)
+                                .getTimeInProcessStateMs(processState);
+            }
+
+            return addStatsAtom(
+                    data,
+                    sessionInfo,
+                    uid,
+                    processState,
+                    timeInState,
+                    powerComponentName,
+                    totalConsumedPowerMah,
+                    powerMah,
+                    powerComponentDurationMillis);
+        }
+
+        private static boolean addStatsForCustomComponent(
+                List<StatsEvent> data,
+                SessionInfo sessionInfo,
+                int uid,
+                @BatteryConsumer.ProcessState int processState,
+                long timeInStateMillis,
+                float totalConsumedPowerMah,
+                BatteryConsumer batteryConsumer,
+                int componentId) {
+
+            if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+                throw new IllegalArgumentException("Invalid custom component id: " + componentId);
+            }
+
+            final float powerMah =
+                    (float) batteryConsumer.getConsumedPowerForCustomComponent(componentId);
+            if (powerMah == 0) {
+                return true;
+            }
+
+            final String powerComponentName =
+                    batteryConsumer.getCustomPowerComponentName(componentId);
+
+            final long powerComponentDurationMillis =
+                    batteryConsumer.getUsageDurationForCustomComponentMillis(componentId);
+
+            return addStatsAtom(
+                    data,
+                    sessionInfo,
+                    uid,
+                    processState,
+                    timeInStateMillis,
+                    powerComponentName,
+                    totalConsumedPowerMah,
+                    powerMah,
+                    powerComponentDurationMillis);
+        }
+
+        /**
+         * Returns true on success and false if reached max atoms capacity and no more atoms should
+         * be added
+         */
+        private static boolean addStatsAtom(
+                List<StatsEvent> data,
+                SessionInfo sessionInfo,
+                int uid,
+                int processState,
+                long timeInStateMillis,
+                String powerComponentName,
+                float totalConsumedPowerMah,
+                float powerComponentMah,
+                long powerComponentDurationMillis) {
+            data.add(
+                    FrameworkStatsLog.buildStatsEvent(
+                            FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+                            sessionInfo.startTs(),
+                            sessionInfo.endTs(),
+                            sessionInfo.duration(),
+                            sessionInfo.dischargePercentage(),
+                            sessionInfo.dischargeDuration(),
+                            uid,
+                            processState,
+                            timeInStateMillis,
+                            powerComponentName,
+                            totalConsumedPowerMah,
+                            powerComponentMah,
+                            powerComponentDurationMillis));
+
+            // Early termination due to statsd dimensions guardrail
+            if (data.size() == STATSD_METRIC_MAX_DIMENSIONS_COUNT) {
+                Slog.w(
+                        TAG,
+                        "BATTERY_USAGE_STATS_PER_UID is complete reaching"
+                                + " dimension guardrail");
+                return false;
+            }
+            return true;
+        }
+    }
+
     @Override
     @RequiresNoPermission
     public boolean isCharging() {
@@ -2824,9 +3115,11 @@
         pw.println("  --checkin: generate output for a checkin report; will write (and clear) the");
         pw.println("             last old completed stats when they had been reset.");
         pw.println("  -c: write the current stats in checkin format.");
-        pw.println("  --proto: write the current aggregate stats (without history) in proto format.");
+        pw.println(
+                "  --proto: write the current aggregate stats (without history) in proto format.");
         pw.println("  --history: show only history data.");
-        pw.println("  --history-start <num>: show only history data starting at given time offset.");
+        pw.println(
+                "  --history-start <num>: show only history data starting at given time offset.");
         pw.println("  --history-create-events <num>: create <num> of battery history events.");
         pw.println("  --charged: only output data since last charged.");
         pw.println("  --daily: only output full daily data.");
@@ -2850,12 +3143,15 @@
         pw.println("  -h: print this help text.");
         pw.println("Battery stats (batterystats) commands:");
         pw.println("  enable|disable <option>");
-        pw.println("    Enable or disable a running option.  Option state is not saved across boots.");
+        pw.println(
+                "    Enable or disable a running option.  Option state is not saved across boots.");
         pw.println("    Options are:");
         pw.println("      full-history: include additional detailed events in battery history:");
         pw.println("          wake_lock_in, alarms and proc events");
         pw.println("      no-auto-reset: don't automatically reset stats when unplugged");
-        pw.println("      pretend-screen-off: pretend the screen is off, even if screen state changes");
+        pw.println(
+                "      pretend-screen-off: pretend the screen is off, even if screen state"
+                        + " changes");
     }
 
     private void dumpSettings(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 222c5a8..12ec248 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.hardware.display.BrightnessInfo;
 import android.text.TextUtils;
 
 import com.android.server.display.brightness.BrightnessEvent;
@@ -50,6 +51,8 @@
 
     private final boolean mIsUserInitiatedChange;
 
+    private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mHdrBrightness = builder.getHdrBrightness();
@@ -64,6 +67,7 @@
         mBrightnessEvent = builder.getBrightnessEvent();
         mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
         mIsUserInitiatedChange = builder.isUserInitiatedChange();
+        mBrightnessMaxReason = builder.getBrightnessMaxReason();
     }
 
     /**
@@ -159,6 +163,13 @@
         return mIsUserInitiatedChange;
     }
 
+    /**
+     * Gets reason for max brightness restriction
+     */
+    public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+        return mBrightnessMaxReason;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -180,6 +191,8 @@
                 .append(Objects.toString(mBrightnessEvent, "null"));
         stringBuilder.append("\n    mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
         stringBuilder.append("\n    mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
+        stringBuilder.append("\n    mBrightnessMaxReason:")
+                .append(BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
         return stringBuilder.toString();
     }
 
@@ -212,7 +225,8 @@
                     == otherState.shouldUpdateScreenBrightnessSetting()
                 && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
                 && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
-                && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
+                && mIsUserInitiatedChange == otherState.isUserInitiatedChange()
+                && mBrightnessMaxReason == otherState.getBrightnessMaxReason();
     }
 
     @Override
@@ -221,7 +235,7 @@
                 mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
                 mCustomAnimationRate,
                 mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
-                mIsUserInitiatedChange);
+                mIsUserInitiatedChange, mBrightnessMaxReason);
     }
 
     /**
@@ -245,12 +259,11 @@
         private float mMinBrightness;
         private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
         private boolean mShouldUpdateScreenBrightnessSetting;
-
         private BrightnessEvent mBrightnessEvent;
-
-        public int mBrightnessAdjustmentFlag = 0;
-
+        private int mBrightnessAdjustmentFlag = 0;
         private boolean mIsUserInitiatedChange;
+        private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -274,6 +287,7 @@
             builder.setBrightnessEvent(state.getBrightnessEvent());
             builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
             builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
+            builder.setBrightnessMaxReason(state.getBrightnessMaxReason());
             return builder;
         }
 
@@ -496,5 +510,21 @@
             mIsUserInitiatedChange = isUserInitiatedChange;
             return this;
         }
+
+        /**
+         * Gets reason for max brightness restriction
+         */
+        public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+            return mBrightnessMaxReason;
+        }
+
+        /**
+         * Sets reason for max brightness restriction
+         */
+        public Builder setBrightnessMaxReason(
+                @BrightnessInfo.BrightnessMaxReason int brightnessMaxReason) {
+            mBrightnessMaxReason = brightnessMaxReason;
+            return this;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58309c2..01604b8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1580,7 +1580,7 @@
         // brightness sources (such as an app override) are not saved to the setting, but should be
         // reflected in HBM calculations.
         mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
-                mBrightnessClamperController.getBrightnessMaxReason());
+                clampedState.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
         // Skip the animation when the screen is off or suspended.
@@ -1783,7 +1783,7 @@
 
             if (userSetBrightnessChanged
                     || newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
-                logBrightnessEvent(newEvent, unthrottledBrightnessState);
+                logBrightnessEvent(newEvent, unthrottledBrightnessState, clampedState);
             }
             if (mBrightnessEventRingBuffer != null) {
                 mBrightnessEventRingBuffer.append(newEvent);
@@ -1976,6 +1976,9 @@
         synchronized (mCachedBrightnessInfo) {
             float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
             float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+            @BrightnessInfo.BrightnessMaxReason int maxReason =
+                    state != null ? state.getBrightnessMaxReason()
+                            : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
             final float minBrightness = Math.max(stateMin, Math.min(
                     mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
             final float maxBrightness = Math.min(
@@ -2002,7 +2005,7 @@
                             mBrightnessRangeController.getTransitionPoint());
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
-                            mBrightnessClamperController.getBrightnessMaxReason());
+                            maxReason);
             return changed;
         }
     }
@@ -2902,7 +2905,8 @@
         return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
     }
 
-    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
+    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness,
+            DisplayBrightnessState brightnessState) {
         int modifier = event.getReason().getModifier();
         int flags = event.getFlags();
         // It's easier to check if the brightness is at maximum level using the brightness
@@ -2939,7 +2943,7 @@
                     event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
                     event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
                     (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
-                    mBrightnessClamperController.getBrightnessMaxReason(),
+                    brightnessState.getBrightnessMaxReason(),
                     // TODO: (flc) add brightnessMinReason here too.
                     (modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
                     event.isRbcEnabled(),
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 88d2c00..d1fb009 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -139,6 +139,7 @@
         builder.setBrightness(cappedBrightness);
         builder.setMaxBrightness(mBrightnessCap);
         builder.setCustomAnimationRate(mCustomAnimationRate);
+        builder.setBrightnessMaxReason(getBrightnessMaxReason());
 
         if (mClamperType != null) {
             builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -163,19 +164,8 @@
         return builder.build();
     }
 
-    /**
-     * See BrightnessThrottler.getBrightnessMaxReason:
-     * used in:
-     * 1) DPC2.CachedBrightnessInfo to determine changes
-     * 2) DPC2.logBrightnessEvent
-     * 3) HBMController - for logging
-     * Method is called in mHandler thread (DisplayControllerHandler), in the same thread
-     * recalculateBrightnessCap and DPC2.updatePowerStateInternal are called.
-     * Should be moved to DisplayBrightnessState OR derived from DisplayBrightnessState
-     * TODO: b/263362199
-     */
     @BrightnessInfo.BrightnessMaxReason
-    public int getBrightnessMaxReason() {
+    private int getBrightnessMaxReason() {
         if (mClamperType == null) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
         } else if (mClamperType == Type.THERMAL) {
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 9968590..8ca0458 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -220,6 +220,11 @@
     }
 
     @AnyThread
+    static void onUserCreated(@UserIdInt int userId) {
+        sWriter.onUserCreated(userId);
+    }
+
+    @AnyThread
     static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
         sWriter.onUserRemoved(userId);
         ioHandler.post(() -> {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 5ab493b..9837ab1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,7 +408,8 @@
                         InputMethodManager
                                 .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
                     }
-                    mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
+                    mService.initializeImeLocked(mCurMethod, mCurToken,
+                            InputMethodBindingController.this);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked(mUserId);
                     mAutofillController.performOnCreateInlineSuggestionsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
new file mode 100644
index 0000000..b835d05
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * 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.inputmethod;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.Slog;
+
+final class InputMethodDrawsNavBarResourceMonitor {
+    private static final String TAG = "InputMethodDrawsNavBarResourceMonitor";
+
+    private static final String SYSTEM_PACKAGE_NAME = "android";
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodDrawsNavBarResourceMonitor() {
+    }
+
+    @WorkerThread
+    static boolean evaluate(@NonNull Context context, @UserIdInt int userId) {
+        final Context userAwareContext;
+        if (context.getUserId() == userId) {
+            userAwareContext = context;
+        } else {
+            userAwareContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+        }
+        try {
+            return userAwareContext.getPackageManager()
+                    .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+                    .getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed",
+                    e);
+            return false;
+        }
+    }
+
+    @FunctionalInterface
+    interface OnUpdateCallback {
+        void onUpdate(@UserIdInt int userId);
+    }
+
+    @SuppressLint("MissingPermission")
+    @AnyThread
+    static void registerCallback(@NonNull Context context, @NonNull Handler ioHandler,
+            @NonNull OnUpdateCallback callback) {
+        final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+        intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+        intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+        final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int userId = getSendingUserId();
+                callback.onUpdate(userId);
+            }
+        };
+        context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, intentFilter,
+                null /* broadcastPermission */, ioHandler, Context.RECEIVER_NOT_EXPORTED);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e1803dc..f5faeef 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,6 +67,7 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -169,14 +170,12 @@
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.input.InputManagerInternal;
@@ -202,7 +201,6 @@
 import java.util.OptionalInt;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
@@ -399,15 +397,6 @@
     @SharedByAllUsersField
     private IntArray mStylusIds;
 
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    @MultiUserUnawareField
-    private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    @MultiUserUnawareField
-    Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
     private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
         /**
          * {@inheritDoc}
@@ -484,13 +473,13 @@
     @SharedByAllUsersField
     boolean mSystemReady;
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     @NonNull
     UserDataRepository.UserData getUserData(@UserIdInt int userId) {
         return mUserDataRepository.getOrCreate(userId);
     }
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     @NonNull
     InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
         return getUserData(userId).mBindingController;
@@ -934,9 +923,13 @@
             // For production code, hook up user lifecycle
             mService.mUserManagerInternal.addUserLifecycleListener(this);
 
+            // Hook up resource change first before initializeUsersAsync() starts reading the
+            // seemingly initial data so that we can eliminate the race condition.
+            InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
+                    mService::onUpdateResourceOverlay);
+
             // Also schedule user init tasks onto an I/O thread.
-            initializeUsersAsync(context, mService.mIoHandler,
-                    mService.mUserManagerInternal.getUserIds());
+            initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
         }
 
         @VisibleForTesting
@@ -1019,7 +1012,9 @@
         @Override
         public void onUserCreated(UserInfo user, @Nullable Object token) {
             // Called directly from UserManagerService. Do not block the calling thread.
-            initializeUsersAsync(mService.mContext, mService.mIoHandler, new int[user.id]);
+            final int userId = user.id;
+            AdditionalSubtypeMapRepository.onUserCreated(userId);
+            initializeUsersAsync(new int[userId]);
         }
 
         @Override
@@ -1057,9 +1052,12 @@
         }
 
         @AnyThread
-        private static void initializeUsersAsync(
-                @NonNull Context context, @NonNull Handler ioHandler, @UserIdInt int[] userIds) {
-            ioHandler.post(() -> {
+        private void initializeUsersAsync(@UserIdInt int[] userIds) {
+            mService.mIoHandler.post(() -> {
+                final var service = mService;
+                final var context = service.mContext;
+                final var userManagerInternal = service.mUserManagerInternal;
+
                 // We first create InputMethodMap for each user without loading AdditionalSubtypes.
                 final int numUsers = userIds.length;
                 final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
@@ -1068,6 +1066,12 @@
                     rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
                             context, userId, AdditionalSubtypeMap.EMPTY_MAP,
                             DirectBootAwareness.AUTO).getMethodMap();
+                    final int profileParentId = userManagerInternal.getProfileParentId(userId);
+                    final boolean value =
+                            InputMethodDrawsNavBarResourceMonitor.evaluate(context,
+                                    profileParentId);
+                    final var userData = mService.getUserData(userId);
+                    userData.mImeDrawsNavBar.set(value);
                 }
 
                 // Then create full InputMethodMap for each user. Note that
@@ -1242,36 +1246,6 @@
         setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
     }
 
-    @GuardedBy("ImfLock.class")
-    private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) {
-        // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
-        // profile parent user.
-        // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
-        final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
-        if (mImeDrawsImeNavBarRes != null
-                && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
-            mImeDrawsImeNavBarRes.close();
-            mImeDrawsImeNavBarRes = null;
-        }
-        if (mImeDrawsImeNavBarRes == null) {
-            final Context userContext;
-            if (mContext.getUserId() == profileParentUserId) {
-                userContext = mContext;
-            } else {
-                userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
-                        0 /* flags */);
-            }
-            mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
-                    com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
-                        synchronized (ImfLock.class) {
-                            if (resource == mImeDrawsImeNavBarRes) {
-                                sendOnNavButtonFlagsChangedLocked();
-                            }
-                        }
-                    });
-        }
-    }
-
     @NonNull
     private static PackageManager getPackageManagerForUser(@NonNull Context context,
             @UserIdInt int userId) {
@@ -1304,8 +1278,6 @@
 
         // Hereafter we start initializing things for "newUserId".
 
-        maybeInitImeNavbarConfigLocked(newUserId);
-
         final var newUserData = getUserData(newUserId);
 
         // TODO(b/342027196): Double check if we need to always reset upon user switching.
@@ -1384,23 +1356,6 @@
                     });
                 }
 
-                // TODO(b/32343335): The entire systemRunning() method needs to be revisited.
-                mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> {
-                    // Note that the synchronization block below guarantees that the task
-                    // can never be completed before the returned Future<?> object is assigned to
-                    // the "mImeDrawsImeNavBarResLazyInitFuture" field.
-                    synchronized (ImfLock.class) {
-                        mImeDrawsImeNavBarResLazyInitFuture = null;
-                        if (currentUserId != mCurrentUserId) {
-                            // This means that the current user is already switched to other user
-                            // before the background task is executed. In this scenario the relevant
-                            // field should already be initialized.
-                            return;
-                        }
-                        maybeInitImeNavbarConfigLocked(currentUserId);
-                    }
-                }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
-
                 mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
                 SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
                         new String[] {
@@ -1436,9 +1391,7 @@
                         getPackageManagerForUser(mContext, currentUserId),
                         newSettings.getEnabledInputMethodList());
 
-                final var unused = SystemServerInitThreadPool.submit(
-                        AdditionalSubtypeMapRepository::startWriterThread,
-                        "Start AdditionalSubtypeMapRepository's writer thread");
+                AdditionalSubtypeMapRepository.startWriterThread();
 
                 if (mConcurrentMultiUserModeEnabled) {
                     for (int userId : mUserManagerInternal.getUserIds()) {
@@ -1922,7 +1875,8 @@
                     userData.mCurClient.mUid, true /* direct */);
         }
 
-        @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+        @InputMethodNavButtonFlags final int navButtonFlags =
+                getInputMethodNavButtonFlagsLocked(userData);
         final SessionState session = userData.mCurClient.mCurSession;
         setEnabledSessionLocked(session, userData);
         session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
@@ -2314,15 +2268,15 @@
 
     @GuardedBy("ImfLock.class")
     void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
-            @UserIdInt int userId) {
+            @NonNull InputMethodBindingController bindingController) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
-                    + getInputMethodBindingController(userId).getCurTokenDisplayId());
+                    + bindingController.getCurTokenDisplayId());
         }
+        final int userId = bindingController.getUserId();
         inputMethod.initializeInternal(token,
                 new InputMethodPrivilegedOperationsImpl(this, token, userId),
-                // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
-                getInputMethodNavButtonFlagsLocked());
+                getInputMethodNavButtonFlagsLocked(getUserData(userId)));
     }
 
     @AnyThread
@@ -2621,23 +2575,17 @@
 
     @GuardedBy("ImfLock.class")
     @InputMethodNavButtonFlags
-    private int getInputMethodNavButtonFlagsLocked() {
-        // TODO(b/345519864): Make mImeDrawsImeNavBarRes multi-user aware.
-        final int userId = mCurrentUserId;
-        final var bindingController = getInputMethodBindingController(userId);
-        if (mImeDrawsImeNavBarResLazyInitFuture != null) {
-            // TODO(b/225366708): Avoid Future.get(), which is internally used here.
-            ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
-                    "Waiting for the lazy init of mImeDrawsImeNavBarRes");
-        }
+    private int getInputMethodNavButtonFlagsLocked(
+            @NonNull UserDataRepository.UserData userData) {
+        final int userId = userData.mUserId;
+        final var bindingController = userData.mBindingController;
         // Whether the current display has a navigation bar. When this is false (e.g. emulator),
         // the IME should not draw the IME navigation bar.
         final int tokenDisplayId = bindingController.getCurTokenDisplayId();
         final boolean hasNavigationBar = mWindowManagerInternal
                 .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
                         ? tokenDisplayId : DEFAULT_DISPLAY);
-        final boolean canImeDrawsImeNavBar =
-                mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
+        final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
         final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
                 InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
         return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2981,7 +2929,7 @@
         final var userData = getUserData(userId);
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
-        sendOnNavButtonFlagsChangedLocked();
+        sendOnNavButtonFlagsChangedLocked(userData);
     }
 
     @GuardedBy("ImfLock.class")
@@ -5005,7 +4953,7 @@
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
                 mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
                 synchronized (ImfLock.class) {
-                    sendOnNavButtonFlagsChangedLocked();
+                    sendOnNavButtonFlagsChangedToAllImesLocked();
                 }
                 return true;
             case MSG_SYSTEM_UNLOCK_USER: {
@@ -5339,7 +5287,7 @@
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
 
-        sendOnNavButtonFlagsChangedLocked();
+        sendOnNavButtonFlagsChangedLocked(userData);
 
         // Notify InputMethodListListeners of the new installed InputMethods.
         final List<InputMethodInfo> inputMethodList = settings.getMethodList();
@@ -5348,14 +5296,38 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void sendOnNavButtonFlagsChangedLocked() {
-        final var bindingController = getInputMethodBindingController(mCurrentUserId);
+    void sendOnNavButtonFlagsChangedToAllImesLocked() {
+        for (int userId : mUserManagerInternal.getUserIds()) {
+            sendOnNavButtonFlagsChangedLocked(getUserData(userId));
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
+    void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+        final var bindingController = userData.mBindingController;
         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod == null) {
             // No need to send the data if the IME is not yet bound.
             return;
         }
-        curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
+        curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
+    }
+
+    @WorkerThread
+    private void onUpdateResourceOverlay(@UserIdInt int userId) {
+        final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
+        final boolean value =
+                InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
+        final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
+        final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+        for (int profileUserId : profileUserIds) {
+            final var userData = getUserData(profileUserId);
+            userData.mImeDrawsNavBar.set(value);
+            updatedUsers.add(userData);
+        }
+        synchronized (ImfLock.class) {
+            updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
+        }
     }
 
     @GuardedBy("ImfLock.class")
@@ -6123,6 +6095,7 @@
                         u.mImeBindingState.dump("        ", p);
                         p.println("      enabledSession=" + u.mEnabledSession);
                         p.println("      inFullscreenMode=" + u.mInFullscreenMode);
+                        p.println("      imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
                         p.println("      switchingController:");
                         u.mSwitchingController.dump(p, "        ");
                         p.println("      mLastEnabledInputMethodsStr="
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 656c87d..06f73f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -202,7 +202,7 @@
         attrs.setTitle("Select input method");
         w.setAttributes(attrs);
         mService.updateSystemUiLocked(userId);
-        mService.sendOnNavButtonFlagsChangedLocked();
+        mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId));
         mSwitchingDialog.show();
     }
 
@@ -242,7 +242,7 @@
             // TODO(b/305849394): Make InputMethodMenuController multi-user aware
             final int userId = mService.getCurrentImeUserIdLocked();
             mService.updateSystemUiLocked(userId);
-            mService.sendOnNavButtonFlagsChangedLocked();
+            mService.sendOnNavButtonFlagsChangedToAllImesLocked();
             mDialogBuilder = null;
             mIms = null;
         }
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
deleted file mode 100644
index 33e7a76..0000000
--- a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-
-import android.annotation.AnyThread;
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
-import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.PatternMatcher;
-import android.util.Slog;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-/**
- * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
- * aware of per-user Runtime Resource Overlay (RRO).
- */
-final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
-    private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
-
-    private static final String SYSTEM_PACKAGE_NAME = "android";
-
-    @UserIdInt
-    private final int mUserId;
-    @NonNull
-    private final AtomicBoolean mValueRef;
-    @NonNull
-    private final AtomicReference<Runnable> mCleanerRef;
-
-    /**
-     * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
-     * with a value change callback for the user associated with the {@link Context}.
-     *
-     * @param userContext The {@link Context} to be used to access the resource. This needs to be
-     *                    associated with the right user because the Runtime Resource Overlay (RRO)
-     *                    is per-user configuration.
-     * @param boolResId The resource ID to be queried.
-     * @param handler {@link Handler} to be used to dispatch {@code callback}.
-     * @param callback The callback to be notified when the specified value might be updated.
-     *                 The callback needs to take care of spurious wakeup. The value returned from
-     *                 {@link #get()} may look to be exactly the same as the previously read value
-     *                 e.g. when the value is changed from {@code false} to {@code true} to
-     *                 {@code false} in a very short period of time, because {@link #get()} always
-     *                 does volatile-read.
-     * @return New {@link OverlayableSystemBooleanResourceWrapper}.
-     */
-    @NonNull
-    @UserHandleAware
-    static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
-            @BoolRes int boolResId, @NonNull Handler handler,
-            @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
-
-        // Note that we cannot fully trust this initial value due to the dead time between obtaining
-        // the value here and setting up a broadcast receiver for change callback below.
-        // We will refresh the value again later after setting up the change callback anyway.
-        final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
-
-        final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
-
-        final OverlayableSystemBooleanResourceWrapper object =
-                new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
-                        cleanerRef);
-
-        final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
-        intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
-        intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
-
-        final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final boolean newValue = evaluate(userContext, boolResId);
-                if (newValue != valueRef.getAndSet(newValue)) {
-                    callback.accept(object);
-                }
-            }
-        };
-        userContext.registerReceiver(broadcastReceiver, intentFilter,
-                null /* broadcastPermission */, handler,
-                Context.RECEIVER_NOT_EXPORTED);
-        cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
-
-        // Make sure that the initial observable value is obtained after the change callback is set.
-        valueRef.set(evaluate(userContext, boolResId));
-        return object;
-    }
-
-    private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
-            @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
-        mUserId = userId;
-        mValueRef = valueRef;
-        mCleanerRef = cleanerRef;
-    }
-
-    /**
-     * @return The boolean resource value.
-     */
-    @AnyThread
-    boolean get() {
-        return mValueRef.get();
-    }
-
-    /**
-     * @return The user ID associated with this resource reader.
-     */
-    @AnyThread
-    @UserIdInt
-    int getUserId() {
-        return mUserId;
-    }
-
-    @AnyThread
-    private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
-        try {
-            return context.getPackageManager()
-                    .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
-                    .getBoolean(boolResId);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
-            return false;
-        }
-    }
-
-    /**
-     * Cleans up the callback.
-     */
-    @AnyThread
-    @Override
-    public void close() {
-        final Runnable cleaner = mCleanerRef.getAndSet(null);
-        if (cleaner != null) {
-            cleaner.run();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 98d7548..7c68d54 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -29,6 +29,7 @@
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
@@ -181,6 +182,12 @@
         String mLastEnabledInputMethodsStr = "";
 
         /**
+         * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+         */
+        @NonNull
+        final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+        /**
          * Intended to be instantiated only from this file.
          */
         private UserData(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 56e4590..46585a5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.KeepForWeakReference;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.util.ArrayUtils;
@@ -261,6 +262,7 @@
 
     private final OverlayActorEnforcer mActorEnforcer;
 
+    @KeepForWeakReference
     private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
 
     private int mPrevStartedUserId = -1;
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6a5a7ac..d34498a 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -47,3 +47,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "add_battery_usage_stats_slice_atom"
+    namespace: "backstage_power"
+    description: "Adds battery_usage_stats_slice atom"
+    bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8768074..3076b87 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7173,7 +7173,7 @@
                 Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
                         + ", isAnimationSet=" + isAnimationSet);
                 if (!w.isDrawn()) {
-                    Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+                    Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
                             + " pv=" + w.isVisibleByPolicy()
                             + " mDrawState=" + winAnimator.drawStateToString()
                             + " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59b5da8..ff46b33 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4709,6 +4709,10 @@
         // Update stored global config and notify everyone about the change.
         mRootWindowContainer.onConfigurationChanged(mTempConfig);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+                    values.orientation);
+        }
 
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         return changes;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3652c4d..9371149 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4082,12 +4082,12 @@
         final Transaction t = mWmService.mTransactionFactory.get();
         forAllWindows(w -> {
             final WindowStateAnimator wsa = w.mWinAnimator;
-            if (wsa.mSurfaceController == null) {
+            if (wsa.mSurfaceControl == null) {
                 return;
             }
             if (!mWmService.mSessions.contains(wsa.mSession)) {
                 Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
-                        + w + " surface=" + wsa.mSurfaceController
+                        + w + " surface=" + wsa.mSurfaceControl
                         + " token=" + w.mToken
                         + " pid=" + w.mSession.mPid
                         + " uid=" + w.mSession.mUid);
@@ -4096,7 +4096,7 @@
                 mTmpWindow = w;
             } else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) {
                 Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
-                        + w + " surface=" + wsa.mSurfaceController
+                        + w + " surface=" + wsa.mSurfaceControl
                         + " token=" + w.mActivityRecord);
                 ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w);
                 wsa.destroySurface(t);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 74dbd15..b496a65 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -623,7 +623,7 @@
                     // occlusion detection depending on the type or if it's a trusted overlay.
                     populateOverlayInputInfo(inputWindowHandle, w);
                     setInputWindowInfoIfNeeded(mInputTransaction,
-                            w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+                            w.mWinAnimator.mSurfaceControl, inputWindowHandle);
                     return;
                 }
                 // Skip this window because it cannot possibly receive input.
@@ -687,7 +687,7 @@
             if (w.mWinAnimator.hasSurface()) {
                 populateInputWindowHandle(inputWindowHandle, w);
                 setInputWindowInfoIfNeeded(mInputTransaction,
-                        w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+                        w.mWinAnimator.mSurfaceControl, inputWindowHandle);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 99697de..f2ccbc4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -667,7 +667,7 @@
 
     boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
             boolean secure) {
-        final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+        final SurfaceControl surfaceControl = winAnimator.mSurfaceControl;
         boolean leakedSurface = false;
         boolean killedApps = false;
         EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(),
@@ -692,7 +692,7 @@
                             return;
                         }
                         final WindowStateAnimator wsa = w.mWinAnimator;
-                        if (wsa.mSurfaceController != null) {
+                        if (wsa.mSurfaceControl != null) {
                             pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
                         }
                     }, false /* traverseTopToBottom */);
@@ -717,7 +717,7 @@
                 // app to request another one.
                 Slog.w(TAG_WM,
                         "Looks like we have reclaimed some memory, clearing surface for retry.");
-                if (surfaceController != null) {
+                if (surfaceControl != null) {
                     ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
                             "SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
                     SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 31fda77..db0374e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -324,7 +324,7 @@
             if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
                 return;
             }
-            t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot);
+            t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
         }, false);
         if (!skipScreenshot) {
             // Use sync apply to apply the change immediately, so that the next
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 75e3e65..f5108f5b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -109,8 +109,8 @@
     private final String mStringName;
     SurfaceSession mSurfaceSession;
     private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
-    /** Set of visible alert/app-overlay window surfaces connected to this session. */
-    private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
+    /** Set of visible alert/app-overlay windows connected to this session. */
+    private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
     private final DragDropController mDragDropController;
     final boolean mCanAddInternalSystemWindow;
     boolean mCanForceShowingInsets;
@@ -769,9 +769,8 @@
         return !mAddedWindows.isEmpty();
     }
 
-    void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
-            boolean visible, int type) {
-
+    void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
+        final int type = window.mAttrs.type;
         if (!isSystemAlertWindowType(type)) {
             return;
         }
@@ -782,7 +781,7 @@
         final boolean noSystemOverlayPermission =
                 !mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
         if (visible) {
-            changed = mAlertWindowSurfaces.add(surfaceController);
+            changed = mAlertWindows.add(window);
             if (type == TYPE_APPLICATION_OVERLAY) {
                 MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
                         false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -791,7 +790,7 @@
                         true /* only log for non-TYPE_APPLICATION_OVERLAY */);
             }
         } else {
-            changed = mAlertWindowSurfaces.remove(surfaceController);
+            changed = mAlertWindows.remove(window);
             if (type == TYPE_APPLICATION_OVERLAY) {
                 MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
                         false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -802,7 +801,7 @@
         }
 
         if (changed && noSystemOverlayPermission) {
-            if (mAlertWindowSurfaces.isEmpty()) {
+            if (mAlertWindows.isEmpty()) {
                 cancelAlertWindowNotification();
             } else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) {
                 mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
@@ -815,7 +814,7 @@
         if (changed && mPid != WindowManagerService.MY_PID) {
             // Notify activity manager that the process contains overlay/alert windows, so it can
             // adjust the importance score for the process.
-            setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
+            setHasOverlayUi(!mAlertWindows.isEmpty());
         }
     }
 
@@ -859,7 +858,7 @@
         }
         mSurfaceSession = null;
         mAddedWindows.clear();
-        mAlertWindowSurfaces.clear();
+        mAlertWindows.clear();
         setHasOverlayUi(false);
         cancelAlertWindowNotification();
     }
@@ -880,7 +879,7 @@
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
                 pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
-                pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+                pw.print(" mAlertWindows="); pw.print(mAlertWindows);
                 pw.print(" mClientDead="); pw.print(mClientDead);
                 pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
         pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
@@ -896,9 +895,9 @@
 
     /** @return {@code true} if there is an alert window surface on the given display. */
     boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
-        for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
-            final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
-            if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+        for (int i = mAlertWindows.size() - 1; i >= 0; i--) {
+            final WindowState window = mAlertWindows.valueAt(i);
+            if (window.mDisplayContent == displayContent) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 439c7bb..561ff7d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -1169,16 +1169,6 @@
         }
     }
 
-    @Override
-    public boolean isActivityEmbedded(IBinder activityToken) {
-        synchronized (mGlobalLock) {
-            final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
-            return activity != null
-                    ? activity.isEmbeddedInHostContainer()
-                    : false;
-        }
-    }
-
     @VisibleForTesting
     @NonNull
     IApplicationThread getAppThread(int pid, int uid) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 700c069..ebbf6e3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2570,7 +2570,7 @@
                         // surface, let the client use that, but don't create new surface at this
                         // point.
                         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
-                        winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
+                        winAnimator.getSurfaceControl(outSurfaceControl);
                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     } else {
                         if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2766,15 +2766,15 @@
             result |= RELAYOUT_RES_SURFACE_CHANGED;
         }
 
-        WindowSurfaceController surfaceController;
+        SurfaceControl surfaceControl;
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
-            surfaceController = winAnimator.createSurfaceLocked();
+            surfaceControl = winAnimator.createSurfaceLocked();
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
-        if (surfaceController != null) {
-            surfaceController.getSurfaceControl(outSurfaceControl);
+        if (surfaceControl != null) {
+            winAnimator.getSurfaceControl(outSurfaceControl);
             ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
 
         } else {
@@ -6773,11 +6773,11 @@
             if (windowState == null) {
                 return false;
             }
-            WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
-            if (surfaceController == null) {
+            final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+            if (surfaceControl == null) {
                 return false;
             }
-            return surfaceController.clearWindowContentFrameStats();
+            return surfaceControl.clearContentFrameStats();
         }
     }
 
@@ -6792,15 +6792,15 @@
             if (windowState == null) {
                 return null;
             }
-            WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
-            if (surfaceController == null) {
+            final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+            if (surfaceControl == null) {
                 return null;
             }
             if (mTempWindowRenderStats == null) {
                 mTempWindowRenderStats = new WindowContentFrameStats();
             }
             WindowContentFrameStats stats = mTempWindowRenderStats;
-            if (!surfaceController.getWindowContentFrameStats(stats)) {
+            if (!surfaceControl.getContentFrameStats(stats)) {
                 return null;
             }
             return stats;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 390e34c..9ebb89d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1501,7 +1501,7 @@
             if (isDrawn()) {
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
                         "Orientation not waiting for draw in %s, surfaceController %s", this,
-                        winAnimator.mSurfaceController);
+                        winAnimator.mSurfaceControl);
                 setOrientationChanging(false);
                 mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
                         - mWmService.mDisplayFreezeTime);
@@ -2425,7 +2425,7 @@
 
         ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
                     System.identityHashCode(mClient.asBinder()),
-                    mWinAnimator.mSurfaceController,
+                    mWinAnimator.mSurfaceControl,
                     Debug.getCallers(5));
 
         final DisplayContent displayContent = getDisplayContent();
@@ -2436,10 +2436,10 @@
             mOnBackInvokedCallbackInfo = null;
 
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                    "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+                    "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
                             + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
                             + "mDisplayFrozen=%b callers=%s",
-                    this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
+                    this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
                     mHasSurface, mWinAnimator.getShown(),
                     isAnimating(TRANSITION | PARENTS),
                     mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
@@ -2616,7 +2616,7 @@
                     + isVisibleRequestedOrAdding() + " isVisible: " + (isVisible()
                     && mActivityRecord != null && mActivityRecord.isVisible()));
             if (!isVisibleRequestedOrAdding()) {
-                Slog.i(TAG_WM, "  mSurfaceController=" + mWinAnimator.mSurfaceController
+                Slog.i(TAG_WM, "  mSurfaceControl=" + mWinAnimator.mSurfaceControl
                         + " relayoutCalled=" + mRelayoutCalled
                         + " viewVis=" + mViewVisibility
                         + " policyVis=" + isVisibleByPolicy()
@@ -4457,7 +4457,7 @@
 
             for (int i = mChildren.size() - 1; i >= 0; --i) {
                 final WindowState c = mChildren.get(i);
-                if (c.mWinAnimator.mSurfaceController != null) {
+                if (c.mWinAnimator.mSurfaceControl != null) {
                     c.performShowLocked();
                     // It hadn't been shown, which means layout not performed on it, so now we
                     // want to make sure to do a layout.  If called from within the transaction
@@ -4914,7 +4914,7 @@
             Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
                     + ", animating=" + isAnimating(TRANSITION | PARENTS));
             if (!isDrawn()) {
-                Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+                Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl
                         + " pv=" + isVisibleByPolicy()
                         + " mDrawState=" + mWinAnimator.mDrawState
                         + " ph=" + isParentWindowHidden()
@@ -5535,13 +5535,13 @@
             // been defined and so we can use static layers and leave it that way.
             if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
                 if (mWinAnimator.hasSurface()) {
-                    w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
+                    w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2);
                 } else {
                     w.assignLayer(t, -2);
                 }
             } else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
                 if (mWinAnimator.hasSurface()) {
-                    w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
+                    w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1);
                 } else {
                     w.assignLayer(t, -1);
                 }
@@ -5717,7 +5717,7 @@
     }
 
     SurfaceControl getClientViewRootSurface() {
-        return mWinAnimator.getSurfaceControl();
+        return mWinAnimator.mSurfaceControl;
     }
 
     /** Drops a buffer for this window's view-root from a transaction */
@@ -6117,11 +6117,10 @@
             }
             getPendingTransaction().setSecure(mSurfaceControl, isSecure);
         } else {
-            if (mWinAnimator.mSurfaceController == null
-                    || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+            if (mWinAnimator.mSurfaceControl == null) {
                 return;
             }
-            getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+            getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
                     isSecure);
         }
         if (mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 397a6357..24a2a62 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.SurfaceControl.METADATA_OWNER_PID;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -44,6 +48,7 @@
 import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
 import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
 import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
 import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
 
@@ -52,6 +57,7 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Trace;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface.OutOfResourcesException;
@@ -95,12 +101,13 @@
     final Session mSession;
     final WindowManagerPolicy mPolicy;
     final Context mContext;
-    final boolean mIsWallpaper;
     private final WallpaperController mWallpaperControllerLocked;
 
     boolean mAnimationIsEntrance;
 
-    WindowSurfaceController mSurfaceController;
+    SurfaceControl mSurfaceControl;
+    private boolean mSurfaceShown;
+    private String mTitle;
 
     float mShownAlpha = 0;
     float mAlpha = 0;
@@ -164,7 +171,6 @@
         mWin = win;
         mSession = win.mSession;
         mAttrType = win.mAttrs.type;
-        mIsWallpaper = win.mIsWallpaper;
         mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController;
     }
 
@@ -198,14 +204,30 @@
     }
 
     void hide(SurfaceControl.Transaction transaction, String reason) {
-        if (!mLastHidden) {
-            //dump();
-            mLastHidden = true;
-
-            if (mSurfaceController != null) {
-                mSurfaceController.hide(transaction, reason);
-            }
+        if (mLastHidden) {
+            return;
         }
+        mLastHidden = true;
+        if (mSurfaceControl == null || !mSurfaceShown) {
+            return;
+        }
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle);
+
+        setShown(false);
+        transaction.hide(mSurfaceControl);
+        if (mWin.mIsWallpaper) {
+            final DisplayContent dc = mWin.getDisplayContent();
+            EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+                    dc.mDisplayId, 0 /* request hidden */,
+                    String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+        }
+    }
+
+    private void setShown(boolean surfaceShown) {
+        mSurfaceShown = surfaceShown;
+        mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown);
+        mWin.onSurfaceShownChanged(surfaceShown);
+        mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown);
     }
 
     boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
@@ -221,7 +243,7 @@
         if (mDrawState == DRAW_PENDING) {
             ProtoLog.v(WM_DEBUG_DRAW,
                     "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
-                    mSurfaceController);
+                    mSurfaceControl);
             if (startingWindow) {
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
             }
@@ -248,7 +270,7 @@
             return false;
         }
         ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
-                mSurfaceController);
+                mSurfaceControl);
         mDrawState = READY_TO_SHOW;
         boolean result = false;
         final ActivityRecord activity = mWin.mActivityRecord;
@@ -271,11 +293,11 @@
         }
     }
 
-    WindowSurfaceController createSurfaceLocked() {
+    SurfaceControl createSurfaceLocked() {
         final WindowState w = mWin;
 
-        if (mSurfaceController != null) {
-            return mSurfaceController;
+        if (mSurfaceControl != null) {
+            return mSurfaceControl;
         }
 
         w.setHasSurface(false);
@@ -312,10 +334,22 @@
             final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
             final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
 
-            mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
-                    flags, this, attrs.type);
+            mTitle = attrs.getTitle().toString();
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+            mSurfaceControl = mWin.makeSurface()
+                    .setParent(mWin.mSurfaceControl)
+                    .setName(mTitle)
+                    .setFormat(format)
+                    .setFlags(flags)
+                    .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
+                    .setMetadata(METADATA_OWNER_UID, mSession.mUid)
+                    .setMetadata(METADATA_OWNER_PID, mSession.mPid)
+                    .setCallsite("WindowSurfaceController")
+                    .setBLASTLayer().build();
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
             if (!setScPropertiesInClient()) {
-                mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+                setColorSpaceAgnosticLocked(
                         (attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
             }
 
@@ -326,7 +360,7 @@
 
             ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
                         "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
-                        mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
+                    mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
                         flags, this);
         } catch (OutOfResourcesException e) {
             Slog.w(TAG, "OutOfResourcesException creating surface");
@@ -340,7 +374,7 @@
         }
 
         if (DEBUG) {
-            Slog.v(TAG, "Got surface: " + mSurfaceController
+            Slog.v(TAG, "Got surface: " + mSurfaceControl
                     + ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
         }
 
@@ -353,15 +387,19 @@
         mLastHidden = true;
 
         if (DEBUG) Slog.v(TAG, "Created surface " + this);
-        return mSurfaceController;
+        return mSurfaceControl;
     }
 
     boolean hasSurface() {
-        return mSurfaceController != null && mSurfaceController.hasSurface();
+        return mSurfaceControl != null;
+    }
+
+    void getSurfaceControl(SurfaceControl outSurfaceControl) {
+        outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl");
     }
 
     void destroySurfaceLocked(SurfaceControl.Transaction t) {
-        if (mSurfaceController == null) {
+        if (mSurfaceControl == null) {
             return;
         }
 
@@ -370,7 +408,7 @@
         try {
             if (DEBUG_VISIBILITY) {
                 logWithStack(TAG, "Window " + this + " destroying surface "
-                        + mSurfaceController + ", session " + mSession);
+                        + mSurfaceControl + ", session " + mSession);
             }
             ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
                     mWin, new RuntimeException().fillInStackTrace());
@@ -384,23 +422,13 @@
             }
         } catch (RuntimeException e) {
             Slog.w(TAG, "Exception thrown when destroying Window " + this
-                    + " surface " + mSurfaceController + " session " + mSession + ": "
+                    + " surface " + mSurfaceControl + " session " + mSession + ": "
                     + e.toString());
         }
-
-        // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
-        // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
-        // so it can be recreated successfully in mPendingDestroySurface case.
-        mWin.setHasSurface(false);
-        if (mSurfaceController != null) {
-            mSurfaceController.setShown(false);
-        }
-        mSurfaceController = null;
-        mDrawState = NO_SURFACE;
     }
 
     void computeShownFrameLocked() {
-        if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+        if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
             return;
         } else if (mWin.isDragResizeChanged()) {
             // This window is awaiting a relayout because user just started (or ended)
@@ -454,14 +482,13 @@
             mLastAlpha = mShownAlpha;
             ProtoLog.i(WM_SHOW_TRANSACTIONS,
                     "SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
-                    mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
+                    mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w);
 
-            boolean prepared =
-                mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
+            t.setAlpha(mSurfaceControl, mShownAlpha);
 
-            if (prepared && mDrawState == HAS_DRAWN) {
+            if (mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
-                    mSurfaceController.showRobustly(t);
+                    showRobustly(t);
                     mLastHidden = false;
                     final DisplayContent displayContent = w.getDisplayContent();
                     if (!displayContent.getLastHasContent()) {
@@ -494,18 +521,38 @@
         }
     }
 
-    void setOpaqueLocked(boolean isOpaque) {
-        if (mSurfaceController == null) {
+    private void showRobustly(SurfaceControl.Transaction t) {
+        if (mSurfaceShown) {
             return;
         }
-        mSurfaceController.setOpaque(isOpaque);
+
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle);
+        if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+        setShown(true);
+        t.show(mSurfaceControl);
+        if (mWin.mIsWallpaper) {
+            final DisplayContent dc = mWin.mDisplayContent;
+            EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+                    dc.mDisplayId, 1 /* request shown */,
+                    String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+        }
+    }
+
+    void setOpaqueLocked(boolean isOpaque) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle);
+        mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+        mService.scheduleAnimationLocked();
     }
 
     void setColorSpaceAgnosticLocked(boolean agnostic) {
-        if (mSurfaceController == null) {
+        if (mSurfaceControl == null) {
             return;
         }
-        mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle);
+        mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
     }
 
     void applyEnterAnimationLocked() {
@@ -521,7 +568,7 @@
         // should be controlled by ActivityRecord in general. Wallpaper is also excluded because
         // WallpaperController should handle it. Also skip play enter animation for the window
         // below starting window.
-        if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+        if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper
                 && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
             applyAnimationLocked(transit, true);
         }
@@ -614,8 +661,10 @@
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        if (mSurfaceController != null) {
-            mSurfaceController.dumpDebug(proto, SURFACE);
+        if (mSurfaceControl != null) {
+            final long dumpToken = proto.start(SURFACE);
+            proto.write(SHOWN, mSurfaceShown);
+            proto.end(dumpToken);
         }
         proto.write(DRAW_STATE, mDrawState);
         mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT);
@@ -626,8 +675,11 @@
         if (mAnimationIsEntrance) {
             pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
         }
-        if (mSurfaceController != null) {
-            mSurfaceController.dump(pw, prefix, dumpAll);
+        if (mSurfaceControl != null) {
+            if (dumpAll) {
+                pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+            }
+            pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
         }
         if (dumpAll) {
             pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
@@ -659,31 +711,24 @@
     }
 
     boolean getShown() {
-        if (mSurfaceController != null) {
-            return mSurfaceController.getShown();
-        }
-        return false;
+        return mSurfaceControl != null && mSurfaceShown;
     }
 
     void destroySurface(SurfaceControl.Transaction t) {
-        try {
-            if (mSurfaceController != null) {
-                mSurfaceController.destroy(t);
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Exception thrown when destroying surface " + this
-                    + " surface " + mSurfaceController + " session " + mSession + ": " + e);
-        } finally {
-            mWin.setHasSurface(false);
-            mSurfaceController = null;
-            mDrawState = NO_SURFACE;
+        if (mSurfaceControl == null) {
+            return;
         }
-    }
-
-    SurfaceControl getSurfaceControl() {
-        if (!hasSurface()) {
-            return null;
+        ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
+                "Destroying surface %s called by %s", this, Debug.getCallers(8));
+        if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) {
+            // The wallpaper surface should have the same lifetime as its window.
+            Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin
+                    + " by " + Debug.getCallers(8));
         }
-        return mSurfaceController.mSurfaceControl;
+        t.remove(mSurfaceControl);
+        setShown(false);
+        mSurfaceControl = null;
+        mWin.setHasSurface(false);
+        mDrawState = NO_SURFACE;
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
deleted file mode 100644
index d9766e0..0000000
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.SurfaceControl.METADATA_OWNER_PID;
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-
-import android.os.Debug;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.WindowContentFrameStats;
-
-import com.android.internal.protolog.ProtoLog;
-
-import java.io.PrintWriter;
-
-class WindowSurfaceController {
-    static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
-
-    final WindowStateAnimator mAnimator;
-
-    SurfaceControl mSurfaceControl;
-
-    // Should only be set from within setShown().
-    private boolean mSurfaceShown = false;
-
-    private final String title;
-
-    private final WindowManagerService mService;
-
-    private final int mWindowType;
-    private final Session mWindowSession;
-
-
-    WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
-            int windowType) {
-        mAnimator = animator;
-
-        title = name;
-
-        mService = animator.mService;
-        final WindowState win = animator.mWin;
-        mWindowType = windowType;
-        mWindowSession = win.mSession;
-
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
-        mSurfaceControl = win.makeSurface()
-                .setParent(win.getSurfaceControl())
-                .setName(name)
-                .setFormat(format)
-                .setFlags(flags)
-                .setMetadata(METADATA_WINDOW_TYPE, windowType)
-                .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
-                .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
-                .setCallsite("WindowSurfaceController")
-                .setBLASTLayer().build();
-
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-    }
-
-    void hide(SurfaceControl.Transaction transaction, String reason) {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
-
-        if (mSurfaceShown) {
-            hideSurface(transaction);
-        }
-    }
-
-    private void hideSurface(SurfaceControl.Transaction transaction) {
-        if (mSurfaceControl == null) {
-            return;
-        }
-        setShown(false);
-        try {
-            transaction.hide(mSurfaceControl);
-            if (mAnimator.mIsWallpaper) {
-                final DisplayContent dc = mAnimator.mWin.getDisplayContent();
-                EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
-                        dc.mDisplayId, 0 /* request hidden */,
-                        String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Exception hiding surface in " + this);
-        }
-    }
-
-    void destroy(SurfaceControl.Transaction t) {
-        ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
-                "Destroying surface %s called by %s", this, Debug.getCallers(8));
-        try {
-            if (mSurfaceControl != null) {
-                if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
-                        && !mAnimator.mWin.mRemoveOnExit) {
-                    // The wallpaper surface should have the same lifetime as its window.
-                    Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
-                            + " by " + Debug.getCallers(8));
-                }
-                t.remove(mSurfaceControl);
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Error destroying surface in: " + this, e);
-        } finally {
-            setShown(false);
-            mSurfaceControl = null;
-        }
-    }
-
-    boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) {
-        if (mSurfaceControl == null) {
-            return false;
-        }
-
-        t.setAlpha(mSurfaceControl, alpha);
-        return true;
-    }
-
-    void setOpaque(boolean isOpaque) {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
-
-        if (mSurfaceControl == null) {
-            return;
-        }
-
-        mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
-        mService.scheduleAnimationLocked();
-    }
-
-    void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
-
-        if (mSurfaceControl == null) {
-            return;
-        }
-        t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
-    }
-
-    void showRobustly(SurfaceControl.Transaction t) {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
-        if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
-                + " during relayout");
-
-        if (mSurfaceShown) {
-            return;
-        }
-
-        setShown(true);
-        t.show(mSurfaceControl);
-        if (mAnimator.mIsWallpaper) {
-            final DisplayContent dc = mAnimator.mWin.getDisplayContent();
-            EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
-                    dc.mDisplayId, 1 /* request shown */,
-                    String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
-        }
-    }
-
-    boolean clearWindowContentFrameStats() {
-        if (mSurfaceControl == null) {
-            return false;
-        }
-        return mSurfaceControl.clearContentFrameStats();
-    }
-
-    boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
-        if (mSurfaceControl == null) {
-            return false;
-        }
-        return mSurfaceControl.getContentFrameStats(outStats);
-    }
-
-    boolean hasSurface() {
-        return mSurfaceControl != null;
-    }
-
-    void getSurfaceControl(SurfaceControl outSurfaceControl) {
-        outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
-    }
-
-    boolean getShown() {
-        return mSurfaceShown;
-    }
-
-    void setShown(boolean surfaceShown) {
-        mSurfaceShown = surfaceShown;
-
-        mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
-
-        mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
-
-        if (mWindowSession != null) {
-            mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
-        }
-    }
-
-    void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-        proto.write(SHOWN, mSurfaceShown);
-        proto.end(token);
-    }
-
-    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
-        if (dumpAll) {
-            pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
-        }
-        pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
-    }
-
-    @Override
-    public String toString() {
-        return mSurfaceControl.toString();
-    }
-}
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 8ff1a7d..114fe32 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,9 +4,4 @@
         <version>2</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
-    <hal format="aidl">
-        <name>android.frameworks.vibrator</name>
-        <version>1</version>
-        <fqname>IVibratorControlService/default</fqname>
-    </hal>
 </manifest>
diff --git a/services/manifest_services_android.frameworks.vibrator.xml b/services/manifest_services_android.frameworks.vibrator.xml
new file mode 100644
index 0000000..c287643
--- /dev/null
+++ b/services/manifest_services_android.frameworks.vibrator.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+    <hal format="aidl">
+        <name>android.frameworks.vibrator</name>
+        <version>1</version>
+        <fqname>IVibratorControlService/default</fqname>
+    </hal>
+</manifest>
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3d40f64..927146d 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -21,6 +21,7 @@
         ":services.net-sources",
     ],
     static_libs: [
+        "modules-utils-build_system",
         "netd-client",
         "networkstack-client",
         "net-utils-services-common",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index bacde10..c2a069d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -68,7 +68,6 @@
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.pm.UserManagerInternal;
@@ -161,7 +160,7 @@
                         .strictness(Strictness.LENIENT)
                         .spyStatic(InputMethodUtils.class)
                         .mockStatic(ServiceManager.class)
-                        .mockStatic(SystemServerInitThreadPool.class)
+                        .spyStatic(AdditionalSubtypeMapRepository.class)
                         .startMocking();
 
         mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
@@ -234,9 +233,8 @@
         doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         any(PackageManager.class), anyList()));
 
-        // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
-        // which is ok to be mocked out for now.
-        doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+        // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out.
+        doNothing().when(AdditionalSubtypeMapRepository::startWriterThread);
 
         mServiceThread =
                 new ServiceThread(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index f690b1b..2d4a29b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.hardware.display.BrightnessInfo;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -112,7 +114,10 @@
                 .append("\n    mBrightnessAdjustmentFlag:")
                 .append(displayBrightnessState.getBrightnessAdjustmentFlag())
                 .append("\n    mIsUserInitiatedChange:")
-                .append(displayBrightnessState.isUserInitiatedChange());
+                .append(displayBrightnessState.isUserInitiatedChange())
+                .append("\n    mBrightnessMaxReason:")
+                .append(BrightnessInfo.briMaxReasonToString(
+                        displayBrightnessState.getBrightnessMaxReason()));
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index e982153..93dfbcb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -30,7 +30,6 @@
 
 import android.content.Context;
 import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -155,12 +154,6 @@
     }
 
     @Test
-    public void testMaxReasonIsNoneOnInit() {
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
-                mClamperController.getBrightnessMaxReason());
-    }
-
-    @Test
     public void testOnDisplayChanged_DelegatesToClamper() {
         mClamperController.onDisplayChanged(mMockDisplayDeviceData);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c1be5ca..63e3e5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -714,7 +714,7 @@
         // Simulate when the window is exiting and cleanupAnimation invoked
         // (e.g. screen off during RecentsAnimation animating), will expect the window receives
         // onExitAnimationDone to destroy the surface when the removal is allowed.
-        win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+        win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
         win1.mHasSurface = true;
         win1.mAnimatingExit = true;
         win1.mRemoveOnExit = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 11d9629..0bf27d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,8 +43,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -758,12 +756,9 @@
         // Simulating win1 has shown IME and being IME layering/input target
         mDisplayContent.setImeLayeringTarget(win1);
         mDisplayContent.setImeInputTarget(win1);
-        mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
         mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
         spyOn(mDisplayContent);
-        doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
-        doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
-                .prepareToShowInTransaction(any(), anyFloat());
+        mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
         makeWindowVisibleAndDrawn(mImeWindow);
         assertTrue(mImeWindow.isOnScreen());
         assertFalse(mImeWindow.isParentWindowHidden());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9b48cb9..c65b76e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -31,7 +31,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.window.flags.Flags.multiCrop;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -551,10 +550,9 @@
     }
 
     private static void makeWallpaperWindowShown(WindowState w) {
-        final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
-        w.mWinAnimator.mSurfaceController = windowSurfaceController;
         w.mWinAnimator.mLastAlpha = 1;
-        when(windowSurfaceController.getShown()).thenReturn(true);
+        spyOn(w.mWinAnimator);
+        doReturn(true).when(w.mWinAnimator).getShown();
     }
 
     private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4958b90..89abe2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -253,22 +253,18 @@
         final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
         spyOn(session);
         assertTrue(session.mCanAddInternalSystemWindow);
-        final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
-        session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
-                LayoutParams.TYPE_PHONE);
+        final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win");
+        session.onWindowSurfaceVisibilityChanged(window, true /* visible */);
         verify(session).setHasOverlayUi(true);
-        session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
-                LayoutParams.TYPE_PHONE);
+        session.onWindowSurfaceVisibilityChanged(window, false /* visible */);
         verify(session).setHasOverlayUi(false);
     }
 
     @Test
     public void testRelayoutExitingWindow() {
         final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
-        final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
-        win.mWinAnimator.mSurfaceController = surfaceController;
         win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
-        doReturn(true).when(surfaceController).hasSurface();
+        win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
         spyOn(win.mTransitionController);
         doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
         doReturn(true).when(win.mTransitionController).inTransition(
@@ -306,7 +302,7 @@
         // and WMS#tryStartExitingAnimation() will destroy the surface directly.
         assertFalse(win.mAnimatingExit);
         assertFalse(win.mHasSurface);
-        assertNull(win.mWinAnimator.mSurfaceController);
+        assertNull(win.mWinAnimator.mSurfaceControl);
 
         // Invisible requested activity should not get the last config even if its view is visible.
         mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,