Adding reorder hinting, hopefully will help with (issue 10654628)

-> Adds instant feedback that reorder _will_ happen, so hopefully will
   improve perceived disambiguation

Change-Id: Icd99e7beeb87e79627a6b9fc78a9132781410379
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index df16d41..a05dd90 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -133,8 +133,8 @@
 
     private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
             HashMap<CellLayout.LayoutParams, Animator>();
-    private HashMap<View, ReorderHintAnimation>
-            mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
+    private HashMap<View, ReorderPreviewAnimation>
+            mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
 
     private boolean mItemPlacementDirty = false;
 
@@ -149,19 +149,20 @@
     private boolean mIsHotseat = false;
     private float mHotseatScale = 1f;
 
-    public static final int MODE_DRAG_OVER = 0;
-    public static final int MODE_ON_DROP = 1;
-    public static final int MODE_ON_DROP_EXTERNAL = 2;
-    public static final int MODE_ACCEPT_DROP = 3;
+    public static final int MODE_SHOW_REORDER_HINT = 0;
+    public static final int MODE_DRAG_OVER = 1;
+    public static final int MODE_ON_DROP = 2;
+    public static final int MODE_ON_DROP_EXTERNAL = 3;
+    public static final int MODE_ACCEPT_DROP = 4;
     private static final boolean DESTRUCTIVE_REORDER = false;
     private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
 
     static final int LANDSCAPE = 0;
     static final int PORTRAIT = 1;
 
-    private static final float REORDER_HINT_MAGNITUDE = 0.12f;
+    private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
     private static final int REORDER_ANIMATION_DURATION = 150;
-    private float mReorderHintAnimationMagnitude;
+    private float mReorderPreviewAnimationMagnitude;
 
     private ArrayList<View> mIntersectingViews = new ArrayList<View>();
     private Rect mOccupiedRect = new Rect();
@@ -225,7 +226,7 @@
         mForegroundPadding =
                 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
 
-        mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
+        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
                 grid.iconSizePx);
 
         mNormalBackground.setFilterBitmap(true);
@@ -2088,6 +2089,8 @@
             }
         }
 
+        solution.intersectingViews = new ArrayList<View>(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.
@@ -2136,8 +2139,9 @@
         }
     }
 
-    ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
-            int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
+    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
@@ -2159,11 +2163,11 @@
             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
             // x, then 1 in y etc.
             if (spanX > minSpanX && (minSpanY == spanY || decX)) {
-                return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
-                        dragView, false, solution);
+                return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
+                        direction, dragView, false, solution);
             } else if (spanY > minSpanY) {
-                return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
-                        dragView, true, solution);
+                return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
+                        direction, dragView, true, solution);
             }
             solution.isSolution = false;
         } else {
@@ -2243,25 +2247,30 @@
         }
     }
 
-    // This method starts or changes the reorder hint animations
-    private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
+
+    // This method starts or changes the reorder preview animations
+    private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
+            View dragView, int delay, int mode) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
             CellAndSpan c = solution.map.get(child);
+            boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
+                    != null && !solution.intersectingViews.contains(child);
+
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (c != null) {
-                ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
-                        c.x, c.y, c.spanX, c.spanY);
+            if (c != null && !skip) {
+                ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
+                        lp.cellY, c.x, c.y, c.spanX, c.spanY);
                 rha.animate();
             }
         }
     }
 
-    // Class which represents the reorder hint animations. These animations show that an item is
+    // Class which represents the reorder preview animations. These animations show that an item is
     // in a temporary state, and hint at where the item will return to.
