Merge "[Media Quality] Add User Ids to all methods" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 2fe857d..38a0144 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24854,6 +24854,7 @@
     method @NonNull public String getId();
     method @NonNull public CharSequence getName();
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
+    method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes();
     method public int getType();
     method public int getVolume();
     method public int getVolumeHandling();
@@ -24869,6 +24870,8 @@
     field public static final String FEATURE_REMOTE_AUDIO_PLAYBACK = "android.media.route.feature.REMOTE_AUDIO_PLAYBACK";
     field public static final String FEATURE_REMOTE_PLAYBACK = "android.media.route.feature.REMOTE_PLAYBACK";
     field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
+    field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_REMOTE = 4; // 0x4
+    field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1; // 0x1
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
     field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
     field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
@@ -24919,6 +24922,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
+    method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 83699ac..77866f7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7779,7 +7779,7 @@
   }
 
   public final class MediaCas implements java.lang.AutoCloseable {
-    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
+    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean);
     method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
   }
 
@@ -8692,8 +8692,8 @@
     method public int setLnaEnabled(boolean);
     method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
     method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
-    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
     method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
+    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean);
     method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
     method public int transferOwner(@NonNull android.media.tv.tuner.Tuner);
     method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a1ede5f..7e73a5d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3846,7 +3846,7 @@
     }
 
     /**
-     * Return the time when the context user was unlocked elapsed milliseconds since boot,
+     * Return the time when the calling user was unlocked elapsed milliseconds since boot,
      * or 0 if not unlocked.
      *
      * @hide
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index a719583..8d846ab 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -445,3 +445,12 @@
     description: "Enable the Wallet role within profiles"
     bug: "356107987"
 }
+
+flag {
+    name: "text_classifier_choice_api_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "API change to enable getTextClassifier by type"
+    bug: "377229653"
+}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 7faa5d7..65e5679 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -58,10 +58,13 @@
 }
 
 flag {
-    name: "enable_desktop_windowing_scvh_cache"
+    name: "enable_desktop_windowing_scvh_cache_bug_fix"
     namespace: "lse_desktop_experience"
     description: "Enables a SurfaceControlViewHost cache for window decorations"
-    bug: "345146928"
+    bug: "360452034"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 549e666..30f6636 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -26,262 +26,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@Suppress  // Failing.
 @RunWith(AndroidJUnit4.class)
 public class MessageQueueTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
-    private static class BaseTestHandler extends TestHandlerThread {
-        Handler mHandler;
-        int mLastMessage;
-        int mCount;
-
-        public BaseTestHandler() {
-        }
-
-        public void go() {
-            mHandler = new Handler() {
-                public void handleMessage(Message msg) {
-                    BaseTestHandler.this.handleMessage(msg);
-                }
-            };
-        }
-
-        public void handleMessage(Message msg) {
-            if (!msg.isInUse()) {
-                failure(new RuntimeException(
-                        "msg.isInuse is false, should always be true, #" + msg.what));
-            }
-            if (mCount <= mLastMessage) {
-                if (msg.what != mCount) {
-                    failure(new RuntimeException(
-                            "Expected message #" + mCount
-                                    + ", received #" + msg.what));
-                } else if (mCount == mLastMessage) {
-                    success();
-                }
-                mCount++;
-            } else {
-                failure(new RuntimeException(
-                        "Message received after done, #" + msg.what));
-            }
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testMessageOrder() throws Exception {
-        TestHandlerThread tester = new BaseTestHandler() {
-            public void go() {
-                super.go();
-                long now = SystemClock.uptimeMillis() + 200;
-                mLastMessage = 4;
-                mCount = 0;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0);
-            }
-        };
-
-        tester.doTest(1000);
-    }
-
-    @Test
-    @MediumTest
-    public void testAtFrontOfQueue() throws Exception {
-        TestHandlerThread tester = new BaseTestHandler() {
-            public void go() {
-                super.go();
-                long now = SystemClock.uptimeMillis() + 200;
-                mLastMessage = 3;
-                mCount = 0;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now);
-                mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2));
-                mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0));
-            }
-
-            public void handleMessage(Message msg) {
-                super.handleMessage(msg);
-                if (msg.what == 0) {
-                    mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1));
-                }
-            }
-        };
-
-        tester.doTest(1000);
-    }
-
-    private static class TestFieldIntegrityHandler extends TestHandlerThread {
-        Handler mHandler;
-        int mLastMessage;
-        int mCount;
-
-        public TestFieldIntegrityHandler() {
-        }
-
-        public void go() {
-            mHandler = new Handler() {
-                public void handleMessage(Message msg) {
-                    TestFieldIntegrityHandler.this.handleMessage(msg);
-                }
-            };
-        }
-
-        public void handleMessage(Message msg) {
-            if (!msg.isInUse()) {
-                failure(new RuntimeException(
-                        "msg.isInuse is false, should always be true, #" + msg.what));
-            }
-            if (mCount <= mLastMessage) {
-                if (msg.what != mCount) {
-                    failure(new RuntimeException(
-                            "Expected message #" + mCount
-                                    + ", received #" + msg.what));
-                } else if (mCount == mLastMessage) {
-                    success();
-                }
-                mCount++;
-            } else {
-                failure(new RuntimeException(
-                        "Message received after done, #" + msg.what));
-            }
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testFieldIntegrity() throws Exception {
-
-        TestHandlerThread tester = new TestFieldIntegrityHandler() {
-            Bundle mBundle;
-
-            public void go() {
-                super.go();
-                mLastMessage = 1;
-                mCount = 0;
-                mHandler.sendMessage(mHandler.obtainMessage(0));
-            }
-
-            public void handleMessage(Message msg) {
-                super.handleMessage(msg);
-                if (msg.what == 0) {
-                    msg.flags = Message.FLAGS_TO_CLEAR_ON_COPY_FROM;
-                    msg.what = 1;
-                    msg.arg1 = 456;
-                    msg.arg2 = 789;
-                    msg.obj = this;
-                    msg.replyTo = null;
-                    mBundle = new Bundle();
-                    msg.data = mBundle;
-                    msg.data.putString("key", "value");
-
-                    Message newMsg = mHandler.obtainMessage();
-                    newMsg.copyFrom(msg);
-                    if (newMsg.isInUse() != false) {
-                        failure(new RuntimeException(
-                                "newMsg.isInUse is true should be false after copyFrom"));
-                    }
-                    if (newMsg.flags != 0) {
-                        failure(new RuntimeException(String.format(
-                        "newMsg.flags is %d should be 0 after copyFrom", newMsg.flags)));
-                    }
-                    if (newMsg.what != 1) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.what is %d should be %d after copyFrom", newMsg.what, 1)));
-                    }
-                    if (newMsg.arg1 != 456) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.arg1 is %d should be %d after copyFrom", msg.arg1, 456)));
-                    }
-                    if (newMsg.arg2 != 789) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.arg2 is %d should be %d after copyFrom", msg.arg2, 789)));
-                    }
-                    if (newMsg.obj != this) {
-                        failure(new RuntimeException(
-                                "newMsg.obj should be 'this' after copyFrom"));
-                    }
-                    if (newMsg.replyTo != null) {
-                        failure(new RuntimeException(
-                                "newMsg.replyTo should be null after copyFrom"));
-                    }
-                    if (newMsg.data == mBundle) {
-                        failure(new RuntimeException(
-                                "newMsg.data should NOT be mBundle after copyFrom"));
-                    }
-                    if (!newMsg.data.getString("key").equals(mBundle.getString("key"))) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.data.getString(\"key\") is %s and does not equal" +
-                                " mBundle.getString(\"key\") which is %s after copyFrom",
-                                newMsg.data.getString("key"),  mBundle.getString("key"))));
-                    }
-                    if (newMsg.when != 0) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.when is %d should be 0 after copyFrom", newMsg.when)));
-                    }
-                    if (newMsg.target != mHandler) {
-                        failure(new RuntimeException(
-                                "newMsg.target is NOT mHandler after copyFrom"));
-                    }
-                    if (newMsg.callback != null) {
-                        failure(new RuntimeException(
-                                "newMsg.callback is NOT null after copyFrom"));
-                    }
-
-                    mHandler.sendMessage(newMsg);
-                } else if (msg.what == 1) {
-                    if (msg.isInUse() != true) {
-                        failure(new RuntimeException(String.format(
-                                "msg.isInUse is false should be true after when processing %d",
-                                msg.what)));
-                    }
-                    if (msg.arg1 != 456) {
-                        failure(new RuntimeException(String.format(
-                                "msg.arg1 is %d should be %d when processing # %d",
-                                msg.arg1, 456, msg.what)));
-                    }
-                    if (msg.arg2 != 789) {
-                        failure(new RuntimeException(String.format(
-                                "msg.arg2 is %d should be %d when processing # %d",
-                                msg.arg2, 789, msg.what)));
-                    }
-                    if (msg.obj != this) {
-                        failure(new RuntimeException(String.format(
-                                "msg.obj should be 'this' when processing # %d", msg.what)));
-                    }
-                    if (msg.replyTo != null) {
-                        failure(new RuntimeException(String.format(
-                                "msg.replyTo should be null when processing # %d", msg.what)));
-                    }
-                    if (!msg.data.getString("key").equals(mBundle.getString("key"))) {
-                        failure(new RuntimeException(String.format(
-                                "msg.data.getString(\"key\") is %s and does not equal" +
-                                " mBundle.getString(\"key\") which is %s when processing # %d",
-                                msg.data.getString("key"),  mBundle.getString("key"), msg.what)));
-                    }
-                    if (msg.when != 0) {
-                        failure(new RuntimeException(String.format(
-                                "msg.when is %d should be 0 when processing # %d",
-                                msg.when, msg.what)));
-                    }
-                    if (msg.target != null) {
-                        failure(new RuntimeException(String.format(
-                                "msg.target is NOT null when processing # %d", msg.what)));
-                    }
-                    if (msg.callback != null) {
-                        failure(new RuntimeException(String.format(
-                                "msg.callback is NOT null when processing # %d", msg.what)));
-                    }
-                } else {
-                    failure(new RuntimeException(String.format(
-                            "Unexpected msg.what is %d" + msg.what)));
-                }
-            }
-        };
-
-        tester.doTest(1000);
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 860431a..bdf598e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -857,14 +857,16 @@
             InputManager inputManager,
             ShellTaskOrganizer shellTaskOrganizer,
             FocusTransitionObserver focusTransitionObserver,
-            @ShellMainThread ShellExecutor mainExecutor) {
+            @ShellMainThread ShellExecutor mainExecutor,
+            DisplayController displayController) {
         if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
                 && manageKeyGestures()
                 && (Flags.enableMoveToNextDisplayShortcut()
                 || Flags.enableTaskResizingKeyboardShortcuts())) {
             return Optional.of(new DesktopModeKeyGestureHandler(context,
                     desktopModeWindowDecorViewModel, desktopTasksController,
-                    inputManager, shellTaskOrganizer, focusTransitionObserver, mainExecutor));
+                    inputManager, shellTaskOrganizer, focusTransitionObserver,
+                    mainExecutor, displayController));
         }
         return Optional.empty();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 43544f6..250e177 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -31,7 +31,8 @@
 import com.android.hardware.input.Flags.manageKeyGestures
 import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
 import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.transition.FocusTransitionObserver
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -48,7 +49,8 @@
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val focusTransitionObserver: FocusTransitionObserver,
     @ShellMainThread private val mainExecutor: ShellExecutor,
-    ) : KeyGestureEventHandler {
+    private val displayController: DisplayController,
+) : KeyGestureEventHandler {
 
     init {
         inputManager.registerKeyGestureEventHandler(this)
@@ -99,12 +101,15 @@
             }
             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> {
                 logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled")
-                getGloballyFocusedFreeformTask()?.let {
+                getGloballyFocusedFreeformTask()?.let { taskInfo ->
                     mainExecutor.execute {
                         desktopTasksController.get().toggleDesktopTaskSize(
-                            it,
-                            ResizeTrigger.MAXIMIZE_MENU,
-                            DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+                            taskInfo,
+                            ToggleTaskSizeInteraction(
+                                isMaximized = isTaskMaximized(taskInfo, displayController),
+                                source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
+                                inputMethod = DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+                            )
                         )
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index 2c432bc..3821998 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -103,8 +103,12 @@
         DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722),
         @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode")
         DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723),
+        @UiEvent(doc = "Tap on the window header restore button in desktop windowing mode")
+        DESKTOP_WINDOW_RESTORE_BUTTON_TAP(2017),
         @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
         DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724),
+        @UiEvent(doc = "Double tap on window header to restore from maximize in desktop windowing")
+        DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE(2018),
         @UiEvent(doc = "Tap on the window Handle to open the Handle Menu")
         DESKTOP_WINDOW_APP_HANDLE_TAP(1998),
         @UiEvent(doc = "Tap on the desktop mode option under app handle menu")
@@ -136,7 +140,11 @@
         @UiEvent(doc = "Tap on the tile to left option in the maximize button menu")
         DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT(2012),
         @UiEvent(doc = "Tap on the tile to right option in the maximize button menu")
-        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013);
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013),
+        @UiEvent(doc = "Moving the desktop window by dragging the header")
+        DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG(2021),
+        @UiEvent(doc = "Double tap on the window header to refocus a desktop window")
+        DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS(2022);
 
         override fun getId(): Int = mId
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index c7cf310..42c3b1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -28,6 +28,7 @@
 import android.graphics.Rect
 import android.os.SystemProperties
 import android.util.Size
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 
 val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
@@ -211,6 +212,31 @@
         minOf(appBounds.height(), appBounds.width()).toFloat()
 }
 
+/** Returns whether the task is maximized. */
+fun isTaskMaximized(
+    taskInfo: RunningTaskInfo,
+    displayController: DisplayController
+): Boolean {
+    val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
+        ?: error("Could not get display layout for display=${taskInfo.displayId}")
+    val stableBounds = Rect()
+    displayLayout.getStableBounds(stableBounds)
+    return isTaskMaximized(taskInfo, stableBounds)
+}
+
+/** Returns whether the task is maximized. */
+fun isTaskMaximized(
+    taskInfo: RunningTaskInfo,
+    stableBounds: Rect
+): Boolean {
+    val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
+    return if (taskInfo.isResizeable) {
+        isTaskBoundsEqual(currentTaskBounds, stableBounds)
+    } else {
+        isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
+    }
+}
+
 /** Returns true if task's width or height is maximized else returns false. */
 fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
     return taskBounds.width() == stableBounds.width() ||
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index c479ab3..5759a66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -84,6 +84,7 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
 import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
