Refactoring folder content

> Refactoring the CellLayout in folder with a custom view so
that it can be replaced easily with a scrollable paged view.
> Moving some methods from the folder to this new view which
assume a single page layout for the folder
> Changing folder from LinearLayout to FrameLayout to properly
handle focus traversal in case of multi-page folders

Change-Id: I073c00b995488f9f5d8123b00357e094ca2cec7c
diff --git a/src/com/android/launcher3/FolderCellLayout.java b/src/com/android/launcher3/FolderCellLayout.java
new file mode 100644
index 0000000..66f5224
--- /dev/null
+++ b/src/com/android/launcher3/FolderCellLayout.java
@@ -0,0 +1,285 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import java.util.ArrayList;
+
+public class FolderCellLayout extends CellLayout {
+
+    private static final int REORDER_ANIMATION_DURATION = 230;
+    private static final int START_VIEW_REORDER_DELAY = 30;
+    private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
+
+    private static final int[] sTempPosArray = new int[2];
+
+    private final FolderKeyEventListener mKeyListener = new FolderKeyEventListener();
+    private final LayoutInflater mInflater;
+    private final IconCache mIconCache;
+
+    private final int mMaxCountX;
+    private final int mMaxCountY;
+    private final int mMaxNumItems;
+
+    // Indicates the last number of items used to set up the grid size
+    private int mAllocatedContentSize;
+
+    private Folder mFolder;
+    private FocusIndicatorView mFocusIndicatorView;
+
+    public FolderCellLayout(Context context) {
+        this(context, null);
+    }
+
+    public FolderCellLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FolderCellLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        mMaxCountX = (int) grid.numColumns;
+        mMaxCountY = (int) grid.numRows;
+        mMaxNumItems = mMaxCountX * mMaxCountY;
+
+        mInflater = LayoutInflater.from(context);
+        mIconCache = app.getIconCache();
+    }
+
+    public void setFolder(Folder folder) {
+        mFolder = folder;
+        mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
+    }
+
+    /**
+     * Sets up the grid size such that {@param count} items can fit in the grid.
+     * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
+     * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
+     */
+    private void setupContentDimensions(int count) {
+        mAllocatedContentSize = count;
+        int countX = getCountX();
+        int countY = getCountY();
+        boolean done = false;
+
+        while (!done) {
+            int oldCountX = countX;
+            int oldCountY = countY;
+            if (countX * countY < count) {
+                // Current grid is too small, expand it
+                if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
+                    countX++;
+                } else if (countY < mMaxCountY) {
+                    countY++;
+                }
+                if (countY == 0) countY++;
+            } else if ((countY - 1) * countX >= count && countY >= countX) {
+                countY = Math.max(0, countY - 1);
+            } else if ((countX - 1) * countY >= count) {
+                countX = Math.max(0, countX - 1);
+            }
+            done = countX == oldCountX && countY == oldCountY;
+        }
+        setGridSize(countX, countY);
+    }
+
+    /**
+     * Binds items to the layout.
+     * @return list of items that could not be bound, probably because we hit the max size limit.
+     */
+    public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
+        ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
+        setupContentDimensions(Math.min(items.size(), mMaxNumItems));
+
+        int countX = getCountX();
+        int rank = 0;
+        for (ShortcutInfo item : items) {
+            if (rank >= mMaxNumItems) {
+                extra.add(item);
+                continue;
+            }
+
+            item.rank = rank;
+            item.cellX = rank % countX;
+            item.cellY = rank / countX;
+            addNewView(item);
+            rank++;
+        }
+        return extra;
+    }
+
+    /**
+     * Create space for a new item at the end, and returns the rank for that item.
+     * Resizes the content if necessary.
+     */
+    public int allocateNewLastItemRank() {
+        int rank = getItemCount();
+        mFolder.rearrangeChildren(rank + 1);
+        return rank;
+    }
+
+    /**
+     * Adds the new item to the end of the grid. Resizes the content if necessary.
+     */
+    public View createAndAddShortcutToEnd(ShortcutInfo item) {
+        int rank = allocateNewLastItemRank();
+        return createAndAddViewForRank(item, rank);
+    }
+
+    public View createAndAddViewForRank(ShortcutInfo item, int rank) {
+        updateItemXY(item, rank);
+        return addNewView(item);
+    }
+
+    /**
+     * Adds the {@param view} to the layout based on {@param rank} and updated the position
+     * related attributes. It assumes that {@param item} is already attached to the view.
+     */
+    public void addViewForRank(View view, ShortcutInfo item, int rank) {
+        updateItemXY(item, rank);
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+        lp.cellX = item.cellX;
+        lp.cellY = item.cellY;
+        addViewToCellLayout(view, -1, (int) item.id, lp, true);
+    }
+
+    /**
+     * Updates the item cellX and cellY position
+     */
+    private void updateItemXY(ShortcutInfo item, int rank) {
+        item.rank = rank;
+        int countX = getCountX();
+        item.cellX = rank % countX;
+        item.cellY = rank / countX;
+    }
+
+    private View addNewView(ShortcutInfo item) {
+        final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
+                R.layout.folder_application, getShortcutsAndWidgets(), false);
+        textView.applyFromShortcutInfo(item, mIconCache, false);
+        textView.setOnClickListener(mFolder);
+        textView.setOnLongClickListener(mFolder);
+        textView.setOnFocusChangeListener(mFocusIndicatorView);
+        textView.setOnKeyListener(mKeyListener);
+
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(
+                item.cellX, item.cellY, item.spanX, item.spanY);
+        addViewToCellLayout(textView, -1, (int)item.id, lp, true);
+        return textView;
+    }
+
+    /**
+     * Refer {@link #findNearestArea(int, int, int, int, View, boolean, int[])}
+     *
+     * @return The rank of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    public int findNearestArea(int pixelX, int pixelY, int spanX, int spanY) {
+        findNearestArea(pixelX, pixelY, spanX, spanY, null, false, sTempPosArray);
+        if (mFolder.isLayoutRtl()) {
+            sTempPosArray[0] = getCountX() - sTempPosArray[0] - 1;
+        }
+
+        // Convert this position to rank.
+        return Math.min(mAllocatedContentSize - 1,
+                sTempPosArray[1] * getCountX() + sTempPosArray[0]);
+    }
+
+    public boolean isFull() {
+        return getItemCount() >= mMaxNumItems;
+    }
+
+    public int getItemCount() {
+        return getShortcutsAndWidgets().getChildCount();
+    }
+
+    /**
+     * Updates position and rank of all the children in the view based.
+     * @param list the ordered list of children.
+     * @param itemCount if greater than the total children count, empty spaces are left at the end.
+     */
+    public void arrangeChildren(ArrayList<View> list, int itemCount) {
+        setupContentDimensions(itemCount);
+        removeAllViews();
+
+        int newX, newY;
+        int rank = 0;
+        int countX = getCountX();
+        for (View v : list) {
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+            newX = rank % countX;
+            newY = rank / countX;
+            ItemInfo info = (ItemInfo) v.getTag();
+            if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
+                info.cellX = newX;
+                info.cellY = newY;
+                info.rank = rank;
+                LauncherModel.addOrMoveItemInDatabase(getContext(), info,
+                        mFolder.mInfo.id, 0, info.cellX, info.cellY);
+            }
+            lp.cellX = info.cellX;
+            lp.cellY = info.cellY;
+            rank ++;
+            addViewToCellLayout(v, -1, (int)info.id, lp, true);
+        }
+    }
+
+    /**
+     * Reorders the items such that the {@param empty} spot moves to {@param target}
+     */
+    public void realTimeReorder(int empty, int target) {
+        boolean wrap;
+        int startX;
+        int endX;
+        int startY;
+        int delay = 0;
+        float delayAmount = START_VIEW_REORDER_DELAY;
+
+        int countX = getCountX();
+        int emptyX = empty % getCountX();
+        int emptyY = empty / countX;
+
+        int targetX = target % countX;
+        int targetY = target / countX;
+
+        if (target > empty) {
+            wrap = emptyX == countX - 1;
+            startY = wrap ? emptyY + 1 : emptyY;
+            for (int y = startY; y <= targetY; y++) {
+                startX = y == emptyY ? emptyX + 1 : 0;
+                endX = y < targetY ? countX - 1 : targetX;
+                for (int x = startX; x <= endX; x++) {
+                    View v = getChildAt(x,y);
+                    if (animateChildToPosition(v, emptyX, emptyY,
+                            REORDER_ANIMATION_DURATION, delay, true, true)) {
+                        emptyX = x;
+                        emptyY = y;
+                        delay += delayAmount;
+                        delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+                    }
+                }
+            }
+        } else {
+            wrap = emptyX == 0;
+            startY = wrap ? emptyY - 1 : emptyY;
+            for (int y = startY; y >= targetY; y--) {
+                startX = y == emptyY ? emptyX - 1 : countX - 1;
+                endX = y > targetY ? 0 : targetX;
+                for (int x = startX; x >= endX; x--) {
+                    View v = getChildAt(x,y);
+                    if (animateChildToPosition(v, emptyX, emptyY,
+                            REORDER_ANIMATION_DURATION, delay, true, true)) {
+                        emptyX = x;
+                        emptyY = y;
+                        delay += delayAmount;
+                        delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+                    }
+                }
+            }
+        }
+    }
+}