Merge "Removing unnecessary iconFactory class when generating badged icon" into main
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index c4375d3..29b24b7 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.testing;
 
-import static com.android.launcher3.testing.shared.TestProtocol.WORKSPACE_LONG_PRESS;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -137,12 +135,10 @@
             }
 
             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
-                testLogD(WORKSPACE_LONG_PRESS, "enablingDebugTracing");
                 TestProtocol.sDebugTracing = true;
                 return response;
 
             case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
-                testLogD(WORKSPACE_LONG_PRESS, "disablingDebugTracing");
                 TestProtocol.sDebugTracing = false;
                 return response;
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 0ef4541..3514447 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -380,10 +381,12 @@
         int navButtonSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.taskbar_nav_buttons_size);
         boolean isRtl = Utilities.isRtl(mContext.getResources());
-        mPropertyHolders.add(new StatePropertyHolder(
-                mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
-                        || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
-                VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
+        if (!isPhoneMode(mContext.getDeviceProfile())) {
+            mPropertyHolders.add(new StatePropertyHolder(
+                    mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
+                            || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
+                    VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
+        }
 
         // home button
         mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 165ed80..5b0c8c3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -480,7 +480,11 @@
 
     @Override
     public void onDestroy() {
-        mAppTransitionManager.onActivityDestroyed();
+        if (mAppTransitionManager != null) {
+            mAppTransitionManager.onActivityDestroyed();
+        }
+        mAppTransitionManager = null;
+
         if (mUnfoldTransitionProgressProvider != null) {
             SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
             mUnfoldTransitionProgressProvider.destroy();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8313e09..d7ff59e 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -855,7 +855,9 @@
         if (windowInsets.isVisible(WindowInsets.Type.ime())) {
             return result;
         }
-        buildAnimationController();
+        if (mGestureState.getEndTarget() == null) {
+            buildAnimationController();
+        }
         // Reapply the current shift to ensure it takes new insets into account, e.g. when long
         // pressing to stash taskbar without moving the finger.
         onCurrentShiftUpdated();
@@ -1223,12 +1225,12 @@
                         : null;
         ActiveGestureLog.INSTANCE.addLog(
                 new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
-                        .append(Float.toString(dpiFromPx(velocityPxPerMs.x)))
+                        .append(dpiFromPx(velocityPxPerMs.x))
                         .append("dp/ms, y=")
-                        .append(Float.toString(dpiFromPx(velocityPxPerMs.y)))
+                        .append(dpiFromPx(velocityPxPerMs.y))
                         .append("dp/ms), angle=")
-                        .append(Double.toString(Math.toDegrees(Math.atan2(
-                                -velocityPxPerMs.y, velocityPxPerMs.x)))), gestureEvent);
+                        .append(Math.toDegrees(Math.atan2(
+                                -velocityPxPerMs.y, velocityPxPerMs.x))), gestureEvent);
 
         if (mGestureState.isHandlingAtomicEvent()) {
             // Button mode, this is only used to go to recents.
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index ac6c274..2aa9240 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -32,6 +32,7 @@
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
 
 import android.content.Context;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.StatsEvent;
 import android.view.View;
@@ -227,6 +228,7 @@
         private Optional<Integer> mCardinality = Optional.empty();
         private int mInputType = SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__UNKNOWN;
         private Optional<Integer> mFeatures = Optional.empty();
+        private Optional<String> mPackageName = Optional.empty();
 
         StatsCompatLogger(Context context, ActivityContext activityContext) {
             mContext = context;
@@ -332,6 +334,12 @@
         }
 
         @Override
+        public StatsLogger withPackageName(@Nullable String packageName) {
+            mPackageName = Optional.ofNullable(packageName);
+            return this;
+        }
+
+        @Override
         public void log(EventEnum event) {
             if (!Utilities.ATLEAST_R) {
                 return;
@@ -431,6 +439,7 @@
             int srcState = mSrcState;
             int dstState = mDstState;
             int inputType = mInputType;
+            String packageName = mPackageName.orElseGet(() -> getPackageName(atomInfo));
             if (IS_VERBOSE) {
                 String name = (event instanceof Enum) ? ((Enum) event).name() :
                         event.getId() + "";
@@ -448,6 +457,9 @@
                 if (atomInfo.hasContainerInfo()) {
                     logStringBuilder.append("\n").append(atomInfo);
                 }
+                if (!TextUtils.isEmpty(packageName)) {
+                    logStringBuilder.append(String.format("\nPackage name: %s", packageName));
+                }
                 Log.d(TAG, logStringBuilder.toString());
             }
 
@@ -472,7 +484,7 @@
                     atomInfo.getItemCase().getNumber() /* target_id */,
                     instanceId.getId() /* instance_id TODO */,
                     0 /* uid TODO */,
-                    getPackageName(atomInfo) /* package_name */,
+                    packageName /* package_name */,
                     getComponentName(atomInfo) /* component_name */,
                     getGridX(atomInfo, false) /* grid_x */,
                     getGridY(atomInfo, false) /* grid_y */,
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 6f4b6cb..278ca56 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -18,6 +18,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.util.Preconditions;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -43,14 +45,6 @@
      */
     public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
 
-    private static final int TYPE_ONE_OFF = 0;
-    private static final int TYPE_FLOAT = 1;
-    private static final int TYPE_INTEGER = 2;
-    private static final int TYPE_BOOL_TRUE = 3;
-    private static final int TYPE_BOOL_FALSE = 4;
-    private static final int TYPE_COMPOUND_STRING = 5;
-    private static final int TYPE_GESTURE_EVENT = 6;
-
     private final EventLog[] logs;
     private int nextIndex;
     private int mCurrentLogId = 100;
@@ -67,9 +61,12 @@
      *                   execution.
      */
     public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent);
+        addLog(CompoundString.NO_OP, gestureEvent);
     }
 
+    /**
+     * Adds a log to be printed at log-dump-time.
+     */
     public void addLog(String event) {
         addLog(event, null);
     }
@@ -82,54 +79,35 @@
         addLog(event, extras, null);
     }
 
-    public void addLog(CompoundString compoundString) {
-        if (compoundString == CompoundString.NO_OP) return;
-        addLog(TYPE_COMPOUND_STRING, "", 0, compoundString, null);
-    }
-
-    public void addLog(
-            CompoundString compoundString,
-            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        if (compoundString == CompoundString.NO_OP) {
-            trackEvent(gestureEvent);
-            return;
-        }
-        addLog(TYPE_COMPOUND_STRING, "", 0, compoundString, gestureEvent);
-    }
-
     /**
-     * Adds a log and track the associated event for error detection.
+     * Adds a log to be printed at log-dump-time and track the associated event for error detection.
      *
      * @param gestureEvent GestureEvent representing the event being logged.
      */
     public void addLog(
             String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent);
+        addLog(new CompoundString(event), gestureEvent);
     }
 
     public void addLog(
             String event,
             int extras,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent);
+        addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
     }
 
     public void addLog(
             String event,
             boolean extras,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(
-                extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE,
-                event,
-                0,
-                CompoundString.NO_OP,
-                gestureEvent);
+        addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
     }
 
-    private void addLog(
-            int type,
-            String event,
-            float extras,
+    public void addLog(CompoundString compoundString) {
+        addLog(compoundString, null);
+    }
+
+    public void addLog(
             CompoundString compoundString,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
@@ -137,7 +115,7 @@
             EventLog eventLog = new EventLog(mCurrentLogId, mIsFullyGesturalNavMode);
             EventEntry eventEntry = new EventEntry();
 
-            eventEntry.update(type, event, extras, compoundString, gestureEvent);
+            eventEntry.update(compoundString, gestureEvent);
             eventLog.eventEntries.add(eventEntry);
             logs[nextIndex] = eventLog;
             nextIndex = (nextIndex + 1) % logs.length;
@@ -146,17 +124,17 @@
 
         // Update the last EventLog
         List<EventEntry> lastEventEntries = lastEventLog.eventEntries;
-        EventEntry lastEntry = lastEventEntries.size() > 0
+        EventEntry lastEntry = !lastEventEntries.isEmpty()
                 ? lastEventEntries.get(lastEventEntries.size() - 1) : null;
 
         // Update the last EventEntry if it's a duplicate
-        if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) {
+        if (isEntrySame(lastEntry, compoundString, gestureEvent)) {
             lastEntry.duplicateCount++;
             return;
         }
         EventEntry eventEntry = new EventEntry();
 
-        eventEntry.update(type, event, extras, compoundString, gestureEvent);
+        eventEntry.update(compoundString, gestureEvent);
         lastEventEntries.add(eventEntry);
     }
 
@@ -181,30 +159,14 @@
 
             writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
             for (EventEntry eventEntry : eventLog.eventEntries) {
+                if (eventEntry.mCompoundString.mIsNoOp) {
+                    continue;
+                }
                 date.setTime(eventEntry.time);
 
-                StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date))
-                        .append(eventEntry.event);
-                switch (eventEntry.type) {
-                    case TYPE_BOOL_FALSE:
-                        msg.append(": false");
-                        break;
-                    case TYPE_BOOL_TRUE:
-                        msg.append(": true");
-                        break;
-                    case TYPE_FLOAT:
-                        msg.append(": ").append(eventEntry.extras);
-                        break;
-                    case TYPE_INTEGER:
-                        msg.append(": ").append((int) eventEntry.extras);
-                        break;
-                    case TYPE_COMPOUND_STRING:
-                        msg.append(eventEntry.mCompoundString);
-                        break;
-                    case TYPE_GESTURE_EVENT:
-                        continue;
-                    default: // fall out
-                }
+                StringBuilder msg = new StringBuilder(prefix + "\t\t")
+                        .append(sdf.format(date))
+                        .append(eventEntry.mCompoundString);
                 if (eventEntry.duplicateCount > 0) {
                     msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events");
                 }
@@ -232,15 +194,9 @@
 
     private boolean isEntrySame(
             EventEntry entry,
-            int type,
-            String event,
-            float extras,
             CompoundString compoundString,
             ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         return entry != null
-                && entry.type == type
-                && entry.event.equals(event)
-                && Float.compare(entry.extras, extras) == 0
                 && entry.mCompoundString.equals(compoundString)
                 && entry.gestureEvent == gestureEvent;
     }
@@ -248,9 +204,6 @@
     /** A single event entry. */
     protected static class EventEntry {
 
-        private int type;
-        private String event;
-        private float extras;
         @NonNull private CompoundString mCompoundString;
         private ActiveGestureErrorDetector.GestureEvent gestureEvent;
         private long time;
@@ -264,14 +217,8 @@
         }
 
         private void update(
-                int type,
-                String event,
-                float extras,
                 @NonNull CompoundString compoundString,
                 ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-            this.type = type;
-            this.event = event;
-            this.extras = extras;
             this.mCompoundString = compoundString;
             this.gestureEvent = gestureEvent;
             time = System.currentTimeMillis();
@@ -302,6 +249,7 @@
         public static final CompoundString NO_OP = new CompoundString();
 
         private final List<String> mSubstrings;
+        private final List<Object> mArgs;
 
         private final boolean mIsNoOp;
 
@@ -313,10 +261,12 @@
             mIsNoOp = substring == null;
             if (mIsNoOp) {
                 mSubstrings = null;
+                mArgs = null;
                 return;
             }
             mSubstrings = new ArrayList<>();
             mSubstrings.add(substring);
+            mArgs = new ArrayList<>();
         }
 
         public CompoundString append(CompoundString substring) {
@@ -338,19 +288,41 @@
         }
 
         public CompoundString append(int num) {
-            if (mIsNoOp) {
-                return this;
-            }
-            mSubstrings.add(Integer.toString(num));
+            mArgs.add(num);
 
-            return this;
+            return append("%d");
+        }
+
+        public CompoundString append(float num) {
+            mArgs.add(num);
+
+            return append("%.2f");
+        }
+
+        public CompoundString append(double num) {
+            mArgs.add(num);
+
+            return append("%.2f");
+        }
+
+        public CompoundString append(boolean bool) {
+            mArgs.add(bool);
+
+            return append("%b");
+        }
+
+        public Object[] getArgs() {
+            return mArgs.toArray();
         }
 
         @Override
         public String toString() {
-            if (mIsNoOp) {
-                return "ERROR: cannot use No-Op compound string";
-            }
+            return String.format(toUnformattedString(), getArgs());
+        }
+
+        public String toUnformattedString() {
+            Preconditions.assertTrue(!mIsNoOp);
+
             StringBuilder sb = new StringBuilder();
             for (String substring : mSubstrings) {
                 sb.append(substring);
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
index 79ca076..21c9e09 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
@@ -44,7 +44,7 @@
     private final PriorityQueue<Task.TaskKey> mQueue;
 
     public TaskKeyByLastActiveTimeCache(int maxSize) {
-        mMap = new HashMap(maxSize);
+        mMap = new HashMap(0);
         mQueue = new PriorityQueue<>(Comparator.comparingLong(t -> t.lastActiveTime));
         mMaxSize = new AtomicInteger(maxSize);
     }
@@ -106,7 +106,8 @@
     }
 
     /**
-     * Adds an entry to the cache, optionally evicting the last accessed entry
+     * Adds an entry to the cache, optionally evicting the last accessed entry excluding the newly
+     * added entry
      */
     @Override
     public final synchronized void put(Task.TaskKey key, V value) {
@@ -117,9 +118,9 @@
                 mQueue.remove(entry.mKey);
             }
 
+            removeExcessIfNeeded(mMaxSize.get() - 1);
             mMap.put(key.id, new Entry<>(key, value));
             mQueue.add(key);
-            removeExcessIfNeeded();
         } else {
             Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
         }
@@ -143,11 +144,11 @@
     @Override
     public synchronized void updateCacheSizeAndRemoveExcess(int cacheSize) {
         mMaxSize.compareAndSet(mMaxSize.get(), cacheSize);
-        removeExcessIfNeeded();
+        removeExcessIfNeeded(mMaxSize.get());
     }
 
-    private synchronized void removeExcessIfNeeded() {
-        while (mQueue.size() > mMaxSize.get() && !mQueue.isEmpty()) {
+    private synchronized void removeExcessIfNeeded(int maxSize) {
+        while (mQueue.size() > maxSize && !mQueue.isEmpty()) {
             Task.TaskKey key = mQueue.poll();
             mMap.remove(key.id);
         }
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
index ba9ae67..fc757b4 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.TestUtil;
@@ -55,7 +54,7 @@
                 "com.google.android.apps.nexuslauncher.tests",
                 "com.android.launcher3.testcomponent.BaseTestingActivity");
         mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         startAppFast(CALCULATOR_APP_PACKAGE);
         mLauncher.enableBlockTimeout(true);
         mLauncher.showTaskbarIfHidden();
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index a89eab5..85440e9 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -22,7 +22,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
@@ -40,7 +40,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         // b/143488140
         mLauncher.goHome();
         // Start an activity where the gestures start.
@@ -49,6 +49,8 @@
 
     @Test
     @NavigationModeSwitch
+    // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
+    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     public void testStressPressHome() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
@@ -61,7 +63,8 @@
 
     @Test
     @NavigationModeSwitch
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
+    // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
+    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     public void testStressSwipeToOverview() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index c4c95bc..3f806d1 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 74f37a4..829e54b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -22,7 +22,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.tapl.KeyboardQuickSwitch;
-import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 
 import org.junit.Assume;
 import org.junit.Test;
@@ -56,7 +56,7 @@
     public void setUp() throws Exception {
         Assume.assumeTrue(mLauncher.isTablet());
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         startAppFast(CALCULATOR_APP_PACKAGE);
         startTestActivity(2);
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 8e142c3..b3cc215 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -45,8 +45,8 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.TestStabilityRule;
@@ -72,7 +72,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         executeOnLauncher(launcher -> {
             RecentsView recentsView = launcher.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index 234fe63..1e33635 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -32,15 +32,14 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.tapl.TaskbarAppIcon;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 import com.android.wm.shell.Flags;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,7 +57,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         if (mLauncher.isTablet()) {
             mLauncher.enableBlockTimeout(true);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
index 907dbcc..0eec8b7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
@@ -19,7 +19,6 @@
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -32,8 +31,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
 import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.After;
@@ -51,7 +50,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
     }
 
     @After
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 8cc8487..2318f54 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -56,7 +56,7 @@
 import com.android.launcher3.testcomponent.ListViewService;
 import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
-import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -102,7 +102,7 @@
         // is started only after starting another app.
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         mModel = LauncherAppState.getInstance(mTargetContext).getModel();
         Executors.MODEL_EXECUTOR.submit(mModel.getModelDbController()::createEmptyDB).get();
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 20e7089..8d84c90 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -164,7 +164,19 @@
 
         <!-- numFolderRows & numFolderColumns defaults to numRows & numColumns, if not specified -->
         <attr name="numFolderRows" format="integer" />
+        <!-- defaults to numFolderRows, if not specified -->
+        <attr name="numFolderRowsLandscape" format="integer" />
+        <!-- defaults to numFolderRows, if not specified -->
+        <attr name="numFolderRowsTwoPanelLandscape" format="integer" />
+        <!-- defaults to numFolderRows, if not specified -->
+        <attr name="numFolderRowsTwoPanelPortrait" format="integer" />
         <attr name="numFolderColumns" format="integer" />
+        <!-- defaults to numFolderColumns, if not specified -->
+        <attr name="numFolderColumnsLandscape" format="integer" />
+        <!-- defaults to numFolderColumns, if not specified -->
+        <attr name="numFolderColumnsTwoPanelLandscape" format="integer" />
+        <!-- defaults to numFolderColumns, if not specified -->
+        <attr name="numFolderColumnsTwoPanelPortrait" format="integer" />
         <!-- Support attributes in FolderStyle -->
         <attr name="folderStyle" format="reference" />
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7ca8b82..badd1c4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -439,8 +439,8 @@
         }
 
         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
-        numFolderRows = inv.numFolderRows;
-        numFolderColumns = inv.numFolderColumns;
+        numFolderRows = inv.numFolderRows[mTypeIndex];
+        numFolderColumns = inv.numFolderColumns[mTypeIndex];
 
         if (mIsScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
             TypedArray folderStyle = context.obtainStyledAttributes(inv.folderStyle,
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 01e65ae..a13dcc1 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -27,6 +27,7 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.app.animation.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
@@ -194,12 +195,13 @@
     }
 
     /**
-     * Scrolls this recycler view to the bottom.
+     * Scrolls this recycler view to the bottom with easing and duration.
      */
-    public void scrollToBottom() {
+    public void scrollToBottomWithMotion() {
         if (mScrollbar != null) {
             mScrollbar.reattachThumbToScroll();
         }
-        smoothScrollToPosition(getAdapter().getItemCount() - 1);
+        // Emphasized interpolators with 500ms duration
+        smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, 500);
     }
 }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 567d0c5..dfbbcaa 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -122,8 +122,8 @@
     /**
      * Number of icons per row and column in the folder.
      */
-    public int numFolderRows;
-    public int numFolderColumns;
+    public int[] numFolderRows;
+    public int[] numFolderColumns;
     public float[] iconSize;
     public float[] iconTextSize;
     public int iconBitmapSize;
@@ -810,8 +810,8 @@
         public final int numSearchContainerColumns;
         public final int deviceCategory;
 
-        private final int numFolderRows;
-        private final int numFolderColumns;
+        private final int[] numFolderRows = new int[COUNT_SIZES];
+        private final int[] numFolderColumns = new int[COUNT_SIZES];
         private final @StyleRes int folderStyle;
         private final @StyleRes int cellStyle;
 
@@ -888,11 +888,39 @@
                     a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
                             R.dimen.taskbar_button_margin_default);
 
-            numFolderRows = a.getInt(
+            numFolderRows[INDEX_DEFAULT] = a.getInt(
                     R.styleable.GridDisplayOption_numFolderRows, numRows);
-            numFolderColumns = a.getInt(
+            numFolderColumns[INDEX_DEFAULT] = a.getInt(
                     R.styleable.GridDisplayOption_numFolderColumns, numColumns);
 
+            if (FeatureFlags.enableResponsiveWorkspace()) {
+                numFolderRows[INDEX_LANDSCAPE] = a.getInt(
+                        R.styleable.GridDisplayOption_numFolderRowsLandscape,
+                        numFolderRows[INDEX_DEFAULT]);
+                numFolderColumns[INDEX_LANDSCAPE] = a.getInt(
+                        R.styleable.GridDisplayOption_numFolderColumnsLandscape,
+                        numFolderColumns[INDEX_DEFAULT]);
+                numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
+                        R.styleable.GridDisplayOption_numFolderRowsTwoPanelPortrait,
+                        numFolderRows[INDEX_DEFAULT]);
+                numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
+                        R.styleable.GridDisplayOption_numFolderColumnsTwoPanelPortrait,
+                        numFolderColumns[INDEX_DEFAULT]);
+                numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
+                        R.styleable.GridDisplayOption_numFolderRowsTwoPanelLandscape,
+                        numFolderRows[INDEX_DEFAULT]);
+                numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
+                        R.styleable.GridDisplayOption_numFolderColumnsTwoPanelLandscape,
+                        numFolderColumns[INDEX_DEFAULT]);
+            } else {
+                numFolderRows[INDEX_LANDSCAPE] = numFolderRows[INDEX_DEFAULT];
+                numFolderColumns[INDEX_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT];
+                numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = numFolderRows[INDEX_DEFAULT];
+                numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = numFolderColumns[INDEX_DEFAULT];
+                numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = numFolderRows[INDEX_DEFAULT];
+                numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT];
+            }
+
             folderStyle = a.getResourceId(R.styleable.GridDisplayOption_folderStyle,
                     INVALID_RESOURCE_HANDLE);
 
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index c9e5b1e..e5a223a 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -515,7 +515,7 @@
             // Switch to the main tab
             switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
             // Scroll to bottom
-            getActiveRecyclerView().scrollToBottom();
+            getActiveRecyclerView().scrollToBottomWithMotion();
         });
     }
 
diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
index b7d8093..7deb653 100644
--- a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
@@ -38,8 +38,8 @@
         mSeam = new View(cellLayout.getContext());
     }
 
-    private ItemConfiguration removeSeamFromSolution(
-            ItemConfiguration solution) {
+    public ItemConfiguration removeSeamFromSolution(ItemConfiguration solution) {
+        solution.map.remove(mSeam);
         solution.map.forEach((view, cell) -> cell.cellX =
                 cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX);
         solution.cellX =
@@ -48,9 +48,8 @@
     }
 
     @Override
-    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
-            int minSpanX, int minSpanY,
-            int spanX, int spanY) {
+    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
+            int minSpanY, int spanX, int spanY) {
         return removeSeamFromSolution(simulateSeam(
                 () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
                         spanY)));
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index 05bd13d..0f6464b 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -62,6 +62,14 @@
     public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
             ItemConfiguration solution) {
+        return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
+                direction, dragView, decX, solution);
+    }
+
+
+    private ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY,
+            int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView,
+            boolean decX, ItemConfiguration solution) {
         // Copy the current state into the solution. This solution will be manipulated as necessary.
         mCellLayout.copyCurrentStateToSolution(solution, false);
         // Copy the current occupied array into the temporary occupied array. This array will be
@@ -83,11 +91,11 @@
             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
             // x, then 1 in y etc.
             if (spanX > minSpanX && (minSpanY == spanY || decX)) {
-                return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
-                        direction, dragView, false, solution);
+                return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1,
+                        spanY, direction, dragView, false, solution);
             } else if (spanY > minSpanY) {
-                return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
-                        direction, dragView, true, solution);
+                return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX,
+                        spanY - 1, direction, dragView, true, solution);
             }
             solution.isSolution = false;
         } else {
@@ -193,8 +201,7 @@
         mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
                 mCellLayout.mDirectionVector);
 
