Merge "Add toast when non-resizable attempts is attempted to be snap resized" into main
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 81e9df6..8370c2e 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -95,6 +95,8 @@
     private static final int FLAG_FULLSCREEN_OVERRIDE_SYSTEM = FLAG_BASE << 7;
     /** Top activity flag for whether has activity has been overridden to fullscreen by user. */
     private static final int FLAG_FULLSCREEN_OVERRIDE_USER = FLAG_BASE << 8;
+    /** Top activity flag for whether min aspect ratio of the activity has been overridden.*/
+    public static final int FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE = FLAG_BASE << 9;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
@@ -108,7 +110,8 @@
             FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP,
             FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON,
             FLAG_FULLSCREEN_OVERRIDE_SYSTEM,
-            FLAG_FULLSCREEN_OVERRIDE_USER
+            FLAG_FULLSCREEN_OVERRIDE_USER,
+            FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE
     })
     public @interface TopActivityFlag {}
 
@@ -118,7 +121,7 @@
     @TopActivityFlag
     private static final int FLAGS_ORGANIZER_INTERESTED = FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP
             | FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON | FLAG_FULLSCREEN_OVERRIDE_SYSTEM
-            | FLAG_FULLSCREEN_OVERRIDE_USER;
+            | FLAG_FULLSCREEN_OVERRIDE_USER | FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE;
 
     @TopActivityFlag
     private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED
@@ -301,6 +304,21 @@
         setTopActivityFlag(FLAG_LETTERBOXED, enable);
     }
 
+    /**
+     * @return {@code true} if the top activity's min aspect ratio has been overridden.
+     */
+    public boolean hasMinAspectRatioOverride() {
+        return isTopActivityFlagEnabled(FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE);
+    }
+
+    /**
+     * Sets the top activity flag for whether the min aspect ratio of the activity has been
+     * overridden.
+     */
+    public void setHasMinAspectRatioOverride(boolean enable) {
+        setTopActivityFlag(FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE, enable);
+    }
+
     /** Clear all top activity flags and set to false. */
     public void clearTopActivityFlags() {
         mTopActivityFlags = FLAG_UNDEFINED;
@@ -392,6 +410,7 @@
                 + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight
                 + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
                 + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
+                + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
                 + " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
                 + "}";
     }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 5903a7f..38f59ad 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1571,12 +1571,29 @@
      */
     @Nullable
     public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
+        if (multiCrop()) {
+            return peekBitmapDimensionsAsUser(which, returnDefault, mContext.getUserId());
+        }
         checkExactlyOneWallpaperFlagSet(which);
         return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which,
                 mContext.getUserId());
     }
 
     /**
+     * Overload of {@link #peekBitmapDimensions(int, boolean)} with a userId argument.
+     * TODO(b/360120606): remove the SuppressWarnings
+     * @hide
+     */
+    @SuppressWarnings("AndroidFrameworkContextUserId")
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @Nullable
+    public Rect peekBitmapDimensionsAsUser(@SetWallpaperFlags int which, boolean returnDefault,
+            int userId) {
+        checkExactlyOneWallpaperFlagSet(which);
+        return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which, userId);
+    }
+
+    /**
      * For the current user, given a list of display sizes, return a list of rectangles representing
      * the area of the current wallpaper that would be shown for each of these sizes.
      *
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index d85e41d..c5d0caf22 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -336,6 +336,39 @@
     }
 
     /**
+     * Returns true if the touchpad visualizer is allowed to appear.
+     *
+     * @param context The application context.
+     * @return Whether it is allowed to show touchpad visualizer or not.
+     *
+     * @hide
+     */
+    public static boolean useTouchpadVisualizer(@NonNull Context context) {
+        if (!isTouchpadVisualizerFeatureFlagEnabled()) {
+            return false;
+        }
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_VISUALIZER, 0, UserHandle.USER_CURRENT) == 1;
+    }
+
+    /**
+     * Sets the touchpad visualizer behaviour.
+     *
+     * @param context The application context.
+     * @param enabled Will enable touchpad visualizer if true, disable it if false
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setTouchpadVisualizer(@NonNull Context context, boolean enabled) {
+        if (!isTouchpadVisualizerFeatureFlagEnabled()) {
+            return;
+        }
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_VISUALIZER, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Returns true if the touchpad should allow tap dragging.
      *
      * The returned value only applies to gesture-compatible touchpads.
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 53a1a67d..e3b1221 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -113,3 +113,14 @@
         purpose: PURPOSE_FEATURE
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "normalized_pwle_effects"
+    is_exported: true
+    description: "Enables functionality to create PWLE effects using advanced and simple APIs"
+    bug: "341052318"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 66d08f9..08e51a1 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,6 +27,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
+import static com.android.window.flags.Flags.noDuplicateSurfaceDestroyedEvents;
 import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
 import static com.android.window.flags.Flags.noVisibilityEventOnDisplayStateChange;
 import static com.android.window.flags.Flags.offloadColorExtraction;
@@ -255,6 +256,7 @@
          */
         private boolean mIsScreenTurningOn;
         boolean mReportedVisible;
+        boolean mReportedSurfaceCreated;
         boolean mDestroyed;
         // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
         // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once
