No code changes, this cl only reorganizes the functions

This is done such that future cls are easier to visualize.

Bug: 188081026
Test: atest ReorderWidgets
Change-Id: I749d21b517dada97887d5b5e7cb5e2ac644d0030
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 19afa78..9d059bb 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1396,649 +1396,6 @@
         return bestXY;
     }
 
-    /**
-     * Find a vacant area that will fit the given bounds nearest the requested
-     * cell location, and will also weigh in a suggested direction vector of the
-     * desired location. This method computers distance based on unit grid distances,
-     * not pixel distances.
-     *
-     * @param cellX The X cell nearest to which you want to search for a vacant area.
-     * @param cellY The Y cell nearest which you want to search for a vacant area.
-     * @param spanX Horizontal span of the object.
-     * @param spanY Vertical span of the object.
-     * @param direction The favored direction in which the views should move from x, y
-     * @param occupied The array which represents which cells in the CellLayout are occupied
-     * @param blockOccupied The array which represents which cells in the specified block (cellX,
-     *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
-     * @param result Array in which to place the result, or null (in which case a new array will
-     *        be allocated)
-     * @return The X, Y cell of a vacant area that can contain this object,
-     *         nearest the requested location.
-     */
-    private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
-            boolean[][] occupied, boolean blockOccupied[][], int[] result) {
-        // Keep track of best-scoring drop area
-        final int[] bestXY = result != null ? result : new int[2];
-        float bestDistance = Float.MAX_VALUE;
-        int bestDirectionScore = Integer.MIN_VALUE;
-
-        final int countX = mCountX;
-        final int countY = mCountY;
-
-        for (int y = 0; y < countY - (spanY - 1); y++) {
-            inner:
-            for (int x = 0; x < countX - (spanX - 1); x++) {
-                // First, let's see if this thing fits anywhere
-                for (int i = 0; i < spanX; i++) {
-                    for (int j = 0; j < spanY; j++) {
-                        if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
-                            continue inner;
-                        }
-                    }
-                }
-
-                float distance = (float) Math.hypot(x - cellX, y - cellY);
-                int[] curDirection = mTmpPoint;
-                computeDirectionVector(x - cellX, y - cellY, curDirection);
-                // The direction score is just the dot product of the two candidate direction
-                // and that passed in.
-                int curDirectionScore = direction[0] * curDirection[0] +
-                        direction[1] * curDirection[1];
-                if (Float.compare(distance,  bestDistance) < 0 ||
-                        (Float.compare(distance, bestDistance) == 0
-                                && curDirectionScore > bestDirectionScore)) {
-                    bestDistance = distance;
-                    bestDirectionScore = curDirectionScore;
-                    bestXY[0] = x;
-                    bestXY[1] = y;
-                }
-            }
-        }
-
-        // Return -1, -1 if no suitable location found
-        if (bestDistance == Float.MAX_VALUE) {
-            bestXY[0] = -1;
-            bestXY[1] = -1;
-        }
-        return bestXY;
-    }
-
-    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
-            int[] direction, ItemConfiguration currentState) {
-        CellAndSpan c = currentState.map.get(v);
-        boolean success = false;
-        mTmpOccupied.markCells(c, false);
-        mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
-
-        findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
-                mTmpOccupied.cells, null, mTempLocation);
-
-        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
-            c.cellX = mTempLocation[0];
-            c.cellY = mTempLocation[1];
-            success = true;
-        }
-        mTmpOccupied.markCells(c, true);
-        return success;
-    }
-
-    /**
-     * 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);
-        }
-    }
-
-    private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
-            int[] direction, View dragView, ItemConfiguration currentState) {
-
-        ViewCluster cluster = new ViewCluster(views, currentState);
-        Rect clusterRect = cluster.getBoundingRect();
-        int whichEdge;
-        int pushDistance;
-        boolean fail = false;
-
-        // Determine the edge of the cluster that will be leading the push and how far
-        // the cluster must be shifted.
-        if (direction[0] < 0) {
-            whichEdge = ViewCluster.LEFT;
-            pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
-        } else if (direction[0] > 0) {
-            whichEdge = ViewCluster.RIGHT;
-            pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
-        } else if (direction[1] < 0) {
-            whichEdge = ViewCluster.TOP;
-            pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
-        } else {
-            whichEdge = ViewCluster.BOTTOM;
-            pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
-        }
-
-        // Break early for invalid push distance.
-        if (pushDistance <= 0) {
-            return false;
-        }
-
-        // Mark the occupied state as false for the group of views we want to move.
-        for (View v: views) {
-            CellAndSpan c = currentState.map.get(v);
-            mTmpOccupied.markCells(c, false);
-        }
-
-        // We save the current configuration -- if we fail to find a solution we will revert
-        // to the initial state. The process of finding a solution modifies the configuration
-        // in place, hence the need for revert in the failure case.
-        currentState.save();
-
-        // The pushing algorithm is simplified by considering the views in the order in which
-        // they would be pushed by the cluster. For example, if the cluster is leading with its
-        // left edge, we consider sort the views by their right edge, from right to left.
-        cluster.sortConfigurationForEdgePush(whichEdge);
-
-        while (pushDistance > 0 && !fail) {
-            for (View v: currentState.sortedViews) {
-                // For each view that isn't in the cluster, we see if the leading edge of the
-                // cluster is contacting the edge of that view. If so, we add that view to the
-                // cluster.
-                if (!cluster.views.contains(v) && v != dragView) {
-                    if (cluster.isViewTouchingEdge(v, whichEdge)) {
-                        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
-                        if (!lp.canReorder) {
-                            // The push solution includes the all apps button, this is not viable.
-                            fail = true;
-                            break;
-                        }
-                        cluster.addView(v);
-                        CellAndSpan c = currentState.map.get(v);
-
-                        // Adding view to cluster, mark it as not occupied.
-                        mTmpOccupied.markCells(c, false);
-                    }
-                }
-            }
-            pushDistance--;
-
-            // The cluster has been completed, now we move the whole thing over in the appropriate
-            // direction.
-            cluster.shift(whichEdge, 1);
-        }
-
-        boolean foundSolution = false;
-        clusterRect = cluster.getBoundingRect();
-
-        // Due to the nature of the algorithm, the only check required to verify a valid solution
-        // is to ensure that completed shifted cluster lies completely within the cell layout.
-        if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
-                clusterRect.bottom <= mCountY) {
-            foundSolution = true;
-        } else {
-            currentState.restore();
-        }
-
-        // In either case, we set the occupied array as marked for the location of the views
-        for (View v: cluster.views) {
-            CellAndSpan c = currentState.map.get(v);
-            mTmpOccupied.markCells(c, true);
-        }
-
-        return foundSolution;
-    }
-
-    private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
-            int[] direction, View dragView, ItemConfiguration currentState) {
-        if (views.size() == 0) return true;
-
-        boolean success = false;
-        Rect boundingRect = new Rect();
-        // We construct a rect which represents the entire group of views passed in
-        currentState.getBoundingRectForViews(views, boundingRect);
-
-        // Mark the occupied state as false for the group of views we want to move.
-        for (View v: views) {
-            CellAndSpan c = currentState.map.get(v);
-            mTmpOccupied.markCells(c, false);
-        }
-
-        GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
-        int top = boundingRect.top;
-        int left = boundingRect.left;
-        // We mark more precisely which parts of the bounding rect are truly occupied, allowing
-        // for interlocking.
-        for (View v: views) {
-            CellAndSpan c = currentState.map.get(v);
-            blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
-        }
-
-        mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
-
-        findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
-                boundingRect.height(), direction,
-                mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
-
-        // If we successfuly found a location by pushing the block of views, we commit it
-        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
-            int deltaX = mTempLocation[0] - boundingRect.left;
-            int deltaY = mTempLocation[1] - boundingRect.top;
-            for (View v: views) {
-                CellAndSpan c = currentState.map.get(v);
-                c.cellX += deltaX;
-                c.cellY += deltaY;
-            }
-            success = true;
-        }
-
-        // In either case, we set the occupied array as marked for the location of the views
-        for (View v: views) {
-            CellAndSpan c = currentState.map.get(v);
-            mTmpOccupied.markCells(c, true);
-        }
-        return success;
-    }
-
-    // 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.
-    private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
-            int[] direction, View ignoreView, ItemConfiguration solution) {
-        if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
-            // If the direction vector has two non-zero components, we try pushing
-            // separately in each of the components.
-            int temp = direction[1];
-            direction[1] = 0;
-
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-            direction[1] = temp;
-            temp = direction[0];
-            direction[0] = 0;
-
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-            // Revert the direction
-            direction[0] = temp;
-
-            // Now we try pushing in each component of the opposite direction
-            direction[0] *= -1;
-            direction[1] *= -1;
-            temp = direction[1];
-            direction[1] = 0;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-
-            direction[1] = temp;
-            temp = direction[0];
-            direction[0] = 0;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-            // revert the direction
-            direction[0] = temp;
-            direction[0] *= -1;
-            direction[1] *= -1;
-
-        } else {
-            // If the direction vector has a single non-zero component, we push first in the
-            // direction of the vector
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-            // Then we try the opposite direction
-            direction[0] *= -1;
-            direction[1] *= -1;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-            // Switch the direction back
-            direction[0] *= -1;
-            direction[1] *= -1;
-
-            // If we have failed to find a push solution with the above, then we try
-            // to find a solution by pushing along the perpendicular axis.
-
-            // Swap the components
-            int temp = direction[1];
-            direction[1] = direction[0];
-            direction[0] = temp;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-
-            // Then we try the opposite direction
-            direction[0] *= -1;
-            direction[1] *= -1;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
-                return true;
-            }
-            // Switch the direction back
-            direction[0] *= -1;
-            direction[1] *= -1;
-
-            // Swap the components back
-            temp = direction[1];
-            direction[1] = direction[0];
-            direction[0] = temp;
-        }
-        return false;
-    }
-
-    private 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;
-
-        mIntersectingViews.clear();
-        mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
-
-        // Mark the desired location of the view currently being dragged.
-        if (ignoreView != null) {
-            CellAndSpan c = solution.map.get(ignoreView);
-            if (c != null) {
-                c.cellX = cellX;
-                c.cellY = cellY;
-            }
-        }
-        Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
-        Rect r1 = new Rect();
-        for (View child: solution.map.keySet()) {
-            if (child == ignoreView) continue;
-            CellAndSpan c = solution.map.get(child);
-            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
-            r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
-            if (Rect.intersects(r0, r1)) {
-                if (!lp.canReorder) {
-                    return false;
-                }
-                mIntersectingViews.add(child);
-            }
-        }
-
-        solution.intersectingViews = new ArrayList<>(mIntersectingViews);
-
-        // First we try to find a solution which respects the push mechanic. That is,
-        // we try to find a solution such that no displaced item travels through another item
-        // without also displacing that item.
-        if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
-                solution)) {
-            return true;
-        }
-
-        // Next we try moving the views as a block, but without requiring the push mechanic.
-        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
-                solution)) {
-            return true;
-        }
-
-        // Ok, they couldn't move as a block, let's move them individually
-        for (View v : mIntersectingViews) {
-            if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /*
-     * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
-     * the provided point and the provided cell
-     */
-    private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
-        double angle = Math.atan(deltaY / deltaX);
-
-        result[0] = 0;
-        result[1] = 0;
-        if (Math.abs(Math.cos(angle)) > 0.5f) {
-            result[0] = (int) Math.signum(deltaX);
-        }
-        if (Math.abs(Math.sin(angle)) > 0.5f) {
-            result[1] = (int) Math.signum(deltaY);
-        }
-    }
-
-    private ItemConfiguration findReorderSolution(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 = findNearestArea(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 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;
-    }
-
-    private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
-        int childCount = mShortcutsAndWidgets.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mShortcutsAndWidgets.getChildAt(i);
-            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
-            CellAndSpan c;
-            if (temp) {
-                c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
-            } else {
-                c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
-            }
-            solution.add(child, c);
-        }
-    }
-
     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
         mTmpOccupied.clear();
 