-        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY,
-                spanX, spanY,
+        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
                 dragView);
 
         // Find a solution involving pushing / displacing any items in the way
@@ -203,8 +210,8 @@
                 new ItemConfiguration());
 
         // We attempt the approach which doesn't shuffle views at all
-        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(
-                pixelX, pixelY, minSpanX, minSpanY, spanX, spanY);
+        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, minSpanX,
+                minSpanY, spanX, spanY);
 
         // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
         // favor a solution in which the item is not resized, but
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 5f73ced..9980218 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -860,6 +860,13 @@
         }
 
         /**
+         * Set the package name of the log message.
+         */
+        default StatsLogger withPackageName(@Nullable String packageName) {
+            return this;
+        }
+
+        /**
          * Builds the final message and logs it as {@link EventEnum}.
          */
         default void log(EventEnum event) {
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index a005c60..5b6c9e0 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -25,11 +25,9 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
-import static com.android.launcher3.testing.shared.TestProtocol.WORKSPACE_LONG_PRESS;
 
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.util.Log;
 import android.view.GestureDetector;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
@@ -41,7 +39,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -112,10 +109,6 @@
                 mTouchDownPoint.set(ev.getX(), ev.getY());
                 // Mouse right button's ACTION_DOWN should immediately show menu
                 if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
-                    if (Utilities.isRunningInTestHarness()) {
-                        Log.d(WORKSPACE_LONG_PRESS, "longPress from mouseHandling timeout: +"
-                                + ViewConfiguration.getLongPressTimeout());
-                    }
                     maybeShowMenu();
                     return true;
                 }