-    class ReorderHintAnimation {
+    class ReorderPreviewAnimation {
         View child;
         float finalDeltaX;
         float finalDeltaY;
@@ -2269,11 +2278,18 @@
         float initDeltaY;
         float finalScale;
         float initScale;
-        private static final int DURATION = 300;
+        int mode;
+        boolean repeating = false;
+        private static final int PREVIEW_DURATION = 300;
+        private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
+
+        public static final int MODE_HINT = 0;
+        public static final int MODE_PREVIEW = 1;
+
         Animator a;
 
-        public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
-                int spanX, int spanY) {
+        public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
+                int cellY1, int spanX, int spanY) {
             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
             final int x0 = mTmpPoint[0];
             final int y0 = mTmpPoint[1];
@@ -2284,20 +2300,22 @@
             final int dY = y1 - y0;
             finalDeltaX = 0;
             finalDeltaY = 0;
+            int dir = mode == MODE_HINT ? -1 : 1;
             if (dX == dY && dX == 0) {
             } else {
                 if (dY == 0) {
-                    finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
+                    finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
                 } else if (dX == 0) {
-                    finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
+                    finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
                 } else {
                     double angle = Math.atan( (float) (dY) / dX);
-                    finalDeltaX = (int) (- Math.signum(dX) *
-                            Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
-                    finalDeltaY = (int) (- Math.signum(dY) *
-                            Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
+                    finalDeltaX = (int) (- dir * Math.signum(dX) *
+                            Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+                    finalDeltaY = (int) (- dir * Math.signum(dY) *
+                            Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
                 }
             }
+            this.mode = mode;
             initDeltaX = child.getTranslationX();
             initDeltaY = child.getTranslationY();
             finalScale = getChildrenScale() - 4.0f / child.getWidth();
@@ -2307,7 +2325,7 @@
 
         void animate() {
             if (mShakeAnimators.containsKey(child)) {
-                ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
+                ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
                 oldAnimation.cancel();
                 mShakeAnimators.remove(child);
                 if (finalDeltaX == 0 && finalDeltaY == 0) {
@@ -2322,14 +2340,15 @@
             a = va;
             va.setRepeatMode(ValueAnimator.REVERSE);
             va.setRepeatCount(ValueAnimator.INFINITE);
-            va.setDuration(DURATION);
+            va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
             va.setStartDelay((int) (Math.random() * 60));
             va.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
                     float r = ((Float) animation.getAnimatedValue()).floatValue();
-                    float x = r * finalDeltaX + (1 - r) * initDeltaX;
-                    float y = r * finalDeltaY + (1 - r) * initDeltaY;
+                    float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
+                    float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
+                    float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
                     child.setTranslationX(x);
                     child.setTranslationY(y);
                     float s = r * finalScale + (1 - r) * initScale;
@@ -2343,6 +2362,7 @@
                     initDeltaX = 0;
                     initDeltaY = 0;
                     initScale = getChildrenScale();
+                    repeating = true;
                 }
             });
             mShakeAnimators.put(child, this);
@@ -2374,8 +2394,8 @@
         }
     }
 
-    private void completeAndClearReorderHintAnimations() {
-        for (ReorderHintAnimation a: mShakeAnimators.values()) {
+    private void completeAndClearReorderPreviewAnimations() {
+        for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
             a.completeAnimationImmediately();
         }
         mShakeAnimators.clear();
@@ -2518,20 +2538,21 @@
     }
 
     void revertTempState() {
-        if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
-        final int count = mShortcutsAndWidgets.getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = mShortcutsAndWidgets.getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
-                lp.tmpCellX = lp.cellX;
-                lp.tmpCellY = lp.cellY;
-                animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
-                        0, false, false);
+        completeAndClearReorderPreviewAnimations();
+        if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
+            final int count = mShortcutsAndWidgets.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View child = mShortcutsAndWidgets.getChildAt(i);
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
+                    lp.tmpCellX = lp.cellX;
+                    lp.tmpCellY = lp.cellY;
+                    animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
+                            0, false, false);
+                }
             }
+            setItemPlacementDirty(false);
         }
-        completeAndClearReorderHintAnimations();
-        setItemPlacementDirty(false);
     }
 
     boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
@@ -2540,7 +2561,7 @@
         regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
 
         // First we determine if things have moved enough to cause a different layout
-        ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
+        ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
                  spanX,  spanY, direction, dragView,  true,  new ItemConfiguration());
 
         setUseTempCoords(true);