@@ -795,32 +796,24 @@
      */
     fun toggleDesktopTaskSize(
         taskInfo: RunningTaskInfo,
-        resizeTrigger: ResizeTrigger,
-        inputMethod: InputMethod,
-        maximizeCujRecorder: (() -> Unit)? = null,
-        unmaximizeCujRecorder: (() -> Unit)? = null,
+        interaction: ToggleTaskSizeInteraction
     ) {
         val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
         desktopModeEventLogger.logTaskResizingStarted(
-            resizeTrigger,
-            inputMethod,
+            interaction.resizeTrigger,
+            interaction.inputMethod,
             taskInfo,
             currentTaskBounds.width(),
             currentTaskBounds.height(),
             displayController
         )
-
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
-        val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
         val destinationBounds = Rect()
-
-        val isMaximized = isTaskMaximized(taskInfo, stableBounds)
+        val isMaximized = interaction.direction == ToggleTaskSizeInteraction.Direction.RESTORE
         // If the task is currently maximized, we will toggle it not to be and vice versa. This is
         // helpful to eliminate the current task from logic to calculate taskbar corner rounding.
-        val willMaximize = !isMaximized
+        val willMaximize = interaction.direction == ToggleTaskSizeInteraction.Direction.MAXIMIZE
         if (isMaximized) {
-            unmaximizeCujRecorder?.invoke()
             // The desktop task is at the maximized width and/or height of the stable bounds.
             // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
             // Otherwise, toggle to the default bounds.
@@ -836,7 +829,6 @@
                 }
             }
         } else {
-            maximizeCujRecorder?.invoke()
             // Save current bounds so that task can be restored back to original bounds if necessary
             // and toggle to the stable bounds.
             desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
@@ -857,10 +849,16 @@
 
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+        interaction.uiEvent?.let { uiEvent ->
+            desktopModeUiEventLogger.log(taskInfo, uiEvent)
+        }
         desktopModeEventLogger.logTaskResizingEnded(
-            resizeTrigger, inputMethod,
-            taskInfo, destinationBounds.width(),
-            destinationBounds.height(), displayController
+            interaction.resizeTrigger,
+            interaction.inputMethod,
+            taskInfo,
+            destinationBounds.width(),
+            destinationBounds.height(),
+            displayController,
         )
         toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
     }
@@ -871,10 +869,7 @@
         currentDragBounds: Rect,
         motionEvent: MotionEvent
     ) {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-        val stableBounds = Rect()
-        displayLayout.getStableBounds(stableBounds)
-        if (isTaskMaximized(taskInfo, stableBounds)) {
+        if (isTaskMaximized(taskInfo, displayController)) {
             // Handle the case where we attempt to drag-to-maximize when already maximized: the task
             // position won't need to change but we want to animate the surface going back to the
             // maximized position.
@@ -892,8 +887,11 @@
 
         toggleDesktopTaskSize(
             taskInfo,
-            ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
-            DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
+            ToggleTaskSizeInteraction(
+                direction = ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                source = ToggleTaskSizeInteraction.Source.HEADER_DRAG_TO_TOP,
+                inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
+            )
         )
     }
 
@@ -911,19 +909,6 @@
         }
     }
 
