Moving classes inside of CellLayout to their own file

This is a no-op change ensure this we have ReorderAlgorithmUnitTest.

Flag: NA
Bug: 229292911
Test: ReorderAlgorithmUnitTest
Change-Id: I6ffe2a1260f869a4686a9f1e652dd1ab6d406269
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 2064fe2..110ca16 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -44,13 +44,13 @@
 
 import androidx.core.graphics.ColorUtils;
 
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.celllayout.DelegatedCellDrawing;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
@@ -418,7 +418,7 @@
     /**
      * Draws Predicted Icon outline on cell layout
      */
-    public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
+    public static class PredictedIconOutlineDrawing extends DelegatedCellDrawing {
 
         private final PredictedAppIcon mIcon;
         private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 1a0f2cf..7257d86 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -67,7 +67,10 @@
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
+import com.android.launcher3.celllayout.DelegatedCellDrawing;
+import com.android.launcher3.celllayout.ItemConfiguration;
 import com.android.launcher3.celllayout.ReorderAlgorithm;
+import com.android.launcher3.celllayout.ViewCluster;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -86,7 +89,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Stack;
@@ -1434,9 +1436,8 @@
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
             CellAndSpan c = solution.map.get(child);
-            boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
-                    != null && !solution.intersectingViews.contains(child);
-
+            boolean skip = mode == ReorderPreviewAnimation.MODE_HINT
+                    && !solution.intersectingViews.contains(child);
 
             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             if (c != null && !skip && (child instanceof Reorderable)) {
@@ -1832,7 +1833,7 @@
     private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
             int[] direction, View dragView, ItemConfiguration currentState) {
 
-        ViewCluster cluster = new ViewCluster(views, currentState);
+        ViewCluster cluster = new ViewCluster(this, views, currentState);
         Rect clusterRect = cluster.getBoundingRect();
         int whichEdge;
         int pushDistance;
@@ -1924,191 +1925,6 @@
         return foundSolution;
     }
 
-    /**
-     * This helper class defines a cluster of views. It helps with defining complex edges
-     * of the cluster and determining how those edges interact with other views. The edges
-     * essentially define a fine-grained boundary around the cluster of views -- like a more
-     * precise version of a bounding box.
-     */
-    private class ViewCluster {
-        final static int LEFT = 1 << 0;
-        final static int TOP = 1 << 1;
-        final static int RIGHT = 1 << 2;
-        final static int BOTTOM = 1 << 3;
-
-        final ArrayList<View> views;
-        final ItemConfiguration config;
-        final Rect boundingRect = new Rect();
-
-        final int[] leftEdge = new int[mCountY];
-        final int[] rightEdge = new int[mCountY];
-        final int[] topEdge = new int[mCountX];
-        final int[] bottomEdge = new int[mCountX];
-        int dirtyEdges;
-        boolean boundingRectDirty;
-
-        @SuppressWarnings("unchecked")
-        public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
-            this.views = (ArrayList<View>) views.clone();
-            this.config = config;
-            resetEdges();
-        }
-
-        void resetEdges() {
-            for (int i = 0; i < mCountX; i++) {
-                topEdge[i] = -1;
-                bottomEdge[i] = -1;
-            }
-            for (int i = 0; i < mCountY; i++) {
-                leftEdge[i] = -1;
-                rightEdge[i] = -1;
-            }
-            dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
-            boundingRectDirty = true;
-        }
-
-        void computeEdge(int which) {
-            int count = views.size();
-            for (int i = 0; i < count; i++) {
-                CellAndSpan cs = config.map.get(views.get(i));
-                switch (which) {
-                    case LEFT:
-                        int left = cs.cellX;
-                        for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
-                            if (left < leftEdge[j] || leftEdge[j] < 0) {
-                                leftEdge[j] = left;
-                            }
-                        }
-                        break;
-                    case RIGHT:
-                        int right = cs.cellX + cs.spanX;
-                        for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
-                            if (right > rightEdge[j]) {
-                                rightEdge[j] = right;
-                            }
-                        }
-                        break;
-                    case TOP:
-                        int top = cs.cellY;
-                        for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
-                            if (top < topEdge[j] || topEdge[j] < 0) {
-                                topEdge[j] = top;
-                            }
-                        }
-                        break;
-                    case BOTTOM:
-                        int bottom = cs.cellY + cs.spanY;
-                        for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
-                            if (bottom > bottomEdge[j]) {
-                                bottomEdge[j] = bottom;
-                            }
-                        }
-                        break;
-                }
-            }
-        }
-
-        boolean isViewTouchingEdge(View v, int whichEdge) {
-            CellAndSpan cs = config.map.get(v);
-
-            if ((dirtyEdges & whichEdge) == whichEdge) {
-                computeEdge(whichEdge);
-                dirtyEdges &= ~whichEdge;
-            }
-
-            switch (whichEdge) {
-                case LEFT:
-                    for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
-                        if (leftEdge[i] == cs.cellX + cs.spanX) {
-                            return true;
-                        }
-                    }
-                    break;
-                case RIGHT:
-                    for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
-                        if (rightEdge[i] == cs.cellX) {
-                            return true;
-                        }
-                    }
-                    break;
-                case TOP:
-                    for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
-                        if (topEdge[i] == cs.cellY + cs.spanY) {
-                            return true;
-                        }
-                    }
-                    break;
-                case BOTTOM:
-                    for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
-                        if (bottomEdge[i] == cs.cellY) {
-                            return true;
-                        }
-                    }
-                    break;
-            }
-            return false;
-        }
-
-        void shift(int whichEdge, int delta) {
-            for (View v: views) {
-                CellAndSpan c = config.map.get(v);
-                switch (whichEdge) {
-                    case LEFT:
-                        c.cellX -= delta;
-                        break;
-                    case RIGHT:
-                        c.cellX += delta;
-                        break;
-                    case TOP:
-                        c.cellY -= delta;
-                        break;
-                    case BOTTOM:
-                    default:
-                        c.cellY += delta;
-                        break;
-                }
-            }
-            resetEdges();
-        }
-
-        public void addView(View v) {
-            views.add(v);
-            resetEdges();
-        }
-
-        public Rect getBoundingRect() {
-            if (boundingRectDirty) {
-                config.getBoundingRectForViews(views, boundingRect);
-            }
-            return boundingRect;
-        }
-
-        final PositionComparator comparator = new PositionComparator();
-        class PositionComparator implements Comparator<View> {
-            int whichEdge = 0;
-            public int compare(View left, View right) {
-                CellAndSpan l = config.map.get(left);
-                CellAndSpan r = config.map.get(right);
-                switch (whichEdge) {
-                    case LEFT:
-                        return (r.cellX + r.spanX) - (l.cellX + l.spanX);
-                    case RIGHT:
-                        return l.cellX - r.cellX;
-                    case TOP:
-                        return (r.cellY + r.spanY) - (l.cellY + l.spanY);
-                    case BOTTOM:
-                    default:
-                        return l.cellY - r.cellY;
-                }
-            }
-        }
-
-        public void sortConfigurationForEdgePush(int edge) {
-            comparator.whichEdge = edge;
-            Collections.sort(config.sortedViews, comparator);
-        }
-    }
-
     // This method tries to find a reordering solution which satisfies the push mechanic by trying
     // to push items in each of the cardinal directions, in an order based on the direction vector
     // passed.
