Add new motion for when Folder preview changes due to onDrop.

Instead of changing the display order of the Folder to preserve
the upper left quadrant, we are opting to change the Folder Icon
preview to always show the upper left quadrant.

This means that when adding items to a Folder, the preview items
may change. (They will change when the column size increases).

Bug: 27944225
Bug: 63140071
Change-Id: I863c2479469d68559cab2878030c2087d48217d6
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index ff357c0..f25345e 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,15 +1,18 @@
 package com.android.launcher3.folder;
 
+
 public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
 
     static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
     private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
-    private static final int MAX_NUM_ITEMS_PER_ROW = 2;
 
-    final float MIN_SCALE = 0.48f;
-    final float MAX_SCALE = 0.58f;
-    final float MAX_RADIUS_DILATION = 0.15f;
-    final float ITEM_RADIUS_SCALE_FACTOR = 1.33f;
+    private static final float MIN_SCALE = 0.48f;
+    private static final float MAX_SCALE = 0.58f;
+    private static final float MAX_RADIUS_DILATION = 0.15f;
+    private static final float ITEM_RADIUS_SCALE_FACTOR = 1.33f;
+
+    private static final int EXIT_INDEX = -2;
+    private static final int ENTER_INDEX = -3;
 
     private float[] mTmpPoint = new float[2];
 
@@ -31,21 +34,29 @@
     @Override
     public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params) {
-
         float totalScale = scaleForItem(index, curNumItems);
         float transX;
         float transY;
         float overlayAlpha = 0;
 
-        // Items beyond those displayed in the preview are animated to the center
-        if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
-            transX = transY = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
+        if (index == getExitIndex()) {
+            // 0 1 * <-- Exit position (row 0, col 2)
+            // 2 3
+            getGridPosition(0, 2, mTmpPoint);
+        } else if (index == getEnterIndex()) {
+            // 0 1
+            // 2 3 * <-- Enter position (row 1, col 2)
+            getGridPosition(1, 2, mTmpPoint);
+        } else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
+            // Items beyond those displayed in the preview are animated to the center
+            mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
         } else {
             getPosition(index, curNumItems, mTmpPoint);
-            transX = mTmpPoint[0];
-            transY = mTmpPoint[1];
         }
 
+        transX = mTmpPoint[0];
+        transY = mTmpPoint[1];
+
         if (params == null) {
             params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
         } else {
@@ -55,6 +66,27 @@
         return params;
     }
 
+    /**
+     * Builds a grid based on the positioning of the items when there are
+     * {@link #MAX_NUM_ITEMS_IN_PREVIEW} in the preview.
+     *
+     * Positions in the grid: 0 1  // 0 is row 0, col 1
+     *                        2 3  // 3 is row 1, col 1
+     */
+    private void getGridPosition(int row, int col, float[] result) {
+        // We use position 0 and 3 to calculate the x and y distances between items.
+        getPosition(0, 4, result);
+        float left = result[0];
+        float top = result[1];
+
+        getPosition(3, 4, result);
+        float dx = result[0] - left;
+        float dy = result[1] - top;
+
+        result[0] = left + (col * dx);
+        result[1] = top + (row * dy);
+    }
+
     private void getPosition(int index, int curNumItems, float[] result) {
         // The case of two items is homomorphic to the case of one.
         curNumItems = Math.max(curNumItems, 2);
@@ -127,4 +159,19 @@
     public boolean clipToBackground() {
         return true;
     }
+
+    @Override
+    public boolean hasEnterExitIndices() {
+        return true;
+    }
+
+    @Override
+    public int getExitIndex() {
+        return EXIT_INDEX;
+    }
+
+    @Override
+    public int getEnterIndex() {
+        return ENTER_INDEX;
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ae6a0e8..c63eeb3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -221,7 +221,15 @@
     }
 
     public void addItem(ShortcutInfo item) {
-        mInfo.add(item, true);
+        addItem(item, true);
+    }
+
+    public void addItem(ShortcutInfo item, boolean animate) {
+        mInfo.add(item, animate);
+    }
+
+    public void removeItem(ShortcutInfo item, boolean animate) {
+        mInfo.remove(item, animate);
     }
 
     public void onDragEnter(ItemInfo dragInfo) {
@@ -276,7 +284,7 @@
     }
 
     private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
-            float scaleRelativeToDragLayer, final int index, Runnable postAnimationRunnable) {
+            float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
         item.cellX = -1;
         item.cellY = -1;
 
@@ -304,13 +312,39 @@
                 workspace.resetTransitionTransform((CellLayout) getParent().getParent());
             }
 
+            boolean itemAdded = false;
+            if (index >= mPreviewLayoutRule.maxNumItems()
+                    && mPreviewLayoutRule.hasEnterExitIndices()) {
+                List<BubbleTextView> oldPreviewItems = getPreviewItemsOnPage(0);
+                addItem(item, false);
+                List<BubbleTextView> newPreviewItems = getPreviewItemsOnPage(0);
+
+                if (!oldPreviewItems.containsAll(newPreviewItems)) {
+                    for (int i = 0; i < newPreviewItems.size(); ++i) {
+                        if (newPreviewItems.get(i).getTag().equals(item)) {
+                            // If the item dropped is going to be in the preview, we update the
+                            // index here to reflect its position in the preview.
+                            index = i;
+                        }
+                    }
+                    mPreviewItemManager.onDrop(oldPreviewItems, newPreviewItems, item);
+                    itemAdded = true;
+                } else {
+                    removeItem(item, false);
+                }
+            }
+
+            if (!itemAdded) {
+                addItem(item);
+            }
+
             int[] center = new int[2];
             float scale = getLocalCenterForIndex(index, index + 1, center);
             center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
             center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
 
             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