@@ -199,10 +192,6 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
-        if (Utilities.isRunningInTestHarness()) {
-            Log.d(WORKSPACE_LONG_PRESS, "longPress from gestureHandler timeout: " +
-                    ViewConfiguration.getLongPressTimeout());
-        }
         maybeShowMenu();
     }
 
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 63d56e4..0e5ab9a 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -51,6 +51,12 @@
         }
     }
 
+    public static void assertTrue(boolean condition) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !condition) {
+            throw new IllegalStateException();
+        }
+    }
+
     private static boolean isSameLooper(Looper looper) {
         return Looper.myLooper() == looper;
     }
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index a421006..30b5663 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -121,8 +121,8 @@
                     listOf(PointF(16f, 16f), PointF(16f, 16f), PointF(16f, 16f), PointF(16f, 16f))
                         .toTypedArray()
 
-                numFolderRows = 3
-                numFolderColumns = 3
+                numFolderRows = intArrayOf(3, 3, 3, 3)
+                numFolderColumns = intArrayOf(3, 3, 3, 3)
                 folderStyle = R.style.FolderStyleDefault
 
                 inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_split
@@ -204,8 +204,8 @@
                     listOf(PointF(16f, 64f), PointF(64f, 16f), PointF(16f, 64f), PointF(16f, 64f))
                         .toTypedArray()
 
-                numFolderRows = 3
-                numFolderColumns = 3
+                numFolderRows = intArrayOf(3, 3, 3, 3)
+                numFolderColumns = intArrayOf(3, 3, 3, 3)
                 folderStyle = R.style.FolderStyleDefault
 
                 inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_6_5
@@ -288,8 +288,8 @@
                     listOf(PointF(16f, 16f), PointF(16f, 16f), PointF(16f, 20f), PointF(20f, 20f))
                         .toTypedArray()
 
-                numFolderRows = 3
-                numFolderColumns = 3
+                numFolderRows = intArrayOf(3, 3, 3, 3)
+                numFolderColumns = intArrayOf(3, 3, 3, 3)
                 folderStyle = R.style.FolderStyleDefault
 
                 inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_split
diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
index 41e3ea1..ee32e97 100644
--- a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
index 2771483..c7942c7 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.expectFail;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
index fd4619e..9f6bbdf 100644
--- a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
+++ b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
diff --git a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
index 0f5d85b..a1f2cef 100644
--- a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
+++ b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.appiconmenu;
 
 import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java b/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