@@ -2532,54 +2348,6 @@
     }
 
     /**
-     * Represents the solution to a reorder of items in the Workspace.
-     */
-    public static class ItemConfiguration extends CellAndSpan {
-        public final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
-        private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
-        public final ArrayList<View> sortedViews = new ArrayList<>();
-        public ArrayList<View> intersectingViews;
-        public boolean isSolution = false;
-
-        public void save() {
-            // Copy current state into savedMap
-            for (View v: map.keySet()) {
-                savedMap.get(v).copyFrom(map.get(v));
-            }
-        }
-
-        public void restore() {
-            // Restore current state from savedMap
-            for (View v: savedMap.keySet()) {
-                map.get(v).copyFrom(savedMap.get(v));
-            }
-        }
-
-        public void add(View v, CellAndSpan cs) {
-            map.put(v, cs);
-            savedMap.put(v, new CellAndSpan());
-            sortedViews.add(v);
-        }
-
-        public int area() {
-            return spanX * spanY;
-        }
-
-        public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
-            boolean first = true;
-            for (View v: views) {
-                CellAndSpan c = map.get(v);
-                if (first) {
-                    outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
-                    first = false;
-                } else {
-                    outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
-                }
-            }
-        }
-    }
-
-    /**
      * Find a starting cell position that will fit the given bounds nearest the requested
      * cell location. Uses Euclidean distance to score multiple vacant areas.
      *
@@ -2758,52 +2526,6 @@
         return new CellLayoutLayoutParams(p);
     }
 
-    // This class stores info for two purposes:
-    // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
-    //    its spanX, spanY, and the screen it is on
-    // 2. When long clicking on an empty cell in a CellLayout, we save information about the
-    //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
-    //    the CellLayout that was long clicked
-    public static final class CellInfo extends CellAndSpan {
-        public final View cell;
-        final int screenId;
-        final int container;
-
-        public CellInfo(View v, ItemInfo info, CellPos cellPos) {
-            cellX = cellPos.cellX;
-            cellY = cellPos.cellY;
-            spanX = info.spanX;
-            spanY = info.spanY;
-            cell = v;
-            screenId = cellPos.screenId;
-            container = info.container;
-        }
-
-        @Override
-        public String toString() {
-            return "Cell[view=" + (cell == null ? "null" : cell.getClass())
-                    + ", x=" + cellX + ", y=" + cellY + "]";
-        }
-    }
-
-    /**
-     * A Delegated cell Drawing for drawing on CellLayout
-     */
-    public abstract static class DelegatedCellDrawing {
-        public int mDelegateCellX;
-        public int mDelegateCellY;
-
-        /**
-         * Draw under CellLayout
-         */
-        public abstract void drawUnderItem(Canvas canvas);
-
-        /**
-         * Draw over CellLayout
-         */
-        public abstract void drawOverItem(Canvas canvas);
-    }
-
     /**
      * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
      * if necessary).
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index 4b5c9ef..123e8ca 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -23,6 +23,7 @@
 import android.view.View;
 
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.celllayout.ItemConfiguration;
 import com.android.launcher3.celllayout.MulticellReorderAlgorithm;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 30f3f5f..be4168d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -74,6 +74,7 @@
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.apppairs.AppPairIcon;
+import com.android.launcher3.celllayout.CellInfo;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -189,7 +190,7 @@
     /**
      * CellInfo for the cell that is currently being dragged
      */