@@ -2554,18 +2575,18 @@
 
             if (commit) {
                 commitTempPlacement();
-                completeAndClearReorderHintAnimations();
+                completeAndClearReorderPreviewAnimations();
                 setItemPlacementDirty(false);
             } else {
-                beginOrAdjustHintAnimations(swapSolution, dragView,
-                        REORDER_ANIMATION_DURATION);
+                beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
+                        REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
             }
             mShortcutsAndWidgets.requestLayout();
         }
         return swapSolution.isSolution;
     }
 
-    int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+    int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
             View dragView, int[] result, int resultSpan[], int mode) {
         // First we determine if things have moved enough to cause a different layout
         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
@@ -2592,7 +2613,8 @@
             mPreviousReorderDirection[1] = mDirectionVector[1];
         }
 
-        ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
+        // 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
@@ -2600,12 +2622,29 @@
                 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
 
         ItemConfiguration finalSolution = null;
+
+        // 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() >= noShuffleSolution.area()) {
             finalSolution = swapSolution;
         } else if (noShuffleSolution.isSolution) {
             finalSolution = noShuffleSolution;
         }
 
+        if (mode == MODE_SHOW_REORDER_HINT) {
+            beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
+                    ReorderPreviewAnimation.MODE_HINT);
+            if (finalSolution != null) {
+                result[0] = finalSolution.dragViewX;
+                result[1] = finalSolution.dragViewY;
+                resultSpan[0] = finalSolution.dragViewSpanX;
+                resultSpan[1] = finalSolution.dragViewSpanY;
+            } else {
+                result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
+            }
+            return result;
+        }
+
         boolean foundSolution = true;
         if (!DESTRUCTIVE_REORDER) {
             setUseTempCoords(true);
@@ -2630,11 +2669,11 @@
                 if (!DESTRUCTIVE_REORDER &&
                         (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
                     commitTempPlacement();
-                    completeAndClearReorderHintAnimations();
+                    completeAndClearReorderPreviewAnimations();
                     setItemPlacementDirty(false);
                 } else {
-                    beginOrAdjustHintAnimations(finalSolution, dragView,
-                            REORDER_ANIMATION_DURATION);
+                    beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
+                            REORDER_ANIMATION_DURATION,  ReorderPreviewAnimation.MODE_PREVIEW);
                 }
             }
         } else {
@@ -2661,6 +2700,7 @@
         HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
         private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
         ArrayList<View> sortedViews = new ArrayList<View>();
+        ArrayList<View> intersectingViews;
         boolean isSolution = false;
         int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index bf4f561..72399e4 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -216,7 +216,7 @@
 
     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
     private static final int FOLDER_CREATION_TIMEOUT = 0;
-    private static final int REORDER_TIMEOUT = 250;
+    public static final int REORDER_TIMEOUT = 350;
     private final Alarm mFolderCreationAlarm = new Alarm();
     private final Alarm mReorderAlarm = new Alarm();
     private FolderRingAnimator mDragFolderRingAnimator = null;
@@ -2709,7 +2709,7 @@
             }
 
             int[] resultSpan = new int[2];
-            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+            mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
                     null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
             boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
@@ -2935,7 +2935,7 @@
                 }
 
                 int[] resultSpan = new int[2];
-                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+                mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
                         mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
 
@@ -3517,6 +3517,11 @@
                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
                     mLastReorderY != reorderY)) {
 
+                int[] resultSpan = new int[2];
+                mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
+                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
+                        child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
+
                 // Otherwise, if we aren't adding to or creating a folder and there's no pending
                 // reorder, then we schedule a reorder
                 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
@@ -3621,7 +3626,7 @@
             mLastReorderX = mTargetCell[0];
             mLastReorderY = mTargetCell[1];
 
-            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+            mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
                 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
                 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
 
@@ -3731,7 +3736,7 @@
                     minSpanY = item.minSpanY;
                 }
                 int[] resultSpan = new int[2];
-                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+                mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
                         null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
 
@@ -3823,7 +3828,7 @@
 
             if (touchXY != null) {
                 // when dragging and dropping, just find the closest free spot
-                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+                mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
                         (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
                         null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
             } else {