index 1fe02b2..d8ae74b 100644
--- a/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
@@ -61,9 +61,7 @@
     }
 
     public static CellLayoutBoard viewsToBoard(List<View> views, int width, int height) {
-        CellLayoutBoard board = new CellLayoutBoard();
-        board.mWidth = width;
-        board.mHeight = height;
+        CellLayoutBoard board = new CellLayoutBoard(width, height);
 
         for (View callView : views) {
             CellLayoutLayoutParams params = (CellLayoutLayoutParams) callView.getLayoutParams();
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 88f54a2..bd52bda 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -22,7 +22,7 @@
 
 import android.content.Context;
 import android.graphics.Point;
-import android.graphics.Rect;
+import android.util.Log;
 import android.view.View;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -31,9 +31,13 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.MultipageCellLayout;
 import com.android.launcher3.celllayout.board.CellLayoutBoard;
 import com.android.launcher3.celllayout.board.IconPoint;
+import com.android.launcher3.celllayout.board.PermutedBoardComparator;
 import com.android.launcher3.celllayout.board.WidgetRect;
+import com.android.launcher3.celllayout.testgenerator.RandomBoardGenerator;
+import com.android.launcher3.celllayout.testgenerator.RandomMultiBoardGenerator;
 import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
 
@@ -53,10 +57,25 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ReorderAlgorithmUnitTest {
+
+    private static final String TAG = "ReorderAlgorithmUnitTest";
+    private static final char MAIN_WIDGET_TYPE = 'z';
+
+    // There is nothing special about this numbers, the random seed is just to be able to reproduce
+    // the test cases and the height and width is a random number similar to what users expect on
+    // their devices
+    private static final int SEED = 897;
+    private static final int MAX_BOARD_SIZE = 13;
+
+    private static final int TOTAL_OF_CASES_GENERATED = 300;
     private Context mApplicationContext;
 
     private int mPrevNumColumns, mPrevNumRows;
 
+    /**
+     * This test reads existing test cases and makes sure the CellLayout produces the same
+     * output for each of them for a given input.
+     */
     @Test
     public void testAllCases() throws IOException {
         List<ReorderAlgorithmUnitTestCase> testCases = getTestCases(
@@ -65,7 +84,7 @@
         List<Integer> failingCases = new ArrayList<>();
         for (int i = 0; i < testCases.size(); i++) {
             try {
-                evaluateTestCase(testCases.get(i));
+                evaluateTestCase(testCases.get(i), false);
             } catch (AssertionError e) {
                 e.printStackTrace();
                 failingCases.add(i);
@@ -75,6 +94,47 @@
                 failingCases.size());
     }
 
+    /**
+     * This test generates random CellLayout configurations and then try to reorder it and makes
+     * sure the result is a valid board meaning it didn't remove any widget or icon.
+     */
+    @Test
+    public void generateValidTests() {
+        Random generator = new Random(SEED);
+        mApplicationContext = new ActivityContextWrapper(getApplicationContext());
+        for (int i = 0; i < TOTAL_OF_CASES_GENERATED; i++) {
+            // Using a new seed so that we can replicate the same test cases.
+            int seed = generator.nextInt();
+            Log.d(TAG, "Seed = " + seed);
+            ReorderAlgorithmUnitTestCase testCase = generateRandomTestCase(
+                    new RandomBoardGenerator(new Random(seed))
+            );
+            Log.d(TAG, "testCase = " + testCase);
+            assertTrue("invalid case " + i,
+                    validateIntegrity(testCase.startBoard, testCase.endBoard, testCase));
+        }
+    }
+
+    /**
+     * Same as above but testing the Multipage CellLayout.
+     */
+    @Test
+    public void generateValidTests_Multi() {
+        Random generator = new Random(SEED);
+        mApplicationContext = new ActivityContextWrapper(getApplicationContext());
+        for (int i = 0; i < TOTAL_OF_CASES_GENERATED; i++) {
+            // Using a new seed so that we can replicate the same test cases.
+            int seed = generator.nextInt();
+            Log.d(TAG, "Seed = " + seed);
+            ReorderAlgorithmUnitTestCase testCase = generateRandomTestCase(
+                    new RandomMultiBoardGenerator(new Random(seed))
+            );
+            Log.d(TAG, "testCase = " + testCase);
+            assertTrue("invalid case " + i,
+                    validateIntegrity(testCase.startBoard, testCase.endBoard, testCase));
+        }
+    }
+
     private void addViewInCellLayout(CellLayout cellLayout, int cellX, int cellY, int spanX,
             int spanY, boolean isWidget) {
         View cell = isWidget ? new View(mApplicationContext) : new DoubleShadowBubbleTextView(
@@ -84,15 +144,16 @@
                 (CellLayoutLayoutParams) cell.getLayoutParams(), true);
     }
 
-    public CellLayout createCellLayout(int width, int height) {
+    public CellLayout createCellLayout(int width, int height, boolean isMulti) {
         Context c = mApplicationContext;
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(c).getDeviceProfile(c).copy(c);
         // modify the device profile.
-        dp.inv.numColumns = width;
+        dp.inv.numColumns = isMulti ? width / 2 : width;
         dp.inv.numRows = height;
         dp.cellLayoutBorderSpacePx = new Point(0, 0);
 
-        CellLayout cl = new CellLayout(getWrappedContext(c, dp));
+        CellLayout cl = isMulti ? new MultipageCellLayout(getWrappedContext(c, dp))
+                : new CellLayout(getWrappedContext(c, dp));
         // I put a very large number for width and height so that all the items can fit, it doesn't
         // need to be exact, just bigger than the sum of cell border
         cl.measure(View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY),
@@ -109,8 +170,8 @@
     }
 
     public ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX,
-            int spanY, int minSpanX, int minSpanY) {
-        CellLayout cl = createCellLayout(board.getWidth(), board.getHeight());
+            int spanY, int minSpanX, int minSpanY, boolean isMulti) {
+        CellLayout cl = createCellLayout(board.getWidth(), board.getHeight(), isMulti);
 
         // The views have to be sorted or the result can vary
         board.getIcons()
@@ -118,11 +179,15 @@
                 .map(IconPoint::getCoord)
                 .sorted(Comparator.comparing(p -> ((Point) p).x).thenComparing(p -> ((Point) p).y))
                 .forEach(p -> addViewInCellLayout(cl, p.x, p.y, 1, 1, false));
-        board.getWidgets().stream()
-                .sorted(Comparator.comparing(WidgetRect::getCellX)
-                        .thenComparing(WidgetRect::getCellY))
-                .forEach(widget -> addViewInCellLayout(cl, widget.getCellX(), widget.getCellY(),
-                        widget.getSpanX(), widget.getSpanY(), true));
+        board.getWidgets()
+                .stream()
+                .sorted(Comparator
+                        .comparing(WidgetRect::getCellX)
+                        .thenComparing(WidgetRect::getCellY)
+                ).forEach(
+                        widget -> addViewInCellLayout(cl, widget.getCellX(), widget.getCellY(),
+                                widget.getSpanX(), widget.getSpanY(), true)
+                );
 
         int[] testCaseXYinPixels = new int[2];
         cl.regionToCenterPoint(x, y, spanX, spanY, testCaseXYinPixels);
@@ -133,6 +198,15 @@
             solution = new ItemConfiguration();
             solution.isSolution = false;
         }
+        if (!solution.isSolution) {
+            cl.copyCurrentStateToSolution(solution, false);
+            if (cl instanceof MultipageCellLayout) {
+                solution =
+                        ((MultipageCellLayout) cl).createReorderAlgorithm().removeSeamFromSolution(
+                                solution);
+            }
+            solution.isSolution = false;
+        }
         return solution;
     }
 
@@ -143,17 +217,18 @@
                 new CellLayoutLayoutParams(val.cellX, val.cellY, val.spanX, val.spanY)));
         CellLayoutBoard board = CellLayoutTestUtils.viewsToBoard(
                 new ArrayList<>(solution.map.keySet()), width, height);
-        board.addWidget(solution.cellX, solution.cellY, solution.spanX, solution.spanY,
-                'z');
+        if (solution.isSolution) {
+            board.addWidget(solution.cellX, solution.cellY, solution.spanX, solution.spanY,
+                    MAIN_WIDGET_TYPE);
+        }
         return board;
     }
 
-    public void evaluateTestCase(ReorderAlgorithmUnitTestCase testCase) {
-        ItemConfiguration solution = solve(testCase.startBoard, testCase.x,
-                testCase.y, testCase.spanX, testCase.spanY, testCase.minSpanX,
-                testCase.minSpanY);
-        assertEquals("should be a valid solution", solution.isSolution,
-                testCase.isValidSolution);
+    public void evaluateTestCase(ReorderAlgorithmUnitTestCase testCase, boolean isMultiCellLayout) {
+        ItemConfiguration solution = solve(testCase.startBoard, testCase.x, testCase.y,
+                testCase.spanX, testCase.spanY, testCase.minSpanX, testCase.minSpanY,
+                isMultiCellLayout);
+        assertEquals("should be a valid solution", solution.isSolution, testCase.isValidSolution);
         if (testCase.isValidSolution) {
             CellLayoutBoard finishBoard = boardFromSolution(solution,
                     testCase.startBoard.getWidth(), testCase.startBoard.getHeight());
@@ -178,36 +253,35 @@
         dp.inv.numRows = mPrevNumRows;
     }
 
-    @SuppressWarnings("UnusedMethod")
-    /**
-     * Utility function used to generate all the test cases
-     */
-    private ReorderAlgorithmUnitTestCase generateRandomTestCase() {
+    private ReorderAlgorithmUnitTestCase generateRandomTestCase(
+            RandomBoardGenerator boardGenerator) {
         ReorderAlgorithmUnitTestCase testCase = new ReorderAlgorithmUnitTestCase();
 
-        int width = getRandom(3, 8);
-        int height = getRandom(3, 8);
+        boolean isMultiCellLayout = boardGenerator instanceof RandomMultiBoardGenerator;
 
-        int targetWidth = getRandom(1, width - 2);
-        int targetHeight = getRandom(1, height - 2);
+        int width = isMultiCellLayout
+                ? boardGenerator.getRandom(3, MAX_BOARD_SIZE / 2) * 2
+                : boardGenerator.getRandom(3, MAX_BOARD_SIZE);
+        int height = boardGenerator.getRandom(3, MAX_BOARD_SIZE);
 
-        int minTargetWidth = getRandom(1, targetWidth);
-        int minTargetHeight = getRandom(1, targetHeight);
+        int targetWidth = boardGenerator.getRandom(1, width - 2);
+        int targetHeight = boardGenerator.getRandom(1, height - 2);
 
-        int x = getRandom(0, width - targetWidth);
-        int y = getRandom(0, height - targetHeight);
+        int minTargetWidth = boardGenerator.getRandom(1, targetWidth);
+        int minTargetHeight = boardGenerator.getRandom(1, targetHeight);
 
-        CellLayoutBoard board = generateBoard(new CellLayoutBoard(width, height),
-                new Rect(0, 0, width, height), targetWidth * targetHeight);
+        int x = boardGenerator.getRandom(0, width - targetWidth);
+        int y = boardGenerator.getRandom(0, height - targetHeight);
+
+        CellLayoutBoard board = boardGenerator.generateBoard(width, height,
+                targetWidth * targetHeight);
 
         ItemConfiguration solution = solve(board, x, y, targetWidth, targetHeight,
-                minTargetWidth, minTargetHeight);
+                minTargetWidth, minTargetHeight, isMultiCellLayout);
 
-        CellLayoutBoard finishBoard = solution.isSolution ? boardFromSolution(solution,
-                board.getWidth(), board.getHeight()) : new CellLayoutBoard(board.getWidth(),
+        CellLayoutBoard finishBoard = boardFromSolution(solution, board.getWidth(),
                 board.getHeight());
 
-
         testCase.startBoard = board;
         testCase.endBoard = finishBoard;
         testCase.isValidSolution = solution.isSolution;
@@ -222,39 +296,24 @@
         return testCase;
     }
 
-    private int getRandom(int start, int end) {
-        int random = end == 0 ? 0 : new Random().nextInt(end);
-        return start + random;
-    }
-
-    private CellLayoutBoard generateBoard(CellLayoutBoard board, Rect area,
-            int emptySpaces) {
-        if (area.height() * area.width() <= 0) return board;
-
-        int width = getRandom(1, area.width() - 1);
-        int height = getRandom(1, area.height() - 1);
-
-        int x = area.left + getRandom(0, area.width() - width);
-        int y = area.top + getRandom(0, area.height() - height);
-
-        if (emptySpaces > 0) {
-            emptySpaces -= width * height;
-        } else if (width * height > 1) {
-            board.addWidget(x, y, width, height);
-        } else {
-            board.addIcon(x, y);
+    /**
+     * Makes sure the final solution has valid integrity meaning that the number and sizes of
+     * widgets is the expect and there are no missing widgets.
+     */
+    public boolean validateIntegrity(CellLayoutBoard startBoard, CellLayoutBoard finishBoard,
+            ReorderAlgorithmUnitTestCase testCase) {
+        if (!testCase.isValidSolution) {
+            // if we couldn't place the widget then the solution should be identical to the board
+            return startBoard.compareTo(finishBoard) == 0;
         }
-
-        generateBoard(board,
-                new Rect(area.left, area.top, area.right, y), emptySpaces);
-        generateBoard(board,
-                new Rect(area.left, y, x, area.bottom), emptySpaces);
-        generateBoard(board,
-                new Rect(x, y + height, area.right, area.bottom), emptySpaces);
-        generateBoard(board,
-                new Rect(x + width, y, area.right, y + height), emptySpaces);
-
-        return board;
+        WidgetRect addedWidget = finishBoard.getWidgetOfType(MAIN_WIDGET_TYPE);
+        finishBoard.removeItem(MAIN_WIDGET_TYPE);
+        Comparator<CellLayoutBoard> comparator = new PermutedBoardComparator();
+        if (comparator.compare(startBoard, finishBoard) != 0) {
+            return false;
+        }
+        return addedWidget.getSpanX() >= testCase.minSpanX
+                && addedWidget.getSpanY() >= testCase.minSpanY;
     }
 
     private static List<ReorderAlgorithmUnitTestCase> getTestCases(String testPath)
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 72de885..30bde0a 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.tapl.WidgetResizeFrame;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.ModelTestExtensions;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
@@ -73,7 +72,7 @@
     @Before
     public void setup() throws Throwable {
         mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
     }
 
     @After
diff --git a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
index e90e145..dbbdcf5 100644
--- a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
+++ b/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,8 +18,11 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 
+import androidx.annotation.NonNull;
+
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -31,73 +34,13 @@
 
 public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
 
-    private boolean intersects(Rect r1, Rect r2) {
-        // If one rectangle is on left side of other
-        if (r1.left > r2.right || r2.left > r1.right) {
-            return false;
-        }
-
-        // If one rectangle is above other
-        if (r1.bottom > r2.top || r2.bottom > r1.top) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect) {
-        for (Rect ignoredRect : ignoredRectangles) {
-            // Using the built in intersects doesn't work because it doesn't account for area 0
-            if (intersects(ignoredRect, rect)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    public static final Comparator<CellLayoutBoard> COMPARATOR = new IdenticalBoardComparator();
 
     @Override
-    public int compareTo(CellLayoutBoard cellLayoutBoard) {
-        // to be equal they need to have the same number of widgets and the same dimensions
-        // their order can be different
-        Set<Rect> widgetsSet = new HashSet<>();
-        Set<Rect> ignoredRectangles = new HashSet<>();
-        for (WidgetRect rect : mWidgetsRects) {
-            if (rect.shouldIgnore()) {
-                ignoredRectangles.add(rect.mBounds);
-            } else {
-                widgetsSet.add(rect.mBounds);
-            }
-        }
-        for (WidgetRect rect : cellLayoutBoard.mWidgetsRects) {
-            // ignore rectangles overlapping with the area marked by x
-            if (overlapsWithIgnored(ignoredRectangles, rect.mBounds)) {
-                continue;
-            }
-            if (!widgetsSet.contains(rect.mBounds)) {
-                return -1;
-            }
-            widgetsSet.remove(rect.mBounds);
-        }
-        if (!widgetsSet.isEmpty()) {
-            return 1;
-        }
-
-        // to be equal they need to have the same number of icons their order can be different
-        Set<Point> iconsSet = new HashSet<>();
-        mIconPoints.forEach(icon -> iconsSet.add(icon.getCoord()));
-        for (IconPoint icon : cellLayoutBoard.mIconPoints) {
-            if (!iconsSet.contains(icon.getCoord())) {
-                return -1;
-            }
-            iconsSet.remove(icon.getCoord());
-        }
-        if (!iconsSet.isEmpty()) {
-            return 1;
-        }
-        return 0;
+    public int compareTo(@NonNull CellLayoutBoard cellLayoutBoard) {
+        return COMPARATOR.compare(this, cellLayoutBoard);
     }
 
-
     private HashSet<Character> mUsedWidgetTypes = new HashSet<>();
 
     static final int INFINITE = 99999;
@@ -112,7 +55,7 @@
 
     WidgetRect mMain = null;
 
-    public int mWidth, mHeight;
+    int mWidth, mHeight;
 
     public CellLayoutBoard() {
         for (int x = 0; x < mWidget.length; x++) {
@@ -123,7 +66,7 @@
     }
 
     public CellLayoutBoard(int width, int height) {
-        mWidget = new char[width][height];
+        mWidget = new char[width + 1][height + 1];
         this.mWidth = width;
         this.mHeight = height;
         for (int x = 0; x < mWidget.length; x++) {
@@ -139,6 +82,15 @@
         return isXInRect && isYInRect;
     }
 
+    public WidgetRect getWidgetAt(Point p) {
+        return getWidgetAt(p.x, p.y);
+    }
+
+    public WidgetRect getWidgetOfType(char type) {
+        return mWidgetsRects.stream()
+                .filter(widgetRect -> widgetRect.mType == type).findFirst().orElse(null);
+    }
+
     public WidgetRect getWidgetAt(int x, int y) {
         return mWidgetsRects.stream()
                 .filter(widgetRect -> pointInsideRect(x, y, widgetRect)).findFirst().orElse(null);
@@ -165,8 +117,8 @@
     }
 
     private void removeWidgetFromBoard(WidgetRect widget) {
-        for (int xi = widget.mBounds.left; xi < widget.mBounds.right; xi++) {
-            for (int yi = widget.mBounds.top; yi < widget.mBounds.bottom; yi++) {
+        for (int xi = widget.mBounds.left; xi <= widget.mBounds.right; xi++) {
+            for (int yi = widget.mBounds.bottom; yi <= widget.mBounds.top; yi++) {
                 mWidget[xi][yi] = '-';
             }
         }
@@ -207,7 +159,7 @@
     private void removeOverlappingItems(Point p) {
         // Remove overlapping widgets and remove them from the board
         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
-            if (widget.mBounds.contains(p.x, p.y)) {
+            if (IdenticalBoardComparator.Companion.touchesPoint(widget.mBounds, p)) {
                 removeWidgetFromBoard(widget);
                 return false;
             }
@@ -237,8 +189,9 @@
     }
 
     private char getNextWidgetType() {
-        for (char type = 'a'; type <= 'z'; type++) {
-            if (type == 'i') continue;
+        for (char type = 'a'; type < 'z'; type++) {
+            if (type == CellType.ICON) continue;
+            if (type == CellType.IGNORE) continue;
             if (mUsedWidgetTypes.contains(type)) continue;
             mUsedWidgetTypes.add(type);
             return type;
@@ -258,6 +211,17 @@
         }
     }
 
+    public void removeItem(char type) {
+        mWidgetsRects.stream()
+                .filter(widgetRect -> widgetRect.mType == type)
+                .forEach(widgetRect -> removeOverlappingItems(
+                        new Point(widgetRect.getCellX(), widgetRect.getCellY())));
+    }
+
+    public void removeItem(Point p) {
+        removeOverlappingItems(p);
+    }
+
     public void addWidget(int x, int y, int spanX, int spanY) {
         addWidget(x, y, spanX, spanY, getNextWidgetType());
     }
@@ -407,8 +371,8 @@
         s.append("\n");
         maxX = Math.min(maxX, mWidget.length);
         maxY = Math.min(maxY, mWidget[0].length);
-        for (int y = 0; y < maxY; y++) {
-            for (int x = 0; x < maxX; x++) {
+        for (int y = 0; y <= maxY; y++) {
+            for (int x = 0; x <= maxX; x++) {
                 s.append(mWidget[x][y]);
             }
             s.append('\n');
diff --git a/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt b/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
new file mode 100644
index 0000000..a4a420c
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.board
+
+import android.graphics.Point
+import android.graphics.Rect
+
+/**
+ * Compares two [CellLayoutBoard] and returns 0 if they are identical, meaning they have the same
+ * widget and icons in the same place, they can be different letters tough.
+ */
+class IdenticalBoardComparator : Comparator<CellLayoutBoard> {
+
+    /** Converts a list of WidgetRect into a map of the count of different widget.bounds */
+    private fun widgetsToBoundsMap(widgets: List<WidgetRect>) =
+        widgets.groupingBy { it.mBounds }.eachCount()
+
+    /** Converts a list of IconPoint into a map of the count of different icon.coord */
+    private fun iconsToPosCountMap(widgets: List<IconPoint>) =
+        widgets.groupingBy { it.getCoord() }.eachCount()
+
+    override fun compare(
+        cellLayoutBoard: CellLayoutBoard,
+        otherCellLayoutBoard: CellLayoutBoard
+    ): Int {
+        // to be equal they need to have the same number of widgets and the same dimensions
+        // their order can be different
+        val widgetsMap: Map<Rect, Int> =
+            widgetsToBoundsMap(cellLayoutBoard.widgets.filter { !it.shouldIgnore() })
+        val ignoredRectangles: Map<Rect, Int> =
+            widgetsToBoundsMap(cellLayoutBoard.widgets.filter { it.shouldIgnore() })
+
+        val otherWidgetMap: Map<Rect, Int> =
+            widgetsToBoundsMap(
+                otherCellLayoutBoard.widgets
+                    .filter { !it.shouldIgnore() }
+                    .filter { !overlapsWithIgnored(ignoredRectangles, it.mBounds) }
+            )
+
+        if (widgetsMap != otherWidgetMap) {
+            return -1
+        }
+
+        // to be equal they need to have the same number of icons their order can be different
+        return if (
+            iconsToPosCountMap(cellLayoutBoard.icons) ==
+                iconsToPosCountMap(otherCellLayoutBoard.icons)
+        ) {
+            0
+        } else {
+            1
+        }
+    }
+
+    private fun overlapsWithIgnored(ignoredRectangles: Map<Rect, Int>, rect: Rect): Boolean {
+        for (ignoredRect in ignoredRectangles.keys) {
+            // Using the built in intersects doesn't work because it doesn't account for area 0
+            if (touches(ignoredRect, rect)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    companion object {
+        /**
+         * Similar function to {@link Rect#intersects} but this one returns true if the rectangles
+         * are intersecting or touching whereas {@link Rect#intersects} doesn't return true when
+         * they are touching.
+         */
+        fun touches(r1: Rect, r2: Rect): Boolean {
+            // If one rectangle is on left side of other
+            return if (r1.left > r2.right || r2.left > r1.right) {
+                false
+            } else r1.bottom <= r2.top && r2.bottom <= r1.top
+
+            // If one rectangle is above other
+        }
+
+        /**
+         * Similar function to {@link Rect#contains} but this one returns true if {link @Point} is
+         * intersecting or touching the {@link Rect}. Similar to {@link touches}.
+         */
+        fun touchesPoint(r1: Rect, p: Point): Boolean {
+            return r1.left <= p.x && p.x <= r1.right && r1.bottom <= p.y && p.y <= r1.top
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt b/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt
new file mode 100644
index 0000000..c3d13a5
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.board
+
+import android.graphics.Point
+
+/**
+ * Compares two [CellLayoutBoard] and returns 0 if they contain the same widgets and icons even if
+ * they are in different positions i.e. in a different permutation.
+ */
+class PermutedBoardComparator : Comparator<CellLayoutBoard> {
+
+    /**
+     * The key for the set is the span since the widgets could change location but shouldn't change
+     * size
+     */
+    private fun boardToSpanCountMap(widgets: List<WidgetRect>) =
+        widgets.groupingBy { Point(it.spanX, it.spanY) }.eachCount()
+    override fun compare(
+        cellLayoutBoard: CellLayoutBoard,
+        otherCellLayoutBoard: CellLayoutBoard
+    ): Int {
+        return if (
+            boardToSpanCountMap(cellLayoutBoard.widgets) !=
+                boardToSpanCountMap(otherCellLayoutBoard.widgets)
+        ) {
+            1
+        } else cellLayoutBoard.icons.size.compareTo(otherCellLayoutBoard.icons.size)
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt b/tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
new file mode 100644
index 0000000..e582973
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testgenerator
+
+import java.util.Random
+
+abstract class DeterministicRandomGenerator(private val generator: Random) {
+    fun getRandom(start: Int, end: Int): Int = start + (if (end == 0) 0 else generator.nextInt(end))
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt b/tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
new file mode 100644
index 0000000..770024f
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testgenerator
+
+import android.graphics.Rect
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import java.util.Random
+
+/** Generates a random CellLayoutBoard. */
+open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerator(generator) {
+    /**
+     * @param remainingEmptySpaces the maximum number of spaces we will fill with icons and widgets
+     *   meaning that if the number is 100 we will try to fill the board with at most 100 spaces
+     *   usually less than 100.
+     * @return a randomly generated board filled with icons and widgets.
+     */
+    open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard? {
+        val cellLayoutBoard = CellLayoutBoard(width, height)
+        return fillBoard(cellLayoutBoard, Rect(0, 0, width, height), remainingEmptySpaces)
+    }
+
+    protected fun fillBoard(
+        board: CellLayoutBoard,
+        area: Rect,
+        remainingEmptySpacesArg: Int
+    ): CellLayoutBoard {
+        var remainingEmptySpaces = remainingEmptySpacesArg
+        if (area.height() * area.width() <= 0) return board
+        val width = getRandom(1, area.width() - 1)
+        val height = getRandom(1, area.height() - 1)
+        val x = area.left + getRandom(0, area.width() - width)
+        val y = area.top + getRandom(0, area.height() - height)
+        if (remainingEmptySpaces > 0) {
+            remainingEmptySpaces -= width * height
+        } else if (board.widgets.size <= 22 && width * height > 1) {
+            board.addWidget(x, y, width, height)
+        } else {
+            board.addIcon(x, y)
+        }
+        fillBoard(board, Rect(area.left, area.top, area.right, y), remainingEmptySpaces)
+        fillBoard(board, Rect(area.left, y, x, area.bottom), remainingEmptySpaces)
+        fillBoard(board, Rect(x, y + height, area.right, area.bottom), remainingEmptySpaces)
+        fillBoard(board, Rect(x + width, y, area.right, y + height), remainingEmptySpaces)
+        return board
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt b/tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
new file mode 100644
index 0000000..da4de7d
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.testgenerator
+
+import android.graphics.Rect
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import java.util.Random
+
+class RandomMultiBoardGenerator(generator: Random) : RandomBoardGenerator(generator) {
+    override fun generateBoard(
+        width: Int,
+        height: Int,
+        remainingEmptySpaces: Int
+    ): CellLayoutBoard {
+        val cellLayoutBoard = CellLayoutBoard(width, height)
+        fillBoard(cellLayoutBoard, Rect(0, 0, width / 2, height), remainingEmptySpaces / 2)
+        return fillBoard(
+            cellLayoutBoard,
+            Rect(width / 2, 0, width, height),
+            remainingEmptySpaces / 2
+        )
+    }
+}
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 7ec7826..e040367 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
index d69287b..ed34307 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.dragging;
 
 import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index cb1102e..5f536c7 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -220,6 +220,23 @@
     @Rule
     public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
+    public static void initialize(AbstractLauncherUiTest test) throws Exception {
+        initialize(test, false);
+    }
+
+    public static void initialize(
+            AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
+        test.reinitializeLauncherData(clearWorkspace);
+        test.mDevice.pressHome();
+        test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+        test.waitForState("Launcher internal state didn't switch to Home",
+                () -> LauncherState.NORMAL);
+        test.waitForResumed("Launcher internal state is still Background");
+        // Check that we switched to home.
+        test.mLauncher.getWorkspace();
+        AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
+    }
+
     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
         final CountDownLatch count = new CountDownLatch(2);
         final SimpleBroadcastReceiver broadcastReceiver =
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index f2cbd92..65d1f46 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -22,8 +22,6 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.LauncherState;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,23 +36,6 @@
         initialize(this);
     }
 
-    public static void initialize(AbstractLauncherUiTest test) throws Exception {
-        initialize(test, false);
-    }
-
-    public static void initialize(
-            AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
-        test.reinitializeLauncherData(clearWorkspace);
-        test.mDevice.pressHome();
-        test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
-        test.waitForState("Launcher internal state didn't switch to Home",
-                () -> LauncherState.NORMAL);
-        test.waitForResumed("Launcher internal state is still Background");
-        // Check that we switched to home.
-        test.mLauncher.getWorkspace();
-        AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
-    }
-
     // Please don't add negative test cases for methods that fail only after a long wait.
     public static void expectFail(String message, Runnable action) {
         boolean failed = false;
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 5c753f9..3c88f1d 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -18,8 +18,6 @@
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 
@@ -44,7 +42,6 @@
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.testcomponent.RequestPinItemActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.Wait.Condition;
@@ -77,7 +74,7 @@
         super.setUp();
         mCallbackAction = UUID.randomUUID().toString();
         mShortcutId = UUID.randomUUID().toString();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
index a5e9868..4ae2baa 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.ui.widget;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index d776f21..59c82a7 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.ui.workspace;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
 
 import static org.junit.Assert.assertEquals;
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index 34c7707..e21918f 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.tapl.HomeAppIcon;
 import com.android.launcher3.tapl.HomeAppIconMenuItem;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.Executors;
 
 import org.junit.Test;
@@ -58,7 +57,7 @@
     @Test
     public void testIconWithoutTheme() throws Exception {
         setThemeEnabled(false);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -76,7 +75,7 @@
     @Test
     public void testShortcutIconWithoutTheme() throws Exception {
         setThemeEnabled(false);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -95,7 +94,7 @@
     @Test
     public void testIconWithTheme() throws Exception {
         setThemeEnabled(true);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -113,7 +112,7 @@
     @Test
     public void testShortcutIconWithTheme() throws Exception {
         setThemeEnabled(true);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index 35b4883..e7112d1 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.TestUtil;
 
@@ -74,7 +73,7 @@
                 .atWorkspace(3, -1, 0).putApp(
                         "com.android.vending", "com.android.vending.AssetBrowserActivity");
         mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         assumeTrue(mLauncher.isTwoPanels());
 
         // Pre verifying the screens