@@ -2335,54 +1692,6 @@
         return solution;
     }
 
-    /* This seems like it should be obvious and straight-forward, but when the direction vector
-    needs to match with the notion of the dragView pushing other views, we have to employ
-    a slightly more subtle notion of the direction vector. The question is what two points is
-    the vector between? The center of the dragView and its desired destination? Not quite, as
-    this doesn't necessarily coincide with the interaction of the dragView and items occupying
-    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,
-            int spanY, View dragView, int[] resultDirection) {
-
-        //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
-        int[] targetDestination = new int[2];
-
-        findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
-        Rect dragRect = new Rect();
-        cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
-        dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
-
-        Rect dropRegionRect = new Rect();
-        getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
-                dragView, dropRegionRect, mIntersectingViews);
-
-        int dropRegionSpanX = dropRegionRect.width();
-        int dropRegionSpanY = dropRegionRect.height();
-
-        cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
-                dropRegionRect.height(), dropRegionRect);
-
-        int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
-        int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
-
-        if (dropRegionSpanX == mCountX || spanX == mCountX) {
-            deltaX = 0;
-        }
-        if (dropRegionSpanY == mCountY || spanY == mCountY) {
-            deltaY = 0;
-        }
-
-        if (deltaX == 0 && deltaY == 0) {
-            // No idea what to do, give a random direction.
-            resultDirection[0] = 1;
-            resultDirection[1] = 0;
-        } else {
-            computeDirectionVector(deltaX, deltaY, resultDirection);
-        }
-    }
-
     // 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,
             View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
@@ -2466,42 +1775,695 @@
         return swapSolution.isSolution;
     }
 
-    int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
-            View dragView, int[] result, int[] resultSpan, int mode) {
-        if (resultSpan == null) {
-            resultSpan = new int[]{-1, -1};
-        }
-        if (result == null) {
-            result = new int[]{-1, -1};
-        }
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location, and will also weigh in a suggested direction vector of the
+     * desired location. This method computers distance based on unit grid distances,
+     * not pixel distances.
+     *
+     * @param cellX The X cell nearest to which you want to search for a vacant area.
+     * @param cellY The Y cell nearest which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param direction The favored direction in which the views should move from x, y
+     * @param occupied The array which represents which cells in the CellLayout are occupied
+     * @param blockOccupied The array which represents which cells in the specified block (cellX,
+     *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
+            boolean[][] occupied, boolean blockOccupied[][], int[] result) {
+        // Keep track of best-scoring drop area
+        final int[] bestXY = result != null ? result : new int[2];
+        float bestDistance = Float.MAX_VALUE;
+        int bestDirectionScore = Integer.MIN_VALUE;
 
-        ItemConfiguration finalSolution;
-        // When we are checking drop validity or actually dropping, we don't recompute the
-        // direction vector, since we want the solution to match the preview, and it's possible
-        // that the exact position of the item has changed to result in a new reordering outcome.
-        if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
-                && mPreviousSolution != null) {
-            finalSolution = mPreviousSolution;
-            // We reset this vector after drop
-            if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
-                mPreviousSolution = null;
+        final int countX = mCountX;
+        final int countY = mCountY;
+
+        for (int y = 0; y < countY - (spanY - 1); y++) {
+            inner:
+            for (int x = 0; x < countX - (spanX - 1); x++) {
+                // First, let's see if this thing fits anywhere
+                for (int i = 0; i < spanX; i++) {
+                    for (int j = 0; j < spanY; j++) {
+                        if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
+                            continue inner;
+                        }
+                    }
+                }
+
+                float distance = (float) Math.hypot(x - cellX, y - cellY);
+                int[] curDirection = mTmpPoint;
+                computeDirectionVector(x - cellX, y - cellY, curDirection);
+                // The direction score is just the dot product of the two candidate direction
+                // and that passed in.
+                int curDirectionScore = direction[0] * curDirection[0] +
+                        direction[1] * curDirection[1];
+                if (Float.compare(distance,  bestDistance) < 0 ||
+                        (Float.compare(distance, bestDistance) == 0
+                                && curDirectionScore > bestDirectionScore)) {
+                    bestDistance = distance;
+                    bestDirectionScore = curDirectionScore;
+                    bestXY[0] = x;
+                    bestXY[1] = y;
+                }
             }
-        } else {
-            finalSolution = calculateReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                    dragView);
-            mPreviousSolution = finalSolution;
         }
 
-        if (finalSolution == null || !finalSolution.isSolution) {
-            result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
-        } else {
-            result[0] = finalSolution.cellX;
-            result[1] = finalSolution.cellY;
-            resultSpan[0] = finalSolution.spanX;
-            resultSpan[1] = finalSolution.spanY;
+        // Return -1, -1 if no suitable location found
+        if (bestDistance == Float.MAX_VALUE) {
+            bestXY[0] = -1;
+            bestXY[1] = -1;
         }
-        performReorder(finalSolution, dragView, mode);
-        return result;
+        return bestXY;
+    }
+
+    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
+            int[] direction, ItemConfiguration currentState) {
+        CellAndSpan c = currentState.map.get(v);
+        boolean success = false;
+        mTmpOccupied.markCells(c, false);
+        mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
+
+        findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
+                mTmpOccupied.cells, null, mTempLocation);
+
+        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+            c.cellX = mTempLocation[0];
+            c.cellY = mTempLocation[1];
+            success = true;
+        }
+        mTmpOccupied.markCells(c, true);
+        return success;
+    }
+
+    private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+            int[] direction, View dragView, ItemConfiguration currentState) {
+
+        ViewCluster cluster = new ViewCluster(views, currentState);
+        Rect clusterRect = cluster.getBoundingRect();
+        int whichEdge;
+        int pushDistance;
+        boolean fail = false;
+
+        // Determine the edge of the cluster that will be leading the push and how far
+        // the cluster must be shifted.
+        if (direction[0] < 0) {
+            whichEdge = ViewCluster.LEFT;
+            pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
+        } else if (direction[0] > 0) {
+            whichEdge = ViewCluster.RIGHT;
+            pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
+        } else if (direction[1] < 0) {
+            whichEdge = ViewCluster.TOP;
+            pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
+        } else {
+            whichEdge = ViewCluster.BOTTOM;
+            pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
+        }
+
+        // Break early for invalid push distance.
+        if (pushDistance <= 0) {
+            return false;
+        }
+
+        // Mark the occupied state as false for the group of views we want to move.
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            mTmpOccupied.markCells(c, false);
+        }
+
+        // We save the current configuration -- if we fail to find a solution we will revert
+        // to the initial state. The process of finding a solution modifies the configuration
+        // in place, hence the need for revert in the failure case.
+        currentState.save();
+
+        // The pushing algorithm is simplified by considering the views in the order in which
+        // they would be pushed by the cluster. For example, if the cluster is leading with its
+        // left edge, we consider sort the views by their right edge, from right to left.
+        cluster.sortConfigurationForEdgePush(whichEdge);
+
+        while (pushDistance > 0 && !fail) {
+            for (View v: currentState.sortedViews) {
+                // For each view that isn't in the cluster, we see if the leading edge of the
+                // cluster is contacting the edge of that view. If so, we add that view to the
+                // cluster.
+                if (!cluster.views.contains(v) && v != dragView) {
+                    if (cluster.isViewTouchingEdge(v, whichEdge)) {
+                        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
+                        if (!lp.canReorder) {
+                            // The push solution includes the all apps button, this is not viable.
+                            fail = true;
+                            break;
+                        }
+                        cluster.addView(v);
+                        CellAndSpan c = currentState.map.get(v);
+
+                        // Adding view to cluster, mark it as not occupied.
+                        mTmpOccupied.markCells(c, false);
+                    }
+                }
+            }
+            pushDistance--;
+
+            // The cluster has been completed, now we move the whole thing over in the appropriate
+            // direction.
+            cluster.shift(whichEdge, 1);
+        }
+
+        boolean foundSolution = false;
+        clusterRect = cluster.getBoundingRect();
+
+        // Due to the nature of the algorithm, the only check required to verify a valid solution
+        // is to ensure that completed shifted cluster lies completely within the cell layout.
+        if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
+                clusterRect.bottom <= mCountY) {
+            foundSolution = true;
+        } else {
+            currentState.restore();
+        }
+
+        // In either case, we set the occupied array as marked for the location of the views
+        for (View v: cluster.views) {
+            CellAndSpan c = currentState.map.get(v);
+            mTmpOccupied.markCells(c, true);
+        }
+
+        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.
+    private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
+            int[] direction, View ignoreView, ItemConfiguration solution) {
+        if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
+            // If the direction vector has two non-zero components, we try pushing
+            // separately in each of the components.
+            int temp = direction[1];
+            direction[1] = 0;
+
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            direction[1] = temp;
+            temp = direction[0];
+            direction[0] = 0;
+
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Revert the direction
+            direction[0] = temp;
+
+            // Now we try pushing in each component of the opposite direction
+            direction[0] *= -1;
+            direction[1] *= -1;
+            temp = direction[1];
+            direction[1] = 0;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+
+            direction[1] = temp;
+            temp = direction[0];
+            direction[0] = 0;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // revert the direction
+            direction[0] = temp;
+            direction[0] *= -1;
+            direction[1] *= -1;
+
+        } else {
+            // If the direction vector has a single non-zero component, we push first in the
+            // direction of the vector
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Then we try the opposite direction
+            direction[0] *= -1;
+            direction[1] *= -1;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Switch the direction back
+            direction[0] *= -1;
+            direction[1] *= -1;
+
+            // If we have failed to find a push solution with the above, then we try
+            // to find a solution by pushing along the perpendicular axis.
+
+            // Swap the components
+            int temp = direction[1];
+            direction[1] = direction[0];
+            direction[0] = temp;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+
+            // Then we try the opposite direction
+            direction[0] *= -1;
+            direction[1] *= -1;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Switch the direction back
+            direction[0] *= -1;
+            direction[1] *= -1;
+
+            // Swap the components back
+            temp = direction[1];
+            direction[1] = direction[0];
+            direction[0] = temp;
+        }
+        return false;
+    }
+
+    /*
+     * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
+     * the provided point and the provided cell
+     */
+    private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
+        double angle = Math.atan(deltaY / deltaX);
+
+        result[0] = 0;
+        result[1] = 0;
+        if (Math.abs(Math.cos(angle)) > 0.5f) {
+            result[0] = (int) Math.signum(deltaX);
+        }
+        if (Math.abs(Math.sin(angle)) > 0.5f) {
+            result[1] = (int) Math.signum(deltaY);
+        }
+    }
+
+    /* This seems like it should be obvious and straight-forward, but when the direction vector
+    needs to match with the notion of the dragView pushing other views, we have to employ
+    a slightly more subtle notion of the direction vector. The question is what two points is
+    the vector between? The center of the dragView and its desired destination? Not quite, as
+    this doesn't necessarily coincide with the interaction of the dragView and items occupying
+    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,
+            int spanY, View dragView, int[] resultDirection) {
+
+        //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
+        int[] targetDestination = new int[2];
+
+        findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
+        Rect dragRect = new Rect();
+        cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
+        dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
+
+        Rect dropRegionRect = new Rect();
+        getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
+                dragView, dropRegionRect, mIntersectingViews);
+
+        int dropRegionSpanX = dropRegionRect.width();
+        int dropRegionSpanY = dropRegionRect.height();
+
+        cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+                dropRegionRect.height(), dropRegionRect);
+
+        int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
+        int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
+
+        if (dropRegionSpanX == mCountX || spanX == mCountX) {
+            deltaX = 0;
+        }
+        if (dropRegionSpanY == mCountY || spanY == mCountY) {
+            deltaY = 0;
+        }
+
+        if (deltaX == 0 && deltaY == 0) {
+            // No idea what to do, give a random direction.
+            resultDirection[0] = 1;
+            resultDirection[1] = 0;
+        } else {
+            computeDirectionVector(deltaX, deltaY, resultDirection);
+        }
+    }
+
+    private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+            int[] direction, View dragView, ItemConfiguration currentState) {
+        if (views.size() == 0) return true;
+
+        boolean success = false;
+        Rect boundingRect = new Rect();
+        // We construct a rect which represents the entire group of views passed in
+        currentState.getBoundingRectForViews(views, boundingRect);
+
+        // Mark the occupied state as false for the group of views we want to move.
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            mTmpOccupied.markCells(c, false);
+        }
+
+        GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
+        int top = boundingRect.top;
+        int left = boundingRect.left;
+        // We mark more precisely which parts of the bounding rect are truly occupied, allowing
+        // for interlocking.
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
+        }
+
+        mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
+
+        findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
+                boundingRect.height(), direction,
+                mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
+
+        // If we successfully found a location by pushing the block of views, we commit it
+        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+            int deltaX = mTempLocation[0] - boundingRect.left;
+            int deltaY = mTempLocation[1] - boundingRect.top;
+            for (View v: views) {
+                CellAndSpan c = currentState.map.get(v);
+                c.cellX += deltaX;
+                c.cellY += deltaY;
+            }
+            success = true;
+        }
+
+        // In either case, we set the occupied array as marked for the location of the views
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            mTmpOccupied.markCells(c, true);
+        }
+        return success;
+    }
+
+    private 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;
+
+        mIntersectingViews.clear();
+        mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+
+        // Mark the desired location of the view currently being dragged.
+        if (ignoreView != null) {
+            CellAndSpan c = solution.map.get(ignoreView);
+            if (c != null) {
+                c.cellX = cellX;
+                c.cellY = cellY;
+            }
+        }
+        Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+        Rect r1 = new Rect();
+        for (View child: solution.map.keySet()) {
+            if (child == ignoreView) continue;
+            CellAndSpan c = solution.map.get(child);
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
+            r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
+            if (Rect.intersects(r0, r1)) {
+                if (!lp.canReorder) {
+                    return false;
+                }
+                mIntersectingViews.add(child);
+            }
+        }
+
+        solution.intersectingViews = new ArrayList<>(mIntersectingViews);
+
+        // First we try to find a solution which respects the push mechanic. That is,
+        // we try to find a solution such that no displaced item travels through another item
+        // without also displacing that item.
+        if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
+                solution)) {
+            return true;
+        }
+
+        // Next we try moving the views as a block, but without requiring the push mechanic.
+        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
+                solution)) {
+            return true;
+        }
+
+        // Ok, they couldn't move as a block, let's move them individually
+        for (View v : mIntersectingViews) {
+            if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private ItemConfiguration findReorderSolution(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 = findNearestArea(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 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;
+    }
+
+    private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
+            CellAndSpan c;
+            if (temp) {
+                c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
+            } else {
+                c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
+            }
+            solution.add(child, c);
+        }
     }
 
     /**
@@ -2573,6 +2535,43 @@
         return null;
     }
 
+    int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+            View dragView, int[] result, int[] resultSpan, int mode) {
+        if (resultSpan == null) {
+            resultSpan = new int[]{-1, -1};
+        }
+        if (result == null) {
+            result = new int[]{-1, -1};
+        }
+        ItemConfiguration finalSolution;
+        // When we are checking drop validity or actually dropping, we don't recompute the
+        // direction vector, since we want the solution to match the preview, and it's possible
+        // that the exact position of the item has changed to result in a new reordering outcome.
+        if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
+                && mPreviousSolution != null) {
+            finalSolution = mPreviousSolution;
+            // We reset this vector after drop
+            if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+                mPreviousSolution = null;
+            }
+        } else {
+            finalSolution = calculateReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
+                    dragView);
+            mPreviousSolution = finalSolution;
+        }
+
+        if (finalSolution == null || !finalSolution.isSolution) {
+            result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
+        } else {
+            result[0] = finalSolution.cellX;
+            result[1] = finalSolution.cellY;
+            resultSpan[0] = finalSolution.spanX;
+            resultSpan[1] = finalSolution.spanY;
+        }
+        performReorder(finalSolution, dragView, mode);
+        return result;
+    }
+
     /**
      * Animates and submits in the DB the given ItemConfiguration depending of the mode.
      *