-    protected CellLayout.CellInfo mDragInfo;
+    protected CellInfo mDragInfo;
 
     /**
      * Target drop area calculated during last acceptDrop call.
@@ -1620,7 +1621,7 @@
         page.setAccessibilityDelegate(null);
     }
 
-    public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
+    public void startDrag(CellInfo cellInfo, DragOptions options) {
         View child = cellInfo.cell;
 
         mDragInfo = cellInfo;
@@ -1784,7 +1785,7 @@
             int spanX;
             int spanY;
             if (mDragInfo != null) {
-                final CellLayout.CellInfo dragCellInfo = mDragInfo;
+                final CellInfo dragCellInfo = mDragInfo;
                 spanX = dragCellInfo.spanX;
                 spanY = dragCellInfo.spanY;
             } else {
@@ -3078,7 +3079,7 @@
      * so that Launcher can sync this object with the correct info when the activity is created/
      * destroyed
      */
-    public CellLayout.CellInfo getDragInfo() {
+    public CellInfo getDragInfo() {
         return mDragInfo;
     }
 
diff --git a/src/com/android/launcher3/celllayout/CellInfo.kt b/src/com/android/launcher3/celllayout/CellInfo.kt
new file mode 100644
index 0000000..5a3b7f7
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/CellInfo.kt
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+import android.view.View
+import com.android.launcher3.celllayout.CellPosMapper.CellPos
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.util.CellAndSpan
+
+// This class stores info for two purposes:
+// 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
+//    its spanX, spanY, and the screen it is on
+// 2. When long clicking on an empty cell in a CellLayout, we save information about the
+//    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
+//    the CellLayout that was long clicked
+class CellInfo(v: View?, info: ItemInfo, cellPos: CellPos) :
+    CellAndSpan(cellPos.cellX, cellPos.cellY, info.spanX, info.spanY) {
+    @JvmField val cell: View?
+    @JvmField val screenId: Int
+    @JvmField val container: Int
+
+    init {
+        cell = v
+        screenId = cellPos.screenId
+        container = info.container
+    }
+
+    override fun toString(): String {
+        return "CellInfo(cell=$cell, screenId=$screenId, container=$container)"
+    }
+}
diff --git a/src/com/android/launcher3/celllayout/DelegatedCellDrawing.kt b/src/com/android/launcher3/celllayout/DelegatedCellDrawing.kt
new file mode 100644
index 0000000..1703f9b
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/DelegatedCellDrawing.kt
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+import android.graphics.Canvas
+
+/** A Delegated cell Drawing for drawing on CellLayout */
+abstract class DelegatedCellDrawing {
+    @JvmField var mDelegateCellX = 0
+    @JvmField var mDelegateCellY = 0
+
+    /** Draw under CellLayout */
+    abstract fun drawUnderItem(canvas: Canvas)
+
+    /** Draw over CellLayout */
+    abstract fun drawOverItem(canvas: Canvas)
+}
diff --git a/src/com/android/launcher3/celllayout/ItemConfiguration.kt b/src/com/android/launcher3/celllayout/ItemConfiguration.kt
new file mode 100644
index 0000000..e775145
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ItemConfiguration.kt
@@ -0,0 +1,61 @@
+/*
+ * 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
+
+import android.graphics.Rect
+import android.util.ArrayMap
+import android.view.View
+import com.android.launcher3.util.CellAndSpan
+
+/** Represents the solution to a reorder of items in the Workspace. */
+class ItemConfiguration : CellAndSpan() {
+    @JvmField val map = ArrayMap<View, CellAndSpan>()
+    private val savedMap = ArrayMap<View, CellAndSpan>()
+
+    @JvmField val sortedViews = ArrayList<View>()
+
+    @JvmField var intersectingViews: ArrayList<View> = ArrayList()
+
+    @JvmField var isSolution = false
+    fun save() {
+        // Copy current state into savedMap
+        map.forEach { (k, v) -> savedMap[k]?.copyFrom(v) }
+    }
+
+    fun restore() {
+        // Restore current state from savedMap
+        savedMap.forEach { (k, v) -> map[k]?.copyFrom(v) }
+    }
+
+    fun add(v: View, cs: CellAndSpan) {
+        map[v] = cs
+        savedMap[v] = CellAndSpan()
+        sortedViews.add(v)
+    }
+
+    fun area(): Int {
+        return spanX * spanY
+    }
+
+    fun getBoundingRectForViews(views: ArrayList<View>, outRect: Rect) {
+        views
+            .mapNotNull { v -> map[v] }
+            .forEachIndexed { i, c ->
+                if (i == 0) outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY)
+                else outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY)
+            }
+    }
+}
diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
index a2e26b3..b7d8093 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 CellLayout.ItemConfiguration removeSeamFromSolution(
-            CellLayout.ItemConfiguration solution) {
+    private ItemConfiguration removeSeamFromSolution(
+            ItemConfiguration solution) {
         solution.map.forEach((view, cell) -> cell.cellX =
                 cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX);
         solution.cellX =
@@ -48,7 +48,7 @@
     }
 
     @Override
-    public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
+    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
             int minSpanX, int minSpanY,
             int spanX, int spanY) {
         return removeSeamFromSolution(simulateSeam(
@@ -57,16 +57,16 @@
     }
 
     @Override
-    public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+    public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            CellLayout.ItemConfiguration solution) {
+            ItemConfiguration solution) {
         return removeSeamFromSolution(simulateSeam(
                 () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
                         direction, dragView, decX, solution)));
     }
 
     @Override