-    private fun isTaskMaximized(
-        taskInfo: RunningTaskInfo,
-        stableBounds: Rect
-    ): Boolean {
-        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
-
-        return if (taskInfo.isResizeable) {
-            isTaskBoundsEqual(currentTaskBounds, stableBounds)
-        } else {
-            isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
-        }
-    }
-
     private fun isMaximizedToStableBoundsEdges(
         taskInfo: RunningTaskInfo,
         stableBounds: Rect
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
new file mode 100644
index 0000000..7afd8d7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.wm.shell.desktopmode.common
+
+import com.android.internal.jank.Cuj
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.AmbiguousSource
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Source
+
+/** Represents a user interaction to toggle a desktop task's size from to maximize or vice versa. */
+data class ToggleTaskSizeInteraction(
+    val direction: Direction,
+    val source: Source,
+    val inputMethod: InputMethod,
+) {
+    constructor(
+        isMaximized: Boolean,
+        source: Source,
+        inputMethod: InputMethod,
+    ) : this(
+        direction = if (isMaximized) Direction.RESTORE else Direction.MAXIMIZE,
+        source = source,
+        inputMethod = inputMethod,
+    )
+
+    val jankTag: String? =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE -> "caption_bar_button"
+            Source.HEADER_BUTTON_TO_RESTORE -> "caption_bar_button"
+            Source.KEYBOARD_SHORTCUT -> null
+            Source.HEADER_DRAG_TO_TOP -> null
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> "maximize_menu"
+            Source.MAXIMIZE_MENU_TO_RESTORE -> "maximize_menu"
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> "double_tap"
+            Source.DOUBLE_TAP_TO_RESTORE -> "double_tap"
+        }
+    val uiEvent: DesktopUiEventEnum? =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE ->
+                DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP
+            Source.HEADER_BUTTON_TO_RESTORE -> DesktopUiEventEnum.DESKTOP_WINDOW_RESTORE_BUTTON_TAP
+            Source.KEYBOARD_SHORTCUT -> null
+            Source.HEADER_DRAG_TO_TOP -> null
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE
+            }
+            Source.MAXIMIZE_MENU_TO_RESTORE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE
+            }
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE
+            }
+            Source.DOUBLE_TAP_TO_RESTORE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE
+            }
+        }
+    val resizeTrigger =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_BUTTON
+            Source.HEADER_BUTTON_TO_RESTORE -> ResizeTrigger.MAXIMIZE_BUTTON
+            Source.KEYBOARD_SHORTCUT -> ResizeTrigger.UNKNOWN_RESIZE_TRIGGER
+            Source.HEADER_DRAG_TO_TOP -> ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_MENU
+            Source.MAXIMIZE_MENU_TO_RESTORE -> ResizeTrigger.MAXIMIZE_MENU
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER
+            Source.DOUBLE_TAP_TO_RESTORE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER
+        }
+    val cujTracing: Int? =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW
+            Source.HEADER_BUTTON_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
+            Source.KEYBOARD_SHORTCUT -> null
+            Source.HEADER_DRAG_TO_TOP -> null
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> null
+            Source.MAXIMIZE_MENU_TO_RESTORE -> null
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> null
+            Source.DOUBLE_TAP_TO_RESTORE -> null
+        }
+
+    /** The direction to which the task is being resized. */
+    enum class Direction {
+        MAXIMIZE,
+        RESTORE,
+    }
+
+    /** The user interaction source. */
+    enum class Source {
+        HEADER_BUTTON_TO_MAXIMIZE,
+        HEADER_BUTTON_TO_RESTORE,
+        KEYBOARD_SHORTCUT,
+        HEADER_DRAG_TO_TOP,
+        MAXIMIZE_MENU_TO_MAXIMIZE,
+        MAXIMIZE_MENU_TO_RESTORE,
+        DOUBLE_TAP_TO_MAXIMIZE,
+        DOUBLE_TAP_TO_RESTORE,
+    }
+
+    /**
+     * Temporary sources for interactions that should be broken into more specific sources, for
+     * example, the header button click should use [Source.HEADER_BUTTON_TO_MAXIMIZE] and
+     * [Source.HEADER_BUTTON_TO_RESTORE].
+     *
+     * TODO: b/341320112 - break these out into different [Source]s.
+     */
+    enum class AmbiguousSource {
+        HEADER_BUTTON,
+        MAXIMIZE_MENU,
+        DOUBLE_TAP,
+    }
+}
+
+/** Returns the non-ambiguous [Source] based on the maximized state of the task. */
+fun AmbiguousSource.toSource(isMaximized: Boolean): Source {
+    return when (this) {
+        AmbiguousSource.HEADER_BUTTON ->
+            if (isMaximized) {
+                Source.HEADER_BUTTON_TO_RESTORE
+            } else {
+                Source.HEADER_BUTTON_TO_MAXIMIZE
+            }
+        AmbiguousSource.MAXIMIZE_MENU ->
+            if (isMaximized) {
+                Source.MAXIMIZE_MENU_TO_RESTORE
+            } else {
+                Source.MAXIMIZE_MENU_TO_MAXIMIZE
+            }
+        AmbiguousSource.DOUBLE_TAP ->
+            if (isMaximized) {
+                Source.DOUBLE_TAP_TO_RESTORE
+            } else {
+                Source.DOUBLE_TAP_TO_MAXIMIZE
+            }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index c9f2d2e..885f3db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -63,6 +63,7 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.shared.FocusTransitionListener;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -119,8 +120,8 @@
     public CaptionWindowDecorViewModel(
             Context context,
             Handler mainHandler,
+            @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread ShellExecutor bgExecutor,
-            ShellExecutor shellExecutor,
             Choreographer mainChoreographer,
             IWindowManager windowManager,
             ShellInit shellInit,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 04ef7c1..17b299d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -111,6 +111,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum;
+import com.android.wm.shell.desktopmode.DesktopModeUtils;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -118,6 +119,8 @@
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction;
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -580,28 +583,51 @@
                 >= MANAGE_WINDOWS_MINIMUM_INSTANCES);
     }
 
-    private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger,
-            MotionEvent motionEvent) {
+    private void onToggleSizeInteraction(
+            int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
+            @Nullable MotionEvent motionEvent) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
         if (decoration == null) {
             return;
         }
-        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger,
-                DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), () -> {
-                    mInteractionJankMonitor.begin(
-                            decoration.mTaskSurface, mContext, mMainHandler,
-                            Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
-                    return null;
-                }, () -> {
-                    mInteractionJankMonitor.begin(
-                            decoration.mTaskSurface, mContext, mMainHandler,
-                            Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW, source);
-                    return null;
-                });
+        final ToggleTaskSizeInteraction interaction =
+                createToggleSizeInteraction(decoration, source, motionEvent);
+        if (interaction == null) {
+            return;
+        }
+        if (interaction.getCujTracing() != null) {
+            mInteractionJankMonitor.begin(
+                    decoration.mTaskSurface, mContext, mMainHandler,
+                    interaction.getCujTracing(), interaction.getJankTag());
+        }
+        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, interaction);
         decoration.closeHandleMenu();
         decoration.closeMaximizeMenu();
     }
 
+    private ToggleTaskSizeInteraction createToggleSizeInteraction(
+            @NonNull DesktopModeWindowDecoration decoration,
+            @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
+            @Nullable MotionEvent motionEvent) {
+        final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+
+        final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(taskInfo.displayId);
+        if (displayLayout == null) {
+            return null;
+        }
+        final Rect stableBounds = new Rect();
+        displayLayout.getStableBounds(stableBounds);
+        boolean isMaximized = DesktopModeUtils.isTaskMaximized(taskInfo, stableBounds);
+
+        return new ToggleTaskSizeInteraction(
+                isMaximized
+                        ? ToggleTaskSizeInteraction.Direction.RESTORE
+                        : ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                ToggleTaskSizeUtilsKt.toSource(source, isMaximized),
+                DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent)
+        );
+    }
+
     private void onEnterOrExitImmersive(int taskId) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
         if (decoration == null) {
@@ -872,8 +898,8 @@
                 } else {
                     // Full immersive is disabled or task doesn't request/support it, so just
                     // toggle between maximize/restore states.
-                    onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button",
-                            ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent);
+                    onToggleSizeInteraction(decoration.mTaskInfo.taskId,
+                            ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
                 }
             } else if (id == R.id.minimize_window) {
                 mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
@@ -1000,6 +1026,8 @@
 
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
             if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) {
+                mDesktopModeUiEventLogger.log(taskInfo,
+                        DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS);
                 mDesktopTasksController.moveTaskToFront(taskInfo);
             }
         }
@@ -1102,6 +1130,8 @@
                     if (!wasDragging) {
                         return false;
                     }
+                    mDesktopModeUiEventLogger.log(taskInfo,
+                            DesktopUiEventEnum.DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG);
                     if (e.findPointerIndex(mDragPointerId) == -1) {
                         mDragPointerId = e.getPointerId(0);
                     }
@@ -1169,7 +1199,8 @@
                 // Disallow double-tap to resize when in full immersive.
                 return false;
             }
-            onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e);
+            onToggleSizeInteraction(mTaskId,
+                    ToggleTaskSizeInteraction.AmbiguousSource.DOUBLE_TAP, e);
             return true;
         }
     }
@@ -1608,7 +1639,8 @@
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
-            onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU,
+            onToggleSizeInteraction(taskInfo.taskId,
+                    ToggleTaskSizeInteraction.AmbiguousSource.MAXIMIZE_MENU,
                     touchEventListener.mMotionEvent);
             return Unit.INSTANCE;
         });
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 9f32d68..a136936 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -25,7 +25,7 @@
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
         <!-- Turns off Wi-fi -->
-        <option name="wifi" value="off"/>
+        <option name="wifi" value="on"/>
         <!-- Turns off Bluetooth -->
         <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
@@ -109,4 +109,11 @@
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
+    <!-- Enable mocking GPS location by the test app -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command"
+            value="appops set com.android.shell android:mock_location allow"/>
+        <option name="teardown-command"
+            value="appops set com.android.shell android:mock_location deny"/>
+    </target_preparer>
 </configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index fd4328d..609a284 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -27,6 +27,7 @@
 import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
 import android.tools.flicker.subject.exceptions.IncorrectRegionException
 import android.tools.flicker.subject.layers.LayerSubject
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
@@ -65,6 +66,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index d4ad4ef8..5698023 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -22,6 +22,7 @@
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -57,6 +58,8 @@
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
     AutoEnterPipOnGoToHomeTest(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
             pipApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 53725fa..880e4cd 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -21,6 +21,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ClosePipTransition
 import org.junit.FixMethodOrder
@@ -56,6 +57,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.closePipWindow(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index a1551b7..4399a23 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -21,6 +21,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
@@ -47,6 +48,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ea5b3e5..49efd1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -31,6 +31,7 @@
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.wm.shell.Flags
@@ -72,6 +73,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
     private val testApp = FixedOrientationAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index a109c4b..97cc9d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -20,6 +20,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.FixMethodOrder
@@ -53,6 +54,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.clickEnterPipButton(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 14ec303..b5b7847 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -20,6 +20,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
 import org.junit.FixMethodOrder
@@ -56,6 +57,8 @@
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
     ExitPipToAppTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 8a34b5e..f9a9df4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -20,6 +20,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
 import org.junit.FixMethodOrder
@@ -54,6 +55,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index 12e2328..79e2e4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -27,6 +27,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.helpers.WindowUtils
 import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.wm.shell.Flags
@@ -68,6 +69,7 @@
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
     EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
     private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     /** Second app used to enter split screen mode */
     private val secondAppForSplitScreen =
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 04016a9..14ae93a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -23,6 +23,7 @@
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
@@ -37,6 +38,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.changeAspectRatio(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 6bcaabc..81162c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -49,7 +49,8 @@
         val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
         setup {
             tapl.setEnableRotation(true)
-            pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+            pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+            pipApp.waitForPip(wmHelper)
 
             // determine the direction of dragging to test for
             isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index d82bfdd..6118d73 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -59,7 +59,8 @@
                 // Launch the PIP activity and wait for it to enter PiP mode
                 setRotation(Rotation.ROTATION_0)
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+                pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+                pipApp.waitForPip(wmHelper)
 
                 // get the initial region bounds and cache them
                 val initRegion = pipApp.getWindowRect(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index dbc97d0..61c59cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -25,6 +25,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.flicker.subject.exceptions.IncorrectRegionException
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
@@ -40,6 +41,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 65b60ce..0867f65 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -18,7 +18,6 @@
 
 import android.platform.test.annotations.Postsubmit
 import android.tools.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.flicker.junit.FlickerBuilderProvider
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
@@ -29,8 +28,6 @@
 import org.junit.runners.Parameterized
 
 abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
-    protected abstract val standardAppHelper: StandardAppHelper
-
     protected abstract val permissions: Array<String>
 
     @FlickerBuilderProvider
@@ -39,7 +36,7 @@
             instrumentation.uiAutomation.adoptShellPermissionIdentity()
             for (permission in permissions) {
                 instrumentation.uiAutomation.grantRuntimePermission(
-                    standardAppHelper.packageName,
+                    pipApp.packageName,
                     permission
                 )
             }
@@ -48,18 +45,18 @@
         }
     }
 
-    /** Checks [standardAppHelper] window remains visible throughout the animation */
+    /** Checks [pipApp] window remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppWindowAlwaysVisible() {
-        flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
+        flicker.assertWm { this.isAppWindowVisible(pipApp.packageNameMatcher) }
     }
 
-    /** Checks [standardAppHelper] layer remains visible throughout the animation */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
-        flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
+        flicker.assertLayers { this.isVisible(pipApp.packageNameMatcher) }
     }
 
     /** Checks the content overlay appears then disappears during the animation */
@@ -70,39 +67,39 @@
     }
 
     /**
-     * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole
+     * Checks that [pipApp] window remains inside the display bounds throughout the whole
      * animation
      */
     @Postsubmit
     @Test
     override fun pipWindowRemainInsideVisibleBounds() {
-        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
             coversAtMost(displayBounds)
         }
     }
 
     /**
-     * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the
+     * Checks that the [pipApp] layer remains inside the display bounds throughout the
      * whole animation
      */
     @Postsubmit
     @Test
     override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
         flicker.assertLayersVisibleRegion(
-            standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+            pipApp.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
         ) {
             coversAtMost(displayBounds)
         }
     }
 
