Decoupling the reorder logic from the CellLayout view
ReorderAlgorithm will now handle all the logic associated with the
reorder. Basically all the logic associated with a reorder in CellLayout
was copy and pasted into ReorderAlgorithm.java.
Test: atest TestReorderAlgorithm
Bug: 229292911
Change-Id: Ie096abc346bf705414e47452a42d1dec5be0a041
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index b96e4df..f514571 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -63,6 +63,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
+import com.android.launcher3.celllayout.ReorderAlgorithm;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
@@ -112,7 +113,7 @@
final PointF mTmpPointF = new PointF();
protected GridOccupancy mOccupied;
- protected GridOccupancy mTmpOccupied;
+ public GridOccupancy mTmpOccupied;
private OnTouchListener mInterceptTouchListener;
@@ -191,7 +192,7 @@
private final ArrayList<View> mIntersectingViews = new ArrayList<>();
private final Rect mOccupiedRect = new Rect();
- private final int[] mDirectionVector = new int[2];
+ public final int[] mDirectionVector = new int[2];
ItemConfiguration mPreviousSolution = null;
private static final int INVALID_DIRECTION = -100;
@@ -1243,8 +1244,8 @@
* @return The X, Y cell of a vacant area that can contain this object,
* nearest the requested location.
*/
- int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
- int spanY, int[] result, int[] resultSpan) {
+ public int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY, int[] result, int[] resultSpan) {
return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
result, resultSpan);
}
@@ -1377,6 +1378,10 @@
return bestXY;
}
+ public GridOccupancy getOccupied() {
+ return mOccupied;
+ }
+
private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
mTmpOccupied.clear();
@@ -1652,38 +1657,8 @@
}
}
- /**
- * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
- * any other item in the way.
- *
- * @param pixelX X coordinate in pixels in the screen
- * @param pixelY Y coordinate in pixels in the screen
- * @param spanX horizontal cell span
- * @param spanY vertical cell span
- * @return the configuration that represents the found reorder
- */
- ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
- int minSpanY, int spanX, int spanY) {
- ItemConfiguration solution = new ItemConfiguration();
- int[] result = new int[2];
- int[] resultSpan = new int[2];
- findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
- resultSpan);
- if (result[0] >= 0 && result[1] >= 0) {
- copyCurrentStateToSolution(solution, false);
- solution.cellX = result[0];
- solution.cellY = result[1];
- solution.spanX = resultSpan[0];
- solution.spanY = resultSpan[1];
- solution.isSolution = true;
- } else {
- solution.isSolution = false;
- }
- return solution;
- }
-
// For a given cell and span, fetch the set of views intersecting the region.
- private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
+ public void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
if (boundingRect != null) {
boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
@@ -1708,7 +1683,7 @@
}
}
- boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
+ public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
View dragView, int[] result) {
result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
@@ -2254,7 +2229,7 @@
those cells. Instead we use some heuristics to often lock the vector to up, down, left
or right, which helps make pushing feel right.
*/
- private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
+ public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
int spanY, View dragView, int[] resultDirection) {
//TODO(adamcohen) b/151776141 use the items visual center for the direction vector
@@ -2346,7 +2321,7 @@
return success;
}
- private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
+ public boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
View ignoreView, ItemConfiguration solution) {
// Return early if get invalid cell positions
if (cellX < 0 || cellY < 0) return false;
@@ -2402,55 +2377,18 @@
return true;
}
+ public ReorderAlgorithm createReorderAlgorithm() {
+ return new ReorderAlgorithm(this);
+ }
+
protected 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);
+ return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
+ spanX, spanY, direction, dragView, decX, solution);
}
- protected 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.
- copyCurrentStateToSolution(solution, false);
- // Copy the current occupied array into the temporary occupied array. This array will be
- // manipulated as necessary to find a solution.
- mOccupied.copyTo(mTmpOccupied);
-
- // We find the nearest cell into which we would place the dragged item, assuming there's
- // nothing in its way.
- int result[] = new int[2];
- result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
-
- boolean success;
- // First we try the exact nearest position of the item being dragged,
- // we will then want to try to move this around to other neighbouring positions
- success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
- solution);
-
- if (!success) {
- // 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 findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1,
- spanY, direction, dragView, false, solution);
- } else if (spanY > minSpanY) {
- return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX,
- spanY - 1, direction, dragView, true, solution);
- }
- solution.isSolution = false;
- } else {
- solution.isSolution = true;
- solution.cellX = result[0];
- solution.cellY = result[1];
- solution.spanX = spanX;
- solution.spanY = spanY;
- }
- return solution;
- }
-
- protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+ public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -2466,35 +2404,6 @@
}
/**
- * Returns a "reorder" if there is empty space without rearranging anything.
- *
- * @param pixelX X coordinate in pixels in the screen
- * @param pixelY Y coordinate in pixels in the screen
- * @param spanX horizontal cell span
- * @param spanY vertical cell span
- * @param dragView view being dragged in reorder
- * @return the configuration that represents the found reorder
- */
- public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
- int spanY, View dragView) {
- int[] result = new int[2];
- if (isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, result)) {
- result[0] = result[1] = -1;
- }
- ItemConfiguration solution = new ItemConfiguration();
- copyCurrentStateToSolution(solution, false);
- solution.isSolution = result[0] != -1;
- if (!solution.isSolution) {
- return solution;
- }
- solution.cellX = result[0];
- solution.cellY = result[1];
- solution.spanX = spanX;
- solution.spanY = spanY;
- return solution;
- }
-
- /**
* When the user drags an Item in the workspace sometimes we need to move the items already in
* the workspace to make space for the new item, this function return a solution for that
* reorder.
@@ -2511,29 +2420,8 @@
*/
public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
int spanX, int spanY, View dragView) {
- getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
-
- ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
- dragView);
-
- // Find a solution involving pushing / displacing any items in the way
- ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
- spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
-
- // We attempt the approach which doesn't shuffle views at all
- 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
- if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
- return swapSolution;
- } else if (closestSpaceSolution.isSolution) {
- return closestSpaceSolution;
- } else if (dropInPlaceSolution.isSolution) {
- return dropInPlaceSolution;
- }
- return null;
+ return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY,
+ spanX, spanY, dragView);
}
int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
@@ -2585,7 +2473,7 @@
* {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link MODE_ACCEPT_DROP}
* defined in {@link CellLayout}.
*/
- void performReorder(ItemConfiguration solution, View dragView, int mode) {
+ public void performReorder(ItemConfiguration solution, View dragView, int mode) {
if (mode == MODE_SHOW_REORDER_HINT) {
beginOrAdjustReorderPreviewAnimations(solution, dragView,
ReorderPreviewAnimation.MODE_HINT);
@@ -2631,38 +2519,41 @@
return mItemPlacementDirty;
}
- static class ItemConfiguration extends CellAndSpan {
- final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
+ /**
+ * 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<>();
- final ArrayList<View> sortedViews = new ArrayList<>();
- ArrayList<View> intersectingViews;
- boolean isSolution = false;
+ public final ArrayList<View> sortedViews = new ArrayList<>();
+ public ArrayList<View> intersectingViews;
+ public boolean isSolution = false;
- void save() {
+ public void save() {
// Copy current state into savedMap
for (View v: map.keySet()) {
savedMap.get(v).copyFrom(map.get(v));
}
}
- void restore() {
+ public void restore() {
// Restore current state from savedMap
for (View v: savedMap.keySet()) {
map.get(v).copyFrom(savedMap.get(v));
}
}
- void add(View v, CellAndSpan cs) {
+ public void add(View v, CellAndSpan cs) {
map.put(v, cs);
savedMap.put(v, new CellAndSpan());
sortedViews.add(v);
}
- int area() {
+ public int area() {
return spanX * spanY;
}
- void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
+ public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
boolean first = true;
for (View v: views) {
CellAndSpan c = map.get(v);
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index 6a518a7..12cb35d 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -23,11 +23,11 @@
import android.view.View;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.celllayout.MulticellReorderAlgorithm;
+import com.android.launcher3.celllayout.ReorderAlgorithm;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
-import java.util.function.Supplier;
-
/**
* CellLayout that simulates a split in the middle for use in foldable devices.
*/
@@ -36,8 +36,6 @@
private final Drawable mLeftBackground;
private final Drawable mRightBackground;
- private View mSeam;
-
private boolean mSeamWasAdded = false;
public MultipageCellLayout(Context context) {
@@ -62,7 +60,6 @@
mCountX = deviceProfile.inv.numColumns * 2;
mCountY = deviceProfile.inv.numRows;
- mSeam = new View(getContext());
setGridSize(mCountX, mCountY);
}
@@ -74,90 +71,18 @@
cellX++;
}
int finalCellX = cellX;
- return simulateSeam(
+ return ((MulticellReorderAlgorithm) createReorderAlgorithm()).simulateSeam(
() -> super.createAreaForResize(finalCellX, cellY, spanX, spanY, dragView,
direction, commit));
}
@Override
- 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)));
+ public ReorderAlgorithm createReorderAlgorithm() {
+ return new MulticellReorderAlgorithm(this);
}
@Override
- protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
- int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
- ItemConfiguration solution) {
- return removeSeamFromSolution(simulateSeam(
- () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
- direction, dragView, decX, solution)));
- }
-
- @Override
- public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY,
- View dragView) {
- return removeSeamFromSolution(simulateSeam(
- () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
- }
-
- void addSeam() {
- CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mCountX / 2, 0, 1, mCountY);
- mSeamWasAdded = true;
- lp.canReorder = false;
- mCountX++;
- mShortcutsAndWidgets.addViewInLayout(mSeam, lp);
- mOccupied = createGridOccupancyWithSeam(mOccupied);
- mTmpOccupied = new GridOccupancy(mCountX, mCountY);
- }
-
- void removeSeam() {
- mCountX--;
- mShortcutsAndWidgets.removeViewInLayout(mSeam);
- mTmpOccupied = new GridOccupancy(mCountX, mCountY);
- mSeamWasAdded = false;
- }
-
- protected <T> T simulateSeam(Supplier<T> f) {
- if (mSeamWasAdded) {
- return f.get();
- }
- GridOccupancy auxGrid = mOccupied;
- addSeam();
- T res = f.get();
- removeSeam();
- mOccupied = auxGrid;
- return res;
- }
-
- private ItemConfiguration removeSeamFromSolution(ItemConfiguration solution) {
- solution.map.forEach((view, cell) -> cell.cellX = cell.cellX > mCountX / 2
- ? cell.cellX - 1 : cell.cellX);
- solution.cellX = solution.cellX > mCountX / 2 ? solution.cellX - 1 : solution.cellX;
- return solution;
- }
-
-
-
- GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
- GridOccupancy grid = new GridOccupancy(getCountX(), getCountY());
- for (int x = 0; x < getCountX(); x++) {
- for (int y = 0; y < getCountY(); y++) {
- int offset = x >= getCountX() / 2 ? 1 : 0;
- if (x == getCountX() / 2) {
- grid.cells[x][y] = true;
- } else {
- grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
- }
- }
- }
- return grid;
- }
-
- @Override
- protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+ public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -196,4 +121,24 @@
mLeftBackground.setBounds(rect.left, rect.top, rect.right / 2 - 20, rect.bottom);
mRightBackground.setBounds(rect.right / 2 + 20, rect.top, rect.right, rect.bottom);
}
+
+ public void setCountX(int countX) {
+ mCountX = countX;
+ }
+
+ public void setCountY(int countY) {
+ mCountY = countY;
+ }
+
+ public void setOccupied(GridOccupancy occupied) {
+ mOccupied = occupied;
+ }
+
+ public boolean isSeamWasAdded() {
+ return mSeamWasAdded;
+ }
+
+ public void setSeamWasAdded(boolean seamWasAdded) {
+ mSeamWasAdded = seamWasAdded;
+ }
}
diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
new file mode 100644
index 0000000..cb12161
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+import com.android.launcher3.MultipageCellLayout;
+import com.android.launcher3.util.GridOccupancy;
+
+import java.util.function.Supplier;
+
+/**
+ * Variant of ReorderAlgorithm which simulates a foldable screen and adds a seam in the middle
+ * to prevent items to be placed in the middle.
+ */
+public class MulticellReorderAlgorithm extends ReorderAlgorithm {
+
+ private final View mSeam;
+
+ public MulticellReorderAlgorithm(CellLayout cellLayout) {
+ super(cellLayout);
+ mSeam = new View(cellLayout.getContext());
+ }
+
+ private CellLayout.ItemConfiguration removeSeamFromSolution(
+ CellLayout.ItemConfiguration solution) {
+ solution.map.forEach((view, cell) -> cell.cellX =
+ cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX);
+ solution.cellX =
+ solution.cellX > mCellLayout.getCountX() / 2 ? solution.cellX - 1 : solution.cellX;
+ return solution;
+ }
+
+ @Override
+ public CellLayout.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)));
+ }
+
+ @Override
+ public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+ int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+ CellLayout.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,
+ int spanY,
+ View dragView) {
+ return removeSeamFromSolution(simulateSeam(
+ () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
+ }
+
+ void addSeam() {
+ MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
+ mcl.setSeamWasAdded(true);
+ CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mcl.getCountX() / 2, 0, 1,
+ mcl.getCountY());
+ lp.canReorder = false;
+ mcl.setCountX(mcl.getCountX() + 1);
+ mcl.getShortcutsAndWidgets().addViewInLayout(mSeam, lp);
+ mcl.setOccupied(createGridOccupancyWithSeam(mcl.getOccupied()));
+ mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY());
+ }
+
+ void removeSeam() {
+ MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
+ mcl.setCountX(mcl.getCountX() - 1);
+ mcl.getShortcutsAndWidgets().removeViewInLayout(mSeam);
+ mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY());
+ mcl.setSeamWasAdded(false);
+ }
+
+ /**
+ * The function supplied here will execute while the CellLayout has a simulated seam added.
+ * @param f function to run under simulation
+ * @param <T> return value of the supplied function
+ * @return Value of supplied function
+ */
+ public <T> T simulateSeam(Supplier<T> f) {
+ MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
+ if (mcl.isSeamWasAdded()) {
+ return f.get();
+ }
+ GridOccupancy auxGrid = mcl.getOccupied();
+ addSeam();
+ T res = f.get();
+ removeSeam();
+ mcl.setOccupied(auxGrid);
+ return res;
+ }
+
+ GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
+ GridOccupancy grid = new GridOccupancy(mCellLayout.getCountX(), mCellLayout.getCountY());
+ for (int x = 0; x < mCellLayout.getCountX(); x++) {
+ for (int y = 0; y < mCellLayout.getCountY(); y++) {
+ int offset = x >= mCellLayout.getCountX() / 2 ? 1 : 0;
+ if (x == mCellLayout.getCountX() / 2) {
+ grid.cells[x][y] = true;
+ } else {
+ grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
+ }
+ }
+ }
+ return grid;
+ }
+}
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
new file mode 100644
index 0000000..5e5eefe
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -0,0 +1,204 @@
+/*
+ * 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;
+
+/**
+ * Contains the logic of a reorder.
+ *
+ * The content of this class was extracted from {@link CellLayout} and should mimic the exact
+ * same behaviour.
+ */
+public class ReorderAlgorithm {
+
+ CellLayout mCellLayout;
+
+ public ReorderAlgorithm(CellLayout cellLayout) {
+ mCellLayout = cellLayout;
+ }
+
+ /**
+ * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method
+ * will move items around and will change the shape of the item if possible to try to find a
+ * solution.
+ *
+ * When changing the size of the widget this method will try first subtracting -1 in the x
+ * dimension and then subtracting -1 in the y dimension until finding a possible solution or
+ * until it no longer can reduce the span.
+ *
+ * @param pixelX X coordinate in pixels in the screen
+ * @param pixelY Y coordinate in pixels in the screen
+ * @param minSpanX minimum possible horizontal span it will try to find a solution for.
+ * @param minSpanY minimum possible vertical span it will try to find a solution for.
+ * @param spanX horizontal cell span
+ * @param spanY vertical cell span
+ * @param direction direction in which it will try to push the items intersecting the desired
+ * view
+ * @param dragView view being dragged in reorder
+ * @param decX whether it will decrease the horizontal or vertical span if it can't find a
+ * solution for the current span.
+ * @param solution variable to store the solution
+ * @return the same solution variable
+ */
+ public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+ int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+ CellLayout.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
+ // manipulated as necessary to find a solution.
+ mCellLayout.getOccupied().copyTo(mCellLayout.mTmpOccupied);
+
+ // We find the nearest cell into which we would place the dragged item, assuming there's
+ // nothing in its way.
+ int[] result = new int[2];
+ result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
+
+ boolean success;
+ // First we try the exact nearest position of the item being dragged,
+ // we will then want to try to move this around to other neighbouring positions
+ success = mCellLayout.rearrangementExists(result[0], result[1], spanX, spanY, direction,
+ dragView, solution);
+
+ if (!success) {
+ // 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);
+ } else if (spanY > minSpanY) {
+ return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
+ direction, dragView, true, solution);
+ }
+ solution.isSolution = false;
+ } else {
+ solution.isSolution = true;
+ solution.cellX = result[0];
+ solution.cellY = result[1];
+ solution.spanX = spanX;
+ solution.spanY = spanY;
+ }
+ return solution;
+ }
+
+ /**
+ * Returns a "reorder" if there is empty space without rearranging anything.
+ *
+ * @param pixelX X coordinate in pixels in the screen
+ * @param pixelY Y coordinate in pixels in the screen
+ * @param spanX horizontal cell span
+ * @param spanY vertical cell span
+ * @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,
+ int spanY, View dragView) {
+ int[] result = new int[2];
+ if (mCellLayout.isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView,
+ result)) {
+ result[0] = result[1] = -1;
+ }
+ CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
+ mCellLayout.copyCurrentStateToSolution(solution, false);
+ solution.isSolution = result[0] != -1;
+ if (!solution.isSolution) {
+ return solution;
+ }
+ solution.cellX = result[0];
+ solution.cellY = result[1];
+ solution.spanX = spanX;
+ solution.spanY = spanY;
+ return solution;
+ }
+
+ /**
+ * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
+ * any other item in the way.
+ *
+ * @param pixelX X coordinate in pixels in the screen
+ * @param pixelY Y coordinate in pixels in the screen
+ * @param spanX horizontal cell span
+ * @param spanY vertical cell span
+ * @return the configuration that represents the found reorder
+ */
+ public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
+ int minSpanX, int minSpanY, int spanX, int spanY) {
+ CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
+ int[] result = new int[2];
+ int[] resultSpan = new int[2];
+ mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
+ resultSpan);
+ if (result[0] >= 0 && result[1] >= 0) {
+ mCellLayout.copyCurrentStateToSolution(solution, false);
+ solution.cellX = result[0];
+ solution.cellY = result[1];
+ solution.spanX = resultSpan[0];
+ solution.spanY = resultSpan[1];
+ solution.isSolution = true;
+ } else {
+ solution.isSolution = false;
+ }
+ return solution;
+ }
+
+ /**
+ * When the user drags an Item in the workspace sometimes we need to move the items already in
+ * the workspace to make space for the new item, this function return a solution for that
+ * reorder.
+ *
+ * @param pixelX X coordinate in the screen of the dragView in pixels
+ * @param pixelY Y coordinate in the screen of the dragView in pixels
+ * @param minSpanX minimum horizontal span the item can be shrunk to
+ * @param minSpanY minimum vertical span the item can be shrunk to
+ * @param spanX occupied horizontal span
+ * @param spanY occupied vertical span
+ * @param dragView the view of the item being draged
+ * @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,
+ int minSpanY, int spanX, int spanY, View dragView) {
+ mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
+ mCellLayout.mDirectionVector);
+
+ CellLayout.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,
+ minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
+ new CellLayout.ItemConfiguration());
+
+ // We attempt the approach which doesn't shuffle views at all
+ CellLayout.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
+ if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
+ return swapSolution;
+ } else if (closestSpaceSolution.isSolution) {
+ return closestSpaceSolution;
+ } else if (dropInPlaceSolution.isSolution) {
+ return dropInPlaceSolution;
+ }
+ return null;
+ }
+}