-    public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
+    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
             int spanY,
             View dragView) {
         return removeSeamFromSolution(simulateSeam(
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index 17786f2..05bd13d 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -59,9 +59,9 @@
      * @param solution  variable to store the solution
      * @return the same solution variable
      */
-    public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+    public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            CellLayout.ItemConfiguration solution) {
+            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
@@ -110,11 +110,11 @@
      * @param dragView view being dragged in reorder
      * @return the configuration that represents the found reorder
      */
-    public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
+    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
             int spanY, View dragView) {
         int[] result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY,
                 new int[2]);
-        CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
+        ItemConfiguration solution = new ItemConfiguration();
         mCellLayout.copyCurrentStateToSolution(solution, false);
 
         solution.isSolution = !isConfigurationRegionOccupied(
@@ -133,7 +133,7 @@
     }
 
     private boolean isConfigurationRegionOccupied(Rect region,
-            CellLayout.ItemConfiguration configuration, View ignoreView) {
+            ItemConfiguration configuration, View ignoreView) {
         return configuration.map.entrySet()
                 .stream()
                 .filter(entry -> entry.getKey() != ignoreView)
@@ -153,9 +153,9 @@
      * @param spanY  vertical cell span
      * @return the configuration that represents the found reorder
      */
-    public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
+    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
             int minSpanX, int minSpanY, int spanX, int spanY) {
-        CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
+        ItemConfiguration solution = new ItemConfiguration();
         int[] result = new int[2];
         int[] resultSpan = new int[2];
         mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
@@ -188,22 +188,22 @@
      * @return returns a solution for the given parameters, the solution contains all the icons and
      * the locations they should be in the given solution.
      */
-    public CellLayout.ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
+    public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, View dragView) {
         mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
                 mCellLayout.mDirectionVector);
 
-        CellLayout.ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY,
+        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY,
                 spanX, spanY,
                 dragView);
 
         // Find a solution involving pushing / displacing any items in the way
-        CellLayout.ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
+        ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
                 minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
-                new CellLayout.ItemConfiguration());
+                new ItemConfiguration());
 
         // We attempt the approach which doesn't shuffle views at all