-    /** Checks that the visible region of [standardAppHelper] always reduces during the animation */
+    /** Checks that the visible region of [pipApp] always reduces during the animation */
     @Postsubmit
     @Test
     override fun pipLayerReduces() {
         flicker.assertLayers {
             val pipLayerList =
                 this.layers {
-                    standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
+                    pipApp.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
                 }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -110,14 +107,14 @@
         }
     }
 
-    /** Checks that [standardAppHelper] window becomes pinned */
+    /** Checks that [pipApp] window becomes pinned */
     @Postsubmit
     @Test
     override fun pipWindowBecomesPinned() {
         flicker.assertWm {
-            invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
+            invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.packageNameMatcher) }
                 .then()
-                .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
+                .invoke("pipWindowIsPinned") { it.isPinned(pipApp.packageNameMatcher) }
         }
     }
 
@@ -129,14 +126,14 @@
     }
 
     /**
-     * Checks that the focus changes between the [standardAppHelper] window and the launcher when
+     * Checks that the focus changes between the [pipApp] window and the launcher when
      * closing the pip window
      */
     @Postsubmit
     @Test
     override fun focusChanges() {
         flicker.assertEventLog {
-            this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity")
+            this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 7b04b76..651c923 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -29,6 +29,8 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+import android.tools.traces.component.ComponentRegexMatcher
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
@@ -63,7 +65,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
-    override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
+    override val pipApp: MapsAppHelper = MapsAppHelper(instrumentation)
 
     override val permissions: Array<String> =
         arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION)
@@ -110,23 +112,23 @@
 
             // normal app open through the Launcher All Apps
             // var mapsAddressOption = "Golden Gate Bridge"
-            // standardAppHelper.open()
-            // standardAppHelper.doSearch(mapsAddressOption)
-            // standardAppHelper.getDirections()
-            // standardAppHelper.startNavigation();
+            // pipApp.open()
+            // pipApp.doSearch(mapsAddressOption)
+            // pipApp.getDirections()
+            // pipApp.startNavigation();
 
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION)
             )
 
-            standardAppHelper.waitForNavigationToStart()
+            pipApp.waitForNavigationToStart()
         }
     }
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = {
         teardown {
-            standardAppHelper.exit(wmHelper)
+            pipApp.exit(wmHelper)
             mainHandler.removeCallbacks(updateLocation)
             // the main looper callback might have tried to provide a new location after the
             // provider is no longer in test mode, causing a crash, this prevents it from happening
@@ -137,14 +139,14 @@
 
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
-    /** Checks [standardAppHelper] layer remains visible throughout the animation */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
         // For Maps the transition goes through the UI mode change that adds a snapshot overlay so
         // we assert only start/end layers matching the app instead.
-        flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) }
-        flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) }
+        flicker.assertLayersStart { this.isVisible(pipApp.packageNameMatcher) }
+        flicker.assertLayersEnd { this.isVisible(pipApp.packageNameMatcher) }
     }
 
     @Postsubmit
@@ -154,4 +156,15 @@
         Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         super.focusChanges()
     }
+
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                ignoreLayers = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+                    + ComponentRegexMatcher(Regex("Background for .* SurfaceView\\[com\\.google\\.android\\.apps\\.maps/com\\.google\\.android\\.maps\\.MapsActivity\\]\\#\\d+"))
+            )
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 6911946..be4cd78 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -61,7 +61,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
-    override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+    override val pipApp: NetflixAppHelper = NetflixAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
 
@@ -69,17 +69,17 @@
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 NetflixAppHelper.getNetflixWatchVideoIntent("81605060"),
                 ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
             )
-            standardAppHelper.waitForVideoPlaying()
+            pipApp.waitForVideoPlaying()
         }
     }
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = {
-        teardown { standardAppHelper.exit(wmHelper) }
+        teardown { pipApp.exit(wmHelper) }
     }
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -143,7 +143,7 @@
         // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
-        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
@@ -156,7 +156,7 @@
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
         // since Netflix uses source rect hint, there is no PiP overlay present
-        flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 5e54f30..3e4ff30 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -57,23 +57,23 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
-    override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+    override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
 
     override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
                 ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
             )
-            standardAppHelper.waitForVideoPlaying()
+            pipApp.waitForVideoPlaying()
         }
     }
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = {
-        teardown { standardAppHelper.exit(wmHelper) }
+        teardown { pipApp.exit(wmHelper) }
     }
 
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 159cba4..2c6cb503 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -63,7 +63,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
     YouTubeEnterPipTest(flicker) {
-    override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+    override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
 
@@ -71,13 +71,13 @@
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
                 ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
             )
-            standardAppHelper.enterFullscreen()
-            standardAppHelper.waitForVideoPlaying()
+            pipApp.enterFullscreen()
+            pipApp.waitForVideoPlaying()
         }
     }
 
@@ -101,7 +101,7 @@
         // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
-        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
@@ -114,7 +114,7 @@
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
         // since YouTube uses source rect hint, there is no PiP overlay present
-        flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 6dd3a17..a72de0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -71,7 +71,9 @@
     @Presubmit
     @Test
     open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
-        flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+        flicker.assertLayersVisibleRegion(
+            pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+        ) {
             coversAtMost(displayBounds)
         }
     }
@@ -117,7 +119,9 @@
     @Presubmit
     @Test
     open fun focusChanges() {
-        flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
+        flicker.assertEventLog {
+            this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
+        }
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index c37bf35..7b6625d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -27,6 +27,7 @@
 import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import android.tools.helpers.WindowUtils
 import android.tools.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.PipApp
 import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -40,7 +41,6 @@
     @Rule
     val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    protected val pipApp = PipAppHelper(instrumentation)
     protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
 
@@ -63,6 +63,11 @@
         }
     }
 
+    /**
+     * Defines the test app to run PIP flicker test.
+     */
+    protected open val pipApp: PipApp = PipAppHelper(instrumentation)
+
     /** Defines the transition used to run the test */
     protected open val thisTransition: FlickerBuilder.() -> Unit = {}
 
@@ -85,10 +90,11 @@
     /** Defines the default method of entering PiP */
     protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            pipApp.launchViaIntentAndWaitForPip(
+            pipApp.launchViaIntent(
                 wmHelper,
                 stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
             )
+            pipApp.waitForPip(wmHelper)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index c4954f9..feb3edc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -20,6 +20,7 @@
 import android.graphics.Point
 import android.os.SystemClock
 import android.tools.Rotation
+import android.tools.device.apphelpers.IStandardAppHelper
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.traces.component.ComponentNameMatcher
@@ -102,8 +103,8 @@
         wmHelper: WindowManagerStateHelper,
         tapl: LauncherInstrumentation,
         device: UiDevice,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper,
+        primaryApp: IStandardAppHelper,
+        secondaryApp: IStandardAppHelper,
         rotation: Rotation
     ) {
         primaryApp.launchViaIntent(wmHelper)
@@ -117,8 +118,8 @@
 
     fun enterSplitViaIntent(
         wmHelper: WindowManagerStateHelper,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
+        primaryApp: IStandardAppHelper,
+        secondaryApp: IStandardAppHelper
     ) {
         val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
         primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 6050695..dc7fb5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -136,8 +136,13 @@
 
         desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
             context,
-            Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
-            inputManager, shellTaskOrganizer, focusTransitionObserver, testExecutor
+            Optional.of(desktopModeWindowDecorViewModel),
+            Optional.of(desktopTasksController),
+            inputManager,
+            shellTaskOrganizer,
+            focusTransitionObserver,
+            testExecutor,
+            displayController
         )
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5c00272..547a7b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -93,6 +93,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -377,7 +378,14 @@
     val task1 = setUpFreeformTask()
 
     val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-    controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task1,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
 
     verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
@@ -399,21 +407,29 @@
   }
 
   @Test
-  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() {
+  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() {
     val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
     val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
 
     val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-    controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task1,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
-      ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.TOUCH,
-      task1,
-      0,
-      0,
-      displayController
+      eq(ResizeTrigger.MAXIMIZE_BUTTON),
+      eq(InputMethod.TOUCH),
+      eq(task1),
+      anyOrNull(),
+      anyOrNull(),
+      eq(displayController),
+      anyOrNull()
     )
     assertThat(argumentCaptor.value).isFalse()
   }
@@ -3359,7 +3375,14 @@
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
 
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3584,7 +3607,14 @@
     // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
     val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
 
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3604,7 +3634,15 @@
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
 
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
+
     assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
     verify(desktopModeEventLogger, never()).logTaskResizingEnded(
       any(), any(), any(), any(),
@@ -3618,11 +3656,25 @@
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3645,12 +3697,26 @@
     }
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
       boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3673,12 +3739,26 @@
     }
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
       STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3699,11 +3779,25 @@
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert last bounds before maximize removed after use
     assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 153be07..a4e3af4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -59,6 +59,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.DesktopImmersiveController
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -398,11 +399,12 @@
         maxOrRestoreListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController).toggleDesktopTaskSize(
-            eq(decor.mTaskInfo),
-            eq(ResizeTrigger.MAXIMIZE_MENU),
-            eq(InputMethod.UNKNOWN_INPUT_METHOD),
-            any(),
-            any()
+            decor.mTaskInfo,
+            ToggleTaskSizeInteraction(
+                ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE,
+                InputMethod.UNKNOWN_INPUT_METHOD
+            )
         )
     }
 