@@ -1381,6 +1383,7 @@
                         if (surfaceCreating) {
                             mIsCreating = true;
                             didSurface = true;
+                            mReportedSurfaceCreated = true;
                             if (DEBUG) Log.v(TAG, "onSurfaceCreated("
                                     + mSurfaceHolder + "): " + this);
                             Trace.beginSection("WPMS.Engine.onSurfaceCreated");
@@ -2264,8 +2267,10 @@
         }
 
         void reportSurfaceDestroyed() {
-            if (mSurfaceCreated) {
+            if ((!noDuplicateSurfaceDestroyedEvents() && mSurfaceCreated)
+                    || (noDuplicateSurfaceDestroyedEvents() && mReportedSurfaceCreated)) {
                 mSurfaceCreated = false;
+                mReportedSurfaceCreated = false;
                 mSurfaceHolder.ungetCallbacks();
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                 if (callbacks != null) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d28c953..03a2672 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -474,7 +474,11 @@
     private final AccessibilitySmartActions mA11ySmartActions;
     private InsertModeController mInsertModeController;
 
-    Editor(TextView textView) {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public Editor(TextView textView) {
         mTextView = textView;
         // Synchronize the filter list, which places the undo input filter at the end.
         mTextView.setFilters(mTextView.getFilters());
@@ -3206,16 +3210,6 @@
             }
         }
 
-        final int menuItemOrderUndo = 2;
-        final int menuItemOrderRedo = 3;
-        final int menuItemOrderCut = 4;
-        final int menuItemOrderCopy = 5;
-        final int menuItemOrderPaste = 6;
-        final int menuItemOrderPasteAsPlainText = 7;
-        final int menuItemOrderSelectAll = 8;
-        final int menuItemOrderShare = 9;
-        final int menuItemOrderAutofill = 10;
-
         menu.setOptionalIconsVisible(true);
         menu.setGroupDividerEnabled(true);
 
@@ -3224,7 +3218,18 @@
         final int keyboard = mTextView.getResources().getConfiguration().keyboard;
         menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
 
-        final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[] {
+        setTextContextMenuItems(menu);
+
+        mPreserveSelection = true;
+
+        // No-op for the old context menu because it doesn't have icons.
+        adjustIconSpacing(menu);
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public void setTextContextMenuItems(ContextMenu menu) {
+        final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[]{
                 // TODO: Make Undo/Redo be public attribute.
                 com.android.internal.R.attr.actionModeUndoDrawable,
                 com.android.internal.R.attr.actionModeRedoDrawable,
@@ -3235,6 +3240,16 @@
                 android.R.attr.actionModeShareDrawable,
         });
 
+        final int menuItemOrderUndo = 2;
+        final int menuItemOrderRedo = 3;
+        final int menuItemOrderCut = 4;
+        final int menuItemOrderCopy = 5;
+        final int menuItemOrderPaste = 6;
+        final int menuItemOrderPasteAsPlainText = 7;
+        final int menuItemOrderSelectAll = 8;
+        final int menuItemOrderShare = 9;
+        final int menuItemOrderAutofill = 10;
+
         menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
                 com.android.internal.R.string.undo)
                 .setAlphabeticShortcut('z')
@@ -3291,12 +3306,7 @@
                 .setEnabled(mTextView.canRequestAutofill()
                         && (selected == null || selected.isEmpty()))
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
-
-        mPreserveSelection = true;
         a.recycle();
-
-        // No-op for the old context menu because it doesn't have icons.
-        adjustIconSpacing(menu);
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 61ecc62..72b268b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12255,7 +12255,11 @@
         return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
     }
 
-    String getSelectedText() {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public String getSelectedText() {
         if (!hasSelection()) {
             return null;
         }
@@ -14080,7 +14084,11 @@
         structure.setInputType(getInputType());
     }
 
-    boolean canRequestAutofill() {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean canRequestAutofill() {
         if (!isAutofillable()) {
             return false;
         }
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 01c78a0..8c6721a 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -39,3 +39,13 @@
   description: "Prevent the system from sending visibility event on display state change."
   bug: "331725519"
 }
+
+flag {
+  name: "no_duplicate_surface_destroyed_events"
+  namespace: "systemui"
+  description: "Prevent the system from sending onSurfaceDestroyed() twice."
+  bug: "344461715"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index cbcbf2db..79a5469 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -401,7 +401,9 @@
             Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
         }
 
-        dumpViewerConfig();
+        if (!android.tracing.Flags.clientSideProtoLogging()) {
+            dumpViewerConfig();
+        }
 
         Log.d(LOG_TAG, "Finished onTracingFlush");
     }
@@ -497,7 +499,8 @@
                     os.write(GROUP_ID, pis.readInt(GROUP_ID));
                     break;
                 case (int) LOCATION:
-                    os.write(LOCATION, pis.readInt(LOCATION));
+                    os.write(LOCATION, pis.readString(LOCATION));
+                    break;
                 default:
                     throw new RuntimeException(
                             "Unexpected field id " + pis.getFieldNumber());
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index f9da832..b11307e 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -23,7 +23,9 @@
 import static org.mockito.ArgumentMatchers.anyChar;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -251,8 +253,9 @@
         when(menu.add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()))
                 .thenReturn(mockAutofillMenuItem);
 
-        EditText et = mActivity.findViewById(R.id.editText);
-        et.setText("Test");
+        EditText et = spy(mActivity.findViewById(R.id.editText));
+        doReturn(true).when(et).canRequestAutofill();
+        doReturn(null).when(et).getSelectedText();
 
         Editor editor = et.getEditorForTesting();
         editor.onCreateContextMenu(menu);
@@ -271,11 +274,11 @@
         when(menu.add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()))
                 .thenReturn(mockAutofillMenuItem);
 
-        EditText et = mActivity.findViewById(R.id.editText);
-        et.setText("Test");
-        et.selectAll();
-        Editor editor = et.getEditorForTesting();
-        editor.onCreateContextMenu(menu);
+        EditText et = spy(mActivity.findViewById(R.id.editText));
+        doReturn(true).when(et).canRequestAutofill();
+        doReturn("test").when(et).getSelectedText();
+        Editor editor = new Editor(et);
+        editor.setTextContextMenuItems(menu);
 
         verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt());
         verify(mockAutofillMenuItem).setEnabled(false);
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 6c03dc3..b68b436 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
@@ -66,7 +66,7 @@
     val initialSize: Size =
         when (taskInfo.configuration.orientation) {
             ORIENTATION_LANDSCAPE -> {
-                if (taskInfo.isResizeable) {
+                if (taskInfo.canChangeAspectRatio) {
                     if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
@@ -85,7 +85,7 @@
             ORIENTATION_PORTRAIT -> {
                 val customPortraitWidthForLandscapeApp =
                     screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
-                if (taskInfo.isResizeable) {
+                if (taskInfo.canChangeAspectRatio) {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
                         // For landscape resizeable activities, respect apps fullscreen height and
                         // apply custom app width.
@@ -189,6 +189,13 @@
     }
 
 /**
+ * Whether the activity's aspect ratio can be changed or if it should be maintained as if it was
+ * unresizeable.
+ */
+private val TaskInfo.canChangeAspectRatio: Boolean
+    get() = isResizeable && !appCompatTaskInfo.hasMinAspectRatioOverride()
+
+/**
  * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
  * entire screen, as area can be offset by left and top start.
  */
@@ -204,7 +211,7 @@
     return Rect(newLeft, newTop, newRight, newBottom)
 }
 
-fun TaskInfo.hasPortraitTopActivity(): Boolean {
+private fun TaskInfo.hasPortraitTopActivity(): Boolean {
     val topActivityScreenOrientation =
         topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
     val appBounds = configuration.windowConfiguration.appBounds
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 a630cef..9530bdb 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
@@ -867,6 +867,18 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+    setUpLandscapeDisplay()
+    val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+      shouldLetterbox = true, aspectRatioOverrideApplied = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
   fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
     setUpPortraitDisplay()
     val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
@@ -888,6 +900,19 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+    setUpPortraitDisplay()
+    val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+      deviceOrientation = ORIENTATION_PORTRAIT,
+      shouldLetterbox = true, aspectRatioOverrideApplied = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+  }
+
+  @Test
   fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
     val task = setUpFullscreenTask()
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2896,7 +2921,8 @@
     shouldLetterbox: Boolean = false,
     gravity: Int = Gravity.NO_GRAVITY,
     enableUserFullscreenOverride: Boolean = false,
-    enableSystemFullscreenOverride: Boolean = false
+    enableSystemFullscreenOverride: Boolean = false,
+    aspectRatioOverrideApplied: Boolean = false
   ): RunningTaskInfo {
     val task = createFullscreenTask(displayId)
     val activityInfo = ActivityInfo()
@@ -2911,6 +2937,7 @@
       appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
 
       if (shouldLetterbox) {
+        appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
         if (deviceOrientation == ORIENTATION_LANDSCAPE &&
             screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
           // Letterbox to portrait size
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 9896f64..2d7db5e 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -131,7 +131,7 @@
     private int mCasSystemId;
     private int mUserId;
     private TunerResourceManager mTunerResourceManager = null;
-    private final Map<Session, Long> mSessionMap = new HashMap<>();
+    private final Map<Session, Integer> mSessionMap = new HashMap<>();
 
     /**
      * Scrambling modes used to open cas sessions.
@@ -1126,10 +1126,10 @@
         }
     }
 
-    private long getSessionResourceHandle() throws MediaCasException {
+    private int getSessionResourceHandle() throws MediaCasException {
         validateInternalStates();
 
-        long[] sessionResourceHandle = new long[1];
+        int[] sessionResourceHandle = new int[1];
         sessionResourceHandle[0] = -1;
         if (mTunerResourceManager != null) {
             CasSessionRequest casSessionRequest = new CasSessionRequest();
@@ -1144,7 +1144,8 @@
         return sessionResourceHandle[0];
     }
 
-    private void addSessionToResourceMap(Session session, long sessionResourceHandle) {
+    private void addSessionToResourceMap(Session session, int sessionResourceHandle) {
+
         if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
             synchronized (mSessionMap) {
                 mSessionMap.put(session, sessionResourceHandle);
@@ -1177,14 +1178,13 @@
      * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public Session openSession() throws MediaCasException {
-        long sessionResourceHandle = getSessionResourceHandle();
+        int sessionResourceHandle = getSessionResourceHandle();
 
         try {
             if (mICas != null) {
                 try {
                     byte[] sessionId = mICas.openSessionDefault();
                     Session session = createFromSessionId(sessionId);
-                    addSessionToResourceMap(session, sessionResourceHandle);
                     Log.d(TAG, "Write Stats Log for succeed to Open Session.");
                     FrameworkStatsLog.write(
                             FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
@@ -1238,7 +1238,7 @@
     @Nullable
     public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
             throws MediaCasException {
-        long sessionResourceHandle = getSessionResourceHandle();
+        int sessionResourceHandle = getSessionResourceHandle();
 
         if (mICas != null) {
             try {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 300ae5d..2c71ee0 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -293,13 +293,13 @@
     private EventHandler mHandler;
     @Nullable
     private FrontendInfo mFrontendInfo;
-    private Long mFrontendHandle;
+    private Integer mFrontendHandle;
     private Tuner mFeOwnerTuner = null;
     private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
     private Integer mDesiredFrontendId = null;
     private int mUserId;
     private Lnb mLnb;
-    private Long mLnbHandle;
+    private Integer mLnbHandle;
     @Nullable
     private OnTuneEventListener mOnTuneEventListener;
     @Nullable
@@ -322,10 +322,10 @@
     private final ReentrantLock mDemuxLock = new ReentrantLock();
     private int mRequestedCiCamId;
 
-    private Long mDemuxHandle;
-    private Long mFrontendCiCamHandle;
+    private Integer mDemuxHandle;
+    private Integer mFrontendCiCamHandle;
     private Integer mFrontendCiCamId;
-    private Map<Long, WeakReference<Descrambler>> mDescramblers = new HashMap<>();
+    private Map<Integer, WeakReference<Descrambler>> mDescramblers = new HashMap<>();
     private List<WeakReference<Filter>> mFilters = new ArrayList<WeakReference<Filter>>();
 
     private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
@@ -947,7 +947,7 @@
     private void releaseDescramblers() {
         synchronized (mDescramblers) {
             if (!mDescramblers.isEmpty()) {
-                for (Map.Entry<Long, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
+                for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
                     Descrambler descrambler = d.getValue().get();
                     if (descrambler != null) {
                         descrambler.close();
@@ -1008,7 +1008,7 @@
     /**
      * Native method to open frontend of the given ID.
      */
-    private native Frontend nativeOpenFrontendByHandle(long handle);
+    private native Frontend nativeOpenFrontendByHandle(int handle);
     private native int nativeShareFrontend(int id);
     private native int nativeUnshareFrontend();
     private native void nativeRegisterFeCbListener(long nativeContext);
@@ -1037,21 +1037,21 @@
     private native int nativeSetMaxNumberOfFrontends(int frontendType, int maxNumber);
     private native int nativeGetMaxNumberOfFrontends(int frontendType);
     private native int nativeRemoveOutputPid(int pid);
-    private native Lnb nativeOpenLnbByHandle(long handle);
+    private native Lnb nativeOpenLnbByHandle(int handle);
     private native Lnb nativeOpenLnbByName(String name);
     private native FrontendStatusReadiness[] nativeGetFrontendStatusReadiness(int[] statusTypes);
 
-    private native Descrambler nativeOpenDescramblerByHandle(long handle);
-    private native int nativeOpenDemuxByhandle(long handle);
+    private native Descrambler nativeOpenDescramblerByHandle(int handle);
+    private native int nativeOpenDemuxByhandle(int handle);
 
     private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
     private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
 
     private native DemuxCapabilities nativeGetDemuxCapabilities();
-    private native DemuxInfo nativeGetDemuxInfo(long demuxHandle);
+    private native DemuxInfo nativeGetDemuxInfo(int demuxHandle);
 
-    private native int nativeCloseDemux(long handle);
-    private native int nativeCloseFrontend(long handle);
+    private native int nativeCloseDemux(int handle);
+    private native int nativeCloseFrontend(int handle);
     private native int nativeClose();
 
     private static native SharedFilter nativeOpenSharedFilter(String token);
@@ -1369,7 +1369,7 @@
     }
 
     private boolean requestFrontend() {
-        long[] feHandle = new long[1];
+        int[] feHandle = new int[1];
         boolean granted = false;
         try {
             TunerFrontendRequest request = new TunerFrontendRequest();
@@ -2377,7 +2377,7 @@
     }
 
     private boolean requestLnb() {
-        long[] lnbHandle = new long[1];
+        int[] lnbHandle = new int[1];
         TunerLnbRequest request = new TunerLnbRequest();
         request.clientId = mClientId;
         boolean granted = mTunerResourceManager.requestLnb(request, lnbHandle);
@@ -2660,7 +2660,7 @@
     }
 
     private boolean requestDemux() {
-        long[] demuxHandle = new long[1];
+        int[] demuxHandle = new int[1];
         TunerDemuxRequest request = new TunerDemuxRequest();
         request.clientId = mClientId;
         request.desiredFilterTypes = mDesiredDemuxInfo.getFilterTypes();
@@ -2673,14 +2673,14 @@
     }
 
     private Descrambler requestDescrambler() {
-        long[] descramblerHandle = new long[1];
+        int[] descramblerHandle = new int[1];
         TunerDescramblerRequest request = new TunerDescramblerRequest();
         request.clientId = mClientId;
         boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle);
         if (!granted) {
             return null;
         }
-        long handle = descramblerHandle[0];
+        int handle = descramblerHandle[0];
         Descrambler descrambler = nativeOpenDescramblerByHandle(handle);
         if (descrambler != null) {
             synchronized (mDescramblers) {
@@ -2694,7 +2694,7 @@
     }
 
     private boolean requestFrontendCiCam(int ciCamId) {
-        long[] ciCamHandle = new long[1];
+        int[] ciCamHandle = new int[1];
         TunerCiCamRequest request = new TunerCiCamRequest();
         request.clientId = mClientId;
         request.ciCamId = ciCamId;
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index bb581eb..d268aeb 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -66,7 +66,7 @@
     private static final String TAG = "TunerResourceManager";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    public static final long INVALID_RESOURCE_HANDLE = -1;
+    public static final int INVALID_RESOURCE_HANDLE = -1;
     public static final int INVALID_OWNER_ID = -1;
     /**
      * Tuner resource type to help generate resource handle
@@ -275,7 +275,7 @@
      * Updates the current TRM of the TunerHAL Frontend information.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestFrontend(TunerFrontendRequest, long[])} and
+     * {@link #requestFrontend(TunerFrontendRequest, int[])} and
      * {@link #releaseFrontend(int, int)} call.
      *
      * @param infos an array of the available {@link TunerFrontendInfo} information.
@@ -331,7 +331,7 @@
      *
      * @param lnbIds ids of the updating lnbs.
      */
-    public void setLnbInfoList(long[] lnbIds) {
+    public void setLnbInfoList(int[] lnbIds) {
         try {
             mService.setLnbInfoList(lnbIds);
         } catch (RemoteException e) {
@@ -406,8 +406,8 @@
      *
      * @return true if there is frontend granted.
      */
-    public boolean requestFrontend(
-            @NonNull TunerFrontendRequest request, @Nullable long[] frontendHandle) {
+    public boolean requestFrontend(@NonNull TunerFrontendRequest request,
+                @Nullable int[] frontendHandle) {
         boolean result = false;
         try {
             result = mService.requestFrontend(request, frontendHandle);
@@ -511,7 +511,7 @@
      *
      * @return true if there is Demux granted.
      */
-    public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull long[] demuxHandle) {
+    public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull int[] demuxHandle) {
         boolean result = false;
         try {
             result = mService.requestDemux(request, demuxHandle);
@@ -544,8 +544,8 @@
      *
      * @return true if there is Descrambler granted.
      */
-    public boolean requestDescrambler(
-            @NonNull TunerDescramblerRequest request, @NonNull long[] descramblerHandle) {
+    public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
+                @NonNull int[] descramblerHandle) {
         boolean result = false;
         try {
             result = mService.requestDescrambler(request, descramblerHandle);
@@ -577,8 +577,8 @@
      *
      * @return true if there is CAS session granted.
      */
-    public boolean requestCasSession(
-            @NonNull CasSessionRequest request, @NonNull long[] casSessionHandle) {
+    public boolean requestCasSession(@NonNull CasSessionRequest request,
+                @NonNull int[] casSessionHandle) {
         boolean result = false;
         try {
             result = mService.requestCasSession(request, casSessionHandle);
@@ -610,7 +610,7 @@
      *
      * @return true if there is ciCam granted.
      */
-    public boolean requestCiCam(TunerCiCamRequest request, long[] ciCamHandle) {
+    public boolean requestCiCam(TunerCiCamRequest request, int[] ciCamHandle) {
         boolean result = false;
         try {
             result = mService.requestCiCam(request, ciCamHandle);
@@ -635,7 +635,7 @@
      * <li>If no Lnb system can be granted, the API would return false.
      * <ul>
      *
-     * <p><strong>Note:</strong> {@link #setLnbInfoList(long[])} must be called before this request.
+     * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this request.
      *
      * @param request {@link TunerLnbRequest} information of the current request.
      * @param lnbHandle a one-element array to return the granted Lnb handle.
@@ -643,7 +643,7 @@
      *
      * @return true if there is Lnb granted.
      */
-    public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull long[] lnbHandle) {
+    public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle) {
         boolean result = false;
         try {
             result = mService.requestLnb(request, lnbHandle);
@@ -664,7 +664,7 @@
      * @param frontendHandle the handle of the released frontend.
      * @param clientId the id of the client that is releasing the frontend.
      */
-    public void releaseFrontend(long frontendHandle, int clientId) {
+    public void releaseFrontend(int frontendHandle, int clientId) {
         try {
             mService.releaseFrontend(frontendHandle, clientId);
         } catch (RemoteException e) {
@@ -680,7 +680,7 @@
      * @param demuxHandle the handle of the released Tuner Demux.
      * @param clientId the id of the client that is releasing the demux.
      */
-    public void releaseDemux(long demuxHandle, int clientId) {
+    public void releaseDemux(int demuxHandle, int clientId) {
         try {
             mService.releaseDemux(demuxHandle, clientId);
         } catch (RemoteException e) {
@@ -696,7 +696,7 @@
      * @param descramblerHandle the handle of the released Tuner Descrambler.
      * @param clientId the id of the client that is releasing the descrambler.
      */
-    public void releaseDescrambler(long descramblerHandle, int clientId) {
+    public void releaseDescrambler(int descramblerHandle, int clientId) {
         try {
             mService.releaseDescrambler(descramblerHandle, clientId);
         } catch (RemoteException e) {
@@ -715,7 +715,7 @@
      * @param casSessionHandle the handle of the released CAS session.
      * @param clientId the id of the client that is releasing the cas session.
      */
-    public void releaseCasSession(long casSessionHandle, int clientId) {
+    public void releaseCasSession(int casSessionHandle, int clientId) {
         try {
             mService.releaseCasSession(casSessionHandle, clientId);
         } catch (RemoteException e) {
@@ -734,7 +734,7 @@
      * @param ciCamHandle the handle of the releasing CiCam.
      * @param clientId the id of the client that is releasing the CiCam.
      */
-    public void releaseCiCam(long ciCamHandle, int clientId) {
+    public void releaseCiCam(int ciCamHandle, int clientId) {
         try {
             mService.releaseCiCam(ciCamHandle, clientId);
         } catch (RemoteException e) {
@@ -747,12 +747,12 @@
      *
      * <p>Client must call this whenever it releases an Lnb.
      *
-     * <p><strong>Note:</strong> {@link #setLnbInfoList(long[])} must be called before this release.
+     * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this release.
      *
      * @param lnbHandle the handle of the released Tuner Lnb.
      * @param clientId the id of the client that is releasing the lnb.
      */
-    public void releaseLnb(long lnbHandle, int clientId) {
+    public void releaseLnb(int lnbHandle, int clientId) {
         try {
             mService.releaseLnb(lnbHandle, clientId);
         } catch (RemoteException e) {
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 109c791..5399697 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
@@ -149,7 +149,7 @@
      *
      * @param lnbIds ids of the updating lnbs.
      */
-    void setLnbInfoList(in long[] lnbIds);
+    void setLnbInfoList(in int[] lnbIds);
 
     /*
      * This API is used by the Tuner framework to request a frontend from the TunerHAL.
@@ -185,7 +185,7 @@
      *
      * @return true if there is frontend granted.
      */
-    boolean requestFrontend(in TunerFrontendRequest request, out long[] frontendHandle);
+    boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle);
 
     /*
      * Sets the maximum usable frontends number of a given frontend type. It is used to enable or
@@ -253,7 +253,7 @@
      *
      * @return true if there is demux granted.
      */
-    boolean requestDemux(in TunerDemuxRequest request, out long[] demuxHandle);
+    boolean requestDemux(in TunerDemuxRequest request, out int[] demuxHandle);
 
     /*
      * This API is used by the Tuner framework to request an available descrambler from the
@@ -277,7 +277,7 @@
      *
      * @return true if there is Descrambler granted.
      */
-    boolean requestDescrambler(in TunerDescramblerRequest request, out long[] descramblerHandle);
+    boolean requestDescrambler(in TunerDescramblerRequest request, out int[] descramblerHandle);
 
     /*
      * This API is used by the Tuner framework to request an available Cas session. This session
@@ -303,7 +303,7 @@
      *
      * @return true if there is CAS session granted.
      */
-    boolean requestCasSession(in CasSessionRequest request, out long[] casSessionHandle);
+    boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle);
 
     /*
      * This API is used by the Tuner framework to request an available CuCam.
@@ -328,7 +328,7 @@
      *
      * @return true if there is CiCam granted.
      */
-    boolean requestCiCam(in TunerCiCamRequest request, out long[] ciCamHandle);
+    boolean requestCiCam(in TunerCiCamRequest request, out int[] ciCamHandle);
 
     /*
      * This API is used by the Tuner framework to request an available Lnb from the TunerHAL.
@@ -352,7 +352,7 @@
      *
      * @return true if there is Lnb granted.
      */
-    boolean requestLnb(in TunerLnbRequest request, out long[] lnbHandle);
+    boolean requestLnb(in TunerLnbRequest request, out int[] lnbHandle);
 
     /*
      * Notifies the TRM that the given frontend has been released.
@@ -365,7 +365,7 @@
      * @param frontendHandle the handle of the released frontend.
      * @param clientId the id of the client that is releasing the frontend.
      */
-    void releaseFrontend(in long frontendHandle, int clientId);
+    void releaseFrontend(in int frontendHandle, int clientId);
 
     /*
      * Notifies the TRM that the Demux with the given handle was released.
@@ -375,7 +375,7 @@
      * @param demuxHandle the handle of the released Tuner Demux.
      * @param clientId the id of the client that is releasing the demux.
      */
-    void releaseDemux(in long demuxHandle, int clientId);
+    void releaseDemux(in int demuxHandle, int clientId);
 
     /*
      * Notifies the TRM that the Descrambler with the given handle was released.
@@ -385,7 +385,7 @@
      * @param descramblerHandle the handle of the released Tuner Descrambler.
      * @param clientId the id of the client that is releasing the descrambler.
      */
-    void releaseDescrambler(in long descramblerHandle, int clientId);
+    void releaseDescrambler(in int descramblerHandle, int clientId);
 
     /*
      * Notifies the TRM that the given Cas session has been released.
@@ -397,7 +397,7 @@
      * @param casSessionHandle the handle of the released CAS session.
      * @param clientId the id of the client that is releasing the cas session.
      */
-    void releaseCasSession(in long casSessionHandle, int clientId);
+    void releaseCasSession(in int casSessionHandle, int clientId);
 
     /**
      * Notifies the TRM that the given CiCam has been released.
@@ -410,7 +410,7 @@
      * @param ciCamHandle the handle of the releasing CiCam.
      * @param clientId the id of the client that is releasing the CiCam.
      */
-    void releaseCiCam(in long ciCamHandle, int clientId);
+    void releaseCiCam(in int ciCamHandle, int clientId);
 
     /*
      * Notifies the TRM that the Lnb with the given handle was released.
@@ -422,7 +422,7 @@
      * @param lnbHandle the handle of the released Tuner Lnb.
      * @param clientId the id of the client that is releasing the lnb.
      */
-    void releaseLnb(in long lnbHandle, int clientId);
+    void releaseLnb(in int lnbHandle, int clientId);
 
     /*
      * Compare two clients' priority.
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl
index 7984c38..c14caf5 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl
@@ -26,7 +26,7 @@
     /**
      * Demux handle
      */
-    long handle;
+    int handle;
 
     /**
      * Supported filter types (defined in {@link android.media.tv.tuner.filter.Filter})
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl
index 274367e..8981ce0 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendInfo.aidl
@@ -26,7 +26,7 @@
     /**
      * Frontend Handle
      */
-    long handle;
+    int handle;
 
     /**
      * Frontend Type
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 5eb2485..00b0e57 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1448,7 +1448,7 @@
     return obj;
 }
 
-jobject JTuner::openFrontendByHandle(jlong feHandle) {
+jobject JTuner::openFrontendByHandle(int feHandle) {
     // TODO: Handle reopening frontend with different handle
     sp<FrontendClient> feClient = sTunerClient->openFrontend(feHandle);
     if (feClient == nullptr) {
@@ -1824,7 +1824,7 @@
     return valObj;
 }
 
-jobject JTuner::openLnbByHandle(jlong handle) {
+jobject JTuner::openLnbByHandle(int handle) {
     if (sTunerClient == nullptr) {
         return nullptr;
     }
@@ -1833,7 +1833,7 @@
     sp<LnbClientCallbackImpl> callback = new LnbClientCallbackImpl();
     lnbClient = sTunerClient->openLnb(handle);
     if (lnbClient == nullptr) {
-        ALOGD("Failed to open lnb, handle = %ld", handle);
+        ALOGD("Failed to open lnb, handle = %d", handle);
         return nullptr;
     }
 
@@ -1947,7 +1947,7 @@
     return (int)result;
 }
 
-Result JTuner::openDemux(jlong handle) {
+Result JTuner::openDemux(int handle) {
     if (sTunerClient == nullptr) {
         return Result::NOT_INITIALIZED;
     }
@@ -2215,7 +2215,7 @@
             numBytesInSectionFilter, filterCaps, filterCapsList, linkCaps, bTimeFilter);
 }
 
-jobject JTuner::getDemuxInfo(jlong handle) {
+jobject JTuner::getDemuxInfo(int handle) {
     if (sTunerClient == nullptr) {
         ALOGE("tuner is not initialized");
         return nullptr;
@@ -3768,8 +3768,8 @@
     return tuner->getFrontendIds();
 }
 
-static jobject android_media_tv_Tuner_open_frontend_by_handle(JNIEnv *env, jobject thiz,
-                                                              jlong handle) {
+static jobject android_media_tv_Tuner_open_frontend_by_handle(
+        JNIEnv *env, jobject thiz, jint handle) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->openFrontendByHandle(handle);
 }
@@ -3901,7 +3901,7 @@
     return tuner->getFrontendInfo(id);
 }
 
-static jobject android_media_tv_Tuner_open_lnb_by_handle(JNIEnv *env, jobject thiz, jlong handle) {
+static jobject android_media_tv_Tuner_open_lnb_by_handle(JNIEnv *env, jobject thiz, jint handle) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->openLnbByHandle(handle);
 }
@@ -4622,7 +4622,7 @@
     return (int)r;
 }
 
-static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz, jlong) {
+static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz, jint) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->openDescrambler();
 }
@@ -4690,12 +4690,12 @@
     return tuner->getDemuxCaps();
 }
 
-static jobject android_media_tv_Tuner_get_demux_info(JNIEnv *env, jobject thiz, jlong handle) {
+static jobject android_media_tv_Tuner_get_demux_info(JNIEnv* env, jobject thiz, jint handle) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->getDemuxInfo(handle);
 }
 
-static jint android_media_tv_Tuner_open_demux(JNIEnv *env, jobject thiz, jlong handle) {
+static jint android_media_tv_Tuner_open_demux(JNIEnv* env, jobject thiz, jint handle) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return (jint)tuner->openDemux(handle);
 }
@@ -4706,7 +4706,7 @@
     return (jint)tuner->close();
 }
 
-static jint android_media_tv_Tuner_close_demux(JNIEnv *env, jobject thiz, jlong /* handle */) {
+static jint android_media_tv_Tuner_close_demux(JNIEnv* env, jobject thiz, jint /* handle */) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->closeDemux();
 }
@@ -4766,7 +4766,7 @@
     return tuner->getFrontendStatusReadiness(types);
 }
 
-static jint android_media_tv_Tuner_close_frontend(JNIEnv *env, jobject thiz, jlong /* handle */) {
+static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->closeFrontend();
 }
@@ -5035,7 +5035,7 @@
     { "nativeGetTunerVersion", "()I", (void *)android_media_tv_Tuner_native_get_tuner_version },
     { "nativeGetFrontendIds", "()Ljava/util/List;",
             (void *)android_media_tv_Tuner_get_frontend_ids },
-    { "nativeOpenFrontendByHandle", "(J)Landroid/media/tv/tuner/Tuner$Frontend;",
+    { "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;",
             (void *)android_media_tv_Tuner_open_frontend_by_handle },
     { "nativeShareFrontend", "(I)I",
             (void *)android_media_tv_Tuner_share_frontend },
@@ -5074,11 +5074,11 @@
             (void *)android_media_tv_Tuner_open_filter },
     { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/filter/TimeFilter;",
             (void *)android_media_tv_Tuner_open_time_filter },
-    { "nativeOpenLnbByHandle", "(J)Landroid/media/tv/tuner/Lnb;",
+    { "nativeOpenLnbByHandle", "(I)Landroid/media/tv/tuner/Lnb;",
             (void *)android_media_tv_Tuner_open_lnb_by_handle },
     { "nativeOpenLnbByName", "(Ljava/lang/String;)Landroid/media/tv/tuner/Lnb;",
             (void *)android_media_tv_Tuner_open_lnb_by_name },
-    { "nativeOpenDescramblerByHandle", "(J)Landroid/media/tv/tuner/Descrambler;",
+    { "nativeOpenDescramblerByHandle", "(I)Landroid/media/tv/tuner/Descrambler;",
             (void *)android_media_tv_Tuner_open_descrambler },
     { "nativeOpenDvrRecorder", "(J)Landroid/media/tv/tuner/dvr/DvrRecorder;",
             (void *)android_media_tv_Tuner_open_dvr_recorder },
@@ -5086,12 +5086,12 @@
             (void *)android_media_tv_Tuner_open_dvr_playback },
     { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
             (void *)android_media_tv_Tuner_get_demux_caps },
-    { "nativeGetDemuxInfo", "(J)Landroid/media/tv/tuner/DemuxInfo;",
+    { "nativeGetDemuxInfo", "(I)Landroid/media/tv/tuner/DemuxInfo;",
             (void *)android_media_tv_Tuner_get_demux_info },
-    { "nativeOpenDemuxByhandle", "(J)I", (void *)android_media_tv_Tuner_open_demux },
+    { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux },
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
-    { "nativeCloseFrontend", "(J)I", (void *)android_media_tv_Tuner_close_frontend },
-    { "nativeCloseDemux", "(J)I", (void *)android_media_tv_Tuner_close_demux },
+    { "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend },
+    { "nativeCloseDemux", "(I)I", (void *)android_media_tv_Tuner_close_demux },
     { "nativeOpenSharedFilter",
             "(Ljava/lang/String;)Landroid/media/tv/tuner/filter/SharedFilter;",
             (void *)android_media_tv_Tuner_open_shared_filter},
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 7af2cd7..3de3ab9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -206,7 +206,7 @@
     int disconnectCiCam();
     int unlinkCiCam(jint id);
     jobject getFrontendIds();
-    jobject openFrontendByHandle(jlong feHandle);
+    jobject openFrontendByHandle(int feHandle);
     int shareFrontend(int feId);
     int unshareFrontend();
     void registerFeCbListener(JTuner* jtuner);
@@ -221,16 +221,16 @@
     int setLnb(sp<LnbClient> lnbClient);
     bool isLnaSupported();
     int setLna(bool enable);
-    jobject openLnbByHandle(jlong handle);
+    jobject openLnbByHandle(int handle);
     jobject openLnbByName(jstring name);
     jobject openFilter(DemuxFilterType type, int bufferSize);
     jobject openTimeFilter();
     jobject openDescrambler();
     jobject openDvr(DvrType type, jlong bufferSize);
     jobject getDemuxCaps();
-    jobject getDemuxInfo(jlong handle);
+    jobject getDemuxInfo(int handle);
     jobject getFrontendStatus(jintArray types);
-    Result openDemux(jlong handle);
+    Result openDemux(int handle);
     jint close();
     jint closeFrontend();
     jint closeDemux();
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 0097710..ea623d9 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -56,7 +56,7 @@
     return ids;
 }
 
-sp<FrontendClient> TunerClient::openFrontend(int64_t frontendHandle) {
+sp<FrontendClient> TunerClient::openFrontend(int32_t frontendHandle) {
     if (mTunerService != nullptr) {
         shared_ptr<ITunerFrontend> tunerFrontend;
         Status s = mTunerService->openFrontend(frontendHandle, &tunerFrontend);
@@ -94,7 +94,7 @@
     return nullptr;
 }
 
-sp<DemuxClient> TunerClient::openDemux(int64_t demuxHandle) {
+sp<DemuxClient> TunerClient::openDemux(int32_t demuxHandle) {
     if (mTunerService != nullptr) {
         shared_ptr<ITunerDemux> tunerDemux;
         Status s = mTunerService->openDemux(demuxHandle, &tunerDemux);
@@ -107,7 +107,7 @@
     return nullptr;
 }
 
-shared_ptr<DemuxInfo> TunerClient::getDemuxInfo(int64_t demuxHandle) {
+shared_ptr<DemuxInfo> TunerClient::getDemuxInfo(int32_t demuxHandle) {
     if (mTunerService != nullptr) {
         DemuxInfo aidlDemuxInfo;
         Status s = mTunerService->getDemuxInfo(demuxHandle, &aidlDemuxInfo);
@@ -141,7 +141,7 @@
     return nullptr;
 }
 
-sp<DescramblerClient> TunerClient::openDescrambler(int64_t descramblerHandle) {
+sp<DescramblerClient> TunerClient::openDescrambler(int32_t descramblerHandle) {
     if (mTunerService != nullptr) {
         shared_ptr<ITunerDescrambler> tunerDescrambler;
         Status s = mTunerService->openDescrambler(descramblerHandle, &tunerDescrambler);
@@ -154,7 +154,7 @@
     return nullptr;
 }
 
-sp<LnbClient> TunerClient::openLnb(int64_t lnbHandle) {
+sp<LnbClient> TunerClient::openLnb(int32_t lnbHandle) {
     if (mTunerService != nullptr) {
         shared_ptr<ITunerLnb> tunerLnb;
         Status s = mTunerService->openLnb(lnbHandle, &tunerLnb);
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index a348586..6ab120b 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -65,7 +65,7 @@
      * @param frontendHandle the handle of the frontend granted by TRM.
      * @return a newly created FrontendClient interface.
      */
-    sp<FrontendClient> openFrontend(int64_t frontendHandle);
+    sp<FrontendClient> openFrontend(int32_t frontendHandle);
 
     /**
      * Retrieve the granted frontend's information.
@@ -81,7 +81,7 @@
      * @param demuxHandle the handle of the demux granted by TRM.
      * @return a newly created DemuxClient interface.
      */
-    sp<DemuxClient> openDemux(int64_t demuxHandle);
+    sp<DemuxClient> openDemux(int32_t demuxHandle);
 
     /**
      * Retrieve the DemuxInfo of a specific demux
@@ -89,7 +89,7 @@
      * @param demuxHandle the handle of the demux to query demux info for
      * @return the demux info
      */
-    shared_ptr<DemuxInfo> getDemuxInfo(int64_t demuxHandle);
+    shared_ptr<DemuxInfo> getDemuxInfo(int32_t demuxHandle);
 
     /**
      * Retrieve a list of demux info
@@ -111,7 +111,7 @@
      * @param descramblerHandle the handle of the descrambler granted by TRM.
      * @return a newly created DescramblerClient interface.
      */
-    sp<DescramblerClient> openDescrambler(int64_t descramblerHandle);
+    sp<DescramblerClient> openDescrambler(int32_t descramblerHandle);
 
     /**
      * Open a new interface of LnbClient given an lnbHandle.
@@ -119,7 +119,7 @@
      * @param lnbHandle the handle of the LNB granted by TRM.
      * @return a newly created LnbClient interface.
      */
-    sp<LnbClient> openLnb(int64_t lnbHandle);
+    sp<LnbClient> openLnb(int32_t lnbHandle);
 
     /**
      * Open a new interface of LnbClient given a LNB name.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 79b3856..5e96381 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -245,9 +245,7 @@
 interface ElementContentPicker {
     /**
      * Return the content in which [element] should be drawn (when using `Modifier.element(key)`) or
-     * composed (when using `MovableElement(key)`) during the given [transition]. If this element
-     * should not be drawn or composed in neither [transition.fromContent] nor
-     * [transition.toContent], return `null`.
+     * composed (when using `MovableElement(key)`) during the given [transition].
      *
      * Important: For [MovableElements][ContentScope.MovableElement], this content picker will
      * *always* be used during transitions to decide whether we should compose that element in a
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
deleted file mode 100644
index b2f5765..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.ui.compose
-
-import androidx.compose.runtime.mutableStateOf
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.SizedTileImpl
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DragAndDropStateTest : SysuiTestCase() {
-    private val listState = EditTileListState(TestEditTiles)
-    private val underTest = DragAndDropState(mutableStateOf(null), listState)
-
-    @Test
-    fun isMoving_returnsCorrectValue() {
-        // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
-
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Assert that the correct tile is marked as moving
-        TestEditTiles.forEach {
-            assertThat(underTest.isMoving(it.tile.tileSpec))
-                .isEqualTo(TestEditTiles[0].tile.tileSpec == it.tile.tileSpec)
-        }
-    }
-
-    @Test
-    fun onMoved_updatesList() {
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tile.tileSpec)
-        assertThat(underTest.currentPosition()).isEqualTo(5)
-
-        // Move the tile to the middle of the list
-        underTest.onMoved(listState.tiles[2].tile.tileSpec)
-        assertThat(underTest.currentPosition()).isEqualTo(2)
-    }
-
-    @Test
-    fun onDrop_resetsMovingTile() {
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tile.tileSpec)
-
-        // Drop the tile
-        underTest.onDrop()
-
-        // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
-    }
-
-    @Test
-    fun onMoveOutOfBounds_removeMovingTileFromCurrentList() {
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Move the tile outside of the list
-        underTest.movedOutOfBounds()
-
-        // Asserts the moving tile is not current
-        assertThat(
-                listState.tiles.firstOrNull { it.tile.tileSpec == TestEditTiles[0].tile.tileSpec }
-            )
-            .isNull()
-    }
-
-    companion object {
-        private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
-            return SizedTileImpl(
-                EditTileViewModel(
-                    tileSpec = TileSpec.create(tileSpec),
-                    icon = Icon.Resource(0, null),
-                    label = Text.Loaded("unused"),
-                    appName = null,
-                    isCurrent = true,
-                    availableEditActions = emptySet(),
-                ),
-                1,
-            )
-        }
-
-        private val TestEditTiles =
-            listOf(
-                createEditTile("tileA"),
-                createEditTile("tileB"),
-                createEditTile("tileC"),
-                createEditTile("tileD"),
-                createEditTile("tileE"),
-                createEditTile("tileF"),
-            )
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
new file mode 100644
index 0000000..4d1dd1c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildAt
+import androidx.compose.ui.test.onChildren
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DragAndDropTest : SysuiTestCase() {
+    @get:Rule val composeRule = createComposeRule()
+
+    // TODO(ostonge): Investigate why drag isn't detected when using performTouchInput
+    @Composable
+    private fun EditTileGridUnderTest(
+        listState: EditTileListState,
+        onSetTiles: (List<TileSpec>) -> Unit
+    ) {
+        DefaultEditTileGrid(
+            currentListState = listState,
+            otherTiles = listOf(),
+            columns = 4,
+            modifier = Modifier.fillMaxSize(),
+            onAddTile = { _, _ -> },
+            onRemoveTile = {},
+            onSetTiles = onSetTiles,
+            onResize = {},
+        )
+    }
+
+    @Test
+    fun draggedTile_shouldDisappear() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(TestEditTiles[0])
+
+        // Tile is being dragged, it should be replaced with a placeholder
+        composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
+
+        // Available tiles should disappear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertDoesNotExist()
+
+        // Remove drop zone should appear
+        composeRule.onNodeWithText("Remove").assertExists()
+
+        // Every other tile should still be in the same order
+        composeRule.assertTileGridContainsExactly(listOf("tileB", "tileC", "tileD_large", "tileE"))
+    }
+
+    @Test
+    fun draggedTile_shouldChangePosition() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(TestEditTiles[0])
+        listState.onMoved(1, false)
+        listState.onDrop()
+
+        // Available tiles should re-appear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertExists()
+
+        // Remove drop zone should disappear
+        composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+        // Tile A and B should swap places
+        composeRule.assertTileGridContainsExactly(
+            listOf("tileB", "tileA", "tileC", "tileD_large", "tileE")
+        )
+    }
+
+    @Test
+    fun draggedTileOut_shouldBeRemoved() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(TestEditTiles[0])
+        listState.movedOutOfBounds()
+        listState.onDrop()
+
+        // Available tiles should re-appear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertExists()
+
+        // Remove drop zone should disappear
+        composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+        // Tile A is gone
+        composeRule.assertTileGridContainsExactly(listOf("tileB", "tileC", "tileD_large", "tileE"))
+    }
+
+    @Test
+    fun draggedNewTileIn_shouldBeAdded() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(createEditTile("newTile"))
+        // Insert after tileD, which is at index 4
+        // [ a ] [ b ] [ c ] [ empty ]
+        // [ tile d ] [ e ]
+        listState.onMoved(4, insertAfter = true)
+        listState.onDrop()
+
+        // Available tiles should re-appear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertExists()
+
+        // Remove drop zone should disappear
+        composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+        // newTile is added after tileD
+        composeRule.assertTileGridContainsExactly(
+            listOf("tileA", "tileB", "tileC", "tileD_large", "newTile", "tileE")
+        )
+    }
+
+    private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List<String>) {
+        onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply {
+            fetchSemanticsNodes().forEachIndexed { index, _ ->
+                get(index).onChildAt(0).assert(hasContentDescription(specs[index]))
+            }
+        }
+    }
+
+    companion object {
+        private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+        private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
+
+        private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
+            return SizedTileImpl(
+                EditTileViewModel(
+                    tileSpec = TileSpec.create(tileSpec),
+                    icon =
+                        Icon.Resource(
+                            android.R.drawable.star_on,
+                            ContentDescription.Loaded(tileSpec)
+                        ),
+                    label = Text.Loaded(tileSpec),
+                    appName = null,
+                    isCurrent = true,
+                    availableEditActions = emptySet(),
+                ),
+                getWidth(tileSpec),
+            )
+        }
+
+        private fun getWidth(tileSpec: String): Int {
+            return if (tileSpec.endsWith("large")) {
+                2
+            } else {
+                1
+            }
+        }
+
+        private val TestEditTiles =
+            listOf(
+                createEditTile("tileA"),
+                createEditTile("tileB"),
+                createEditTile("tileC"),
+                createEditTile("tileD_large"),
+                createEditTile("tileE"),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index a3a6a33..7f01fad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -23,6 +23,9 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.SpacerGridCell
+import com.android.systemui.qs.panels.ui.model.TileGridCell
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -32,80 +35,130 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class EditTileListStateTest : SysuiTestCase() {
-    val underTest = EditTileListState(TestEditTiles)
+    private val underTest = EditTileListState(TestEditTiles, 4)
 
     @Test
-    fun movingNonExistentTile_tileAdded() {
-        val newTile = createEditTile("other_tile", false)
-        underTest.move(newTile, TestEditTiles[0].tile.tileSpec)
-
-        assertThat(underTest.tiles[0]).isEqualTo(newTile)
-        assertThat(underTest.tiles.subList(1, underTest.tiles.size))
-            .containsExactly(*TestEditTiles.toTypedArray())
+    fun noDrag_listUnchanged() {
+        underTest.tiles.forEach { assertThat(it).isNotInstanceOf(SpacerGridCell::class.java) }
+        assertThat(underTest.tiles.map { (it as TileGridCell).tile.tileSpec })
+            .containsExactly(*TestEditTiles.map { it.tile.tileSpec }.toTypedArray())
     }
 
     @Test
-    fun movingTileToNonExistentTarget_listUnchanged() {
-        underTest.move(TestEditTiles[0], TileSpec.create("other_tile"))
+    fun startDrag_listHasSpacers() {
+        underTest.onStarted(TestEditTiles[0])
 
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+        // [ a ] [ b ] [ c ] [ X ]
+        // [ Large D ] [ e ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer"))
+        assertThat(underTest.isMoving(TestEditTiles[0].tile.tileSpec)).isTrue()
+        assertThat(underTest.dragInProgress).isTrue()
     }
 
     @Test
-    fun movingTileToItself_listUnchanged() {
-        underTest.move(TestEditTiles[0], TestEditTiles[0].tile.tileSpec)
+    fun moveDrag_listChanges() {
+        underTest.onStarted(TestEditTiles[4])
+        underTest.onMoved(3, false)
 
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+        // Tile E goes to index 3
+        // [ a ] [ b ] [ c ] [ e ]
+        // [ Large D ] [ X ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "e", "d", "spacer", "spacer"))
     }
 
     @Test
-    fun movingTileToSameSection_listUpdates() {
-        // Move tile at index 0 to index 1. Tile 0 should remain current.
-        underTest.move(TestEditTiles[0], TestEditTiles[1].tile.tileSpec)
+    fun moveDragOnSidesOfLargeTile_listChanges() {
+        val draggedCell = TestEditTiles[4]
 
-        // Assert the tiles 0 and 1 have changed places.
-        assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
-        assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[0])
+        underTest.onStarted(draggedCell)
+        underTest.onMoved(4, true)
 
-        // Assert the rest of the list is unchanged
-        assertThat(underTest.tiles.subList(2, 5))
-            .containsExactly(*TestEditTiles.subList(2, 5).toTypedArray())
+        // Tile E goes to the right side of tile D, list is unchanged
+        // [ a ] [ b ] [ c ] [ X ]
+        // [ Large D ] [ e ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer"))
+
+        underTest.onMoved(4, false)
+
+        // Tile E goes to the left side of tile D, they swap positions
+        // [ a ] [ b ] [ c ] [ e ]
+        // [ Large D ] [ X ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "e", "d", "spacer", "spacer"))
     }
 
-    fun removingTile_listUpdates() {
-        // Remove tile at index 0
-        underTest.remove(TestEditTiles[0].tile.tileSpec)
+    @Test
+    fun moveNewTile_tileIsAdded() {
+        val newTile = createEditTile("newTile", 2)
 
-        // Assert the tile was removed
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.subList(1, 6).toTypedArray())
+        underTest.onStarted(newTile)
+        underTest.onMoved(5, false)
+
+        // New tile goes to index 5
+        // [ a ] [ b ] [ c ] [ X ]
+        // [ Large D ] [ newTile ]
+        // [ e ] [ X ] [ X ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(
+                listOf("a", "b", "c", "spacer", "d", "newTile", "e", "spacer", "spacer", "spacer")
+            )
+    }
+
+    @Test
+    fun droppedNewTile_spacersDisappear() {
+        underTest.onStarted(TestEditTiles[0])
+        underTest.onDrop()
+
+        assertThat(underTest.tiles.toStrings()).isEqualTo(listOf("a", "b", "c", "d", "e"))
+        assertThat(underTest.isMoving(TestEditTiles[0].tile.tileSpec)).isFalse()
+        assertThat(underTest.dragInProgress).isFalse()
+    }
+
+    @Test
+    fun movedTileOutOfBounds_tileDisappears() {
+        underTest.onStarted(TestEditTiles[0])
+        underTest.movedOutOfBounds()
+
+        assertThat(underTest.tiles.toStrings()).doesNotContain(TestEditTiles[0].tile.tileSpec.spec)
+    }
+
+    private fun List<GridCell>.toStrings(): List<String> {
+        return map {
+            if (it is TileGridCell) {
+                it.tile.tileSpec.spec
+            } else {
+                "spacer"
+            }
+        }
     }
 
     companion object {
-        private fun createEditTile(
-            tileSpec: String,
-            isCurrent: Boolean
-        ): SizedTile<EditTileViewModel> {
+        private fun createEditTile(tileSpec: String, width: Int): SizedTile<EditTileViewModel> {
             return SizedTileImpl(
                 EditTileViewModel(
                     tileSpec = TileSpec.create(tileSpec),
                     icon = Icon.Resource(0, null),
                     label = Text.Loaded("unused"),
                     appName = null,
-                    isCurrent = isCurrent,
+                    isCurrent = true,
                     availableEditActions = emptySet(),
                 ),
-                1,
+                width,
             )
         }
 
+        // [ a ] [ b ] [ c ]
+        // [ Large D ] [ e ] [ f ]
         private val TestEditTiles =
             listOf(
-                createEditTile("tileA", true),
-                createEditTile("tileB", true),
-                createEditTile("tileC", true),
-                createEditTile("tileD", false),
-                createEditTile("tileE", false),
-                createEditTile("tileF", false),
+                createEditTile("a", 1),
+                createEditTile("b", 1),
+                createEditTile("c", 1),
+                createEditTile("d", 2),
+                createEditTile("e", 1),
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a301155..baf8f5a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -139,6 +140,7 @@
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
     @Inject Lazy<CommandQueue> mCommandQueue;
     @Inject Lazy<UiEventLogger> mUiEventLogger;
+    @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
     @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
     @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController;
@@ -184,6 +186,7 @@
         mProviders.put(CommandQueue.class, mCommandQueue::get);
         mProviders.put(UiEventLogger.class, mUiEventLogger::get);
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
+        mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
         mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get);
         mProviders.put(ScreenOffAnimationController.class, mScreenOffAnimationController::get);
         mProviders.put(AmbientState.class, mAmbientStateLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt
deleted file mode 100644
index 3e382d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.inputdevice.tutorial.ui.view
-
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
-import androidx.compose.runtime.Composable
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
-import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
-import java.util.Optional
-import javax.inject.Inject
-
-/**
- * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
- * either of them are actually not connected when this is launched
- */
-class KeyboardTouchpadTutorialActivity
-@Inject
-constructor(
-    private val viewModelFactory: KeyboardTouchpadTutorialViewModel.Factory,
-    private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
-) : ComponentActivity() {
-
-    private val vm by
-        viewModels<KeyboardTouchpadTutorialViewModel>(factoryProducer = { viewModelFactory })
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        enableEdgeToEdge()
-        setContent {
-            PlatformTheme {
-                KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) { finish() }
-            }
-        }
-        // required to handle 3+ fingers on touchpad
-        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
-    }
-
-    override fun onResume() {
-        super.onResume()
-        vm.onOpened()
-    }
-
-    override fun onPause() {
-        super.onPause()
-        vm.onClosed()
-    }
-}
-
-@Composable
-fun KeyboardTouchpadTutorialContainer(
-    vm: KeyboardTouchpadTutorialViewModel,
-    touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
-    closeTutorial: () -> Unit
-) {}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
deleted file mode 100644
index 39b1ec0..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.inputdevice.tutorial.ui.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
-import java.util.Optional
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-class KeyboardTouchpadTutorialViewModel(
-    private val gesturesInteractor: Optional<TouchpadGesturesInteractor>
-) : ViewModel() {
-
-    private val _screen = MutableStateFlow(Screen.BACK_GESTURE)
-    val screen: StateFlow<Screen> = _screen
-
-    fun goTo(screen: Screen) {
-        _screen.value = screen
-    }
-
-    fun onOpened() {
-        gesturesInteractor.ifPresent { it.disableGestures() }
-    }
-
-    fun onClosed() {
-        gesturesInteractor.ifPresent { it.enableGestures() }
-    }
-
-    class Factory
-    @Inject
-    constructor(private val gesturesInteractor: Optional<TouchpadGesturesInteractor>) :
-        ViewModelProvider.Factory {
-
-        @Suppress("UNCHECKED_CAST")
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            return KeyboardTouchpadTutorialViewModel(gesturesInteractor) as T
-        }
-    }
-}
-
-enum class Screen {
-    BACK_GESTURE,
-    HOME_GESTURE,
-    ACTION_KEY
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialModule.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialModule.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/TouchpadTutorialScreensProvider.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/TouchpadTutorialScreensProvider.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/KeyboardTouchpadConnectionInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/KeyboardTouchpadConnectionInteractor.kt
new file mode 100644
index 0000000..3f1f68a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/KeyboardTouchpadConnectionInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+@SysUISingleton
+class KeyboardTouchpadConnectionInteractor
+@Inject
+constructor(
+    keyboardRepository: KeyboardRepository,
+    touchpadRepository: TouchpadRepository,
+) {
+
+    val connectionState: Flow<ConnectionState> =
+        combine(
+            keyboardRepository.isAnyKeyboardConnected,
+            touchpadRepository.isAnyTouchpadConnected
+        ) { keyboardConnected, touchpadConnected ->
+            ConnectionState(keyboardConnected, touchpadConnected)
+        }
+}
+
+data class ConnectionState(val keyboardConnected: Boolean, val touchpadConnected: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
new file mode 100644
index 0000000..34ecc95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.ui.view
+
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.lifecycle.Lifecycle.State.STARTED
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
+import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel.Factory.ViewModelFactoryAssistedProvider
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
+import java.util.Optional
+import javax.inject.Inject
+import kotlinx.coroutines.launch
+
+/**
+ * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
+ * either of them are actually not connected when this is launched
+ */
+class KeyboardTouchpadTutorialActivity
+@Inject
+constructor(
+    private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
+    private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+) : ComponentActivity() {
+
+    companion object {
+        const val INTENT_TUTORIAL_TYPE_KEY = "tutorial_type"
+        const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
+        const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
+    }
+
+    private val vm by
+        viewModels<KeyboardTouchpadTutorialViewModel>(
+            factoryProducer = {
+                viewModelFactoryAssistedProvider.create(touchpadTutorialScreensProvider.isPresent)
+            }
+        )
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enableEdgeToEdge()
+        // required to handle 3+ fingers on touchpad
+        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        lifecycle.addObserver(vm)
+        lifecycleScope.launch {
+            vm.closeActivity.collect { finish ->
+                if (finish) {
+                    finish()
+                }
+            }
+        }
+        setContent {
+            PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
+        }
+    }
+}
+
+@Composable
+fun KeyboardTouchpadTutorialContainer(
+    vm: KeyboardTouchpadTutorialViewModel,
+    touchpadScreens: Optional<TouchpadTutorialScreensProvider>,
+) {
+    val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED)
+    when (activeScreen) {
+        BACK_GESTURE ->
+            touchpadScreens
+                .get()
+                .BackGesture(onDoneButtonClicked = vm::onDoneButtonClicked, onBack = vm::onBack)
+        HOME_GESTURE ->
+            touchpadScreens
+                .get()
+                .HomeGesture(onDoneButtonClicked = vm::onDoneButtonClicked, onBack = vm::onBack)
+        ACTION_KEY ->
+            ActionKeyTutorialScreen(
+                onDoneButtonClicked = vm::onDoneButtonClicked,
+                onBack = vm::onBack
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
new file mode 100644
index 0000000..315c102
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.ui.viewmodel
+
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
+import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.RequiredHardware.KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.RequiredHardware.TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Optional
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.launch
+
+class KeyboardTouchpadTutorialViewModel(
+    private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
+    private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
+    private val hasTouchpadTutorialScreens: Boolean,
+    handle: SavedStateHandle
+) : ViewModel(), DefaultLifecycleObserver {
+
+    private fun startingScreen(handle: SavedStateHandle): Screen {
+        val tutorialType: String? = handle[INTENT_TUTORIAL_TYPE_KEY]
+        return if (tutorialType == INTENT_TUTORIAL_TYPE_KEYBOARD) ACTION_KEY else BACK_GESTURE
+    }
+
+    private val _screen = MutableStateFlow(startingScreen(handle))
+    val screen: Flow<Screen> = _screen.filter { it.canBeShown() }
+
+    private val _closeActivity: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    val closeActivity: StateFlow<Boolean> = _closeActivity
+
+    private val screensBackStack = ArrayDeque(listOf(_screen.value))
+
+    private var connectionState: ConnectionState =
+        ConnectionState(keyboardConnected = false, touchpadConnected = false)
+
+    init {
+        viewModelScope.launch {
+            keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it }
+        }
+
+        viewModelScope.launch {
+            screen
+                .runningFold<Screen, Pair<Screen?, Screen?>>(null to null) {
+                    previousScreensPair,
+                    currentScreen ->
+                    previousScreensPair.second to currentScreen
+                }
+                .collect { (previousScreen, currentScreen) ->
+                    // ignore first empty emission
+                    if (currentScreen != null) {
+                        setupDeviceState(previousScreen, currentScreen)
+                    }
+                }
+        }
+
+        viewModelScope.launch {
+            // close activity if screen requires touchpad but we don't have it. This can only happen
+            // when current sysui build doesn't contain touchpad module dependency
+            _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true }
+        }
+    }
+
+    override fun onCleared() {
+        // this shouldn't be needed as onTutorialInvisible should already clear device state but
+        // it'd be really bad if we'd block gestures/shortcuts after leaving tutorial so just to be
+        // extra sure...
+        clearDeviceStateForScreen(_screen.value)
+    }
+
+    override fun onStart(owner: LifecycleOwner) {
+        setupDeviceState(previousScreen = null, currentScreen = _screen.value)
+    }
+
+    override fun onStop(owner: LifecycleOwner) {
+        clearDeviceStateForScreen(_screen.value)
+    }
+
+    fun onDoneButtonClicked() {
+        var nextScreen = _screen.value.next()
+        while (nextScreen != null) {
+            if (requiredHardwarePresent(nextScreen)) {
+                break
+            }
+            nextScreen = nextScreen.next()
+        }
+        if (nextScreen == null) {
+            _closeActivity.value = true
+        } else {
+            _screen.value = nextScreen
+            screensBackStack.add(nextScreen)
+        }
+    }
+
+    private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens
+
+    private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) {
+        if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return
+        previousScreen?.let { clearDeviceStateForScreen(it) }
+        when (currentScreen.requiredHardware) {
+            TOUCHPAD -> gesturesInteractor.get().disableGestures()
+            KEYBOARD -> {} // TODO(b/358587037) disabled keyboard shortcuts
+        }
+    }
+
+    private fun clearDeviceStateForScreen(screen: Screen) {
+        when (screen.requiredHardware) {
+            TOUCHPAD -> gesturesInteractor.get().enableGestures()
+            KEYBOARD -> {} // TODO(b/358587037) enable keyboard shortcuts
+        }
+    }
+
+    private fun requiredHardwarePresent(screen: Screen): Boolean =
+        when (screen.requiredHardware) {
+            KEYBOARD -> connectionState.keyboardConnected
+            TOUCHPAD -> connectionState.touchpadConnected
+        }
+
+    fun onBack() {
+        if (screensBackStack.size <= 1) {
+            _closeActivity.value = true
+        } else {
+            screensBackStack.removeLast()
+            _screen.value = screensBackStack.last()
+        }
+    }
+
+    class Factory
+    @AssistedInject
+    constructor(
+        private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
+        private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor,
+        @Assisted private val hasTouchpadTutorialScreens: Boolean,
+    ) : AbstractSavedStateViewModelFactory() {
+
+        @AssistedFactory
+        fun interface ViewModelFactoryAssistedProvider {
+            fun create(@Assisted hasTouchpadTutorialScreens: Boolean): Factory
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T : ViewModel> create(
+            key: String,
+            modelClass: Class<T>,
+            handle: SavedStateHandle
+        ): T =
+            KeyboardTouchpadTutorialViewModel(
+                gesturesInteractor,
+                keyboardTouchpadConnected,
+                hasTouchpadTutorialScreens,
+                handle
+            )
+                as T
+    }
+}
+
+enum class RequiredHardware {
+    TOUCHPAD,
+    KEYBOARD
+}
+
+enum class Screen(val requiredHardware: RequiredHardware) {
+    BACK_GESTURE(requiredHardware = TOUCHPAD),
+    HOME_GESTURE(requiredHardware = TOUCHPAD),
+    ACTION_KEY(requiredHardware = KEYBOARD);
+
+    fun next(): Screen? =
+        when (this) {
+            BACK_GESTURE -> HOME_GESTURE
+            HOME_GESTURE -> ACTION_KEY
+            ACTION_KEY -> null
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index d119ed4..28a17ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -94,7 +94,7 @@
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    launch("$TAG#viewModel") {
+                    launch {
                         viewModel.collect { buttonModel ->
                             updateButton(
                                 view = button,
@@ -104,7 +104,7 @@
                         }
                     }
 
-                    launch("$TAG#updateButtonAlpha") {
+                    launch {
                         updateButtonAlpha(
                             view = button,
                             viewModel = viewModel,
@@ -112,7 +112,7 @@
                         )
                     }
 
-                    launch("$TAG#configurationBasedDimensions") {
+                    launch {
                         configurationBasedDimensions.collect { dimensions ->
                             button.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 62759a4..3c25e62 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -54,7 +54,6 @@
 import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
 import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel
 import com.android.systemui.media.controls.util.MediaDataUtils
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
@@ -76,7 +75,6 @@
         falsingManager: FalsingManager,
         @Background backgroundDispatcher: CoroutineDispatcher,
         @Main mainDispatcher: CoroutineDispatcher,
-        mediaFlags: MediaFlags,
     ) {
         val mediaCard = viewHolder.player
         mediaCard.repeatWhenAttached {
@@ -91,7 +89,6 @@
                                 falsingManager,
                                 backgroundDispatcher,
                                 mainDispatcher,
-                                mediaFlags
                             )
                         }
                     }
@@ -107,7 +104,6 @@
         falsingManager: FalsingManager,
         backgroundDispatcher: CoroutineDispatcher,
         mainDispatcher: CoroutineDispatcher,
-        mediaFlags: MediaFlags,
     ) {
         // Set up media control location and its listener.
         viewModel.onLocationChanged(viewController.currentEndLocation)
@@ -164,18 +160,6 @@
             isSongUpdated
         )
 
-        // TODO: We don't need to refresh this state constantly, only if the
-        // state actually changed to something which might impact the
-        // measurement. State refresh interferes with the translation
-        // animation, only run it if it's not running.
-        if (!viewController.metadataAnimationHandler.isRunning) {
-            // Don't refresh in scene framework, because it will calculate
-            // with invalid layout sizes
-            if (!mediaFlags.isSceneContainerEnabled()) {
-                viewController.refreshState()
-            }
-        }
-
         if (viewModel.playTurbulenceNoise) {
             viewController.setUpTurbulenceNoise()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index c21301c..fb2bbde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -746,7 +746,6 @@
                     falsingManager,
                     backgroundDispatcher,
                     mainDispatcher,
-                    mediaFlags
                 )
                 mediaContent.addView(viewHolder.player, position)
                 controllerById[commonViewModel.instanceId.toString()] = viewController
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 2c57813..0b9cd96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -20,103 +20,40 @@
 import androidx.compose.foundation.draganddrop.dragAndDropSource
 import androidx.compose.foundation.draganddrop.dragAndDropTarget
 import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draganddrop.DragAndDropEvent
 import androidx.compose.ui.draganddrop.DragAndDropTarget
 import androidx.compose.ui.draganddrop.DragAndDropTransferData
 import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.center
+import androidx.compose.ui.unit.toRect
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
-@Composable
-fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
-    val draggedCell: MutableState<SizedTile<EditTileViewModel>?> = remember { mutableStateOf(null) }
-    return remember(listState) { DragAndDropState(draggedCell, listState) }
-}
-
-/**
- * Holds the [TileSpec] of the tile being moved and modify the [EditTileListState] based on drag and
- * drop events.
- */
-class DragAndDropState(
-    val draggedCell: MutableState<SizedTile<EditTileViewModel>?>,
-    private val listState: EditTileListState,
-) {
+/** Holds the [TileSpec] of the tile being moved and receives drag and drop events. */
+interface DragAndDropState {
+    val draggedCell: SizedTile<EditTileViewModel>?
     val dragInProgress: Boolean
-        get() = draggedCell.value != null
 
-    /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
-    fun currentPosition(): Int {
-        return draggedCell.value?.let { listState.indexOf(it.tile.tileSpec) } ?: -1
-    }
+    fun isMoving(tileSpec: TileSpec): Boolean
 
-    fun isMoving(tileSpec: TileSpec): Boolean {
-        return draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
-    }
+    fun onStarted(cell: SizedTile<EditTileViewModel>)
 
-    fun onStarted(cell: SizedTile<EditTileViewModel>) {
-        draggedCell.value = cell
-    }
+    fun onMoved(target: Int, insertAfter: Boolean)
 
-    fun onMoved(targetSpec: TileSpec) {
-        draggedCell.value?.let { listState.move(it, targetSpec) }
-    }
+    fun movedOutOfBounds()
 
-    fun movedOutOfBounds() {
-        // Removing the tiles from the current tile grid if it moves out of bounds. This clears
-        // the spacer and makes it apparent that dropping the tile at that point would remove it.
-        draggedCell.value?.let { listState.remove(it.tile.tileSpec) }
-    }
-
-    fun onDrop() {
-        draggedCell.value = null
-    }
-}
-
-/**
- * Registers a tile as a [DragAndDropTarget] to receive drag events and update the
- * [DragAndDropState] with the tile's position, which can be used to insert a temporary placeholder.
- *
- * @param dragAndDropState The [DragAndDropState] using the tiles list
- * @param tileSpec The [TileSpec] of the tile
- * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
- * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
- */
-@Composable
-fun Modifier.dragAndDropTile(
-    dragAndDropState: DragAndDropState,
-    tileSpec: TileSpec,
-    acceptDrops: (TileSpec) -> Boolean,
-    onDrop: (TileSpec, Int) -> Unit,
-): Modifier {
-    val target =
-        remember(dragAndDropState) {
-            object : DragAndDropTarget {
-                override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.draggedCell.value?.let {
-                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
-                        dragAndDropState.onDrop()
-                        true
-                    } ?: false
-                }
-
-                override fun onEntered(event: DragAndDropEvent) {
-                    dragAndDropState.onMoved(tileSpec)
-                }
-            }
-        }
-    return dragAndDropTarget(
-        shouldStartDragAndDrop = { event ->
-            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
-        },
-        target = target,
-    )
+    fun onDrop()
 }
 
 /**
@@ -135,7 +72,7 @@
         remember(dragAndDropState) {
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.draggedCell.value?.let {
+                    return dragAndDropState.draggedCell?.let {
                         onDrop(it.tile.tileSpec)
                         dragAndDropState.onDrop()
                         true
@@ -156,19 +93,22 @@
 }
 
 /**
- * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list
- * containers to catch drops outside of tiles.
+ * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on the lazy tile
+ * grid to receive drag and drops events.
  *
+ * @param gridState The [LazyGridState] of the tile list
+ * @param contentOffset The [Offset] of the tile list
  * @param dragAndDropState The [DragAndDropState] using the tiles list
- * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
- * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
+ * @param onDrop Callback when a tile is dropped
  */
 @Composable
 fun Modifier.dragAndDropTileList(
+    gridState: LazyGridState,
+    contentOffset: Offset,
     dragAndDropState: DragAndDropState,
-    acceptDrops: (TileSpec) -> Boolean,
-    onDrop: (TileSpec, Int) -> Unit,
+    onDrop: () -> Unit,
 ): Modifier {
+    val currentContentOffset by rememberUpdatedState(contentOffset)
     val target =
         remember(dragAndDropState) {
             object : DragAndDropTarget {
@@ -176,9 +116,23 @@
                     dragAndDropState.onDrop()
                 }
 
+                override fun onMoved(event: DragAndDropEvent) {
+                    // Drag offset relative to the list's top left corner
+                    val relativeDragOffset = event.dragOffsetRelativeTo(currentContentOffset)
+                    val targetItem =
+                        gridState.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+                            // Check if the drag is on this item
+                            IntRect(item.offset, item.size).toRect().contains(relativeDragOffset)
+                        }
+
+                    targetItem?.let {
+                        dragAndDropState.onMoved(it.index, insertAfter(it, relativeDragOffset))
+                    }
+                }
+
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.draggedCell.value?.let {
-                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
+                    return dragAndDropState.draggedCell?.let {
+                        onDrop()
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -188,12 +142,22 @@
     return dragAndDropTarget(
         target = target,
         shouldStartDragAndDrop = { event ->
-            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
+            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE)
         },
     )
 }
 
+private fun DragAndDropEvent.dragOffsetRelativeTo(offset: Offset): Offset {
+    return toAndroidDragEvent().run { Offset(x, y) } - offset
+}
+
+private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
+    // We want to insert the tile after the target if we're aiming at the right side of a large tile
+    // TODO(ostonge): Verify this behavior in RTL
+    val itemCenter = item.offset + item.size.center
+    return item.span != 1 && offset.x > itemCenter.x
+}
+
 fun Modifier.dragAndDropTileSource(
     sizedTile: SizedTile<EditTileViewModel>,
     onTap: (TileSpec) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
index 3bda775..1674865 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
@@ -43,6 +43,7 @@
             Modifier,
             viewModel::addTile,
             viewModel::removeTile,
+            viewModel::setTiles,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index fa3008e..4830ba7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -17,46 +17,106 @@
 package com.android.systemui.qs.panels.ui.compose
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.runtime.toMutableStateList
 import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.model.toGridCells
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
+/**
+ * Creates the edit tile list state that is remembered across compositions.
+ *
+ * Changes to the tiles or columns will recreate the state.
+ */
 @Composable
 fun rememberEditListState(
     tiles: List<SizedTile<EditTileViewModel>>,
+    columns: Int,
 ): EditTileListState {
-    return remember(tiles) { EditTileListState(tiles) }
+    return remember(tiles, columns) { EditTileListState(tiles, columns) }
 }
 
 /** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>) {
-    val tiles: SnapshotStateList<SizedTile<EditTileViewModel>> = tiles.toMutableStateList()
+class EditTileListState(
+    tiles: List<SizedTile<EditTileViewModel>>,
+    private val columns: Int,
+) : DragAndDropState {
+    private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
+    override val draggedCell
+        get() = _draggedCell.value
 
-    fun move(sizedTile: SizedTile<EditTileViewModel>, target: TileSpec) {
-        val fromIndex = indexOf(sizedTile.tile.tileSpec)
-        val toIndex = indexOf(target)
+    override val dragInProgress: Boolean
+        get() = _draggedCell.value != null
 
-        if (toIndex == -1 || fromIndex == toIndex) {
-            return
-        }
+    private val _tiles: SnapshotStateList<GridCell> =
+        tiles.toGridCells(columns).toMutableStateList()
+    val tiles: List<GridCell>
+        get() = _tiles.toList()
 
-        if (fromIndex == -1) {
-            // If tile isn't in the list, simply insert it
-            tiles.add(toIndex, sizedTile)
-        } else {
-            // If tile is present in the list, move it
-            tiles.apply { add(toIndex, removeAt(fromIndex)) }
-        }
-    }
-
-    fun remove(tileSpec: TileSpec) {
-        tiles.removeIf { it.tile.tileSpec == tileSpec }
+    fun tileSpecs(): List<TileSpec> {
+        return _tiles.filterIsInstance<TileGridCell>().map { it.tile.tileSpec }
     }
 
     fun indexOf(tileSpec: TileSpec): Int {
-        return tiles.indexOfFirst { it.tile.tileSpec == tileSpec }
+        return _tiles.indexOfFirst { it is TileGridCell && it.tile.tileSpec == tileSpec }
+    }
+
+    override fun isMoving(tileSpec: TileSpec): Boolean {
+        return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
+    }
+
+    override fun onStarted(cell: SizedTile<EditTileViewModel>) {
+        _draggedCell.value = cell
+
+        // Add visible spacers to the grid to indicate where the user can move a tile
+        regenerateGrid(includeSpacers = true)
+    }
+
+    override fun onMoved(target: Int, insertAfter: Boolean) {
+        val draggedTile = _draggedCell.value ?: return
+
+        val fromIndex = indexOf(draggedTile.tile.tileSpec)
+        if (fromIndex == target) {
+            return
+        }
+
+        val insertionIndex = if (insertAfter) target + 1 else target
+        if (fromIndex != -1) {
+            val cell = _tiles.removeAt(fromIndex)
+            regenerateGrid(includeSpacers = true)
+            _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell)
+        } else {
+            // Add the tile with a temporary row which will get reassigned when regenerating spacers
+            _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0))
+        }
+
+        regenerateGrid(includeSpacers = true)
+    }
+
+    override fun movedOutOfBounds() {
+        val draggedTile = _draggedCell.value ?: return
+
+        _tiles.removeIf { cell ->
+            cell is TileGridCell && cell.tile.tileSpec == draggedTile.tile.tileSpec
+        }
+    }
+
+    override fun onDrop() {
+        _draggedCell.value = null
+
+        // Remove the spacers
+        regenerateGrid(includeSpacers = false)
+    }
+
+    private fun regenerateGrid(includeSpacers: Boolean) {
+        _tiles.filterIsInstance<TileGridCell>().toGridCells(columns, includeSpacers).let {
+            _tiles.clear()
+            _tiles.addAll(it)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
index e2f6bcf..fd276c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
@@ -39,6 +39,7 @@
         modifier: Modifier,
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit,
+        onSetTiles: (List<TileSpec>) -> Unit,
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index bd925fe..d948dfd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -76,6 +76,7 @@
         modifier: Modifier,
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit,
+        onSetTiles: (List<TileSpec>) -> Unit,
     ) {
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
         val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
@@ -91,12 +92,16 @@
                 }
             }
 
+        val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
+        val currentListState = rememberEditListState(currentTiles, columns)
         DefaultEditTileGrid(
-            sizedTiles = sizedTiles,
+            currentListState = currentListState,
+            otherTiles = otherTiles,
             columns = columns,
             modifier = modifier,
             onAddTile = onAddTile,
             onRemoveTile = onRemoveTile,
+            onSetTiles = onSetTiles,
             onResize = iconTilesViewModel::resize,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 9c0701e..c06d6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -52,8 +52,11 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridItemScope
 import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -75,9 +78,13 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.onClick
@@ -89,6 +96,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.background
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
@@ -96,8 +104,10 @@
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.SpacerGridCell
 import com.android.systemui.qs.panels.ui.model.TileGridCell
-import com.android.systemui.qs.panels.ui.model.toTileGridCells
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -269,10 +279,12 @@
 @Composable
 fun TileLazyGrid(
     modifier: Modifier = Modifier,
+    state: LazyGridState = rememberLazyGridState(),
     columns: GridCells,
     content: LazyGridScope.() -> Unit,
 ) {
     LazyVerticalGrid(
+        state = state,
         columns = columns,
         verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
         horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
@@ -283,23 +295,18 @@
 
 @Composable
 fun DefaultEditTileGrid(
-    sizedTiles: List<SizedTile<EditTileViewModel>>,
+    currentListState: EditTileListState,
+    otherTiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     modifier: Modifier,
     onAddTile: (TileSpec, Int) -> Unit,
     onRemoveTile: (TileSpec) -> Unit,
+    onSetTiles: (List<TileSpec>) -> Unit,
     onResize: (TileSpec) -> Unit,
 ) {
-    val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
-    val currentListState = rememberEditListState(currentTiles)
-    val dragAndDropState = rememberDragAndDropState(currentListState)
-
     val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
         onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
     }
-    val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
-        onAddTile(tileSpec, position)
-    }
     val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
 
     CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
@@ -309,10 +316,10 @@
             modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
         ) {
             AnimatedContent(
-                targetState = dragAndDropState.dragInProgress,
+                targetState = currentListState.dragInProgress,
                 modifier = Modifier.wrapContentSize()
             ) { dragIsInProgress ->
-                EditGridHeader(Modifier.dragAndDropRemoveZone(dragAndDropState, onRemoveTile)) {
+                EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
                     if (dragIsInProgress) {
                         RemoveTileTarget()
                     } else {
@@ -322,18 +329,17 @@
             }
 
             CurrentTilesGrid(
-                currentListState.tiles,
+                currentListState,
                 columns,
                 tilePadding,
                 onRemoveTile,
                 onResize,
-                dragAndDropState,
-                onDropAdd,
+                onSetTiles,
             )
 
             // Hide available tiles when dragging
             AnimatedVisibility(
-                visible = !dragAndDropState.dragInProgress,
+                visible = !currentListState.dragInProgress,
                 enter = fadeIn(),
                 exit = fadeOut()
             ) {
@@ -349,7 +355,7 @@
                         columns,
                         tilePadding,
                         addTileToEnd,
-                        dragAndDropState,
+                        currentListState,
                     )
                 }
             }
@@ -359,7 +365,7 @@
                 modifier =
                     Modifier.fillMaxWidth()
                         .weight(1f)
-                        .dragAndDropRemoveZone(dragAndDropState, onRemoveTile)
+                        .dragAndDropRemoveZone(currentListState, onRemoveTile)
             )
         }
     }
@@ -375,7 +381,7 @@
     ) {
         Box(
             contentAlignment = Alignment.Center,
-            modifier = modifier.fillMaxWidth().height(TileDefaults.EditGridHeaderHeight)
+            modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight)
         ) {
             content()
         }
@@ -414,35 +420,42 @@
 
 @Composable
 private fun CurrentTilesGrid(
-    tiles: List<SizedTile<EditTileViewModel>>,
+    listState: EditTileListState,
     columns: Int,
     tilePadding: Dp,
     onClick: (TileSpec) -> Unit,
     onResize: (TileSpec) -> Unit,
-    dragAndDropState: DragAndDropState,
-    onDrop: (TileSpec, Int) -> Unit
+    onSetTiles: (List<TileSpec>) -> Unit,
 ) {
-    // Current tiles
+    val currentListState by rememberUpdatedState(listState)
+
     CurrentTilesContainer {
-        val cells = tiles.toTileGridCells(columns)
         val tileHeight = tileHeight()
-        val totalRows = cells.lastOrNull()?.row ?: 0
+        val totalRows = listState.tiles.lastOrNull()?.row ?: 0
         val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
+        val gridState = rememberLazyGridState()
+        var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
+
         TileLazyGrid(
+            state = gridState,
             modifier =
                 Modifier.height(totalHeight)
-                    .dragAndDropTileList(dragAndDropState, { true }, onDrop),
+                    .dragAndDropTileList(gridState, gridContentOffset, listState) {
+                        onSetTiles(currentListState.tileSpecs())
+                    }
+                    .onGloballyPositioned { coordinates ->
+                        gridContentOffset = coordinates.positionInRoot()
+                    }
+                    .testTag(CURRENT_TILES_GRID_TEST_TAG),
             columns = GridCells.Fixed(columns)
         ) {
             editTiles(
-                cells,
+                listState.tiles,
                 ClickAction.REMOVE,
                 onClick,
-                dragAndDropState,
+                listState,
                 onResize = onResize,
                 indicatePosition = true,
-                acceptDrops = { true },
-                onDrop = onDrop,
             )
         }
     }
@@ -464,7 +477,7 @@
 
     // Available tiles
     TileLazyGrid(
-        modifier = Modifier.height(availableGridHeight),
+        modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG),
         columns = GridCells.Fixed(columns)
     ) {
         editTiles(
@@ -472,7 +485,6 @@
             ClickAction.ADD,
             onClick,
             dragAndDropState = dragAndDropState,
-            acceptDrops = { false },
             showLabels = true,
         )
         editTiles(
@@ -480,7 +492,6 @@
             ClickAction.ADD,
             onClick,
             dragAndDropState = dragAndDropState,
-            acceptDrops = { false },
             showLabels = true,
         )
     }
@@ -495,64 +506,109 @@
     return ((tileHeight + padding) * rows) - padding
 }
 
+private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
+    return if (this is TileGridCell && !dragAndDropState.isMoving(tile.tileSpec)) {
+        key
+    } else {
+        index
+    }
+}
+
 fun LazyGridScope.editTiles(
-    cells: List<TileGridCell>,
+    cells: List<GridCell>,
     clickAction: ClickAction,
     onClick: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
-    acceptDrops: (TileSpec) -> Boolean,
     onResize: (TileSpec) -> Unit = {},
-    onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
     showLabels: Boolean = false,
     indicatePosition: Boolean = false,
 ) {
     items(
         count = cells.size,
-        key = { cells[it].key },
+        key = { cells[it].key(it, dragAndDropState) },
         span = { cells[it].span },
         contentType = { TileType }
     ) { index ->
-        val cell = cells[index]
-        val tileHeight = tileHeight(cell.isIcon && showLabels)
-
-        if (!dragAndDropState.isMoving(cell.tile.tileSpec)) {
-            val onClickActionName =
-                when (clickAction) {
-                    ClickAction.ADD ->
-                        stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
-                    ClickAction.REMOVE ->
-                        stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
-                }
-            val stateDescription =
-                if (indicatePosition) {
-                    stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+        when (val cell = cells[index]) {
+            is TileGridCell ->
+                if (dragAndDropState.isMoving(cell.tile.tileSpec)) {
+                    // If the tile is being moved, replace it with a visible spacer
+                    SpacerGridCell(
+                        Modifier.background(
+                                color = MaterialTheme.colorScheme.secondary,
+                                alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
+                                shape = TileDefaults.TileShape
+                            )
+                            .animateItem()
+                    )
                 } else {
-                    ""
+                    TileGridCell(
+                        cell = cell,
+                        index = index,
+                        dragAndDropState = dragAndDropState,
+                        clickAction = clickAction,
+                        onClick = onClick,
+                        onResize = onResize,
+                        showLabels = showLabels,
+                        indicatePosition = indicatePosition
+                    )
                 }
-            EditTile(
-                tileViewModel = cell.tile,
-                iconOnly = cell.isIcon,
-                showLabels = showLabels,
-                modifier =
-                    Modifier.height(tileHeight)
-                        .animateItem()
-                        .semantics {
-                            onClick(onClickActionName) { false }
-                            this.stateDescription = stateDescription
-                        }
-                        .dragAndDropTile(dragAndDropState, cell.tile.tileSpec, acceptDrops, onDrop)
-                        .dragAndDropTileSource(
-                            cell,
-                            onClick,
-                            onResize,
-                            dragAndDropState,
-                        )
-            )
+            is SpacerGridCell -> SpacerGridCell()
         }
     }
 }
 
 @Composable
+private fun LazyGridItemScope.TileGridCell(
+    cell: TileGridCell,
+    index: Int,
+    dragAndDropState: DragAndDropState,
+    clickAction: ClickAction,
+    onClick: (TileSpec) -> Unit,
+    onResize: (TileSpec) -> Unit = {},
+    showLabels: Boolean = false,
+    indicatePosition: Boolean = false,
+) {
+    val tileHeight = tileHeight(cell.isIcon && showLabels)
+    val onClickActionName =
+        when (clickAction) {
+            ClickAction.ADD -> stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+            ClickAction.REMOVE ->
+                stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+        }
+    val stateDescription =
+        if (indicatePosition) {
+            stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+        } else {
+            ""
+        }
+    EditTile(
+        tileViewModel = cell.tile,
+        iconOnly = cell.isIcon,
+        showLabels = showLabels,
+        modifier =
+            Modifier.height(tileHeight)
+                .animateItem()
+                .semantics {
+                    onClick(onClickActionName) { false }
+                    this.stateDescription = stateDescription
+                }
+                .dragAndDropTileSource(
+                    SizedTileImpl(cell.tile, cell.width),
+                    onClick,
+                    onResize,
+                    dragAndDropState,
+                )
+    )
+}
+
+@Composable
+private fun SpacerGridCell(modifier: Modifier = Modifier) {
+    // By default, spacers are invisible and exist purely to catch drag movements
+    Box(modifier.height(tileHeight()).fillMaxWidth().tilePadding())
+}
+
+@Composable
 fun EditTile(
     tileViewModel: EditTileViewModel,
     iconOnly: Boolean,
@@ -641,7 +697,7 @@
             }
         Image(
             painter = painter,
-            contentDescription = null,
+            contentDescription = icon.contentDescription?.load(),
             colorFilter = ColorFilter.tint(color = color),
             modifier = iconModifier
         )
@@ -678,10 +734,14 @@
     val icon: Color,
 )
 
+private object EditModeTileDefaults {
+    const val PLACEHOLDER_ALPHA = .3f
+    val EditGridHeaderHeight = 60.dp
+}
+
 private object TileDefaults {
     val TileShape = CircleShape
     val IconTileWithLabelHeight = 140.dp
-    val EditGridHeaderHeight = 60.dp
 
     @Composable
     fun activeTileColors(): TileColors =
@@ -722,3 +782,6 @@
         }
     }
 }
+
+private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index c241fd8..8ca8de7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -22,6 +22,12 @@
 import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 
+/** Represents an item from a grid associated with a row and a span */
+interface GridCell {
+    val row: Int
+    val span: GridItemSpan
+}
+
 /**
  * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's
  * positioned at
@@ -29,10 +35,12 @@
 @Immutable
 data class TileGridCell(
     override val tile: EditTileViewModel,
-    val row: Int,
-    val key: String = "${tile.tileSpec.spec}-$row",
+    override val row: Int,
     override val width: Int,
-) : SizedTile<EditTileViewModel> {
+    override val span: GridItemSpan = GridItemSpan(width)
+) : GridCell, SizedTile<EditTileViewModel> {
+    val key: String = "${tile.tileSpec.spec}-$row"
+
     constructor(
         sizedTile: SizedTile<EditTileViewModel>,
         row: Int
@@ -41,12 +49,30 @@
         row = row,
         width = sizedTile.width,
     )
-
-    val span = GridItemSpan(width)
 }
 
-fun List<SizedTile<EditTileViewModel>>.toTileGridCells(columns: Int): List<TileGridCell> {
+/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
+@Immutable
+data class SpacerGridCell(
+    override val row: Int,
+    override val span: GridItemSpan = GridItemSpan(1)
+) : GridCell
+
+fun List<SizedTile<EditTileViewModel>>.toGridCells(
+    columns: Int,
+    includeSpacers: Boolean = false
+): List<GridCell> {
     return splitInRowsSequence(this, columns)
-        .flatMapIndexed { index, sizedTiles -> sizedTiles.map { TileGridCell(it, index) } }
+        .flatMapIndexed { rowIndex, sizedTiles ->
+            val row: List<GridCell> = sizedTiles.map { TileGridCell(it, rowIndex) }
+
+            if (includeSpacers) {
+                // Fill the incomplete rows with spacers
+                val numSpacers = columns - sizedTiles.sumOf { it.width }
+                row.toMutableList().apply { repeat(numSpacers) { add(SpacerGridCell(rowIndex)) } }
+            } else {
+                row
+            }
+        }
         .toList()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index ef2c8bf..42715be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -179,6 +179,10 @@
         currentTilesInteractor.removeTiles(listOf(tileSpec))
     }
 
+    fun setTiles(tileSpecs: List<TileSpec>) {
+        currentTilesInteractor.setTiles(tileSpecs)
+    }
+
     /** Immediately resets the current tiles to the default list. */
     fun resetCurrentTilesToDefault() {
         throw NotImplementedError("This is not supported yet")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 2a33a16..5f10b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import android.os.Handler
 import android.os.Looper
+import android.service.quicksettings.Tile
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
@@ -94,7 +95,13 @@
 
     override fun getTileLabel(): CharSequence = tileState.label
 
-    override fun newTileState() = QSTile.State()
+    override fun newTileState(): QSTile.State {
+        return QSTile.State().apply {
+            label = mContext.getString(R.string.quick_settings_modes_label)
+            icon = ResourceIcon.get(R.drawable.qs_dnd_icon_off)
+            state = Tile.STATE_INACTIVE
+        }
+    }
 
     override fun handleClick(expandable: Expandable?) = runBlocking {
         userActionInteractor.handleClick(expandable)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index b2873c5..5ea9e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -346,7 +346,6 @@
         mCallback = null;
     }
 
-    @VisibleForTesting
     boolean isAirplaneModeEnabled() {
         return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index f018336..71f8639 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -30,7 +30,6 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
-import android.telephony.TelephonyManager;
 import android.text.Html;
 import android.text.Layout;
 import android.text.TextUtils;
@@ -50,9 +49,14 @@
 import android.widget.TextView;
 
 import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.MutableLiveData;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -110,7 +114,6 @@
     protected boolean mCanConfigWifi;
 
     private final InternetDialogManager mInternetDialogManager;
-    private TelephonyManager mTelephonyManager;
     @Nullable
     private AlertDialog mAlertDialog;
     private final UiEventLogger mUiEventLogger;
@@ -169,6 +172,13 @@
     @Nullable
     private Job mClickJob;
 
+    // These are to reduce the UI janky frame duration. b/323286540
+    private LifecycleRegistry mLifecycleRegistry;
+    @VisibleForTesting
+    LifecycleOwner mLifecycleOwner;
+    @VisibleForTesting
+    MutableLiveData<InternetContent> mDataInternetContent = new MutableLiveData<>();
+
     @AssistedFactory
     public interface Factory {
         InternetDialogDelegate create(
@@ -205,7 +215,6 @@
         mInternetDialogManager = internetDialogManager;
         mInternetDialogController = internetDialogController;
         mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId();
-        mTelephonyManager = mInternetDialogController.getTelephonyManager();
         mCanConfigMobileData = canConfigMobileData;
         mCanConfigWifi = canConfigWifi;
         mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
@@ -227,6 +236,14 @@
             mDialog.dismiss();
         }
         mDialog = dialog;
+        mLifecycleOwner = new LifecycleOwner() {
+            @NonNull
+            @Override
+            public Lifecycle getLifecycle() {
+                return mLifecycleRegistry;
+            }
+        };
+        mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
 
         return dialog;
     }
@@ -249,7 +266,9 @@
 
         mWifiNetworkHeight = context.getResources()
                 .getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height);
-
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+        mDataInternetContent.observe(
+                mLifecycleOwner, (internetContent) -> updateDialogUI(internetContent));
         mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
         mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
         mDivider = mDialogView.requireViewById(R.id.divider);
@@ -294,6 +313,8 @@
         if (DEBUG) {
             Log.d(TAG, "onStart");
         }
+
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
         mInternetDialogController.onStart(this, mCanConfigWifi);
         if (!mCanConfigWifi) {
             hideWifiViews();
@@ -315,6 +336,7 @@
         if (DEBUG) {
             Log.d(TAG, "onStop");
         }
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
         mMobileNetworkLayout.setOnClickListener(null);
         mConnectedWifListLayout.setOnClickListener(null);
         if (mSecondaryMobileNetworkLayout != null) {
@@ -348,31 +370,50 @@
      *                                  otherwise {@code false}.
      */
     void updateDialog(boolean shouldUpdateMobileNetwork) {
-        if (DEBUG) {
-            Log.d(TAG, "updateDialog");
-        }
-        mInternetDialogTitle.setText(getDialogTitleText());
-        mInternetDialogSubTitle.setText(getSubtitleText());
-        mAirplaneModeButton.setVisibility(
-                mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
+        mBackgroundExecutor.execute(() -> {
+            mDataInternetContent.postValue(getInternetContent(shouldUpdateMobileNetwork));
+        });
+    }
 
-        updateEthernet();
-        if (shouldUpdateMobileNetwork) {
-            setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular(),
-                    mInternetDialogController.isCarrierNetworkActive());
+    private void updateDialogUI(InternetContent internetContent) {
+        if (DEBUG) {
+            Log.d(TAG, "updateDialog ");
         }
 
+        mInternetDialogTitle.setText(internetContent.mInternetDialogTitleString);
+        mInternetDialogSubTitle.setText(internetContent.mInternetDialogSubTitle);
+        mAirplaneModeButton.setVisibility(
+                internetContent.mIsAirplaneModeEnabled ? View.VISIBLE : View.GONE);
+
+        updateEthernet(internetContent);
+        setMobileDataLayout(internetContent);
+
         if (!mCanConfigWifi) {
             return;
         }
+        updateWifiToggle(internetContent);
+        updateConnectedWifi(internetContent);
+        updateWifiListAndSeeAll(internetContent);
+        updateWifiScanNotify(internetContent);
+    }
 
-        final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
-        final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
-        final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
-        updateWifiToggle(isWifiEnabled, isDeviceLocked);
-        updateConnectedWifi(isWifiEnabled, isDeviceLocked);
-        updateWifiListAndSeeAll(isWifiEnabled, isDeviceLocked);
-        updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked);
+    private InternetContent getInternetContent(boolean shouldUpdateMobileNetwork) {
+        InternetContent internetContent = new InternetContent();
+        internetContent.mShouldUpdateMobileNetwork = shouldUpdateMobileNetwork;
+        internetContent.mInternetDialogTitleString = getDialogTitleText();
+        internetContent.mInternetDialogSubTitle = getSubtitleText();
+        internetContent.mActiveNetworkIsCellular =
+                mInternetDialogController.activeNetworkIsCellular();
+        internetContent.mIsCarrierNetworkActive =
+                mInternetDialogController.isCarrierNetworkActive();
+        internetContent.mIsAirplaneModeEnabled = mInternetDialogController.isAirplaneModeEnabled();
+        internetContent.mHasEthernet = mInternetDialogController.hasEthernet();
+        internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled();
+        internetContent.mHasActiveSubIdOnDds = mInternetDialogController.hasActiveSubIdOnDds();
+        internetContent.mIsMobileDataEnabled = mInternetDialogController.isMobileDataEnabled();
+        internetContent.mIsDeviceLocked = mInternetDialogController.isDeviceLocked();
+        internetContent.mIsWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
+        return internetContent;
     }
 
     private void setOnClickListener(SystemUIDialog dialog) {
@@ -436,39 +477,39 @@
     }
 
     @MainThread
-    private void updateEthernet() {
+    private void updateEthernet(InternetContent internetContent) {
         mEthernetLayout.setVisibility(
-                mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
+                internetContent.mHasEthernet ? View.VISIBLE : View.GONE);
     }
 
-    private void setMobileDataLayout(boolean activeNetworkIsCellular,
-            boolean isCarrierNetworkActive) {
-
-        if (mDialog != null) {
-            setMobileDataLayout(mDialog, activeNetworkIsCellular, isCarrierNetworkActive);
+    private void setMobileDataLayout(InternetContent internetContent) {
+        if (!internetContent.mShouldUpdateMobileNetwork && mDialog == null) {
+            return;
         }
+        setMobileDataLayout(mDialog, internetContent);
     }
 
-    private void setMobileDataLayout(SystemUIDialog dialog, boolean activeNetworkIsCellular,
-            boolean isCarrierNetworkActive) {
-        boolean isNetworkConnected = activeNetworkIsCellular || isCarrierNetworkActive;
+    private void setMobileDataLayout(SystemUIDialog dialog, InternetContent internetContent) {
+        boolean isNetworkConnected =
+                internetContent.mActiveNetworkIsCellular
+                        || internetContent.mIsCarrierNetworkActive;
         // 1. Mobile network should be gone if airplane mode ON or the list of active
         //    subscriptionId is null.
         // 2. Carrier network should be gone if airplane mode ON and Wi-Fi is OFF.
         if (DEBUG) {
-            Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive);
+            Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = "
+                    + internetContent.mIsCarrierNetworkActive);
         }
 
-        boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
-        if (!mInternetDialogController.hasActiveSubIdOnDds()
-                && (!isWifiEnabled || !isCarrierNetworkActive)) {
+        if (!internetContent.mHasActiveSubIdOnDds && (!internetContent.mIsWifiEnabled
+                || !internetContent.mIsCarrierNetworkActive)) {
             mMobileNetworkLayout.setVisibility(View.GONE);
             if (mSecondaryMobileNetworkLayout != null) {
                 mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
             }
         } else {
             mMobileNetworkLayout.setVisibility(View.VISIBLE);
-            mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
+            mMobileDataToggle.setChecked(internetContent.mIsMobileDataEnabled);
             mMobileTitleText.setText(getMobileNetworkTitle(mDefaultDataSubId));
             String summary = getMobileNetworkSummary(mDefaultDataSubId);
             if (!TextUtils.isEmpty(summary)) {
@@ -508,7 +549,7 @@
                 if (stub != null) {
                     stub.inflate();
                 }
-                mSecondaryMobileNetworkLayout = dialog.findViewById(
+                mSecondaryMobileNetworkLayout = mDialogView.findViewById(
                         R.id.secondary_mobile_network_layout);
                 mSecondaryMobileNetworkLayout.setOnClickListener(
                         this::onClickConnectedSecondarySub);
@@ -567,7 +608,7 @@
             }
 
             // Set airplane mode to the summary for carrier network
-            if (mInternetDialogController.isAirplaneModeEnabled()) {
+            if (internetContent.mIsAirplaneModeEnabled) {
                 mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
                 mAirplaneModeSummaryText.setText(
                         dialog.getContext().getText(R.string.airplane_mode));
@@ -579,17 +620,18 @@
     }
 
     @MainThread
-    private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
-        if (mWiFiToggle.isChecked() != isWifiEnabled) {
-            mWiFiToggle.setChecked(isWifiEnabled);
+    private void updateWifiToggle(InternetContent internetContent) {
+        if (mWiFiToggle.isChecked() != internetContent.mIsWifiEnabled) {
+            mWiFiToggle.setChecked(internetContent.mIsWifiEnabled);
         }
-        if (isDeviceLocked) {
+        if (internetContent.mIsDeviceLocked) {
             mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null)
                     ? R.style.TextAppearance_InternetDialog_Active
                     : R.style.TextAppearance_InternetDialog);
         }
         mTurnWifiOnLayout.setBackground(
-                (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+                (internetContent.mIsDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn
+                        : null);
 
         if (!mCanChangeWifiState && mWiFiToggle.isEnabled()) {
             mWiFiToggle.setEnabled(false);
@@ -601,8 +643,9 @@
     }
 
     @MainThread
-    private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
-        if (mDialog == null || !isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
+    private void updateConnectedWifi(InternetContent internetContent) {
+        if (mDialog == null || !internetContent.mIsWifiEnabled || mConnectedWifiEntry == null
+                || internetContent.mIsDeviceLocked) {
             mConnectedWifListLayout.setVisibility(View.GONE);
             mShareWifiButton.setVisibility(View.GONE);
             return;
@@ -627,8 +670,8 @@
     }
 
     @MainThread
-    private void updateWifiListAndSeeAll(boolean isWifiEnabled, boolean isDeviceLocked) {
-        if (!isWifiEnabled || isDeviceLocked) {
+    private void updateWifiListAndSeeAll(InternetContent internetContent) {
+        if (!internetContent.mIsWifiEnabled || internetContent.mIsDeviceLocked) {
             mWifiRecyclerView.setVisibility(View.GONE);
             mSeeAllLayout.setVisibility(View.GONE);
             return;
@@ -670,9 +713,10 @@
     }
 
     @MainThread
-    private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled,
-            boolean isDeviceLocked) {
-        if (mDialog == null || isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) {
+    private void updateWifiScanNotify(InternetContent internetContent) {
+        if (mDialog == null || internetContent.mIsWifiEnabled
+                || !internetContent.mIsWifiScanEnabled
+                || internetContent.mIsDeviceLocked) {
             mWifiScanNotifyLayout.setVisibility(View.GONE);
             return;
         }
@@ -805,62 +849,62 @@
 
     @Override
     public void onRefreshCarrierInfo() {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onSimStateChanged() {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     @WorkerThread
     public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     @WorkerThread
     public void onLost(Network network) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onSubscriptionsChanged(int defaultDataSubId) {
         mDefaultDataSubId = defaultDataSubId;
-        mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onUserMobileDataStateChanged(boolean enabled) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onServiceStateChanged(ServiceState serviceState) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     @WorkerThread
     public void onDataConnectionStateChanged(int state, int networkType) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onCarrierNetworkChange(boolean active) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
@@ -912,4 +956,20 @@
             return mId;
         }
     }
+
+    @VisibleForTesting
+    static class InternetContent {
+        CharSequence mInternetDialogTitleString = "";
+        CharSequence mInternetDialogSubTitle = "";
+        boolean mIsAirplaneModeEnabled = false;
+        boolean mHasEthernet = false;
+        boolean mShouldUpdateMobileNetwork = false;
+        boolean mActiveNetworkIsCellular = false;
+        boolean mIsCarrierNetworkActive = false;
+        boolean mIsWifiEnabled = false;
+        boolean mHasActiveSubIdOnDds = false;
+        boolean mIsMobileDataEnabled = false;
+        boolean mIsDeviceLocked = false;
+        boolean mIsWifiScanEnabled = false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index dda02db..e7d5cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -32,8 +32,6 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
-import androidx.annotation.NonNull;
-
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Flags;
@@ -49,6 +47,7 @@
 
 public class PhoneStatusBarView extends FrameLayout {
     private static final String TAG = "PhoneStatusBarView";
+    private final StatusBarContentInsetsProvider mContentInsetsProvider;
     private final StatusBarWindowController mStatusBarWindowController;
 
     private int mRotationOrientation = -1;
@@ -61,10 +60,6 @@
     private int mStatusBarHeight;
     @Nullable
     private Gefingerpoken mTouchEventHandler;
-    @Nullable
-    private HasCornerCutoutFetcher mHasCornerCutoutFetcher;
-    @Nullable
-    private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
 
@@ -75,6 +70,7 @@
 
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
     }
 
@@ -82,14 +78,6 @@
         mTouchEventHandler = handler;
     }
 
-    void setHasCornerCutoutFetcher(@NonNull HasCornerCutoutFetcher cornerCutoutFetcher) {
-        mHasCornerCutoutFetcher = cornerCutoutFetcher;
-    }
-
-    void setInsetsFetcher(@NonNull InsetsFetcher insetsFetcher) {
-        mInsetsFetcher = insetsFetcher;
-    }
-
     void init(StatusBarUserChipViewModel viewModel) {
         StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
         StatusBarUserChipViewBinder.bind(container, viewModel);
@@ -282,14 +270,7 @@
             return;
         }
 
-        boolean hasCornerCutout;
-        if (mHasCornerCutoutFetcher != null) {
-            hasCornerCutout = mHasCornerCutoutFetcher.fetchHasCornerCutout();
-        } else {
-            Log.e(TAG, "mHasCornerCutoutFetcher unexpectedly null");
-            hasCornerCutout = true;
-        }
-
+        boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout();
         if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) {
             mCutoutSpace.setVisibility(View.GONE);
             return;
@@ -307,12 +288,8 @@
     }
 
     private void updateSafeInsets() {
-        if (mInsetsFetcher == null) {
-            Log.e(TAG, "mInsetsFetcher unexpectedly null");
-            return;
-        }
-
-        Insets insets  = mInsetsFetcher.fetchInsets();
+        Insets insets = mContentInsetsProvider
+                .getStatusBarContentInsetsForCurrentRotation();
         setPadding(
                 insets.left,
                 insets.top,
@@ -326,12 +303,4 @@
         }
         mStatusBarWindowController.refreshStatusBarHeight();
     }
-
-    interface HasCornerCutoutFetcher {
-        boolean fetchHasCornerCutout();
-    }
-
-    interface InsetsFetcher {
-        Insets fetchInsets();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 456265b..468a3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -73,7 +73,6 @@
     private val configurationController: ConfigurationController,
     private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
     private val darkIconDispatcher: DarkIconDispatcher,
-    private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
 ) : ViewController<PhoneStatusBarView>(view) {
 
     private lateinit var battery: BatteryMeterView
@@ -156,14 +155,7 @@
     }
 
     init {
-        // These should likely be done in `onInit`, not `init`.
         mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
-        mView.setHasCornerCutoutFetcher {
-            statusBarContentInsetsProvider.currentRotationHasCornerCutout()
-        }
-        mView.setInsetsFetcher {
-            statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
-        }
         mView.init(userChipViewModel)
     }
 
@@ -318,7 +310,6 @@
         private val configurationController: ConfigurationController,
         private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
         private val darkIconDispatcher: DarkIconDispatcher,
-        private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
@@ -344,7 +335,6 @@
                 configurationController,
                 statusOverlayHoverListenerFactory,
                 darkIconDispatcher,
-                statusBarContentInsetsProvider,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 2e29bbd..b1c6455 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -21,6 +21,7 @@
 import static android.app.WallpaperManager.SetWallpaperFlags;
 
 import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased;
+import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.offloadColorExtraction;
 
 import android.annotation.Nullable;
@@ -190,7 +191,10 @@
             }
             mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
             mSurfaceHolder = surfaceHolder;
-            Rect dimensions = mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true);
+            Rect dimensions = !multiCrop()
+                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
+                    : mWallpaperManager.peekBitmapDimensionsAsUser(getSourceFlag(), true,
+                    mUserTracker.getUserId());
             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
             mSurfaceHolder.setFixedSize(width, height);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
new file mode 100644
index 0000000..0c716137
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.ui.viewmodel
+
+import androidx.lifecycle.Lifecycle.Event
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.SavedStateHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
+import com.android.systemui.keyboard.data.repository.keyboardRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import com.android.systemui.touchpad.tutorial.touchpadGesturesInteractor
+import com.android.systemui.util.coroutines.MainDispatcherRule
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sysUiState = kosmos.sysUiState
+    private val touchpadRepo = PrettyFakeTouchpadRepository()
+    private val keyboardRepo = kosmos.keyboardRepository
+    private var startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+    private val viewModel by lazy { createViewModel(startingPeripheral) }
+
+    // createUnsafe so its methods don't have to be called on Main thread
+    private val lifecycle = LifecycleRegistry.createUnsafe(mock(LifecycleOwner::class.java))
+
+    @get:Rule val mainDispatcherRule = MainDispatcherRule(kosmos.testDispatcher)
+
+    private fun createViewModel(
+        startingPeripheral: String = INTENT_TUTORIAL_TYPE_TOUCHPAD,
+        hasTouchpadTutorialScreens: Boolean = true,
+    ): KeyboardTouchpadTutorialViewModel {
+        val viewModel =
+            KeyboardTouchpadTutorialViewModel(
+                Optional.of(kosmos.touchpadGesturesInteractor),
+                KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
+                hasTouchpadTutorialScreens,
+                SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
+            )
+        lifecycle.addObserver(viewModel)
+        return viewModel
+    }
+
+    @Test
+    fun screensOrder_whenTouchpadAndKeyboardConnected() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+            // reached the last screen
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE, ACTION_KEY).inOrder()
+            assertThat(closeActivity).isFalse()
+        }
+
+    @Test
+    fun screensOrder_whenKeyboardDisconnectsDuringTutorial() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            // back gesture screen
+            goToNextScreen()
+            // home gesture screen
+            peripheralsState(keyboardConnected = false, touchpadConnected = true)
+            goToNextScreen()
+            // no action key screen because keyboard disconnected
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrderUntilFinish_whenTouchpadAndKeyboardConnected() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+            // we're at the last screen so "next screen" should be actually closing activity
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE, ACTION_KEY).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenGoingBackToPreviousScreens() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            // back gesture
+            goToNextScreen()
+            // home gesture
+            goToNextScreen()
+            // action key
+
+            goBack()
+            // home gesture
+            goBack()
+            // back gesture
+            goBack()
+            // finish activity
+
+            assertThat(screens)
+                .containsExactly(BACK_GESTURE, HOME_GESTURE, ACTION_KEY, HOME_GESTURE, BACK_GESTURE)
+                .inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenGoingBackAndOnlyKeyboardConnected() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_KEYBOARD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = false)
+
+            // action key screen
+            goBack()
+            // activity finished
+
+            assertThat(screens).containsExactly(ACTION_KEY).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenTouchpadConnected() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+
+            peripheralsState(keyboardConnected = false, touchpadConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenKeyboardConnected() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_KEYBOARD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+
+            peripheralsState(keyboardConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(ACTION_KEY).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun touchpadGesturesDisabled_onlyDuringTouchpadTutorial() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            collectValues(viewModel.screen) // just to initialize viewModel
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            assertGesturesDisabled()
+            goToNextScreen()
+            goToNextScreen()
+            // end of touchpad tutorial, keyboard tutorial starts
+            assertGesturesNotDisabled()
+        }
+
+    @Test
+    fun activityFinishes_ifTouchpadModuleIsNotPresent() =
+        testScope.runTest {
+            val viewModel =
+                createViewModel(
+                    startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD,
+                    hasTouchpadTutorialScreens = false
+                )
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(touchpadConnected = true)
+
+            assertThat(screens).isEmpty()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun touchpadGesturesDisabled_whenTutorialGoesToForeground() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            collectValues(viewModel.screen) // just to initialize viewModel
+            peripheralsState(touchpadConnected = true)
+
+            lifecycle.handleLifecycleEvent(Event.ON_START)
+
+            assertGesturesDisabled()
+        }
+
+    @Test
+    fun touchpadGesturesNotDisabled_whenTutorialGoesToBackground() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            collectValues(viewModel.screen)
+            peripheralsState(touchpadConnected = true)
+
+            lifecycle.handleLifecycleEvent(Event.ON_START)
+            lifecycle.handleLifecycleEvent(Event.ON_STOP)
+
+            assertGesturesNotDisabled()
+        }
+
+    @Test
+    fun keyboardShortcutsDisabled_onlyDuringKeyboardTutorial() =
+        testScope.runTest {
+            // TODO(b/358587037)
+        }
+
+    private fun TestScope.goToNextScreen() {
+        viewModel.onDoneButtonClicked()
+        runCurrent()
+    }
+
+    private fun TestScope.goBack() {
+        viewModel.onBack()
+        runCurrent()
+    }
+
+    private fun TestScope.peripheralsState(
+        keyboardConnected: Boolean = false,
+        touchpadConnected: Boolean = false
+    ) {
+        keyboardRepo.setIsAnyKeyboardConnected(keyboardConnected)
+        touchpadRepo.setIsAnyTouchpadConnected(touchpadConnected)
+        runCurrent()
+    }
+
+    private fun TestScope.assertGesturesNotDisabled() = assertFlagEnabled(enabled = false)
+
+    private fun TestScope.assertGesturesDisabled() = assertFlagEnabled(enabled = true)
+
+    private fun TestScope.assertFlagEnabled(enabled: Boolean) {
+        // sysui state is changed on background scope so let's make sure it's executed
+        runCurrent()
+        assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED))
+            .isEqualTo(enabled)
+    }
+
+    // replace below when we have better fake
+    internal class PrettyFakeTouchpadRepository : TouchpadRepository {
+
+        private val _isAnyTouchpadConnected = MutableStateFlow(false)
+        override val isAnyTouchpadConnected: Flow<Boolean> = _isAnyTouchpadConnected
+
+        fun setIsAnyTouchpadConnected(connected: Boolean) {
+            _isAnyTouchpadConnected.value = connected
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index ff8c448..643debf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -6,20 +6,20 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AlertDialog;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Handler;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.testing.TestableLooper;
+import android.testing.UiThreadTest;
 import android.view.View;
+import android.view.Window;
 import android.widget.LinearLayout;
 import android.widget.Switch;
 import android.widget.TextView;
@@ -44,20 +44,18 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
 import java.util.List;
 
-@Ignore("b/257089187")
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@UiThreadTest
 public class InternetDialogDelegateTest extends SysuiTestCase {
 
     private static final String MOBILE_NETWORK_TITLE = "Mobile Title";
@@ -87,6 +85,8 @@
     private SystemUIDialog.Factory mSystemUIDialogFactory;
     @Mock
     private SystemUIDialog mSystemUIDialog;
+    @Mock
+    private Window mWindow;
 
     private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InternetDialogDelegate mInternetDialogDelegate;
@@ -121,13 +121,16 @@
         when(mInternetDialogController.getMobileNetworkSummary(anyInt()))
                 .thenReturn(MOBILE_NETWORK_SUMMARY);
         when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
-
+        when(mInternetDialogController.getActiveAutoSwitchNonDdsSubId()).thenReturn(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         mMockitoSession = ExtendedMockito.mockitoSession()
                 .spyStatic(WifiEnterpriseRestrictionUtils.class)
                 .startMocking();
         when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true);
         when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class)))
                 .thenReturn(mSystemUIDialog);
+        when(mSystemUIDialog.getContext()).thenReturn(mContext);
+        when(mSystemUIDialog.getWindow()).thenReturn(mWindow);
         createInternetDialog();
     }
 
@@ -146,6 +149,8 @@
                 mBgExecutor,
                 mKeyguard,
                 mSystemUIDialogFactory);
+        mInternetDialogDelegate.createDialog();
+        mInternetDialogDelegate.onCreate(mSystemUIDialog, null);
         mInternetDialogDelegate.mAdapter = mInternetAdapter;
         mInternetDialogDelegate.mConnectedWifiEntry = mInternetWifiEntry;
         mInternetDialogDelegate.mWifiEntriesCount = mWifiEntries.size();
@@ -163,10 +168,12 @@
         mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
         mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
         mAirplaneModeSummaryText = mDialogView.requireViewById(R.id.airplane_mode_summary);
+        mInternetDialogDelegate.onStart(mSystemUIDialog);
     }
 
     @After
     public void tearDown() {
+        mInternetDialogDelegate.onStop(mSystemUIDialog);
         mInternetDialogDelegate.dismissDialog();
         mMockitoSession.finishMocking();
     }
@@ -191,59 +198,77 @@
     @Test
     public void updateDialog_withApmOn_internetDialogSubTitleGone() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_apmOffAndHasEthernet_showEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         when(mInternetDialogController.hasEthernet()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_apmOffAndNoEthernet_hideEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         when(mInternetDialogController.hasEthernet()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndHasEthernet_showEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         when(mInternetDialogController.hasEthernet()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndNoEthernet_hideEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         when(mInternetDialogController.hasEthernet()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -252,41 +277,56 @@
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
-    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayout() {
-        // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
-        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
-        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-        when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
-
-        mInternetDialogDelegate.updateDialog(true);
-
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
-
+    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayoutVisible() {
         // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
+                });
+    }
+
+    @Test
+    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayoutGone() {
+        // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
+        mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndNoCarrierNetwork_mobileDataLayoutGone() {
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -295,11 +335,14 @@
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
@@ -308,30 +351,39 @@
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOffAndHasCarrierNetwork_notShowApmSummary() {
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndNoCarrierNetwork_notShowApmSummary() {
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -340,10 +392,13 @@
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
         mMobileToggleSwitch.setChecked(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+                });
     }
 
     @Test
@@ -352,26 +407,32 @@
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
         mMobileToggleSwitch.setChecked(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+                });
     }
 
     @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
-        mInternetDialogDelegate.dismissDialog();
+        when(mInternetDialogController.getActiveAutoSwitchNonDdsSubId()).thenReturn(1);
         doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
-        createInternetDialog();
         // The preconditions WiFi ON and Internet WiFi are already in setUp()
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
 
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
-        LinearLayout secondaryLayout = mDialogView.requireViewById(
-                R.id.secondary_mobile_network_layout);
-        assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+                    LinearLayout secondaryLayout = mDialogView.requireViewById(
+                            R.id.secondary_mobile_network_layout);
+                    assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -379,10 +440,13 @@
         // The precondition WiFi ON is already in setUp()
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -390,14 +454,17 @@
         // The precondition WiFi ON is already in setUp()
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         mInternetDialogDelegate.mWifiEntriesCount = 0;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        // Show a blank block to fix the dialog height even if there is no WiFi list
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(3);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    // Show a blank block to fix the dialog height even if there is no WiFi list
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(3);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+                });
     }
 
     @Test
@@ -405,28 +472,34 @@
         // The precondition WiFi ON is already in setUp()
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         mInternetDialogDelegate.mWifiEntriesCount = 1;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        // Show a blank block to fix the dialog height even if there is no WiFi list
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(3);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    // Show a blank block to fix the dialog height even if there is no WiFi list
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(3);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() {
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         mInternetDialogDelegate.mWifiEntriesCount = 0;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
-        // Show a blank block to fix the dialog height even if there is no WiFi list
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(2);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+                    // Show a blank block to fix the dialog height even if there is no WiFi list
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(2);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+                });
     }
 
     @Test
@@ -435,13 +508,16 @@
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         mInternetDialogDelegate.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT;
         mInternetDialogDelegate.mHasMoreWifiEntries = true;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(3);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(3);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
@@ -449,13 +525,16 @@
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         mInternetDialogDelegate.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1;
         mInternetDialogDelegate.mHasMoreWifiEntries = true;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(2);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(2);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
@@ -463,32 +542,38 @@
         // The preconditions WiFi entries are already in setUp()
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
         mInternetDialogDelegate.mConnectedWifiEntry = null;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Show WiFi Toggle without background
-        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiToggle.getBackground()).isNull();
-        // Hide Wi-Fi networks and See all
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Show WiFi Toggle without background
+                    assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiToggle.getBackground()).isNull();
+                    // Hide Wi-Fi networks and See all
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_deviceLockedAndHasConnectedWifi_showWifiToggleWithBackground() {
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Show WiFi Toggle with highlight background
-        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiToggle.getBackground()).isNotNull();
-        // Hide Wi-Fi networks and See all
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Show WiFi Toggle with highlight background
+                    assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiToggle.getBackground()).isNotNull();
+                    // Hide Wi-Fi networks and See all
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -496,13 +581,16 @@
         mInternetDialogDelegate.dismissDialog();
         when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(false);
         createInternetDialog();
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Disable Wi-Fi switch and show restriction message in summary.
-        assertThat(mWifiToggleSwitch.isEnabled()).isFalse();
-        assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiToggleSummary.getText().length()).isNotEqualTo(0);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Disable Wi-Fi switch and show restriction message in summary.
+                    assertThat(mWifiToggleSwitch.isEnabled()).isFalse();
+                    assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiToggleSummary.getText().length()).isNotEqualTo(0);
+                });
     }
 
     @Test
@@ -510,50 +598,38 @@
         mInternetDialogDelegate.dismissDialog();
         when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true);
         createInternetDialog();
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Enable Wi-Fi switch and hide restriction message in summary.
-        assertThat(mWifiToggleSwitch.isEnabled()).isTrue();
-        assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Enable Wi-Fi switch and hide restriction message in summary.
+                    assertThat(mWifiToggleSwitch.isEnabled()).isTrue();
+                    assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_showSecondaryDataSub() {
-        mInternetDialogDelegate.dismissDialog();
+        when(mInternetDialogController.getActiveAutoSwitchNonDdsSubId()).thenReturn(1);
         doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
         doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
-        createInternetDialog();
-
         clearInvocations(mInternetDialogController);
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        LinearLayout primaryLayout = mDialogView.requireViewById(
-                R.id.mobile_network_layout);
-        LinearLayout secondaryLayout = mDialogView.requireViewById(
-                R.id.secondary_mobile_network_layout);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    LinearLayout primaryLayout = mDialogView.requireViewById(
+                            R.id.mobile_network_layout);
+                    LinearLayout secondaryLayout = mDialogView.requireViewById(
+                            R.id.secondary_mobile_network_layout);
 
-        verify(mInternetDialogController).getMobileNetworkSummary(1);
-        assertThat(primaryLayout.getBackground()).isNotEqualTo(secondaryLayout.getBackground());
-
-        // Tap the primary sub info
-        primaryLayout.performClick();
-        ArgumentCaptor<AlertDialog> dialogArgumentCaptor =
-                ArgumentCaptor.forClass(AlertDialog.class);
-        verify(mDialogTransitionAnimator).showFromDialog(dialogArgumentCaptor.capture(),
-                eq(mSystemUIDialog), eq(null), eq(false));
-        AlertDialog dialog = dialogArgumentCaptor.getValue();
-        dialog.show();
-        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
-        TestableLooper.get(this).processAllMessages();
-        verify(mInternetDialogController).setAutoDataSwitchMobileDataPolicy(1, false);
-
-        // Tap the secondary sub info
-        secondaryLayout.performClick();
-        verify(mInternetDialogController).launchMobileNetworkSettings(any(View.class));
-
-        dialog.dismiss();
+                    verify(mInternetDialogController).getMobileNetworkSummary(1);
+                    assertThat(primaryLayout.getBackground()).isNotEqualTo(
+                            secondaryLayout.getBackground());
+                });
     }
 
     @Test
@@ -561,6 +637,12 @@
         // The preconditions WiFi ON and WiFi entries are already in setUp()
 
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+                });
 
         assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
     }
@@ -569,8 +651,13 @@
     public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() {
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         when(mInternetDialogController.isWifiScanEnabled()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+                });
 
         assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
     }
@@ -580,8 +667,13 @@
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true);
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+                });
 
         assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
     }
@@ -591,33 +683,43 @@
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true);
         when(mInternetDialogController.isDeviceLocked()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE);
-        TextView wifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text);
-        assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0);
-        assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE);
+                    TextView wifiScanNotifyText = mDialogView.requireViewById(
+                            R.id.wifi_scan_notify_text);
+                    assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0);
+                    assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull();
+                });
     }
 
     @Test
     public void updateDialog_wifiIsDisabled_uncheckWifiSwitch() {
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         mWifiToggleSwitch.setChecked(true);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mWifiToggleSwitch.isChecked()).isFalse();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiToggleSwitch.isChecked()).isFalse();
+                });
     }
 
     @Test
-    public void updateDialog_wifiIsEnabled_checkWifiSwitch() {
+    public void updateDialog_wifiIsEnabled_checkWifiSwitch() throws Exception {
         when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
         mWifiToggleSwitch.setChecked(false);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mWifiToggleSwitch.isChecked()).isTrue();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiToggleSwitch.isChecked()).isTrue();
+                });
     }
 
     @Test
@@ -699,21 +801,28 @@
     public void updateDialog_shareWifiIntentNull_hideButton() {
         when(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(any()))
                 .thenReturn(null);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility()).isEqualTo(
+                            View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_shareWifiShareable_showButton() {
         when(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(any()))
                 .thenReturn(new Intent());
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility())
-                .isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility())
+                            .isEqualTo(View.VISIBLE);
+                });
     }
 
     private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 70ac31d..30e7247 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -391,7 +391,6 @@
                 configurationController,
                 mStatusOverlayHoverListenerFactory,
                 fakeDarkIconDispatcher,
-                mock(StatusBarContentInsetsProvider::class.java),
             )
             .create(view)
             .also { it.init() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 648ddf8..575b051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -56,14 +56,21 @@
     private val systemIconsContainer: View
         get() = view.requireViewById(R.id.system_icons)
 
+    private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>()
     private val windowController = mock<StatusBarWindowController>()
 
     @Before
     fun setUp() {
+        mDependency.injectTestDependency(
+            StatusBarContentInsetsProvider::class.java,
+            contentInsetsProvider
+        )
         mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
         context.ensureTestableResources()
         view = spy(createStatusBarView())
         whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(Insets.NONE)
     }
 
     @Test
@@ -234,7 +241,8 @@
     @Test
     fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
-        view.setInsetsFetcher { insets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets)
 
         view.onAttachedToWindow()
 
@@ -245,23 +253,10 @@
     }
 
     @Test
-    fun onAttachedToWindow_noInsetsFetcher_noCrash() {
-        // Don't call `PhoneStatusBarView.setInsetsFetcher`
-
-        // WHEN the view is attached
-        view.onAttachedToWindow()
-
-        // THEN there's no crash, and the padding stays as it was
-        assertThat(view.paddingLeft).isEqualTo(0)
-        assertThat(view.paddingTop).isEqualTo(0)
-        assertThat(view.paddingRight).isEqualTo(0)
-        assertThat(view.paddingBottom).isEqualTo(0)
-    }
-
-    @Test
     fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        view.setInsetsFetcher { insets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets)
 
         view.onConfigurationChanged(Configuration())
 
@@ -272,31 +267,17 @@
     }
 
     @Test
-    fun onConfigurationChanged_noInsetsFetcher_noCrash() {
-        // Don't call `PhoneStatusBarView.setInsetsFetcher`
-
-        // WHEN the view is attached
-        view.onConfigurationChanged(Configuration())
-
-        // THEN there's no crash, and the padding stays as it was
-        assertThat(view.paddingLeft).isEqualTo(0)
-        assertThat(view.paddingTop).isEqualTo(0)
-        assertThat(view.paddingRight).isEqualTo(0)
-        assertThat(view.paddingBottom).isEqualTo(0)
-    }
-
-    @Test
     fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() {
         val previousInsets =
             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        view.setInsetsFetcher { previousInsets }
-
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(previousInsets)
         context.orCreateTestableResources.overrideConfiguration(Configuration())
         view.onAttachedToWindow()
 
         val newInsets = Insets.NONE
-        view.setInsetsFetcher { newInsets }
-
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(newInsets)
         view.onConfigurationChanged(Configuration())
 
         assertThat(view.paddingLeft).isEqualTo(previousInsets.left)
@@ -309,14 +290,16 @@
     fun onConfigurationChanged_densityChanged_updatesInsets() {
         val previousInsets =
             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        view.setInsetsFetcher { previousInsets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(previousInsets)
         val configuration = Configuration()
         configuration.densityDpi = 123
         context.orCreateTestableResources.overrideConfiguration(configuration)
         view.onAttachedToWindow()
 
         val newInsets = Insets.NONE
-        view.setInsetsFetcher { newInsets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(newInsets)
         configuration.densityDpi = 456
         view.onConfigurationChanged(configuration)
 
@@ -330,14 +313,16 @@
     fun onConfigurationChanged_fontScaleChanged_updatesInsets() {
         val previousInsets =
             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        view.setInsetsFetcher { previousInsets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(previousInsets)
         val configuration = Configuration()
         configuration.fontScale = 1f
         context.orCreateTestableResources.overrideConfiguration(configuration)
         view.onAttachedToWindow()
 
         val newInsets = Insets.NONE
-        view.setInsetsFetcher { newInsets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(newInsets)
         configuration.fontScale = 2f
         view.onConfigurationChanged(configuration)
 
@@ -363,7 +348,8 @@
     @Test
     fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50)
-        view.setInsetsFetcher { insets }
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets)
 
         view.onApplyWindowInsets(WindowInsets(Rect()))
 
@@ -404,7 +390,7 @@
             /* typeVisibilityMap = */ booleanArrayOf(),
             /* isRound = */ false,
             /* forceConsumingTypes = */ 0,
-            /* forceConsumingOpaqueCaptionBar = */ false,
+            /* forceConsumingCaptionBar = */ false,
             /* suppressScrimTypes = */ 0,
             /* displayCutout = */ DisplayCutout.NO_CUTOUT,
             /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 6fb70de..60a15915f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -223,8 +223,11 @@
     }
 
     private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+        // TODO(b/281648899) remove the when(mWallpaperManager.peekBitmapDimensions(...))
         when(mWallpaperManager.peekBitmapDimensions(anyInt(), anyBoolean()))
                 .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
+        when(mWallpaperManager.peekBitmapDimensionsAsUser(anyInt(), anyBoolean(), anyInt()))
+                .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
         when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
         when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 0b309b5..4dd3ae7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -62,7 +62,9 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
@@ -80,8 +82,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.wmshell.BubblesManager
-import com.google.common.util.concurrent.MoreExecutors
-import com.google.common.util.concurrent.SettableFuture
 import java.util.Optional
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -309,7 +309,7 @@
             entry.ranking = rb.build()
         }
 
-        return generateRow(entry, FLAG_CONTENT_VIEW_ALL)
+        return generateRow(entry, INFLATION_FLAGS)
     }
 
     private fun generateRow(
@@ -374,7 +374,8 @@
         private const val PKG = "com.android.systemui"
         private const val UID = 1000
         private val USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser())
-
+        private val INFLATION_FLAGS =
+            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
         private const val IS_CONVERSATION_FLAG = "test.isConversation"
 
         private val Notification.isConversationStyleNotification
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialKosmos.kt
new file mode 100644
index 0000000..f502df0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.displayTracker
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+
+var Kosmos.touchpadGesturesInteractor: TouchpadGesturesInteractor by
+    Kosmos.Fixture {
+        TouchpadGesturesInteractor(sysUiState, displayTracker, testScope.backgroundScope)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt
new file mode 100644
index 0000000..5776203
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.coroutines
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Overrides main dispatcher to passed testDispatcher. You probably want to use it when using
+ * viewModelScope which has hardcoded main dispatcher.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class MainDispatcherRule(val testDispatcher: TestDispatcher) : TestWatcher() {
+    override fun starting(description: Description) {
+        Dispatchers.setMain(testDispatcher)
+    }
+
+    override fun finished(description: Description) {
+        Dispatchers.resetMain()
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 000f312..ef61d02 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -71,6 +71,8 @@
                         (reason) -> updateTouchpadTapToClickEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_DRAGGING),
                         (reason) -> updateTouchpadTapDraggingEnabled()),
+                Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_VISUALIZER),
+                        (reason) -> updateTouchpadHardwareStateNotificationsEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
@@ -177,6 +179,10 @@
         mNative.setTouchpadTapDraggingEnabled(InputSettings.useTouchpadTapDragging(mContext));
     }
 
+    private void updateTouchpadHardwareStateNotificationsEnabled() {
+        mNative.setShouldNotifyTouchpadHardwareState(InputSettings.useTouchpadVisualizer(mContext));
+    }
+
     private void updateTouchpadRightClickZoneEnabled() {
         mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index a9d40bb..69a9f4d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -135,6 +135,8 @@
 
     void setTouchpadTapDraggingEnabled(boolean enabled);
 
+    void setShouldNotifyTouchpadHardwareState(boolean enabled);
+
     void setTouchpadRightClickZoneEnabled(boolean enabled);
 
     void setShowTouches(boolean enabled);
@@ -395,6 +397,9 @@
         public native void setTouchpadTapDraggingEnabled(boolean enabled);
 
         @Override
+        public native void setShouldNotifyTouchpadHardwareState(boolean enabled);
+
+        @Override
         public native void setTouchpadRightClickZoneEnabled(boolean enabled);
 
         @Override
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 0962319..8e37527 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -67,19 +67,19 @@
     /**
      * The handle of the primary frontend resource
      */
-    private long mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+    private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
 
     /**
      * List of the frontend handles that are used by the current client.
      */
-    private Set<Long> mUsingFrontendHandles = new HashSet<>();
+    private Set<Integer> mUsingFrontendHandles = new HashSet<>();
 
     /**
      * List of the client ids that share frontend with the current client.
      */
     private Set<Integer> mShareFeClientIds = new HashSet<>();
 
-    private Set<Long> mUsingDemuxHandles = new HashSet<>();
+    private Set<Integer> mUsingDemuxHandles = new HashSet<>();
 
     /**
      * Client id sharee that has shared frontend with the current client.
@@ -89,7 +89,7 @@
     /**
      * List of the Lnb handles that are used by the current client.
      */
-    private Set<Long> mUsingLnbHandles = new HashSet<>();
+    private Set<Integer> mUsingLnbHandles = new HashSet<>();
 
     /**
      * List of the Cas system ids that are used by the current client.
@@ -184,7 +184,7 @@
      *
      * @param frontendHandle being used.
      */
-    public void useFrontend(long frontendHandle) {
+    public void useFrontend(int frontendHandle) {
         mUsingFrontendHandles.add(frontendHandle);
     }
 
@@ -193,14 +193,14 @@
      *
      * @param frontendHandle being used.
      */
-    public void setPrimaryFrontend(long frontendHandle) {
+    public void setPrimaryFrontend(int frontendHandle) {
         mPrimaryUsingFrontendHandle = frontendHandle;
     }
 
     /**
      * Get the primary frontend used by the client
      */
-    public long getPrimaryFrontend() {
+    public int getPrimaryFrontend() {
         return mPrimaryUsingFrontendHandle;
     }
 
@@ -222,7 +222,7 @@
         mShareFeClientIds.remove(clientId);
     }
 
-    public Set<Long> getInUseFrontendHandles() {
+    public Set<Integer> getInUseFrontendHandles() {
         return mUsingFrontendHandles;
     }
 
@@ -253,14 +253,14 @@
      *
      * @param demuxHandle the demux being used.
      */
-    public void useDemux(long demuxHandle) {
+    public void useDemux(int demuxHandle) {
         mUsingDemuxHandles.add(demuxHandle);
     }
 
     /**
      * Get the set of demux handles in use.
      */
-    public Set<Long> getInUseDemuxHandles() {
+    public Set<Integer> getInUseDemuxHandles() {
         return mUsingDemuxHandles;
     }
 
@@ -269,7 +269,7 @@
      *
      * @param demuxHandle the demux handl being released.
      */
-    public void releaseDemux(long demuxHandle) {
+    public void releaseDemux(int demuxHandle) {
         mUsingDemuxHandles.remove(demuxHandle);
     }
 
@@ -278,11 +278,11 @@
      *
      * @param lnbHandle being used.
      */
-    public void useLnb(long lnbHandle) {
+    public void useLnb(int lnbHandle) {
         mUsingLnbHandles.add(lnbHandle);
     }
 
-    public Set<Long> getInUseLnbHandles() {
+    public Set<Integer> getInUseLnbHandles() {
         return mUsingLnbHandles;
     }
 
@@ -291,7 +291,7 @@
      *
      * @param lnbHandle being released.
      */
-    public void releaseLnb(long lnbHandle) {
+    public void releaseLnb(int lnbHandle) {
         mUsingLnbHandles.remove(lnbHandle);
     }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
index 14bc216..df73565 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
@@ -69,7 +69,7 @@
     public static class Builder extends TunerResourceBasic.Builder {
         private int mFilterTypes;
 
-        Builder(long handle) {
+        Builder(int handle) {
             super(handle);
         }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
index 953d974..7ef75e3 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
@@ -42,7 +42,7 @@
     /**
      * An array to save all the FE handles under the same exclisive group.
      */
-    private Set<Long> mExclusiveGroupMemberHandles = new HashSet<>();
+    private Set<Integer> mExclusiveGroupMemberHandles = new HashSet<>();
 
     private FrontendResource(Builder builder) {
         super(builder);
@@ -58,7 +58,7 @@
         return mExclusiveGroupId;
     }
 
-    public Set<Long> getExclusiveGroupMemberFeHandles() {
+    public Set<Integer> getExclusiveGroupMemberFeHandles() {
         return mExclusiveGroupMemberHandles;
     }
 
@@ -67,7 +67,7 @@
      *
      * @param handle the handle to be added.
      */
-    public void addExclusiveGroupMemberFeHandle(long handle) {
+    public void addExclusiveGroupMemberFeHandle(int handle) {
         mExclusiveGroupMemberHandles.add(handle);
     }
 
@@ -76,7 +76,7 @@
      *
      * @param handles the handle collection to be added.
      */
-    public void addExclusiveGroupMemberFeHandles(Collection<Long> handles) {
+    public void addExclusiveGroupMemberFeHandles(Collection<Integer> handles) {
         mExclusiveGroupMemberHandles.addAll(handles);
     }
 
@@ -85,7 +85,7 @@
      *
      * @param id the id to be removed.
      */
-    public void removeExclusiveGroupMemberFeId(long handle) {
+    public void removeExclusiveGroupMemberFeId(int handle) {
         mExclusiveGroupMemberHandles.remove(handle);
     }
 
@@ -104,7 +104,7 @@
         @Type private int mType;
         private int mExclusiveGroupId;
 
-        Builder(long handle) {
+        Builder(int handle) {
             super(handle);
         }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
index ab28371..41cacea 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
@@ -37,7 +37,8 @@
      * Builder class for {@link LnbResource}.
      */
     public static class Builder extends TunerResourceBasic.Builder {
-        Builder(long handle) {
+
+        Builder(int handle) {
             super(handle);
         }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
index d2ff8fa..07853fc 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
@@ -28,7 +28,7 @@
      * Handle of the current resource. Should not be changed and should be aligned with the driver
      * level implementation.
      */
-    final long mHandle;
+    final int mHandle;
 
     /**
      * If the current resource is in use.
@@ -44,7 +44,7 @@
         this.mHandle = builder.mHandle;
     }
 
-    public long getHandle() {
+    public int getHandle() {
         return mHandle;
     }
 
@@ -78,9 +78,9 @@
      * Builder class for {@link TunerResourceBasic}.
      */
     public static class Builder {
-        private final long mHandle;
+        private final int mHandle;
 
-        Builder(long handle) {
+        Builder(int handle) {
             this.mHandle = handle;
         }
 
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 45a40ed..0afb049 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -78,18 +78,12 @@
 
     private static final int INVALID_FE_COUNT = -1;
 
-    private static final int RESOURCE_ID_SHIFT = 24;
-    private static final int RESOURCE_TYPE_SHIFT = 56;
-    private static final long RESOURCE_COUNT_MASK = 0xffffff;
-    private static final long RESOURCE_ID_MASK = 0xffffffff;
-    private static final long RESOURCE_TYPE_MASK = 0xff;
-
     // Map of the registered client profiles
     private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
     private int mNextUnusedClientId = 0;
 
     // Map of the current available frontend resources
-    private Map<Long, FrontendResource> mFrontendResources = new HashMap<>();
+    private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
     // SparseIntArray of the max usable number for each frontend resource type
     private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray();
     // SparseIntArray of the currently used number for each frontend resource type
@@ -99,15 +93,15 @@
 
     // Backups for the frontend resource maps for enabling testing with custom resource maps
     // such as TunerTest.testHasUnusedFrontend1()
-    private Map<Long, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
+    private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
     private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray();
     private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray();
     private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray();
 
     // Map of the current available demux resources
-    private Map<Long, DemuxResource> mDemuxResources = new HashMap<>();
+    private Map<Integer, DemuxResource> mDemuxResources = new HashMap<>();
     // Map of the current available lnb resources
-    private Map<Long, LnbResource> mLnbResources = new HashMap<>();
+    private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
     // Map of the current available Cas resources
     private Map<Integer, CasResource> mCasResources = new HashMap<>();
     // Map of the current available CiCam resources
@@ -278,7 +272,7 @@
         }
 
         @Override
-        public void setLnbInfoList(long[] lnbHandles) throws RemoteException {
+        public void setLnbInfoList(int[] lnbHandles) throws RemoteException {
             enforceTrmAccessPermission("setLnbInfoList");
             if (lnbHandles == null) {
                 throw new RemoteException("Lnb handle list can't be null");
@@ -289,8 +283,8 @@
         }
 
         @Override
-        public boolean requestFrontend(
-                @NonNull TunerFrontendRequest request, @NonNull long[] frontendHandle) {
+        public boolean requestFrontend(@NonNull TunerFrontendRequest request,
+                @NonNull int[] frontendHandle) {
             enforceTunerAccessPermission("requestFrontend");
             enforceTrmAccessPermission("requestFrontend");
             if (frontendHandle == null) {
@@ -375,8 +369,8 @@
         }
 
         @Override
-        public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull long[] demuxHandle)
-                throws RemoteException {
+        public boolean requestDemux(@NonNull TunerDemuxRequest request,
+                    @NonNull int[] demuxHandle) throws RemoteException {
             enforceTunerAccessPermission("requestDemux");
             enforceTrmAccessPermission("requestDemux");
             if (demuxHandle == null) {
@@ -393,7 +387,7 @@
 
         @Override
         public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
-                @NonNull long[] descramblerHandle) throws RemoteException {
+                    @NonNull int[] descramblerHandle) throws RemoteException {
             enforceDescramblerAccessPermission("requestDescrambler");
             enforceTrmAccessPermission("requestDescrambler");
             if (descramblerHandle == null) {
@@ -410,7 +404,7 @@
 
         @Override
         public boolean requestCasSession(@NonNull CasSessionRequest request,
-                @NonNull long[] casSessionHandle) throws RemoteException {
+                @NonNull int[] casSessionHandle) throws RemoteException {
             enforceTrmAccessPermission("requestCasSession");
             if (casSessionHandle == null) {
                 throw new RemoteException("casSessionHandle can't be null");
@@ -425,8 +419,8 @@
         }
 
         @Override
-        public boolean requestCiCam(@NonNull TunerCiCamRequest request, @NonNull long[] ciCamHandle)
-                throws RemoteException {
+        public boolean requestCiCam(@NonNull TunerCiCamRequest request,
+                @NonNull int[] ciCamHandle) throws RemoteException {
             enforceTrmAccessPermission("requestCiCam");
             if (ciCamHandle == null) {
                 throw new RemoteException("ciCamHandle can't be null");
@@ -441,7 +435,7 @@
         }
 
         @Override
-        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull long[] lnbHandle)
+        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle)
                 throws RemoteException {
             enforceTunerAccessPermission("requestLnb");
             enforceTrmAccessPermission("requestLnb");
@@ -458,7 +452,7 @@
         }
 
         @Override
-        public void releaseFrontend(long frontendHandle, int clientId) throws RemoteException {
+        public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseFrontend");
             enforceTrmAccessPermission("releaseFrontend");
             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
@@ -487,7 +481,7 @@
         }
 
         @Override
-        public void releaseDemux(long demuxHandle, int clientId) throws RemoteException {
+        public void releaseDemux(int demuxHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseDemux");
             enforceTrmAccessPermission("releaseDemux");
             if (DEBUG) {
@@ -518,7 +512,7 @@
         }
 
         @Override
-        public void releaseDescrambler(long descramblerHandle, int clientId) {
+        public void releaseDescrambler(int descramblerHandle, int clientId) {
             enforceTunerAccessPermission("releaseDescrambler");
             enforceTrmAccessPermission("releaseDescrambler");
             if (DEBUG) {
@@ -527,7 +521,7 @@
         }
 
         @Override
-        public void releaseCasSession(long casSessionHandle, int clientId) throws RemoteException {
+        public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
             enforceTrmAccessPermission("releaseCasSession");
             if (!validateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
@@ -551,7 +545,7 @@
         }
 
         @Override
-        public void releaseCiCam(long ciCamHandle, int clientId) throws RemoteException {
+        public void releaseCiCam(int ciCamHandle, int clientId) throws RemoteException {
             enforceTrmAccessPermission("releaseCiCam");
             if (!validateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCamHandle)) {
@@ -579,7 +573,7 @@
         }
 
         @Override
-        public void releaseLnb(long lnbHandle, int clientId) throws RemoteException {
+        public void releaseLnb(int lnbHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseLnb");
             enforceTrmAccessPermission("releaseLnb");
             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
@@ -877,7 +871,7 @@
         // A set to record the frontends pending on updating. Ids will be removed
         // from this set once its updating finished. Any frontend left in this set when all
         // the updates are done will be removed from mFrontendResources.
-        Set<Long> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());
+        Set<Integer> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());
 
         // Update frontendResources map and other mappings accordingly
         for (int i = 0; i < infos.length; i++) {
@@ -896,7 +890,7 @@
             }
         }
 
-        for (long removingHandle : updatingFrontendHandles) {
+        for (int removingHandle : updatingFrontendHandles) {
             // update the exclusive group id member list
             removeFrontendResource(removingHandle);
         }
@@ -914,7 +908,7 @@
         // A set to record the demuxes pending on updating. Ids will be removed
         // from this set once its updating finished. Any demux left in this set when all
         // the updates are done will be removed from mDemuxResources.
-        Set<Long> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
+        Set<Integer> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
 
         // Update demuxResources map and other mappings accordingly
         for (int i = 0; i < infos.length; i++) {
@@ -932,13 +926,13 @@
             }
         }
 
-        for (long removingHandle : updatingDemuxHandles) {
+        for (int removingHandle : updatingDemuxHandles) {
             // update the exclusive group id member list
             removeDemuxResource(removingHandle);
         }
     }
     @VisibleForTesting
-    protected void setLnbInfoListInternal(long[] lnbHandles) {
+    protected void setLnbInfoListInternal(int[] lnbHandles) {
         if (DEBUG) {
             for (int i = 0; i < lnbHandles.length; i++) {
                 Slog.d(TAG, "updateLnbInfo(lnbHanle=" + lnbHandles[i] + ")");
@@ -948,7 +942,7 @@
         // A set to record the Lnbs pending on updating. Handles will be removed
         // from this set once its updating finished. Any lnb left in this set when all
         // the updates are done will be removed from mLnbResources.
-        Set<Long> updatingLnbHandles = new HashSet<>(getLnbResources().keySet());
+        Set<Integer> updatingLnbHandles = new HashSet<>(getLnbResources().keySet());
 
         // Update lnbResources map and other mappings accordingly
         for (int i = 0; i < lnbHandles.length; i++) {
@@ -964,7 +958,7 @@
             }
         }
 
-        for (long removingHandle : updatingLnbHandles) {
+        for (int removingHandle : updatingLnbHandles) {
             removeLnbResource(removingHandle);
         }
     }
@@ -1009,7 +1003,7 @@
     }
 
     @VisibleForTesting
-    protected boolean requestFrontendInternal(TunerFrontendRequest request, long[] frontendHandle) {
+    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestFrontend(request=" + request + ")");
         }
@@ -1021,8 +1015,8 @@
             return false;
         }
         clientPriorityUpdateOnRequest(requestClient);
-        long grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        long inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
         boolean isRequestFromSameProcess = false;
@@ -1056,7 +1050,7 @@
                         // we need to check the max used num if the target frontend type is not
                         // currently in primary use (and simply blocked due to exclusive group)
                         ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
-                        long primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+                        int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
                         FrontendResource primaryFe = getFrontendResource(primaryFeId);
                         if (fr.getType() != primaryFe.getType()
                                 && isFrontendMaxNumUseReached(fr.getType())) {
@@ -1108,7 +1102,7 @@
             getClientProfile(shareeFeClientId).stopSharingFrontend(selfClientId);
             getClientProfile(selfClientId).releaseFrontend();
         }
-        for (long feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
+        for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
             getClientProfile(selfClientId).useFrontend(feId);
         }
         getClientProfile(selfClientId).setShareeFeClientId(targetClientId);
@@ -1123,14 +1117,14 @@
         currentOwnerProfile.stopSharingFrontend(newOwnerId);
         newOwnerProfile.setShareeFeClientId(ClientProfile.INVALID_RESOURCE_ID);
         currentOwnerProfile.setShareeFeClientId(newOwnerId);
-        for (long inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
+        for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
             getFrontendResource(inUseHandle).setOwner(newOwnerId);
         }
         // change the primary frontend
         newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend());
         currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE);
         // double check there is no other resources tied to the previous owner
-        for (long inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
+        for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
             int ownerId = getFrontendResource(inUseHandle).getOwnerClientId();
             if (ownerId != newOwnerId) {
                 Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle
@@ -1162,8 +1156,8 @@
         ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
         ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
 
-        Set<Long> inUseLnbHandles = new HashSet<>();
-        for (Long lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
+        Set<Integer> inUseLnbHandles = new HashSet<>();
+        for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
             // link lnb handle to the new profile
             newOwnerProfile.useLnb(lnbHandle);
 
@@ -1175,7 +1169,7 @@
         }
 
         // unlink lnb handles from the original owner
-        for (Long lnbHandle : inUseLnbHandles) {
+        for (Integer lnbHandle : inUseLnbHandles) {
             currentOwnerProfile.releaseLnb(lnbHandle);
         }
 
@@ -1198,7 +1192,7 @@
     }
 
     @VisibleForTesting
-    protected boolean requestLnbInternal(TunerLnbRequest request, long[] lnbHandle) {
+    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestLnb(request=" + request + ")");
         }
@@ -1206,8 +1200,8 @@
         lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         ClientProfile requestClient = getClientProfile(request.clientId);
         clientPriorityUpdateOnRequest(requestClient);
-        long grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        long inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
         boolean isRequestFromSameProcess = false;
@@ -1254,8 +1248,7 @@
     }
 
     @VisibleForTesting
-    protected boolean requestCasSessionInternal(
-            CasSessionRequest request, long[] casSessionHandle) {
+    protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestCasSession(request=" + request + ")");
         }
@@ -1308,7 +1301,7 @@
     }
 
     @VisibleForTesting
-    protected boolean requestCiCamInternal(TunerCiCamRequest request, long[] ciCamHandle) {
+    protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
         }
@@ -1331,7 +1324,6 @@
             ciCamHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
             updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
-            Slog.e(TAG, "requestCiCamInternal(ciCamHandle=" + ciCamHandle[0] + ")");
             return true;
         }
         for (int ownerId : ciCam.getOwnerClientIds()) {
@@ -1357,7 +1349,6 @@
             ciCamHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
             updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
-            Slog.e(TAG, "requestCiCamInternal(ciCamHandle=" + ciCamHandle[0] + ")");
             return true;
         }
         return false;
@@ -1441,7 +1432,7 @@
     }
 
     @VisibleForTesting
-    protected boolean requestDemuxInternal(TunerDemuxRequest request, long[] demuxHandle) {
+    protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDemux(request=" + request + ")");
         }
@@ -1464,8 +1455,8 @@
         }
 
         clientPriorityUpdateOnRequest(requestClient);
-        long grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        long inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
         boolean isRequestFromSameProcess = false;
@@ -1559,7 +1550,7 @@
 
     @VisibleForTesting
     protected boolean requestDescramblerInternal(
-            TunerDescramblerRequest request, long[] descramblerHandle) {
+            TunerDescramblerRequest request, int[] descramblerHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDescrambler(request=" + request + ")");
         }
@@ -1878,20 +1869,20 @@
         return false;
     }
 
-    private void updateFrontendClientMappingOnNewGrant(long grantingHandle, int ownerClientId) {
+    private void updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
         FrontendResource grantingFrontend = getFrontendResource(grantingHandle);
         ClientProfile ownerProfile = getClientProfile(ownerClientId);
         grantingFrontend.setOwner(ownerClientId);
         increFrontendNum(mFrontendUsedNums, grantingFrontend.getType());
         ownerProfile.useFrontend(grantingHandle);
-        for (long exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
+        for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
             getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
             ownerProfile.useFrontend(exclusiveGroupMember);
         }
         ownerProfile.setPrimaryFrontend(grantingHandle);
     }
 
-    private void updateDemuxClientMappingOnNewGrant(long grantingHandle, int ownerClientId) {
+    private void updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
         DemuxResource grantingDemux = getDemuxResource(grantingHandle);
         if (grantingDemux != null) {
             ClientProfile ownerProfile = getClientProfile(ownerClientId);
@@ -1906,7 +1897,7 @@
         ownerProfile.releaseDemux(releasingDemux.getHandle());
     }
 
-    private void updateLnbClientMappingOnNewGrant(long grantingHandle, int ownerClientId) {
+    private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
         LnbResource grantingLnb = getLnbResource(grantingHandle);
         ClientProfile ownerProfile = getClientProfile(ownerClientId);
         grantingLnb.setOwner(ownerClientId);
@@ -1990,23 +1981,23 @@
 
     @VisibleForTesting
     @Nullable
-    protected FrontendResource getFrontendResource(long frontendHandle) {
+    protected FrontendResource getFrontendResource(int frontendHandle) {
         return mFrontendResources.get(frontendHandle);
     }
 
     @VisibleForTesting
-    protected Map<Long, FrontendResource> getFrontendResources() {
+    protected Map<Integer, FrontendResource> getFrontendResources() {
         return mFrontendResources;
     }
 
     @VisibleForTesting
     @Nullable
-    protected DemuxResource getDemuxResource(long demuxHandle) {
+    protected DemuxResource getDemuxResource(int demuxHandle) {
         return mDemuxResources.get(demuxHandle);
     }
 
     @VisibleForTesting
-    protected Map<Long, DemuxResource> getDemuxResources() {
+    protected Map<Integer, DemuxResource> getDemuxResources() {
         return mDemuxResources;
     }
 
@@ -2065,8 +2056,8 @@
         }
     }
 
-    private void replaceFeResourceMap(
-            Map<Long, FrontendResource> srcMap, Map<Long, FrontendResource> dstMap) {
+    private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer,
+            FrontendResource> dstMap) {
         if (dstMap != null) {
             dstMap.clear();
             if (srcMap != null && srcMap.size() > 0) {
@@ -2119,7 +2110,7 @@
             if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
                 newFe.addExclusiveGroupMemberFeHandle(fe.getHandle());
                 newFe.addExclusiveGroupMemberFeHandles(fe.getExclusiveGroupMemberFeHandles());
-                for (long excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
+                for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
                     getFrontendResource(excGroupmemberFeHandle)
                             .addExclusiveGroupMemberFeHandle(newFe.getHandle());
                 }
@@ -2137,7 +2128,7 @@
         mDemuxResources.put(newDemux.getHandle(), newDemux);
     }
 
-    private void removeFrontendResource(long removingHandle) {
+    private void removeFrontendResource(int removingHandle) {
         FrontendResource fe = getFrontendResource(removingHandle);
         if (fe == null) {
             return;
@@ -2149,7 +2140,7 @@
             }
             clearFrontendAndClientMapping(ownerClient);
         }
-        for (long excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
+        for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
             getFrontendResource(excGroupmemberFeHandle)
                     .removeExclusiveGroupMemberFeId(fe.getHandle());
         }
@@ -2157,7 +2148,7 @@
         mFrontendResources.remove(removingHandle);
     }
 
-    private void removeDemuxResource(long removingHandle) {
+    private void removeDemuxResource(int removingHandle) {
         DemuxResource demux = getDemuxResource(removingHandle);
         if (demux == null) {
             return;
@@ -2170,12 +2161,12 @@
 
     @VisibleForTesting
     @Nullable
-    protected LnbResource getLnbResource(long lnbHandle) {
+    protected LnbResource getLnbResource(int lnbHandle) {
         return mLnbResources.get(lnbHandle);
     }
 
     @VisibleForTesting
-    protected Map<Long, LnbResource> getLnbResources() {
+    protected Map<Integer, LnbResource> getLnbResources() {
         return mLnbResources;
     }
 
@@ -2184,7 +2175,7 @@
         mLnbResources.put(newLnb.getHandle(), newLnb);
     }
 
-    private void removeLnbResource(long removingHandle) {
+    private void removeLnbResource(int removingHandle) {
         LnbResource lnb = getLnbResource(removingHandle);
         if (lnb == null) {
             return;
@@ -2288,7 +2279,7 @@
         if (profile == null) {
             return;
         }
-        for (Long feId : profile.getInUseFrontendHandles()) {
+        for (Integer feId : profile.getInUseFrontendHandles()) {
             FrontendResource fe = getFrontendResource(feId);
             int ownerClientId = fe.getOwnerClientId();
             if (ownerClientId == profile.getId()) {
@@ -2299,9 +2290,10 @@
             if (ownerClientProfile != null) {
                 ownerClientProfile.stopSharingFrontend(profile.getId());
             }
+
         }
 
-        long primaryFeId = profile.getPrimaryFrontend();
+        int primaryFeId = profile.getPrimaryFrontend();
         if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
             FrontendResource primaryFe = getFrontendResource(primaryFeId);
             if (primaryFe != null) {
@@ -2318,7 +2310,7 @@
             return;
         }
         // Clear Lnb
-        for (Long lnbHandle : profile.getInUseLnbHandles()) {
+        for (Integer lnbHandle : profile.getInUseLnbHandles()) {
             getLnbResource(lnbHandle).removeOwner();
         }
         // Clear Cas
@@ -2330,7 +2322,7 @@
             getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId());
         }
         // Clear Demux
-        for (Long demuxHandle : profile.getInUseDemuxHandles()) {
+        for (Integer demuxHandle : profile.getInUseDemuxHandles()) {
             getDemuxResource(demuxHandle).removeOwner();
         }
         // Clear Frontend
@@ -2343,31 +2335,24 @@
         return mClientProfiles.keySet().contains(clientId);
     }
 
-    /**
-     *   Generate resource handle for resourceType and resourceId
-     *   Resource Handle Allotment : 64 bits (long)
-     *   8 bits - resourceType
-     *   32 bits - resourceId
-     *   24 bits - resourceRequestCount
-     */
-    private long generateResourceHandle(
+    private int generateResourceHandle(
             @TunerResourceManager.TunerResourceType int resourceType, int resourceId) {
-        return (resourceType & RESOURCE_TYPE_MASK) << RESOURCE_TYPE_SHIFT
-                | (resourceId & RESOURCE_ID_MASK) << RESOURCE_ID_SHIFT
-                | (mResourceRequestCount++ & RESOURCE_COUNT_MASK);
+        return (resourceType & 0x000000ff) << 24
+                | (resourceId << 16)
+                | (mResourceRequestCount++ & 0xffff);
     }
 
     @VisibleForTesting
-    protected int getResourceIdFromHandle(long resourceHandle) {
+    protected int getResourceIdFromHandle(int resourceHandle) {
         if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-            return (int) resourceHandle;
+            return resourceHandle;
         }
-        return (int) ((resourceHandle >> RESOURCE_ID_SHIFT) & RESOURCE_ID_MASK);
+        return (resourceHandle & 0x00ff0000) >> 16;
     }
 
-    private boolean validateResourceHandle(int resourceType, long resourceHandle) {
+    private boolean validateResourceHandle(int resourceType, int resourceHandle) {
         if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE
-                || ((resourceHandle >> RESOURCE_TYPE_SHIFT) & RESOURCE_TYPE_MASK) != resourceType) {
+                || ((resourceHandle & 0xff000000) >> 24) != resourceType) {
             return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 4427605..cd795ae 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -233,7 +233,8 @@
         return mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
     }
 
-    private float getDisplaySizeMinAspectRatio() {
+    @VisibleForTesting
+    float getDisplaySizeMinAspectRatio() {
         final DisplayArea displayArea = mActivityRecord.getDisplayArea();
         if (displayArea == null) {
             return mActivityRecord.info.getMinAspectRatio();
@@ -270,7 +271,6 @@
         return !mAllowUserAspectRatioOverrideOptProp.isFalse();
     }
 
-    @VisibleForTesting
     int getUserMinAspectRatioOverrideCode() {
         try {
             return mActivityRecord.mAtmService.getPackageManager()
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 94ad61f..e3ff851 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -187,6 +187,8 @@
         appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController
                 .getAppCompatCameraOverrides().getFreeformCameraCompatMode();
+        appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task));
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 8477c6c..b936556 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -258,7 +258,7 @@
      * Whether we should apply the user aspect ratio override to the min aspect ratio for the
      * current app.
      */
-    private boolean shouldApplyUserMinAspectRatioOverride(@NonNull Task task) {
+    boolean shouldApplyUserMinAspectRatioOverride(@NonNull Task task) {
         if (!shouldEnableUserAspectRatioSettings(task)) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 8f1828d..c3db7dd 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -16,31 +16,28 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
-import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
-import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
 import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
 import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
-import android.app.AppCompatTaskInfo;
 import android.app.TaskInfo;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.ActivityInfo.WindowLayout;
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.Size;
 import android.view.Gravity;
 
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.utils.DesktopModeFlagsUtil;
 
 import java.util.function.Consumer;
@@ -60,7 +57,7 @@
      * Updates launch bounds for an activity with respect to its activity options, window layout,
      * android manifest and task configuration.
      */
-    static void updateInitialBounds(@NonNull Task task, @Nullable ActivityInfo.WindowLayout layout,
+    static void updateInitialBounds(@NonNull Task task, @Nullable WindowLayout layout,
             @Nullable ActivityRecord activity, @Nullable ActivityOptions options,
             @NonNull Rect outBounds, @NonNull Consumer<String> logger) {
         // Use stable frame instead of raw frame to avoid launching freeform windows on top of
@@ -98,7 +95,8 @@
      * fullscreen size, aspect ratio, orientation and resizability to calculate an area this is
      * compatible with the applications previous configuration.
      */
-    private static @NonNull Rect calculateInitialBounds(@NonNull Task task,
+    @NonNull
+    private static Rect calculateInitialBounds(@NonNull Task task,
             @NonNull ActivityRecord activity, @NonNull Rect stableBounds
     ) {
         final TaskInfo taskInfo = task.getTaskInfo();
@@ -116,18 +114,19 @@
             // applied.
             return centerInScreen(idealSize, screenBounds);
         }
-        // TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
-        float appAspectRatio = calculateAspectRatio(task, activity);
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
         final float tdaWidth = stableBounds.width();
         final float tdaHeight = stableBounds.height();
-        final int activityOrientation = activity.getOverrideOrientation();
+        final int activityOrientation = getActivityOrientation(activity, task);
         final Size initialSize = switch (taskInfo.configuration.orientation) {
             case ORIENTATION_LANDSCAPE -> {
                 // Device in landscape orientation.
                 if (appAspectRatio == 0) {
                     appAspectRatio = 1;
                 }
-                if (taskInfo.isResizeable) {
+                if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
                     if (isFixedOrientationPortrait(activityOrientation)) {
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
@@ -139,14 +138,13 @@
                 }
                 // If activity is unresizeable, regardless of orientation, calculate maximum size
                 // (within the ideal size) maintaining original aspect ratio.
-                yield maximizeSizeGivenAspectRatio(
-                        activity.getOverrideOrientation(), idealSize, appAspectRatio);
+                yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio);
             }
             case ORIENTATION_PORTRAIT -> {
                 // Device in portrait orientation.
                 final int customPortraitWidthForLandscapeApp = screenBounds.width()
                         - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
-                if (taskInfo.isResizeable) {
+                if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
                     if (isFixedOrientationLandscape(activityOrientation)) {
                         if (appAspectRatio == 0) {
                             appAspectRatio = tdaWidth / (tdaWidth - 1);
@@ -180,11 +178,38 @@
     }
 
     /**
+     * Whether the activity's aspect ratio can be changed or if it should be maintained as if it was
+     * unresizeable.
+     */
+    private static boolean canChangeAspectRatio(
+            @NonNull DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy,
+            @NonNull TaskInfo taskInfo, @NonNull Task task) {
+        return taskInfo.isResizeable
+                && !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task);
+    }
+
+    private static @ScreenOrientation int getActivityOrientation(
+            @NonNull ActivityRecord activity, @NonNull Task task) {
+        final int activityOrientation = activity.getOverrideOrientation();
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
+                && (!isFixedOrientation(activityOrientation)
+                    || activityOrientation == SCREEN_ORIENTATION_LOCKED)) {
+            // If a user aspect ratio override should be applied, treat the activity as portrait if
+            // it has not specified a fix orientation.
+            return SCREEN_ORIENTATION_PORTRAIT;
+        }
+        return activityOrientation;
+    }
+
+    /**
      * Calculates the largest size that can fit in a given area while maintaining a specific aspect
      * ratio.
      */
-    private static @NonNull Size maximizeSizeGivenAspectRatio(
-            @ActivityInfo.ScreenOrientation int orientation,
+    @NonNull
+    private static Size maximizeSizeGivenAspectRatio(
+            @ScreenOrientation int orientation,
             @NonNull Size targetArea,
             float aspectRatio
     ) {
@@ -229,68 +254,11 @@
     }
 
     /**
-     * Calculates the aspect ratio of an activity from its fullscreen bounds.
-     */
-    @VisibleForTesting
-    static float calculateAspectRatio(@NonNull Task task, @NonNull ActivityRecord activity) {
-        final TaskInfo taskInfo = task.getTaskInfo();
-        final float fullscreenWidth = task.getDisplayArea().getBounds().width();
-        final float fullscreenHeight = task.getDisplayArea().getBounds().height();
-        final float maxAspectRatio = activity.getMaxAspectRatio();
-        final float minAspectRatio = activity.getMinAspectRatio();
-        float desiredAspectRatio = 0;
-        if (taskInfo.isRunning) {
-            final AppCompatTaskInfo appCompatTaskInfo =  taskInfo.appCompatTaskInfo;
-            final int appLetterboxWidth =
-                    taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth;
-            final int appLetterboxHeight =
-                    taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight;
-            if (appCompatTaskInfo.isTopActivityLetterboxed()) {
-                desiredAspectRatio = (float) Math.max(appLetterboxWidth, appLetterboxHeight)
-                        / Math.min(appLetterboxWidth, appLetterboxHeight);
-            } else {
-                desiredAspectRatio = Math.max(fullscreenHeight, fullscreenWidth)
-                        / Math.min(fullscreenHeight, fullscreenWidth);
-            }
-        } else {
-            final float letterboxAspectRatioOverride =
-                    getFixedOrientationLetterboxAspectRatio(activity, task);
-            if (!task.mDisplayContent.getIgnoreOrientationRequest()) {
-                desiredAspectRatio = DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-            } else if (letterboxAspectRatioOverride
-                    > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
-                desiredAspectRatio = letterboxAspectRatioOverride;
-            }
-        }
-        // If the activity matches display orientation, the display aspect ratio should be used
-        if (activityMatchesDisplayOrientation(
-                taskInfo.configuration.orientation,
-                activity.getOverrideOrientation())) {
-            desiredAspectRatio = Math.max(fullscreenWidth, fullscreenHeight)
-                    / Math.min(fullscreenWidth, fullscreenHeight);
-        }
-        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
-            desiredAspectRatio = maxAspectRatio;
-        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
-            desiredAspectRatio = minAspectRatio;
-        }
-        return desiredAspectRatio;
-    }
-
-    private static boolean activityMatchesDisplayOrientation(
-            @Configuration.Orientation int deviceOrientation,
-            @ActivityInfo.ScreenOrientation int activityOrientation) {
-        if (deviceOrientation == ORIENTATION_PORTRAIT) {
-            return isFixedOrientationPortrait(activityOrientation);
-        }
-        return isFixedOrientationLandscape(activityOrientation);
-    }
-
-    /**
      * Calculates the desired initial bounds for applications in desktop windowing. This is done as
      * a scale of the screen bounds.
      */
-    private static @NonNull Size calculateIdealSize(@NonNull Rect screenBounds, float scale) {
+    @NonNull
+    private static Size calculateIdealSize(@NonNull Rect screenBounds, float scale) {
         final int width = (int) (screenBounds.width() * scale);
         final int height = (int) (screenBounds.height() * scale);
         return new Size(width, height);
@@ -299,7 +267,8 @@
     /**
      * Adjusts bounds to be positioned in the middle of the screen.
      */
-    private static @NonNull Rect centerInScreen(@NonNull Size desiredSize,
+    @NonNull
+    private static Rect centerInScreen(@NonNull Size desiredSize,
             @NonNull Rect screenBounds) {
         // TODO(b/325240051): Position apps with bottom heavy offset
         final int heightOffset = (screenBounds.height() - desiredSize.getHeight()) / 2;
@@ -309,57 +278,4 @@
         resultBounds.offset(screenBounds.left + widthOffset, screenBounds.top + heightOffset);
         return resultBounds;
     }
-
-    private static float getFixedOrientationLetterboxAspectRatio(@NonNull ActivityRecord activity,
-            @NonNull Task task) {
-        return activity.shouldCreateCompatDisplayInsets()
-                ? getDefaultMinAspectRatioForUnresizableApps(activity, task)
-                : activity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                        .getDefaultMinAspectRatio();
-    }
-
-    private static float getDefaultMinAspectRatioForUnresizableApps(
-            @NonNull ActivityRecord activity,
-            @NonNull Task task) {
-        final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
-                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
-        if (appCompatAspectRatioOverrides.isSplitScreenAspectRatioForUnresizableAppsEnabled()) {
-            // Default letterbox aspect ratio for unresizable apps.
-            return getSplitScreenAspectRatio(activity, task);
-        }
-
-        if (appCompatAspectRatioOverrides.getDefaultMinAspectRatioForUnresizableAppsFromConfig()
-                > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
-            return appCompatAspectRatioOverrides
-                    .getDefaultMinAspectRatioForUnresizableAppsFromConfig();
-        }
-
-        return appCompatAspectRatioOverrides.getDefaultMinAspectRatio();
-    }
-
-    /**
-     * Calculates the aspect ratio of the available display area when an app enters split-screen on
-     * a given device, taking into account any dividers and insets.
-     */
-    private static float getSplitScreenAspectRatio(@NonNull ActivityRecord activity,
-            @NonNull Task task) {
-        final int dividerWindowWidth =
-                activity.mWmService.mContext.getResources().getDimensionPixelSize(
-                        R.dimen.docked_stack_divider_thickness);
-        final int dividerInsets =
-                activity.mWmService.mContext.getResources().getDimensionPixelSize(
-                        R.dimen.docked_stack_divider_insets);
-        final int dividerSize = dividerWindowWidth - dividerInsets * 2;
-        final Rect bounds = new Rect(0, 0,
-                task.mDisplayContent.getDisplayInfo().appWidth,
-                task.mDisplayContent.getDisplayInfo().appHeight);
-        if (bounds.width() >= bounds.height()) {
-            bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
-            bounds.right = bounds.centerX();
-        } else {
-            bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
-            bounds.bottom = bounds.centerY();
-        }
-        return computeAspectRatio(bounds);
-    }
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4d6a90c..07d39d9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -291,6 +291,7 @@
     void setTouchpadNaturalScrollingEnabled(bool enabled);
     void setTouchpadTapToClickEnabled(bool enabled);
     void setTouchpadTapDraggingEnabled(bool enabled);
+    void setShouldNotifyTouchpadHardwareState(bool enabled);
     void setTouchpadRightClickZoneEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
@@ -440,6 +441,9 @@
         // True to enable tap dragging on touchpads.
         bool touchpadTapDraggingEnabled{false};
 
+        // True if hardware state update notifications should be sent to the policy.
+        bool shouldNotifyTouchpadHardwareState{false};
+
         // True to enable a zone on the right-hand side of touchpads where clicks will be turned
         // into context (a.k.a. "right") clicks.
         bool touchpadRightClickZoneEnabled{false};
@@ -698,6 +702,7 @@
         outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
         outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
         outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
+        outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
         outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1260,6 +1265,22 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setShouldNotifyTouchpadHardwareState(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.shouldNotifyTouchpadHardwareState == enabled) {
+            return;
+        }
+
+        ALOGI("Should touchpad hardware state be notified: %s.", toString(enabled));
+        mLocked.shouldNotifyTouchpadHardwareState = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
     { // acquire lock
         std::scoped_lock _l(mLock);
@@ -2144,6 +2165,13 @@
     im->setTouchpadTapDraggingEnabled(enabled);
 }
 
+static void nativeSetShouldNotifyTouchpadHardwareState(JNIEnv* env, jobject nativeImplObj,
+                                                       jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setShouldNotifyTouchpadHardwareState(enabled);
+}
+
 static void nativeSetTouchpadRightClickZoneEnabled(JNIEnv* env, jobject nativeImplObj,
                                                    jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2762,6 +2790,8 @@
          (void*)nativeSetTouchpadNaturalScrollingEnabled},
         {"setTouchpadTapToClickEnabled", "(Z)V", (void*)nativeSetTouchpadTapToClickEnabled},
         {"setTouchpadTapDraggingEnabled", "(Z)V", (void*)nativeSetTouchpadTapDraggingEnabled},
+        {"setShouldNotifyTouchpadHardwareState", "(Z)V",
+         (void*)nativeSetShouldNotifyTouchpadHardwareState},
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9ed645b..d5013517 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12215,34 +12215,30 @@
      * permittedList or are a system app.
      */
     private boolean checkPackagesInPermittedListOrSystem(List<String> enabledPackages,
-            List<String> permittedList, int userIdToCheck) {
+            List<String> permittedList, int userId) {
         long id = mInjector.binderClearCallingIdentity();
         try {
-            // If we have an enabled packages list for a managed profile the packages
-            // we should check are installed for the parent user.
-            UserInfo user = getUserInfo(userIdToCheck);
-            if (user.isManagedProfile()) {
-                userIdToCheck = user.profileGroupId;
-            }
-
             for (String enabledPackage : enabledPackages) {
-                boolean systemService = false;
+                if (permittedList.contains(enabledPackage)) {
+                    continue;
+                }
                 try {
                     ApplicationInfo applicationInfo = mIPackageManager.getApplicationInfo(
-                            enabledPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            userIdToCheck);
+                            enabledPackage, PackageManager.MATCH_ANY_USER, userId);
 
                     if (applicationInfo == null) {
+                        Slogf.wtf(LOG_TAG, "Can't find ApplicationInfo for %s", enabledPackage);
                         return false;
                     }
 
-                    systemService = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+                    if (!applicationInfo.isSystemApp()) {
+                        Slogf.w(LOG_TAG,
+                                "Enabled package neither permitted nor system: %s", enabledPackage);
+                        return false;
+                    }
                 } catch (RemoteException e) {
                     Slogf.i(LOG_TAG, "Can't talk to package managed", e);
                 }
-                if (!systemService && !permittedList.contains(enabledPackage)) {
-                    return false;
-                }
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
index 6393e11..1db9e8d 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
@@ -1,7 +1,7 @@
 aconfig_declarations {
     name: "device_state_flags",
     package: "com.android.server.policy.feature.flags",
-    container: "system",
+    container: "system_ext",
     srcs: [
         "device_state_flags.aconfig",
     ],
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 21e33dd..f827b55 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.server.policy.feature.flags"
-container: "system"
+container: "system_ext"
 
 flag {
     name: "enable_dual_display_blocking"
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e72d9e7..b7483d6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -70,14 +70,14 @@
 
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.longThat;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -1733,12 +1733,20 @@
         pi.applicationInfo.flags = flags;
         doReturn(pi).when(getServices().ipackageManager).getPackageInfo(
                 eq(packageName),
-                anyLong(),
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) == 0),
+                eq(userId));
+        doReturn(pi).when(getServices().ipackageManager).getPackageInfo(
+                eq(packageName),
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) != 0),
+                anyInt());
+        doReturn(pi.applicationInfo).when(getServices().ipackageManager).getApplicationInfo(
+                eq(packageName),
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) == 0),
                 eq(userId));
         doReturn(pi.applicationInfo).when(getServices().ipackageManager).getApplicationInfo(
                 eq(packageName),
-                anyLong(),
-                eq(userId));
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) != 0),
+                anyInt());
         doReturn(true).when(getServices().ipackageManager).isPackageAvailable(packageName, userId);
         // Setup application UID with the PackageManager
         getServices().addTestPackageUid(packageName, uid);
@@ -1757,7 +1765,7 @@
         mServiceContext.packageName = mRealTestContext.getPackageName();
         mServiceContext.applicationInfo = new ApplicationInfo();
         mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         StringParceledListSlice oneCert = asSlice(new String[] {"1"});
         StringParceledListSlice fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
@@ -4551,7 +4559,7 @@
 
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
         // feature is disabled because there are non-affiliated secondary users.
@@ -4597,12 +4605,12 @@
         setupDeviceOwner();
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
         // feature is disabled because there are non-affiliated secondary users.
         getServices().removeUser(CALLER_USER_HANDLE);
-        when(getServices().iipConnectivityMetrics.addNetdEventCallback(anyInt(), anyObject()))
+        when(getServices().iipConnectivityMetrics.addNetdEventCallback(anyInt(), any()))
                 .thenReturn(true);
 
         // No logs were retrieved so far.
@@ -4667,7 +4675,7 @@
         mContext.packageName = admin1.getPackageName();
         addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S);
         when(getServices().iipConnectivityMetrics
-                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+                .addNetdEventCallback(anyInt(), any())).thenReturn(true);
 
         // Check no logs have been retrieved so far.
         assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
@@ -4699,7 +4707,7 @@
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
         when(getServices().iipConnectivityMetrics
-                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+                .addNetdEventCallback(anyInt(), any())).thenReturn(true);
 
         // Check no logs have been retrieved so far.
         assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
@@ -6296,13 +6304,13 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, MANAGED_PROFILE_USER_ID)).isTrue();
+                nonSystemPackage, MANAGED_PROFILE_USER_ID)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
+                systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
+                nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, UserHandle.USER_SYSTEM)).isTrue();
+                systemListener, UserHandle.USER_SYSTEM)).isTrue();
 
         // Setting an empty allowlist - only system listeners allowed in managed profile, but
         // all allowed in primary profile
@@ -6313,13 +6321,13 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, MANAGED_PROFILE_USER_ID)).isFalse();
+                nonSystemPackage, MANAGED_PROFILE_USER_ID)).isFalse();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
+                systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
+                nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, UserHandle.USER_SYSTEM)).isTrue();
+                systemListener, UserHandle.USER_SYSTEM)).isTrue();
     }
 
     @Test
@@ -6455,7 +6463,7 @@
         if (admin1.getPackageName().equals(callerContext.getPackageName())) {
             admin1Context = callerContext;
         }
-        when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(admin1Context.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // caller: device admin or delegated certificate installer
         callerContext.applicationInfo = new ApplicationInfo();
@@ -6528,7 +6536,7 @@
         if (admin1.getPackageName().equals(callerContext.getPackageName())) {
             admin1Context = callerContext;
         }
-        when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(admin1Context.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // caller: device admin or delegated certificate installer
         callerContext.applicationInfo = new ApplicationInfo();
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 8e36335..963b27e 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
@@ -119,7 +119,8 @@
                 tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
-        Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
+        Map<Integer, FrontendResource> resources =
+                mTunerResourceManagerService.getFrontendResources();
         for (int id = 0; id < infos.length; id++) {
             assertThat(resources.get(infos[id].handle)
                     .getExclusiveGroupMemberFeHandles().size()).isEqualTo(0);
@@ -146,14 +147,15 @@
                 tunerFrontendInfo(3 /*handle*/, FrontendSettings.TYPE_ATSC, 1 /*exclusiveGroupId*/);
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
-        Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
+        Map<Integer, FrontendResource> resources =
+                mTunerResourceManagerService.getFrontendResources();
         assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE)
                 .containsExactlyElementsIn(Arrays.asList(infos));
 
-        assertThat(resources.get(0L).getExclusiveGroupMemberFeHandles()).isEmpty();
-        assertThat(resources.get(1L).getExclusiveGroupMemberFeHandles()).containsExactly(2L, 3L);
-        assertThat(resources.get(2L).getExclusiveGroupMemberFeHandles()).containsExactly(1L, 3L);
-        assertThat(resources.get(3L).getExclusiveGroupMemberFeHandles()).containsExactly(1L, 2L);
+        assertThat(resources.get(0).getExclusiveGroupMemberFeHandles()).isEmpty();
+        assertThat(resources.get(1).getExclusiveGroupMemberFeHandles()).containsExactly(2, 3);
+        assertThat(resources.get(2).getExclusiveGroupMemberFeHandles()).containsExactly(1, 3);
+        assertThat(resources.get(3).getExclusiveGroupMemberFeHandles()).containsExactly(1, 2);
     }
 
     @Test
@@ -166,11 +168,11 @@
                 tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/);
 
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
-        Map<Long, FrontendResource> resources0 =
+        Map<Integer, FrontendResource> resources0 =
                 mTunerResourceManagerService.getFrontendResources();
 
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
-        Map<Long, FrontendResource> resources1 =
+        Map<Integer, FrontendResource> resources1 =
                 mTunerResourceManagerService.getFrontendResources();
 
         assertThat(resources0).isEqualTo(resources1);
@@ -193,7 +195,8 @@
                 tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
         mTunerResourceManagerService.setFrontendInfoListInternal(infos1);
 
-        Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
+        Map<Integer, FrontendResource> resources =
+                mTunerResourceManagerService.getFrontendResources();
         for (int id = 0; id < infos1.length; id++) {
             assertThat(resources.get(infos1[id].handle)
                     .getExclusiveGroupMemberFeHandles().size()).isEqualTo(0);
@@ -219,7 +222,8 @@
                 tunerFrontendInfo(1 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
         mTunerResourceManagerService.setFrontendInfoListInternal(infos1);
 
-        Map<Long, FrontendResource> resources = mTunerResourceManagerService.getFrontendResources();
+        Map<Integer, FrontendResource> resources =
+                mTunerResourceManagerService.getFrontendResources();
         for (int id = 0; id < infos1.length; id++) {
             assertThat(resources.get(infos1[id].handle)
                     .getExclusiveGroupMemberFeHandles().size()).isEqualTo(0);
@@ -236,7 +240,7 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos0);
         TunerFrontendRequest request =
                 tunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
@@ -259,7 +263,7 @@
 
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
@@ -292,7 +296,7 @@
 
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(0);
@@ -329,7 +333,7 @@
                 1 /*exclusiveGroupId*/);
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService
@@ -381,7 +385,7 @@
 
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
 
@@ -431,13 +435,13 @@
 
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
+                .getInUseFrontendHandles()).isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle, infos[1].handle)));
 
         request =
                 tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
@@ -476,7 +480,7 @@
 
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
@@ -521,7 +525,7 @@
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
         CasSessionRequest request = casSessionRequest(clientId0[0], 1 /*casSystemId*/);
-        long[] casSessionHandle = new long[1];
+        int[] casSessionHandle = new int[1];
         // Request for 2 cas sessions.
         assertThat(mTunerResourceManagerService
                 .requestCasSessionInternal(request, casSessionHandle)).isTrue();
@@ -577,7 +581,7 @@
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
         TunerCiCamRequest request = tunerCiCamRequest(clientId0[0], 1 /*ciCamId*/);
-        long[] ciCamHandle = new long[1];
+        int[] ciCamHandle = new int[1];
         // Request for 2 ciCam sessions.
         assertThat(mTunerResourceManagerService
                 .requestCiCamInternal(request, ciCamHandle)).isTrue();
@@ -621,7 +625,7 @@
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
         CasSessionRequest request = casSessionRequest(clientId[0], 1 /*casSystemId*/);
-        long[] casSessionHandle = new long[1];
+        int[] casSessionHandle = new int[1];
         // Request for 1 cas sessions.
         assertThat(mTunerResourceManagerService
                 .requestCasSessionInternal(request, casSessionHandle)).isTrue();
@@ -658,7 +662,7 @@
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
         TunerCiCamRequest request = tunerCiCamRequest(clientId[0], 1 /*ciCamId*/);
-        long[] ciCamHandle = new long[1];
+        int[] ciCamHandle = new int[1];
         // Request for 1 ciCam sessions.
         assertThat(mTunerResourceManagerService
                 .requestCiCamInternal(request, ciCamHandle)).isTrue();
@@ -704,17 +708,17 @@
                 clientId1[0], clientPriorities[1], 0/*niceValue*/);
 
         // Init lnb resources.
-        long[] lnbHandles = {1};
+        int[] lnbHandles = {1};
         mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
 
         TunerLnbRequest request = new TunerLnbRequest();
         request.clientId = clientId0[0];
-        long[] lnbHandle = new long[1];
+        int[] lnbHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]).getInUseLnbHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(lnbHandles[0])));
 
         request = new TunerLnbRequest();
         request.clientId = clientId1[0];
@@ -743,12 +747,12 @@
         assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
 
         // Init lnb resources.
-        long[] lnbHandles = {0};
+        int[] lnbHandles = {0};
         mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
 
         TunerLnbRequest request = new TunerLnbRequest();
         request.clientId = clientId[0];
-        long[] lnbHandle = new long[1];
+        int[] lnbHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
@@ -782,7 +786,7 @@
 
         TunerFrontendRequest request =
                 tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
@@ -819,12 +823,12 @@
         infos[2] = tunerDemuxInfo(2 /* handle */, Filter.TYPE_TS);
         mTunerResourceManagerService.setDemuxInfoListInternal(infos);
 
-        long[] demuxHandle0 = new long[1];
+        int[] demuxHandle0 = new int[1];
         // first with undefined type (should be the first one with least # of caps)
         TunerDemuxRequest request = tunerDemuxRequest(clientId0[0], Filter.TYPE_UNDEFINED);
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
                 .isTrue();
-        assertThat(demuxHandle0[0]).isEqualTo(1L);
+        assertThat(demuxHandle0[0]).isEqualTo(1);
         DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
         mTunerResourceManagerService.releaseDemuxInternal(dr);
 
@@ -833,20 +837,20 @@
         demuxHandle0[0] = -1;
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
                 .isFalse();
-        assertThat(demuxHandle0[0]).isEqualTo(-1L);
+        assertThat(demuxHandle0[0]).isEqualTo(-1);
 
         // now with TS (should be the one with least # of caps that supports TS)
         request.desiredFilterTypes = Filter.TYPE_TS;
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
                 .isTrue();
-        assertThat(demuxHandle0[0]).isEqualTo(2L);
+        assertThat(demuxHandle0[0]).isEqualTo(2);
 
         // request for another TS
         int[] clientId1 = new int[1];
         mTunerResourceManagerService.registerClientProfileInternal(
                 profile1, null /*listener*/, clientId1);
         assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        long[] demuxHandle1 = new long[1];
+        int[] demuxHandle1 = new int[1];
         TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_TS);
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1))
                 .isTrue();
@@ -895,14 +899,14 @@
 
         // let clientId0(prio:100) request for IP - should succeed
         TunerDemuxRequest request0 = tunerDemuxRequest(clientId0[0], Filter.TYPE_IP);
-        long[] demuxHandle0 = new long[1];
+        int[] demuxHandle0 = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request0, demuxHandle0)).isTrue();
         assertThat(demuxHandle0[0]).isEqualTo(0);
 
         // let clientId1(prio:50) request for IP - should fail
         TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_IP);
-        long[] demuxHandle1 = new long[1];
+        int[] demuxHandle1 = new int[1];
         demuxHandle1[0] = -1;
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request1, demuxHandle1)).isFalse();
@@ -922,7 +926,7 @@
 
         // let clientId2(prio:50) request for TS - should succeed
         TunerDemuxRequest request2 = tunerDemuxRequest(clientId2[0], Filter.TYPE_TS);
-        long[] demuxHandle2 = new long[1];
+        int[] demuxHandle2 = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request2, demuxHandle2)).isTrue();
         assertThat(demuxHandle2[0]).isEqualTo(0);
@@ -947,7 +951,7 @@
                 profile, null /*listener*/, clientId);
         assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
 
-        long[] desHandle = new long[1];
+        int[] desHandle = new int[1];
         TunerDescramblerRequest request = new TunerDescramblerRequest();
         request.clientId = clientId[0];
         assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
@@ -1057,7 +1061,7 @@
                 1 /*exclusiveGroupId*/);
 
         /**** Init Lnb Resources ****/
-        long[] lnbHandles = {1};
+        int[] lnbHandles = {1};
         mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
 
         // Update frontend list in TRM
@@ -1066,7 +1070,7 @@
         /**** Request Frontend ****/
 
         // Predefined frontend request and array to save returned frontend handle
-        long[] frontendHandle = new long[1];
+        int[] frontendHandle = new int[1];
         TunerFrontendRequest request = tunerFrontendRequest(
                 ownerClientId0[0] /*clientId*/,
                 FrontendSettings.TYPE_DVBT);
@@ -1076,9 +1080,12 @@
                 .requestFrontendInternal(request, frontendHandle))
                 .isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
-        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
 
         /**** Share Frontend ****/
 
@@ -1106,15 +1113,24 @@
                         shareClientId0[0],
                         shareClientId1[0])));
         // Verify in use frontend list in all the primary owner and share owner clients
-        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
-        assertThat(mTunerResourceManagerService.getClientProfile(shareClientId0[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
-        assertThat(mTunerResourceManagerService.getClientProfile(shareClientId1[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId1[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
 
         /**** Remove Frontend Share Owner ****/
 
@@ -1126,12 +1142,18 @@
                 .getShareFeClientIds())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         shareClientId0[0])));
-        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
-        assertThat(mTunerResourceManagerService.getClientProfile(shareClientId0[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
 
         /**** Request Shared Frontend with Higher Priority Client ****/
 
@@ -1151,9 +1173,12 @@
                 .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
                 .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
-        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId1[0])
-                           .getInUseFrontendHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle, infos[1].handle)));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].handle,
+                        infos[1].handle)));
         assertThat(mTunerResourceManagerService
                 .getClientProfile(ownerClientId0[0])
                 .getInUseFrontendHandles()
@@ -1210,7 +1235,7 @@
         // Predefined Lnb request and handle array
         TunerLnbRequest requestLnb = new TunerLnbRequest();
         requestLnb.clientId = shareClientId0[0];
-        long[] lnbHandle = new long[1];
+        int[] lnbHandle = new int[1];
 
         // Request for an Lnb
         assertThat(mTunerResourceManagerService
@@ -1239,13 +1264,15 @@
                 .getInUseFrontendHandles()
                 .isEmpty())
                 .isTrue();
-        assertThat(mTunerResourceManagerService.getClientProfile(shareClientId0[0])
-                           .getInUseLnbHandles())
-                .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseLnbHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        lnbHandles[0])));
     }
 
     private TunerFrontendInfo tunerFrontendInfo(
-            long handle, int frontendType, int exclusiveGroupId) {
+            int handle, int frontendType, int exclusiveGroupId) {
         TunerFrontendInfo info = new TunerFrontendInfo();
         info.handle = handle;
         info.type = frontendType;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 07e95d8..6d508ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,11 +21,19 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
@@ -34,7 +42,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
-import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -44,6 +51,8 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.ActivityOptions;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
@@ -55,8 +64,12 @@
 
 import com.android.window.flags.Flags;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 /**
@@ -74,6 +87,9 @@
     private static final Rect PORTRAIT_DISPLAY_BOUNDS = new Rect(0, 0, 1600, 2560);
     private static final float LETTERBOX_ASPECT_RATIO = 1.3f;
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     @Before
     public void setUp() throws Exception {
         mActivity = new ActivityBuilder(mAtm).build();
@@ -199,14 +215,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -219,14 +238,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -239,7 +261,9 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
 
         spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
@@ -251,7 +275,8 @@
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -264,7 +289,9 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
 
         spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
@@ -276,7 +303,8 @@
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -285,19 +313,466 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth =
                 (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
+    public void testSmallAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testMediumAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testLargeAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testSplitScreenAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / activity.mAppCompatController
+                        .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
+    public void testSmallAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testMediumAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testLargeAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testSplitScreenAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio32Override_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue3_2 = 3 / 2f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_3_2,
+                userAspectRatioOverrideValue3_2);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue3_2);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio43Override_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue4_3 = 4 / 3f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_4_3,
+                userAspectRatioOverrideValue4_3);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue4_3);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio169Override_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue16_9 = 16 / 9f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_16_9,
+                userAspectRatioOverrideValue16_9);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue16_9);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioSplitScreenOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                userAspectRatioOverrideValueSplitScreen);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValueSplitScreen);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioDisplaySizeOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+                userAspectRatioOverrideValueDisplaySize);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValueDisplaySize);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio32Override_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue3_2 = 3 / 2f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_3_2,
+                userAspectRatioOverrideValue3_2);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValue3_2);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio43Override_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue4_3 = 4 / 3f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_4_3,
+                userAspectRatioOverrideValue4_3);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValue4_3);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio169Override_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue16_9 = 16 / 9f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_16_9,
+                userAspectRatioOverrideValue16_9);
+
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue16_9);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioSplitScreenOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                userAspectRatioOverrideValueSplitScreen);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValueSplitScreen);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioDisplaySizeOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+                userAspectRatioOverrideValueDisplaySize);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValueDisplaySize);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -310,14 +785,18 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
+
 
         final int desiredWidth =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -326,18 +805,23 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testUnResizablePortraitBounds_landscapeDevice_unResizable_portraitOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredWidth = (int) (desiredHeight / LETTERBOX_ASPECT_RATIO);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -350,14 +834,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -370,14 +857,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -390,11 +880,13 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
-                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .isUserFullscreenOverrideEnabled();
 
         final int desiredWidth =
@@ -402,7 +894,8 @@
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -415,11 +908,13 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
-                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .isSystemOverrideToFullscreenEnabled();
 
         final int desiredWidth =
@@ -427,7 +922,8 @@
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -436,19 +932,24 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
         final int desiredHeight = (int)
                 ((PORTRAIT_DISPLAY_BOUNDS.width() / LETTERBOX_ASPECT_RATIO) + 0.5f);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -461,14 +962,18 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
+
 
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -477,18 +982,23 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testUnResizableLandscapeBounds_portraitDevice_unResizable_landscapeOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
         final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -755,24 +1265,64 @@
         assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
     }
 
-    private Task createTask(DisplayContent display, int orientation, Boolean isResizeable) {
+    private Task createTask(DisplayContent display, Boolean isResizeable) {
         final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE
                 : RESIZE_MODE_UNRESIZEABLE;
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
         task.setResizeMode(resizeMode);
-        mActivity = new ActivityBuilder(task.mAtmService)
+        return task;
+    }
+
+    private ActivityRecord createActivity(DisplayContent display, int orientation, Task task,
+            boolean ignoreOrientationRequest) {
+        final ActivityRecord activity = new ActivityBuilder(task.mAtmService)
                 .setTask(task)
+                .setComponent(ComponentName.createRelative(task.mAtmService.mContext,
+                        DesktopModeLaunchParamsModifierTests.class.getName()))
+                .setUid(android.os.Process.myUid())
                 .setScreenOrientation(orientation)
                 .setOnTop(true).build();
+        activity.onDisplayChanged(display);
+        activity.setOccludesParent(true);
+        activity.setVisible(true);
+        activity.setVisibleRequested(true);
+        activity.mDisplayContent.setIgnoreOrientationRequest(ignoreOrientationRequest);
 
-        mActivity.onDisplayChanged(display);
-        mActivity.setOccludesParent(true);
-        mActivity.setVisible(true);
-        mActivity.setVisibleRequested(true);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(/* ignoreOrientationRequest */ true);
+        return activity;
+    }
 
-        return task;
+    private void setDesiredAspectRatio(ActivityRecord activity, float aspectRatio) {
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        spyOn(desktopAppCompatAspectRatioPolicy);
+        doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy)
+                .getDesiredAspectRatio(any());
+    }
+
+    private void applyUserMinAspectRatioOverride(ActivityRecord activity, int overrideCode,
+            float overrideValue) {
+        // Set desired aspect ratio to be below minimum so override can take effect.
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        spyOn(desktopAppCompatAspectRatioPolicy);
+        doReturn(1f).when(desktopAppCompatAspectRatioPolicy)
+                .getDesiredAspectRatio(any());
+
+        // Enable user aspect ratio settings
+        final AppCompatConfiguration appCompatConfiguration =
+                activity.mWmService.mAppCompatConfiguration;
+        spyOn(appCompatConfiguration);
+        doReturn(true).when(appCompatConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+
+        // Simulate user min aspect ratio override being set.
+        final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
+                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+        spyOn(appCompatAspectRatioOverrides);
+        doReturn(overrideValue).when(appCompatAspectRatioOverrides).getUserMinAspectRatio();
+        doReturn(overrideCode).when(appCompatAspectRatioOverrides)
+                .getUserMinAspectRatioOverrideCode();
     }
 
     private TestDisplayContent createDisplayContent(int orientation, Rect displayBounds) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 65e3baa..1e1055b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2394,7 +2394,13 @@
     private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
             float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
             boolean enabled) {
-        final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+        final ActivityRecord activity = getActivityBuilderWithoutTask().build();
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        spyOn(desktopAppCompatAspectRatioPolicy);
+        doReturn(enabled).when(desktopAppCompatAspectRatioPolicy)
+                .hasMinAspectRatioOverride(any());
+        mTask.addChild(activity);
         activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         spyOn(activity.mWmService.mAppCompatConfiguration);
         doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
@@ -4944,8 +4950,11 @@
     }
 
     private ActivityBuilder getActivityBuilderOnSameTask() {
+        return getActivityBuilderWithoutTask().setTask(mTask);
+    }
+
+    private ActivityBuilder getActivityBuilderWithoutTask() {
         return new ActivityBuilder(mAtm)
-                .setTask(mTask)
                 .setComponent(ComponentName.createRelative(mContext,
                         SizeCompatTests.class.getName()))
                 .setUid(android.os.Process.myUid());
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 3c72498..8829f74 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -151,6 +151,7 @@
         verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
         verify(native).setTouchpadTapToClickEnabled(anyBoolean())
         verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
+        verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
         verify(native).setMotionClassifierEnabled(anyBoolean())