-        CellLayout.ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(
+        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(
                 pixelX, pixelY, minSpanX, minSpanY, spanX, spanY);
 
         // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
diff --git a/src/com/android/launcher3/celllayout/ViewCluster.kt b/src/com/android/launcher3/celllayout/ViewCluster.kt
new file mode 100644
index 0000000..49693e3
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ViewCluster.kt
@@ -0,0 +1,185 @@
+/*
+ * 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
+
+import android.graphics.Rect
+import android.view.View
+import com.android.launcher3.CellLayout
+import java.util.Collections
+
+/**
+ * This helper class defines a cluster of views. It helps with defining complex edges of the cluster
+ * and determining how those edges interact with other views. The edges essentially define a
+ * fine-grained boundary around the cluster of views -- like a more precise version of a bounding
+ * box.
+ */
+class ViewCluster(
+    private val mCellLayout: CellLayout,
+    views: ArrayList<View>,
+    val config: ItemConfiguration
+) {
+
+    @JvmField val views = ArrayList<View>(views)
+    private val boundingRect = Rect()
+
+    private val leftEdge = IntArray(mCellLayout.countY)
+    private val rightEdge = IntArray(mCellLayout.countY)
+    private val topEdge = IntArray(mCellLayout.countX)
+    private val bottomEdge = IntArray(mCellLayout.countX)
+
+    private var dirtyEdges = 0
+    private var boundingRectDirty = false
+
+    val comparator: PositionComparator = PositionComparator()
+
+    init {
+        resetEdges()
+    }
+    private fun resetEdges() {
+        for (i in 0 until mCellLayout.countX) {
+            topEdge[i] = -1
+            bottomEdge[i] = -1
+        }
+        for (i in 0 until mCellLayout.countY) {
+            leftEdge[i] = -1
+            rightEdge[i] = -1
+        }
+        dirtyEdges = LEFT or TOP or RIGHT or BOTTOM
+        boundingRectDirty = true
+    }
+
+    private fun computeEdge(which: Int) =
+        views
+            .mapNotNull { v -> config.map[v] }
+            .forEach { cs ->
+                val left = cs.cellX
+                val right = cs.cellX + cs.spanX
+                val top = cs.cellY
+                val bottom = cs.cellY + cs.spanY
+                when (which) {
+                    LEFT ->
+                        for (j in top until bottom) {
+                            if (left < leftEdge[j] || leftEdge[j] < 0) {
+                                leftEdge[j] = left
+                            }
+                        }
+                    RIGHT ->
+                        for (j in top until bottom) {
+                            if (right > rightEdge[j]) {
+                                rightEdge[j] = right
+                            }
+                        }
+                    TOP ->
+                        for (j in left until right) {
+                            if (top < topEdge[j] || topEdge[j] < 0) {
+                                topEdge[j] = top
+                            }
+                        }
+                    BOTTOM ->
+                        for (j in left until right) {
+                            if (bottom > bottomEdge[j]) {
+                                bottomEdge[j] = bottom
+                            }
+                        }
+                }
+            }
+
+    fun isViewTouchingEdge(v: View?, whichEdge: Int): Boolean {
+        val cs = config.map[v] ?: return false
+        val left = cs.cellX
+        val right = cs.cellX + cs.spanX
+        val top = cs.cellY
+        val bottom = cs.cellY + cs.spanY
+        if ((dirtyEdges and whichEdge) == whichEdge) {
+            computeEdge(whichEdge)
+            dirtyEdges = dirtyEdges and whichEdge.inv()
+        }
+        return when (whichEdge) {
+            // In this case if any of the values of leftEdge is equal to right, which is the
+            // rightmost x value of the view, it means that the cluster is touching the view from
+            // the left the same logic applies for the other sides.
+            LEFT -> edgeContainsValue(top, bottom, leftEdge, right)
+            RIGHT -> edgeContainsValue(top, bottom, rightEdge, left)
+            TOP -> edgeContainsValue(left, right, topEdge, bottom)
+            BOTTOM -> edgeContainsValue(left, right, bottomEdge, top)
+            else -> false
+        }
+    }
+
+    private fun edgeContainsValue(start: Int, end: Int, edge: IntArray, value: Int): Boolean {
+        for (i in start until end) {
+            if (edge[i] == value) {
+                return true
+            }
+        }
+        return false
+    }
+
+    fun shift(whichEdge: Int, delta: Int) {
+        views
+            .mapNotNull { v -> config.map[v] }
+            .forEach { c ->
+                when (whichEdge) {
+                    LEFT -> c.cellX -= delta
+                    RIGHT -> c.cellX += delta
+                    TOP -> c.cellY -= delta
+                    BOTTOM -> c.cellY += delta
+                    else -> c.cellY += delta
+                }
+            }
+        resetEdges()
+    }
+
+    fun addView(v: View) {
+        views.add(v)
+        resetEdges()
+    }
+
+    fun getBoundingRect(): Rect {
+        if (boundingRectDirty) {
+            config.getBoundingRectForViews(views, boundingRect)
+        }
+        return boundingRect
+    }
+
+    inner class PositionComparator : Comparator<View?> {
+        var whichEdge = 0
+        override fun compare(left: View?, right: View?): Int {
+            val l = config.map[left]
+            val r = config.map[right]
+            if (l == null || r == null) throw NullPointerException()
+            return when (whichEdge) {
+                LEFT -> r.cellX + r.spanX - (l.cellX + l.spanX)
+                RIGHT -> l.cellX - r.cellX
+                TOP -> r.cellY + r.spanY - (l.cellY + l.spanY)
+                BOTTOM -> l.cellY - r.cellY
+                else -> l.cellY - r.cellY
+            }
+        }
+    }
+
+    fun sortConfigurationForEdgePush(edge: Int) {
+        comparator.whichEdge = edge
+        Collections.sort(config.sortedViews, comparator)
+    }
+
+    companion object {
+        const val LEFT = 1 shl 0
+        const val TOP = 1 shl 1
+        const val RIGHT = 1 shl 2
+        const val BOTTOM = 1 shl 3
+    }
+}
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index b320ceb..ec03803 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -48,6 +48,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.celllayout.DelegatedCellDrawing;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
@@ -55,7 +56,7 @@
  * This object represents a FolderIcon preview background. It stores drawing / measurement
  * information, handles drawing, and animation (accept state <--> rest state).
  */
-public class PreviewBackground extends CellLayout.DelegatedCellDrawing {
+public class PreviewBackground extends DelegatedCellDrawing {
 
     private static final boolean DRAW_SHADOW = false;
     private static final boolean DRAW_STROKE = false;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 0c322cc..96cc412 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -27,9 +27,9 @@
 import android.view.View;
 import android.view.View.OnLongClickListener;
 
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.celllayout.CellInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -86,7 +86,7 @@
             }
         }
 
-        CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info,
+        CellInfo longClickCellInfo = new CellInfo(v, info,
                 launcher.getCellPosMapper().mapModelToPresenter(info));
         launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
     }
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 91a0634..15e2d70 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -105,7 +105,7 @@
         };
     }
 