@@ -1061,11 +1063,12 @@
 
         verify(mockDesktopTasksController)
             .toggleDesktopTaskSize(
-                eq(decor.mTaskInfo),
-                eq(ResizeTrigger.MAXIMIZE_BUTTON),
-                    eq(InputMethod.UNKNOWN_INPUT_METHOD),
-                any(),
-                any(),
+                decor.mTaskInfo,
+                ToggleTaskSizeInteraction(
+                    ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                    ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+                    InputMethod.UNKNOWN_INPUT_METHOD
+                )
             )
     }
 
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 1ecba31..3efb5f9 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -1010,17 +1010,17 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param resourceHolderRetain Set to {@code true} to allow the resource holder to retain
-     *     ownership, or false to allow the resource challenger to acquire the resource.
-     *     If not explicitly set, resourceHolderRetain is set to {@code false}.
+     *@param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      * @hide
      */
     @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
     @SystemApi
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
-    public void setResourceHolderRetain(boolean resourceHolderRetain) {
+    public void setResourceOwnershipRetention(boolean enabled) {
         if (mTunerResourceManager != null) {
-            mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+            mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled);
         }
     }
 
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 0902278..d433ec87 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -21,6 +21,7 @@
 
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
 import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+import static com.android.media.flags.Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2;
 import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES;
 
@@ -421,6 +422,51 @@
      */
     public static final int TYPE_GROUP = 2000;
 
+    /** @hide */
+    @IntDef(
+            prefix = {"ROUTING_TYPE_"},
+            value = {
+                FLAG_ROUTING_TYPE_SYSTEM_AUDIO,
+                FLAG_ROUTING_TYPE_SYSTEM_VIDEO,
+                FLAG_ROUTING_TYPE_REMOTE
+            },
+            flag = true)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RoutingType {}
+
+    /**
+     * Indicates that a route supports routing of the system audio.
+     *
+     * <p>Providers that support this type of routing require the {@link
+     * android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission.
+     */
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1;
+
+    /**
+     * Indicates that a route supports routing of the system video.
+     *
+     * @hide
+     */
+    // TODO: b/380431086 - Enable this API once we add support for system video routing.
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int FLAG_ROUTING_TYPE_SYSTEM_VIDEO = 1 << 1;
+
+    /**
+     * Indicates that a route supports routing playback to remote routes through control commands.
+     *
+     * <p>This type of routing does not affect affect this system's audio or video, but instead
+     * relies on the device that corresponds to this route to fetch and play the media. It also
+     * requires the media app to take care of initializing and controlling playback.
+     */
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int FLAG_ROUTING_TYPE_REMOTE = 1 << 2;
+
+    private static final int FLAG_ROUTING_TYPE_ALL =
+            FLAG_ROUTING_TYPE_SYSTEM_AUDIO
+                    | FLAG_ROUTING_TYPE_SYSTEM_VIDEO
+                    | FLAG_ROUTING_TYPE_REMOTE;
+
     /**
      * Route feature: Live audio.
      * <p>
@@ -553,6 +599,7 @@
     private final List<String> mFeatures;
     @Type
     private final int mType;
+    @RoutingType private final int mRoutingTypeFlags;
     private final boolean mIsSystem;
     private final Uri mIconUri;
     private final CharSequence mDescription;
@@ -576,6 +623,7 @@
         mName = builder.mName;
         mFeatures = builder.mFeatures;
         mType = builder.mType;
+        mRoutingTypeFlags = builder.mRoutingTypeFlags;
         mIsSystem = builder.mIsSystem;
         mIconUri = builder.mIconUri;
         mDescription = builder.mDescription;
@@ -600,6 +648,7 @@
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mFeatures = in.createStringArrayList();
         mType = in.readInt();
+        mRoutingTypeFlags = validateRoutingTypeFlags(in.readInt());
         mIsSystem = in.readBoolean();
         mIconUri = in.readParcelable(null, android.net.Uri.class);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -660,6 +709,13 @@
         return mType;
     }
 
+    /** Returns the flags that indicate the routing types supported by this route. */
+    @RoutingType
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public int getSupportedRoutingTypes() {
+        return mRoutingTypeFlags;
+    }
+
     /**
      * Returns whether the route is a system route or not.
      * <p>
@@ -904,6 +960,7 @@
         pw.println(indent + "mName=" + mName);
         pw.println(indent + "mFeatures=" + mFeatures);
         pw.println(indent + "mType=" + getDeviceTypeString(mType));
+        pw.println(indent + "mRoutingTypeFlags=" + getRoutingTypeFlagsString(mRoutingTypeFlags));
         pw.println(indent + "mIsSystem=" + mIsSystem);
         pw.println(indent + "mIconUri=" + mIconUri);
         pw.println(indent + "mDescription=" + mDescription);
@@ -941,6 +998,7 @@
                 && Objects.equals(mName, other.mName)
                 && Objects.equals(mFeatures, other.mFeatures)
                 && (mType == other.mType)
+                && (mRoutingTypeFlags == other.mRoutingTypeFlags)
                 && (mIsSystem == other.mIsSystem)
                 && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mDescription, other.mDescription)
@@ -966,6 +1024,7 @@
                 mName,
                 mFeatures,
                 mType,
+                mRoutingTypeFlags,
                 mIsSystem,
                 mIconUri,
                 mDescription,
@@ -994,6 +1053,8 @@
                 .append(getName())
                 .append(", type=")
                 .append(getDeviceTypeString(getType()))
+                .append(", routingTypes=")
+                .append(getRoutingTypeFlagsString(getSupportedRoutingTypes()))
                 .append(", isSystem=")
                 .append(isSystemRoute())
                 .append(", features=")
@@ -1035,6 +1096,7 @@
         TextUtils.writeToParcel(mName, dest, flags);
         dest.writeStringList(mFeatures);
         dest.writeInt(mType);
+        dest.writeInt(mRoutingTypeFlags);
         dest.writeBoolean(mIsSystem);
         dest.writeParcelable(mIconUri, flags);
         TextUtils.writeToParcel(mDescription, dest, flags);
@@ -1143,6 +1205,34 @@
         }
     }
 
+    /** Returns a human-readable representation of the given {@code routingTypeFlags}. */
+    private static String getRoutingTypeFlagsString(@RoutingType int routingTypeFlags) {
+        List<String> typeStrings = new ArrayList<>();
+        if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_AUDIO) != 0) {
+            typeStrings.add("SYSTEM_AUDIO");
+        }
+        if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_VIDEO) != 0) {
+            typeStrings.add("SYSTEM_VIDEO");
+        }
+        if ((routingTypeFlags & FLAG_ROUTING_TYPE_REMOTE) != 0) {
+            typeStrings.add("REMOTE");
+        }
+        return String.join(/* delimiter= */ "|", typeStrings);
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the provided {@code routingTypeFlags} are not
+     * valid. Otherwise, returns the provided value.
+     */
+    private static int validateRoutingTypeFlags(@RoutingType int routingTypeFlags) {
+        if (routingTypeFlags == 0 || (routingTypeFlags & ~FLAG_ROUTING_TYPE_ALL) != 0) {
+            throw new IllegalArgumentException(
+                    "Invalid routing type flags: " + Integer.toHexString(routingTypeFlags));
+        } else {
+            return routingTypeFlags;
+        }
+    }
+
     /**
      * Builder for {@link MediaRoute2Info media route info}.
      */
@@ -1153,6 +1243,7 @@
 
         @Type
         private int mType = TYPE_UNKNOWN;
+        @RoutingType private int mRoutingTypeFlags = FLAG_ROUTING_TYPE_REMOTE;
         private boolean mIsSystem;
         private Uri mIconUri;
         private CharSequence mDescription;
@@ -1224,6 +1315,7 @@
             mName = routeInfo.mName;
             mFeatures = new ArrayList<>(routeInfo.mFeatures);
             mType = routeInfo.mType;
+            mRoutingTypeFlags = routeInfo.mRoutingTypeFlags;
             mIsSystem = routeInfo.mIsSystem;
             mIconUri = routeInfo.mIconUri;
             mDescription = routeInfo.mDescription;
@@ -1301,6 +1393,18 @@
         }
 
         /**
+         * Sets the routing types that this route supports.
+         *
+         * @see MediaRoute2Info#getSupportedRoutingTypes()
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+        public Builder setSupportedRoutingTypes(@RoutingType int routingTypeFlags) {
+            mRoutingTypeFlags = validateRoutingTypeFlags(routingTypeFlags);
+            return this;
+        }
+
+        /**
          * Sets whether the route is a system route or not.
          * @hide
          */
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 572db97..3451dfc 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -93,7 +93,7 @@
     name: "set_resource_holder_retain"
     is_exported: true
     namespace: "media_tv"
-    description: "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
+    description: "Feature flag to add setResourceOwnershipRetention api to MediaCas and Tuner JAVA."
     bug: "372973197"
 }
 
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b1adb77..7f7a239 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -757,14 +757,14 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
-     *     false to allow the resource challenger to acquire the resource. If not explicitly set,
-     *     resourceHolderRetain is set to false.
+     * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
     @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