-                      center[1] - animateView.getMeasuredHeight() / 2);
+                    center[1] - animateView.getMeasuredHeight() / 2);
 
             float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
 
@@ -319,13 +353,14 @@
                     1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
                     new DecelerateInterpolator(2), new AccelerateInterpolator(2),
                     postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
-            addItem(item);
+
             mFolder.hideItem(item);
 
-            mPreviewItemManager.hidePreviewItem(index, true);
+            if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
+            final int finalIndex = index;
             postDelayed(new Runnable() {
                 public void run() {
-                    mPreviewItemManager.hidePreviewItem(index, false);
+                    mPreviewItemManager.hidePreviewItem(finalIndex, false);
                     mFolder.showItem(item);
                     invalidate();
                 }
@@ -655,11 +690,16 @@
 
     interface PreviewLayoutRule {
         PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
-            PreviewItemDrawingParams params);
+                PreviewItemDrawingParams params);
         void init(int availableSpace, float intrinsicIconSize, boolean rtl);
         float scaleForItem(int index, int totalNumItems);
         float getIconSize();
         int maxNumItems();
         boolean clipToBackground();
+
+        boolean hasEnterExitIndices();
+        int getExitIndex();
+        int getEnterIndex();
+
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
index 11c1078..d054a5d 100644
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -40,19 +40,14 @@
     }
 
     public void setFolderInfo(FolderInfo info) {
-        FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX,
+        int numItemsInFolder = info.contents.size();
+        FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
                 mMaxGridCountY, mMaxItemsPerPage, mGridSize);
         mGridCountX = mGridSize[0];
-        int numItemsInFolder = info.contents.size();
+
         mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
                 && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON
                 && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW;
-
-        if (mDisplayingUpperLeftQuadrant) {
-            FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX,
-                    mMaxGridCountY, mMaxItemsPerPage, mGridSize);
-            mGridCountX = mGridSize[0];
-        }
     }
 
     /**
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 74c2102..7a05f67 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -27,6 +27,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
@@ -273,4 +274,80 @@
     float getIntrinsicIconSize() {
         return mIntrinsicIconSize;
     }
+
+    /**
+     * Handles the case where items in the preview are either:
+     *  - Moving into the preview
+     *  - Moving into a new position
+     *  - Moving out of the preview
+     *
+     * @param oldParams The list of items in the old preview.
+     * @param newParams The list of items in the new preview.
+     * @param dropped The item that was dropped onto the FolderIcon.
+     */
+    public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams,
+            ShortcutInfo dropped) {
+        int numItems = newParams.size();
+        final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
+        buildParamsForPage(0, params, false);
+
+        // New preview items for items that are moving in (except for the dropped item).
+        List<BubbleTextView> moveIn = new ArrayList<>();
+        for (BubbleTextView btv : newParams) {
+            if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) {
+                moveIn.add(btv);
+            }
+        }
+        for (int i = 0; i < moveIn.size(); ++i) {
+            int prevIndex = newParams.indexOf(moveIn.get(i));
+            PreviewItemDrawingParams p = params.get(prevIndex);
+            computePreviewItemDrawingParams(prevIndex, numItems, p);
+            updateTransitionParam(p, moveIn.get(i), mIcon.mPreviewLayoutRule.getEnterIndex(),
+                    newParams.indexOf(moveIn.get(i)));
+        }
+
+        // Items that are moving into new positions within the preview.
+        for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) {
+            int oldIndex = oldParams.indexOf(newParams.get(newIndex));
+            if (oldIndex >= 0 && newIndex != oldIndex) {
+                PreviewItemDrawingParams p = params.get(newIndex);
+                updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex);
+            }
+        }
+
+        // Old preview items that need to be moved out.
+        List<BubbleTextView> moveOut = new ArrayList<>(oldParams);
+        moveOut.removeAll(newParams);
+        for (int i = 0; i < moveOut.size(); ++i) {
+            BubbleTextView item = moveOut.get(i);
+            int oldIndex = oldParams.indexOf(item);
+            PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
+            updateTransitionParam(p, item, oldIndex, mIcon.mPreviewLayoutRule.getExitIndex());
+            params.add(0, p); // We want these items first so that they are on drawn last.
+        }
+
+        for (int i = 0; i < params.size(); ++i) {
+            if (params.get(i).anim != null) {
+                params.get(i).anim.start();
+            }
+        }
+    }
+
+    private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv,
+            int prevIndex, int newIndex) {
+        p.drawable = btv.getCompoundDrawables()[1];
+        if (!mIcon.mFolder.isOpen()) {
+            // Set the callback to FolderIcon as it is responsible to drawing the icon. The
+            // callback will be released when the folder is opened.
+            p.drawable.setCallback(mIcon);
+        }
+
+        FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex,
+                FolderIcon.NUM_ITEMS_IN_PREVIEW, newIndex, FolderIcon.NUM_ITEMS_IN_PREVIEW,
+                DROP_IN_ANIMATION_DURATION, null);
+        if (p.anim != null && !p.anim.hasEqualFinalState(anim)) {
+            p.anim.cancel();
+        }
+        p.anim = anim;
+    }
 }
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 138dc1c..7d10556 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -97,4 +97,19 @@
     public boolean clipToBackground() {
         return false;
     }
+
+    @Override
+    public boolean hasEnterExitIndices() {
+        return false;
+    }
+
+    @Override
+    public int getExitIndex() {
+        throw new RuntimeException("hasEnterExitIndices not supported");
+    }
+
+    @Override
+    public int getEnterIndex() {
+        throw new RuntimeException("hasEnterExitIndices not supported");
+    }
 }