-    public CellLayout.ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX,
+    public ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX,
             int spanY, int minSpanX, int minSpanY) {
         CellLayout cl = createCellLayout(board.getWidth(), board.getHeight());
 
@@ -123,17 +123,17 @@
 
         int[] testCaseXYinPixels = new int[2];
         cl.regionToCenterPoint(x, y, spanX, spanY, testCaseXYinPixels);
-        CellLayout.ItemConfiguration solution = cl.createReorderAlgorithm().calculateReorder(
+        ItemConfiguration solution = cl.createReorderAlgorithm().calculateReorder(
                 testCaseXYinPixels[0], testCaseXYinPixels[1], minSpanX, minSpanY, spanX, spanY,
                 null);
         if (solution == null) {
-            solution = new CellLayout.ItemConfiguration();
+            solution = new ItemConfiguration();
             solution.isSolution = false;
         }
         return solution;
     }
 
-    public CellLayoutBoard boardFromSolution(CellLayout.ItemConfiguration solution, int width,
+    public CellLayoutBoard boardFromSolution(ItemConfiguration solution, int width,
             int height) {
         // Update the views with solution value
         solution.map.forEach((key, val) -> key.setLayoutParams(
@@ -146,7 +146,7 @@
     }
 
     public void evaluateTestCase(ReorderAlgorithmUnitTestCase testCase) {
-        CellLayout.ItemConfiguration solution = solve(testCase.startBoard, testCase.x,
+        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,
@@ -197,7 +197,7 @@
         CellLayoutBoard board = generateBoard(new CellLayoutBoard(width, height),
                 new Rect(0, 0, width, height), targetWidth * targetHeight);
 
-        CellLayout.ItemConfiguration solution = solve(board, x, y, targetWidth, targetHeight,
+        ItemConfiguration solution = solve(board, x, y, targetWidth, targetHeight,
                 minTargetWidth, minTargetHeight);
 
         CellLayoutBoard finishBoard = solution.isSolution ? boardFromSolution(solution,