-    public void setResourceHolderRetain(boolean resourceHolderRetain) {
-        mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+    public void setResourceOwnershipRetention(boolean enabled) {
+        mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled);
     }
 
     /**
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index be65ad9..2ed642e 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -227,15 +228,16 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param clientId The client id used to set ownership of resource to owner in case of resource
+     * @param clientId The client id used to set ownership of resource in case of resource
      *     challenger situation.
-     * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
-     *     false to allow the resource challenger to acquire the resource. If not explicitly set,
-     *     resourceHolderRetain is set to false.
+     * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
-    public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    public void setResourceOwnershipRetention(int clientId, boolean enabled) {
         try {
-            mService.setResourceHolderRetain(clientId, resourceHolderRetain);
+            mService.setResourceOwnershipRetention(clientId, enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index c57be1b0..50f9fe5 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -156,12 +156,13 @@
      * scenario, when both Resource Holder and Resource Challenger have same processId and same
      * priority.
      *
-     * @param clientId The resourceHolderRetain of the client is updated using client ID.
-     * @param resourceHolderRetain set to true to allow the Resource Holder to retain ownership, or
-     *     false to allow the Resource Challenger to acquire the resource. If not explicitly set,
-     *     resourceHolderRetain is set to false.
+     * @param clientId The client id used to set ownership of resource in case of resource
+     *     challenger situation.
+     * @param enabled Set to {@code true} to allow the Resource Holder to retain ownership,
+     *     or false to allow the Resource Challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
-    void setResourceHolderRetain(int clientId, boolean resourceHolderRetain);
+    void setResourceOwnershipRetention(int clientId, boolean enabled);
 
     /*
      * This API is used by the Tuner framework to request a frontend from the TunerHAL.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a266e7e..c3dc84d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -39,6 +39,7 @@
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitions
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.observableTransitionState
@@ -78,6 +79,7 @@
     sceneByKey: Map<SceneKey, Scene>,
     overlayByKey: Map<OverlayKey, Overlay>,
     initialSceneKey: SceneKey,
+    sceneTransitions: SceneTransitions,
     dataSourceDelegator: SceneDataSourceDelegator,
     qsSceneAdapter: Provider<QSSceneAdapter>,
     modifier: Modifier = Modifier,
@@ -87,7 +89,7 @@
         MutableSceneTransitionLayoutState(
             initialScene = initialSceneKey,
             canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
-            transitions = SceneContainerTransitions,
+            transitions = sceneTransitions,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index af8c357..b33a83c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -68,6 +68,7 @@
 import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
 import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.scene.ui.composable.SceneContainer
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.testKosmos
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.awaitCancellation
@@ -115,7 +116,13 @@
     private val Kosmos.initialSceneKey by Fixture { Scenes.Bouncer }
     private val Kosmos.sceneContainerConfig by Fixture {
         val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0)
-        SceneContainerConfig(sceneKeys, initialSceneKey, emptyList(), navigationDistances)
+        SceneContainerConfig(
+            sceneKeys,
+            initialSceneKey,
+            SceneContainerTransitions,
+            emptyList(),
+            navigationDistances,
+        )
     }
     private val view = mock<View>()
 
@@ -182,6 +189,7 @@
                                         Scenes.Bouncer to bouncerScene,
                                     ),
                                 initialSceneKey = Scenes.Bouncer,
+                                sceneTransitions = SceneContainerTransitions,
                                 overlayByKey = emptyMap(),
                                 dataSourceDelegator = kosmos.sceneDataSourceDelegator,
                                 qsSceneAdapter = { kosmos.fakeQsSceneAdapter },
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 44dd34a..8ddd1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
+import com.android.systemui.communal.ui.compose.sceneTransitions
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.communal.util.CommunalColorsImpl
 import com.android.systemui.communal.widgets.CommunalWidgetModule
@@ -113,6 +114,7 @@
                 SceneContainerConfig(
                     sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
                     initialSceneKey = CommunalScenes.Blank,
+                    transitions = sceneTransitions,
                     navigationDistances =
                         mapOf(CommunalScenes.Blank to 0, CommunalScenes.Communal to 1),
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index e441a23..e36e40d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
@@ -98,6 +99,7 @@
                         Scenes.Shade.takeUnless { DualShade.isEnabled },
                     ),
                 initialSceneKey = Scenes.Gone,
+                transitions = SceneContainerTransitions,
                 overlayKeys =
                     listOfNotNull(
                         Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 4beec10..fe01452 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
@@ -106,6 +107,7 @@
                         Scenes.Shade.takeUnless { DualShade.isEnabled },
                     ),
                 initialSceneKey = Scenes.Lockscreen,
+                transitions = SceneContainerTransitions,
                 overlayKeys =
                     listOfNotNull(
                         Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 16ed59f4..c1646b8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import dagger.Module
 import dagger.Provides
 
@@ -35,7 +36,7 @@
 
             // List SceneResolver modules for supported SceneFamilies
             HomeSceneFamilyResolverModule::class,
-        ],
+        ]
 )
 object ShadelessSceneContainerFrameworkModule {
 
@@ -46,20 +47,12 @@
         return SceneContainerConfig(
             // Note that this list is in z-order. The first one is the bottom-most and the
             // last one is top-most.
-            sceneKeys =
-                listOf(
-                    Scenes.Gone,
-                    Scenes.Lockscreen,
-                    Scenes.Bouncer,
-                ),
+            sceneKeys = listOf(Scenes.Gone, Scenes.Lockscreen, Scenes.Bouncer),
             initialSceneKey = Scenes.Lockscreen,
+            transitions = SceneContainerTransitions,
             overlayKeys = emptyList(),
             navigationDistances =
-                mapOf(
-                    Scenes.Gone to 0,
-                    Scenes.Lockscreen to 0,
-                    Scenes.Bouncer to 1,
-                )
+                mapOf(Scenes.Gone to 0, Scenes.Lockscreen to 0, Scenes.Bouncer to 1),
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 2311e47..ce7be83 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -18,6 +18,7 @@
 
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitions
 
 /** Models the configuration of the scene container. */
 data class SceneContainerConfig(
@@ -38,6 +39,9 @@
      */
     val initialSceneKey: SceneKey,
 
+    /** Transition definitions to be used when animating between scene transitions. */
+    val transitions: SceneTransitions,
+
     /**
      * The keys to all overlays in the container, sorted by z-order such that the last one renders
      * on top of all previous ones. Overlay keys within the same container must not repeat but it's
@@ -61,7 +65,7 @@
      * Note that this is not the z-order of rendering; that's determined by the order of declaration
      * of scenes in the [sceneKeys] list.
      */
-    val navigationDistances: Map<SceneKey, Int>
+    val navigationDistances: Map<SceneKey, Int>,
 ) {
     init {
         check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 1e3a233..1c15c74 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -196,6 +196,7 @@
                             sceneByKey = sceneByKey,
                             overlayByKey = overlayByKey,
                             initialSceneKey = containerConfig.initialSceneKey,
+                            sceneTransitions = containerConfig.transitions,
                             dataSourceDelegator = dataSourceDelegator,
                             qsSceneAdapter = qsSceneAdapter,
                         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 3300c96..0eca818 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -13,6 +13,7 @@
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.FakeOverlay
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
@@ -60,6 +61,7 @@
     SceneContainerConfig(
         sceneKeys = sceneKeys,
         initialSceneKey = initialSceneKey,
+        transitions = SceneContainerTransitions,
         overlayKeys = overlayKeys,
         navigationDistances = navigationDistances,
     )
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index a32d3cb..05aeb42 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -57,7 +57,6 @@
     final boolean visibleToInstantApp;
     public final boolean exported;
     final int initialPriority;
-    final ApplicationInfo applicationInfo;
 
     BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
             String _packageName, String _featureId, String _receiverId, String _requiredPermission,
@@ -74,10 +73,9 @@
         instantApp = _instantApp;
         visibleToInstantApp = _visibleToInstantApp;
         exported = _exported;
-        applicationInfo = _applicationInfo;
         initialPriority = getPriority();
         setPriority(calculateAdjustedPriority(owningUid, initialPriority,
-                applicationInfo, platformCompat));
+                _applicationInfo, platformCompat));
     }
 
     public @Nullable String getReceiverClassName() {
@@ -91,7 +89,7 @@
     }
 
     public @NonNull ApplicationInfo getApplicationInfo() {
-        return applicationInfo;
+        return receiverList.app.info;
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6e98bff..f049ef3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -725,6 +725,13 @@
         }
         mPowerStatusController.setPowerStatus(getInitialPowerStatus());
         setProhibitMode(false);
+        if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+            Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+                    + " mode.");
+            mHdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                    HDMI_CEC_CONTROL_ENABLED);
+            setWasCecDisabledOnStandbyByLowEnergyMode(false);
+        }
         mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
 
@@ -771,14 +778,6 @@
             Slog.i(TAG, "Device does not support eARC.");
         }
         mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
-        if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
-            Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
-                    + " mode.");
-            getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
-                    HDMI_CEC_CONTROL_ENABLED);
-            setWasCecDisabledOnStandbyByLowEnergyMode(false);
-            setCecEnabled(HDMI_CEC_CONTROL_ENABLED);
-        }
         if (isCecControlEnabled()) {
             initializeCec(INITIATED_BY_BOOT_UP);
         } else {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 38bc026..e191ff2 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -119,14 +119,14 @@
      * If resource holder retains ownership of the resource in a challenge scenario then value is
      * true.
      */
-    private boolean mResourceHolderRetain;
+    private boolean mResourceOwnershipRetention;
 
     private ClientProfile(Builder builder) {
         this.mId = builder.mId;
         this.mTvInputSessionId = builder.mTvInputSessionId;
         this.mUseCase = builder.mUseCase;
         this.mProcessId = builder.mProcessId;
-        this.mResourceHolderRetain = builder.mResourceHolderRetain;
+        this.mResourceOwnershipRetention = builder.mResourceOwnershipRetention;
     }
 
     public int getId() {
@@ -149,8 +149,8 @@
      * Returns true when the resource holder retains ownership of the resource in a challenge
      * scenario.
      */
-    public boolean shouldResourceHolderRetain() {
-        return mResourceHolderRetain;
+    public boolean resourceOwnershipRetentionEnabled() {
+        return mResourceOwnershipRetention;
     }
 
     /**
@@ -199,12 +199,12 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
-     *     false (or resourceHolderRetain not set at all) to allow the resource challenger to
-     *     acquire the resource. If not explicitly set, resourceHolderRetain is set to false.
+     * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
-    public void setResourceHolderRetain(boolean resourceHolderRetain) {
-        mResourceHolderRetain = resourceHolderRetain;
+    public void setResourceOwnershipRetention(boolean enabled) {
+        mResourceOwnershipRetention = enabled;
     }
 
     /**
@@ -389,7 +389,7 @@
         private String mTvInputSessionId;
         private int mUseCase;
         private int mProcessId;
-        private boolean mResourceHolderRetain = false;
+        private boolean mResourceOwnershipRetention = false;
 
         Builder(int id) {
             this.mId = id;
@@ -428,12 +428,12 @@
         /**
          * Builder for {@link ClientProfile}.
          *
-         * @param resourceHolderRetain the determining factor for resource ownership during
-         *     challenger scenario. The default behavior favors the resource challenger and grants
-         *     them ownership of the resource if resourceHolderRetain is not explicitly set to true.
+         * @param enabled the determining factor for resource ownership during challenger scenario.
+         *     The default behavior favors the resource challenger and grants them ownership of
+         *     the resource if resourceOwnershipRetention is not explicitly set to true.
          */
-        public Builder resourceHolderRetain(boolean resourceHolderRetain) {
-            this.mResourceHolderRetain = resourceHolderRetain;
+        public Builder resourceOwnershipRetention(boolean enabled) {
+            this.mResourceOwnershipRetention = enabled;
             return this;
         }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 5ae8c11..bb192c0 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -231,10 +231,10 @@
         }
 
         @Override
-        public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
-            enforceTrmAccessPermission("setResourceHolderRetain");
+        public void setResourceOwnershipRetention(int clientId, boolean enabled) {
+            enforceTrmAccessPermission("setResourceOwnershipRetention");
             synchronized (mLock) {
-                getClientProfile(clientId).setResourceHolderRetain(resourceHolderRetain);
+                getClientProfile(clientId).setResourceOwnershipRetention(enabled);
             }
         }
 
@@ -1079,7 +1079,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
                 reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
                 return true;
@@ -1265,7 +1266,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
                 reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
                 return true;
@@ -1352,7 +1354,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 casSessionHandle[0] = cas.getHandle();
                 reclaimOwnerId[0] = lowestPriorityOwnerId;
                 return true;
@@ -1439,7 +1442,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 ciCamHandle[0] = ciCam.getHandle();
                 reclaimOwnerId[0] = lowestPriorityOwnerId;
                 return true;
@@ -1677,7 +1681,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
                 reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
                 return true;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 112414e..fde6ce2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.media.tv.flags.Flags.mediaQualityFw;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -2616,9 +2617,11 @@
                 t.traceEnd();
             }
 
-            t.traceBegin("StartMediaQuality");
-            mSystemServiceManager.startService(MediaQualityService.class);
-            t.traceEnd();
+            if (mediaQualityFw() && isTv) {
+                t.traceBegin("StartMediaQuality");
+                mSystemServiceManager.startService(MediaQualityService.class);
+                t.traceEnd();
+            }
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
                 t.traceBegin("StartMediaResourceMonitor");
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 5852af7..d20f73d 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -506,9 +506,9 @@
         assertThat(client0.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle)));
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // (client0) to maintain ownership such as requester will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder (client0) to maintain ownership such as requester will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
@@ -520,10 +520,10 @@
                 .isEqualTo(client0.getId());
         assertThat(client0.isReclaimed()).isFalse();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
                 .isTrue();
@@ -645,9 +645,9 @@
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // to maintain ownership such as requester (client1) will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder to maintain ownership such as requester (client1) will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = casSessionRequest(client1.getId(), 1);
         assertThat(
@@ -663,10 +663,10 @@
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
         assertThat(client0.isReclaimed()).isFalse();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(
                 mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
@@ -759,9 +759,9 @@
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // (client0) to maintain ownership such as requester will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder (client0) to maintain ownership such as requester will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = tunerCiCamRequest(client1.getId(), 1);
         assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
@@ -776,10 +776,10 @@
         assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
         assertThat(client0.isReclaimed()).isFalse();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
                 .isTrue();
@@ -927,9 +927,9 @@
         assertThat(client0.getProfile().getInUseLnbHandles())
                 .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // (client0) to maintain ownership such as requester will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder (client0) to maintain ownership such as requester will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = new TunerLnbRequest();
         request.clientId = client1.getId();
@@ -942,10 +942,10 @@
         assertThat(client0.isReclaimed()).isFalse();
         assertThat(client1.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
deleted file mode 100644
index eeee7b4..0000000
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ /dev/null
@@ -1,306 +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.wm.flicker.helpers;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import androidx.annotation.Nullable;
-
-/**
- * Injects gestures given an {@link Instrumentation} object.
- */
-public class GestureHelper {
-    // Inserted after each motion event injection.
-    private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
-
-    private final UiAutomation mUiAutomation;
-
-    /**
-     * Primary pointer should be cached here for separate release
-     */
-    @Nullable private PointerProperties mPrimaryPtrProp;
-    @Nullable private PointerCoords mPrimaryPtrCoord;
-    private long mPrimaryPtrDownTime;
-
-    /**
-     * A pair of floating point values.
-     */
-    public static class Tuple {
-        public float x;
-        public float y;
-
-        public Tuple(float x, float y) {
-            this.x = x;
-            this.y = y;
-        }
-    }
-
-    public GestureHelper(Instrumentation instrumentation) {
-        mUiAutomation = instrumentation.getUiAutomation();
-    }
-
-    /**
-     * Injects a series of {@link MotionEvent}s to simulate tapping.
-     *
-     * @param point coordinates of pointer to tap
-     * @param times the number of times to tap
-     */
-    public boolean tap(@NonNull Tuple point, int times) throws InterruptedException {
-        PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
-        PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1);
-
-        for (int i = 0; i <= times; i++) {
-            // If already tapped, inject delay in between movements
-            if (times > 0) {
-                SystemClock.sleep(50L);
-            }
-            if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
-                return false;
-            }
-            // Delay before releasing tap
-            SystemClock.sleep(100L);
-            if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
-     *
-     * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
-     * will be cached for potential release later on by {@code releasePrimaryPointer()}
-     *
-     * @param startPoint initial coordinates of the primary pointer
-     * @param endPoint final coordinates of the primary pointer
-     * @param steps number of steps to take to animate dragging
-     * @return true if gesture is injected successfully
-     */
-    public boolean dragWithoutRelease(@NonNull Tuple startPoint,
-            @NonNull Tuple endPoint, int steps) {
-        PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
-        PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1);
-
-        PointerProperties[] ptrProps = new PointerProperties[] { ptrProp };
-        PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord };
-
-        long downTime = SystemClock.uptimeMillis();
-
-        if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) {
-            return false;
-        }
-
-        // cache the primary pointer info for later potential release
-        mPrimaryPtrProp = ptrProp;
-        mPrimaryPtrCoord = ptrCoord;
-        mPrimaryPtrDownTime = downTime;
-
-        return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps);
-    }
-
-    /**
-     * Release primary pointer if previous gesture has cached the primary pointer info.
-     *
-     * @return true if the release was injected successfully
-     */
-    public boolean releasePrimaryPointer() {
-        if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) {
-            return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime);
-        }
-
-        return false;
-    }
-
-    /**
-     * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
-     *
-     * @param startPoint1 initial coordinates of the first pointer
-     * @param startPoint2 initial coordinates of the second pointer
-     * @param endPoint1 final coordinates of the first pointer
-     * @param endPoint2 final coordinates of the second pointer
-     * @param steps number of steps to take to animate pinching
-     * @return true if gesture is injected successfully
-     */
-    public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2,
-            @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) {
-        PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
-        PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER);
-
-        PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1);
-        PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1);
-
-        PointerProperties[] ptrProps = new PointerProperties[] {
-                ptrProp1, ptrProp2
-        };
-
-        PointerCoords[] ptrCoords = new PointerCoords[] {
-                ptrCoord1, ptrCoord2
-        };
-
-        long downTime = SystemClock.uptimeMillis();
-
-        if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) {
-            return false;
-        }
-
-        if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) {
-            return false;
-        }
-
-        if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 },
-                downTime, steps)) {
-            return false;
-        }
-
-        if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) {
-            return false;
-        }
-
-        return primaryPointerUp(ptrProp1, ptrCoord1, downTime);
-    }
-
-    private boolean primaryPointerDown(@NonNull PointerProperties prop,
-            @NonNull PointerCoords coord, long downTime) {
-        MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1,
-                new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
-        return injectEventSync(event);
-    }
-
-    private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props,
-            @NonNull PointerCoords[] coords, long downTime, int index) {
-        // at least 2 pointers are needed
-        if (props.length != coords.length || coords.length < 2) {
-            return false;
-        }
-
-        long eventTime = SystemClock.uptimeMillis();
-
-        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN
-                + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
-        return injectEventSync(event);
-    }
-
-    private boolean movePointers(@NonNull PointerProperties[] props,
-            @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) {
-        // the number of endpoints should be the same as the number of pointers
-        if (props.length != coords.length || coords.length != endPoints.length) {
-            return false;
-        }
-
-        // prevent division by 0 and negative number of steps
-        if (steps < 1) {
-            steps = 1;
-        }
-
-        // save the starting points before updating any pointers
-        Tuple[] startPoints = new Tuple[coords.length];
-
-        for (int i = 0; i < coords.length; i++) {
-            startPoints[i] = new Tuple(coords[i].x, coords[i].y);
-        }
-
-        MotionEvent event;
-        long eventTime;
-
-        for (int i = 0; i < steps; i++) {
-            // inject a delay between movements
-            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
-
-            // update the coordinates
-            for (int j = 0; j < coords.length; j++) {
-                coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
-                coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
-            }
-
-            eventTime = SystemClock.uptimeMillis();
-
-            event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE,
-                    coords.length, props, coords);
-
-            boolean didInject = injectEventSync(event);
-
-            if (!didInject) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private boolean primaryPointerUp(@NonNull PointerProperties prop,
-            @NonNull PointerCoords coord, long downTime) {
-        long eventTime = SystemClock.uptimeMillis();
-
-        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1,
-                new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
-        return injectEventSync(event);
-    }
-
-    private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props,
-            @NonNull PointerCoords[] coords, long downTime, int index) {
-        // at least 2 pointers are needed
-        if (props.length != coords.length || coords.length < 2) {
-            return false;
-        }
-
-        long eventTime = SystemClock.uptimeMillis();
-
-        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP
-                + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
-        return injectEventSync(event);
-    }
-
-    private PointerCoords getPointerCoord(float x, float y, float pressure, float size) {
-        PointerCoords ptrCoord = new PointerCoords();
-        ptrCoord.x = x;
-        ptrCoord.y = y;
-        ptrCoord.pressure = pressure;
-        ptrCoord.size = size;
-        return ptrCoord;
-    }
-
-    private PointerProperties getPointerProp(int id, int toolType) {
-        PointerProperties ptrProp = new PointerProperties();
-        ptrProp.id = id;
-        ptrProp.toolType = toolType;
-        return ptrProp;
-    }
-
-    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
-            int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) {
-        return MotionEvent.obtain(downTime, eventTime, action, pointerCount,
-                ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f,
-                0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
-    }
-
-    private boolean injectEventSync(InputEvent event) {
-        return mUiAutomation.injectInputEvent(event, true);
-    }
-}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index fd13d14..d5334cb 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -21,6 +21,7 @@
 import android.graphics.Region
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.helpers.FIND_TIMEOUT
+import android.tools.helpers.GestureHelper
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.component.ComponentNameMatcher
 import android.tools.traces.parsers.WindowManagerStateHelper
@@ -38,7 +39,8 @@
         ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent()
 ) : StandardAppHelper(instr, launcherName, component) {
 
-    private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
+    private val gestureHelper: GestureHelper =
+        GestureHelper(instrumentation)
 
     fun clickRestart(wmHelper: WindowManagerStateHelper) {
         val restartButton =
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index db4838e..de17bf4 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -18,29 +18,26 @@
 
 import android.app.Instrumentation
 import android.content.Intent
-import android.graphics.Rect
 import android.graphics.Region
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
-import android.tools.datatypes.coversMoreThan
-import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.apphelpers.BasePipAppHelper
 import android.tools.helpers.FIND_TIMEOUT
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.ConditionsFactory
+import android.tools.traces.component.ComponentNameMatcher
 import android.tools.traces.component.IComponentMatcher
 import android.tools.traces.parsers.WindowManagerStateHelper
 import android.tools.traces.parsers.toFlickerComponent
-import android.util.Log
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
 
-open class PipAppHelper(instrumentation: Instrumentation) :
-    StandardAppHelper(
-        instrumentation,
-        ActivityOptions.Pip.LABEL,
-        ActivityOptions.Pip.COMPONENT.toFlickerComponent()
-    ) {
+open class PipAppHelper(
+    instrumentation: Instrumentation,
+    appName: String = ActivityOptions.Pip.LABEL,
+    componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(),
+) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) {
     private val mediaSessionManager: MediaSessionManager
         get() =
             context.getSystemService(MediaSessionManager::class.java)
@@ -52,189 +49,6 @@
                 it.packageName == packageName
             }
 
-    private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
-
-    open fun clickObject(resId: String) {
-        val selector = By.res(packageName, resId)
-        val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
-
-        obj.click()
-    }
-
-    /** Drags the PIP window to the provided final coordinates without releasing the pointer. */
-    fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
-        val initWindowRect = Rect(getWindowRect(wmHelper))
-
-        // initial pointer at the center of the window
-        val initialCoord =
-            GestureHelper.Tuple(
-                initWindowRect.centerX().toFloat(),
-                initWindowRect.centerY().toFloat()
-            )
-
-        // the offset to the right (or left) of the window center to drag the window to
-        val offset = 50
-
-        // the actual final x coordinate with the offset included;
-        // if the pip window is closer to the right edge of the display the offset is negative
-        // otherwise the offset is positive
-        val endX =
-            initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
-        val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
-
-        // drag to the final coordinate
-        gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps)
-    }
-
-    /**
-     * Releases the primary pointer.
-     *
-     * Injects the release of the primary pointer if the primary pointer info was cached after
-     * another gesture was injected without pointer release.
-     */
-    fun releasePipAfterDragging() {
-        gestureHelper.releasePrimaryPointer()
-    }
-
-    /**
-     * Drags the PIP window away from the screen edge while not crossing the display center.
-     *
-     * @throws IllegalStateException if default display bounds are not available
-     */
-    fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) {
-        val initWindowRect = Rect(getWindowRect(wmHelper))
-
-        // initial pointer at the center of the window
-        val startX = initWindowRect.centerX()
-        val y = initWindowRect.centerY()
-
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
-
-        // the offset to the right (or left) of the display center to drag the window to
-        val offset = 20
-
-        // the actual final x coordinate with the offset included;
-        // if the pip window is closer to the right edge of the display the offset is positive
-        // otherwise the offset is negative
-        val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1)
-
-        // drag the window to the left but not beyond the center of the display
-        uiDevice.drag(startX, y, endX, y, steps)
-    }
-
-    /**
-     * Returns true if PIP window is closer to the right edge of the display than left.
-     *
-     * @throws IllegalStateException if default display bounds are not available
-     */
-    fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean {
-        val windowRect = getWindowRect(wmHelper)
-
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
-
-        return windowRect.centerX() > displayRect.centerX()
-    }
-
-    /**
-     * Expands the PIP window by using the pinch out gesture.
-     *
-     * @param percent The percentage by which to increase the pip window size.
-     * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
-     */
-    fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
-        // the percentage must be between 0.0f and 1.0f
-        if (percent <= 0.0f || percent > 1.0f) {
-            throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
-        }
-
-        val windowRect = getWindowRect(wmHelper)
-
-        // first pointer's initial x coordinate is halfway between the left edge and the center
-        val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
-        // second pointer's initial x coordinate is halfway between the right edge and the center
-        val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
-        // horizontal distance the window should increase by
-        val distIncrease = windowRect.width() * percent
-
-        // final x-coordinates
-        val finalLeftX = initLeftX - (distIncrease / 2)
-        val finalRightX = initRightX + (distIncrease / 2)
-
-        // y-coordinate is the same throughout this animation
-        val yCoord = windowRect.centerY().toFloat()
-
-        var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
-        // if distance per step is at least 1, then we can use the number of steps requested
-        if (distIncrease.toInt() / (steps * 2) >= 1) {
-            adjustedSteps = steps
-        }
-
-        // if the distance per step is less than 1, carry out the animation in two steps
-        gestureHelper.pinch(
-            GestureHelper.Tuple(initLeftX, yCoord),
-            GestureHelper.Tuple(initRightX, yCoord),
-            GestureHelper.Tuple(finalLeftX, yCoord),
-            GestureHelper.Tuple(finalRightX, yCoord),
-            adjustedSteps
-        )
-
-        waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
-    }
-
-    /**
-     * Minimizes the PIP window by using the pinch in gesture.
-     *
-     * @param percent The percentage by which to decrease the pip window size.
-     * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
-     */
-    fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
-        // the percentage must be between 0.0f and 1.0f
-        if (percent <= 0.0f || percent > 1.0f) {
-            throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
-        }
-
-        val windowRect = getWindowRect(wmHelper)
-
-        // first pointer's initial x coordinate is halfway between the left edge and the center
-        val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
-        // second pointer's initial x coordinate is halfway between the right edge and the center
-        val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
-        // decrease by the distance specified through the percentage
-        val distDecrease = windowRect.width() * percent
-
-        // get the final x-coordinates and make sure they are not passing the center of the window
-        val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
-        val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
-
-        // y-coordinate is the same throughout this animation
-        val yCoord = windowRect.centerY().toFloat()
-
-        var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
-        // if distance per step is at least 1, then we can use the number of steps requested
-        if (distDecrease.toInt() / (steps * 2) >= 1) {
-            adjustedSteps = steps
-        }
-
-        // if the distance per step is less than 1, carry out the animation in two steps
-        gestureHelper.pinch(
-            GestureHelper.Tuple(initLeftX, yCoord),
-            GestureHelper.Tuple(initRightX, yCoord),
-            GestureHelper.Tuple(finalLeftX, yCoord),
-            GestureHelper.Tuple(finalRightX, yCoord),
-            adjustedSteps
-        )
-
-        waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect))
-    }
-
     /**
      * Launches the app through an intent instead of interacting with the launcher and waits until
      * the app window is in PIP mode
@@ -331,126 +145,6 @@
         closePipWindow(WindowManagerStateHelper(instrumentation))
     }
 
-    /** Returns the pip window bounds. */
-    fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
-        val windowRegion = wmHelper.getWindowRegion(this)
-        require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
-        return windowRegion.bounds
-    }
-
-    /** Taps the pip window and dismisses it by clicking on the X button. */
-    open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the dismiss button
-        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
-        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
-        val dismissPipObject =
-            uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found")
-        val dismissButtonBounds = dismissPipObject.visibleBounds
-        uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
-
-        // Wait for animation to complete.
-        wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
-    }
-
-    open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the dismiss button
-        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
-        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
-    }
-
-    /** Close the pip window by pressing the expand button */
-    fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the expand button
-        val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
-        uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
-        val expandPipObject =
-            uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found")
-        val expandButtonBounds = expandPipObject.visibleBounds
-        uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
-        wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify()
-    }
-
-    /** Double click on the PIP window to expand it */
-    fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        Log.d(TAG, "First click")
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        Log.d(TAG, "Second click")
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        Log.d(TAG, "Wait for app transition to end")
-        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
-        waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
-    }
-
-    private fun waitForPipWindowToExpandFrom(
-        wmHelper: WindowManagerStateHelper,
-        windowRect: Region
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("pipWindowExpanded") {
-                val pipAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        this.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val pipRegion = pipAppWindow.frameRegion
-                return@add pipRegion.coversMoreThan(windowRect)
-            }
-            .waitForAndVerify()
-    }
-
-    private fun waitForPipWindowToMinimizeFrom(
-        wmHelper: WindowManagerStateHelper,
-        windowRect: Region
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("pipWindowMinimized") {
-                val pipAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        this.windowMatchesAnyOf(window)
-                    }
-                Log.d(TAG, "window " + pipAppWindow)
-                if (pipAppWindow == null) return@add false
-                val pipRegion = pipAppWindow.frameRegion
-                Log.d(
-                    TAG,
-                    "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion)
-                )
-                return@add windowRect.coversMoreThan(pipRegion)
-            }
-            .waitForAndVerify()
-    }
-
-    /**
-     * Waits until the PIP window snaps horizontally to the provided bounds.
-     *
-     * @param finalBounds the bounds to wait for PIP window to snap to
-     */
-    fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("pipWindowSnapped") {
-                val pipAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        this.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val pipRegionBounds = pipAppWindow.frameRegion.bounds
-                return@add pipRegionBounds.left == finalBounds.left &&
-                    pipRegionBounds.right == finalBounds.right
-            }
-            .add(ConditionsFactory.isWMStateComplete())
-            .waitForAndVerify()
-    }
-
     companion object {
         private const val TAG = "PipAppHelper"
         private const val ENTER_PIP_BUTTON_ID = "enter_pip"
@@ -459,8 +153,5 @@
         private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
         private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
         private const val SOURCE_RECT_HINT = "set_source_rect_hint"
-        // minimum number of steps to take, when animating gestures, needs to be 2
-        // so that there is at least a single intermediate layer that flicker tests can check
-        private const val MIN_STEPS_TO_ANIMATE = 2
     }
-}
+}
\ No newline at end of file