Extracting a common interface out of FolderCellLayout

Change-Id: Ia94a75ac232b8b425c8befdf2e4f064678531505
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 80fc328..4414672 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -48,6 +48,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.Workspace.ItemOperator;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -95,7 +96,7 @@
 
     private FolderIcon mFolderIcon;
 
-    private FolderCellLayout mContent;
+    private FolderContent mContent;
     private View mContentWrapper;
     FolderEditText mFolderName;
 
@@ -160,17 +161,9 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mContentWrapper = findViewById(R.id.folder_content_wrapper);
-        mContent = (FolderCellLayout) findViewById(R.id.folder_content);
+        mContent = (FolderContent) findViewById(R.id.folder_content);
         mContent.setFolder(this);
 
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
-        mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
-        mContent.setGridSize(0, 0);
-        mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
-        mContent.setInvertIfRtl(true);
-
         mFolderName = (FolderEditText) findViewById(R.id.folder_name);
         mFolderName.setFolder(this);
         mFolderName.setOnFocusChangeListener(this);
@@ -435,8 +428,8 @@
             reveal.setDuration(mMaterialExpandDuration);
             reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
 
-            mContent.setAlpha(0f);
-            Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContent, "alpha", 0f, 1f);
+            mContentWrapper.setAlpha(0f);
+            Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContentWrapper, "alpha", 0f, 1f);
             iconsAlpha.setDuration(mMaterialExpandDuration);
             iconsAlpha.setStartDelay(mMaterialExpandStagger);
             iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
@@ -459,11 +452,11 @@
 
             openFolderAnim = anim;
 
-            mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
+            mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null);
             onCompleteRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    mContent.setLayerType(LAYER_TYPE_NONE, null);
+                    mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
                 }
             };
         }
@@ -471,8 +464,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                        String.format(getContext().getString(R.string.folder_opened),
-                        mContent.getCountX(), mContent.getCountY()));
+                        mContent.getAccessibilityDescription());
                 mState = STATE_ANIMATING;
             }
             @Override
@@ -483,7 +475,7 @@
                     onCompleteRunnable.run();
                 }
 
-                setFocusOnFirstChild();
+                mContent.setFocusOnFirstChild();
             }
         });
         openFolderAnim.start();
@@ -512,13 +504,6 @@
         }
     }
 
-    private void setFocusOnFirstChild() {
-        View firstChild = mContent.getChildAt(0, 0);
-        if (firstChild != null) {
-            firstChild.requestFocus();
-        }
-    }
-
     public void animateClosed() {
         if (!(getParent() instanceof DragLayer)) return;
         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
@@ -576,7 +561,7 @@
         r[0] -= getPaddingLeft();
         r[1] -= getPaddingTop();
 
-        mTargetRank = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1);
+        mTargetRank = mContent.findNearestArea((int) r[0], (int) r[1]);
         if (mTargetRank != mPrevTargetRank) {
             mReorderAlarm.cancelAlarm();
             mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
@@ -869,10 +854,6 @@
         return mContent.getItemCount();
     }
 
-    public View getItemAt(int index) {
-        return mContent.getShortcutsAndWidgets().getChildAt(index);
-    }
-
     private void onCloseComplete() {
         DragLayer parent = (DragLayer) getParent();
         if (parent != null) {
@@ -933,7 +914,7 @@
                 }
             }
         };
-        View finalChild = getItemAt(0);
+        View finalChild = mContent.getLastItem();
         if (finalChild != null) {
             mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
         } else {
@@ -949,8 +930,7 @@
     // This method keeps track of the last item in the folder for the purposes
     // of keyboard focus
     private void updateTextViewFocus() {
-        View lastChild = getItemAt(getItemCount() - 1);
-        getItemAt(getItemCount() - 1);
+        View lastChild = mContent.getLastItem();
         if (lastChild != null) {
             mFolderName.setNextFocusDownId(lastChild.getId());
             mFolderName.setNextFocusRightId(lastChild.getId());
@@ -1037,7 +1017,7 @@
         // If the item was dropped onto this open folder, we have done the work associated
         // with adding the item to the folder, as indicated by mSuppressOnAdd being set
         if (mSuppressOnAdd) return;
-        mContent.createAndAddShortcutToEnd(item);
+        mContent.createAndAddViewForRank(item, mContent.allocateNewLastItemRank());
         LauncherModel.addOrMoveItemInDatabase(
                 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
     }
@@ -1059,16 +1039,14 @@
         }
     }
 
-    private View getViewForInfo(ShortcutInfo item) {
-        for (int j = 0; j < mContent.getCountY(); j++) {
-            for (int i = 0; i < mContent.getCountX(); i++) {
-                View v = mContent.getChildAt(i, j);
-                if (v.getTag() == item) {
-                    return v;
-                }
+    private View getViewForInfo(final ShortcutInfo item) {
+        return mContent.iterateOverItems(new ItemOperator() {
+
+            @Override
+            public boolean evaluate(ItemInfo info, View view, View parent) {
+                return info == item;
             }
-        }
-        return null;
+        });
     }
 
     public void onItemsChanged() {
@@ -1081,14 +1059,14 @@
     public ArrayList<View> getItemsInReadingOrder() {
         if (mItemsInvalidated) {
             mItemsInReadingOrder.clear();
-            for (int j = 0; j < mContent.getCountY(); j++) {
-                for (int i = 0; i < mContent.getCountX(); i++) {
-                    View v = mContent.getChildAt(i, j);
-                    if (v != null) {
-                        mItemsInReadingOrder.add(v);
-                    }
+            mContent.iterateOverItems(new ItemOperator() {
+
+                @Override
+                public boolean evaluate(ItemInfo info, View view, View parent) {
+                    mItemsInReadingOrder.add(view);
+                    return false;
                 }
-            }
+            });
             mItemsInvalidated = false;
         }
         return mItemsInReadingOrder;
@@ -1108,4 +1086,69 @@
     public void getHitRectRelativeToDragLayer(Rect outRect) {
         getHitRect(outRect);
     }
+
+    public static interface FolderContent {
+        void setFolder(Folder f);
+
+        void removeView(View v);
+
+        boolean isFull();
+        int getItemCount();
+
+        int getDesiredWidth();
+        int getDesiredHeight();
+        void setFixedSize(int width, int height);
+
+        /**
+         * Iterates over all its items in a reading order.
+         * @return the view for which the operator returned true.
+         */
+        View iterateOverItems(ItemOperator op);
+        View getLastItem();
+
+        String getAccessibilityDescription();
+
+        /**
+         * Binds items to the layout.
+         * @return list of items that could not be bound, probably because we hit the max size limit.
+         */
+        ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> children);
+
+        /**
+         * Create space for a new item at the end, and returns the rank for that item.
+         * Resizes the content if necessary.
+         */
+        int allocateNewLastItemRank();
+
+        View createAndAddViewForRank(ShortcutInfo item, int rank);
+
+        /**
+         * 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.
+         */
+        void addViewForRank(View view, ShortcutInfo item, int rank);
+
+        /**
+         * Reorders the items such that the {@param empty} spot moves to {@param target}
+         */
+        void realTimeReorder(int empty, int target);
+
+        /**
+         * @return the rank of the cell nearest to the provided pixel position.
+         */
+        int findNearestArea(int pixelX, int pixelY);
+
+        /**
+         * 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.
+         */
+        void arrangeChildren(ArrayList<View> list, int itemCount);
+
+        /**
+         * Sets the focus on the first visible child.
+         */
+        void setFocusOnFirstChild();
+    }
 }
diff --git a/src/com/android/launcher3/FolderCellLayout.java b/src/com/android/launcher3/FolderCellLayout.java
index 66f5224..21f2a11 100644
--- a/src/com/android/launcher3/FolderCellLayout.java
+++ b/src/com/android/launcher3/FolderCellLayout.java
@@ -5,9 +5,11 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
+import com.android.launcher3.Workspace.ItemOperator;
+
 import java.util.ArrayList;
 
-public class FolderCellLayout extends CellLayout {
+public class FolderCellLayout extends CellLayout implements Folder.FolderContent {
 
     private static final int REORDER_ANIMATION_DURATION = 230;
     private static final int START_VIEW_REORDER_DELAY = 30;
@@ -48,8 +50,13 @@
 
         mInflater = LayoutInflater.from(context);
         mIconCache = app.getIconCache();
+
+        setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+        getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+        setInvertIfRtl(true);
     }
 
+    @Override
     public void setFolder(Folder folder) {
         mFolder = folder;
         mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
@@ -87,10 +94,7 @@
         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.
-     */
+    @Override
     public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
         ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
         setupContentDimensions(Math.min(items.size(), mMaxNumItems));
@@ -112,33 +116,20 @@
         return extra;
     }
 
-    /**
-     * Create space for a new item at the end, and returns the rank for that item.
-     * Resizes the content if necessary.
-     */
+    @Override
     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);
-    }
-
+    @Override
     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.
-     */
+    @Override
     public void addViewForRank(View view, ShortcutInfo item, int rank) {
         updateItemXY(item, rank);
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
@@ -174,12 +165,10 @@
 
     /**
      * 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);
+    @Override
+    public int findNearestArea(int pixelX, int pixelY) {
+        findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray);
         if (mFolder.isLayoutRtl()) {
             sTempPosArray[0] = getCountX() - sTempPosArray[0] - 1;
         }
@@ -189,19 +178,17 @@
                 sTempPosArray[1] * getCountX() + sTempPosArray[0]);
     }
 
+    @Override
     public boolean isFull() {
         return getItemCount() >= mMaxNumItems;
     }
 
+    @Override
     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.
-     */
+    @Override
     public void arrangeChildren(ArrayList<View> list, int itemCount) {
         setupContentDimensions(itemCount);
         removeAllViews();
@@ -228,9 +215,40 @@
         }
     }
 
-    /**
-     * Reorders the items such that the {@param empty} spot moves to {@param target}
-     */
+    @Override
+    public View iterateOverItems(ItemOperator op) {
+        for (int j = 0; j < getCountY(); j++) {
+            for (int i = 0; i < getCountX(); i++) {
+                View v = getChildAt(i, j);
+                if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) {
+                    return v;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getAccessibilityDescription() {
+        return String.format(getContext().getString(R.string.folder_opened),
+                getCountX(), getCountY());
+    }
+
+    @Override
+    public void setFocusOnFirstChild() {
+        View firstChild = getChildAt(0, 0);
+        if (firstChild != null) {
+            firstChild.requestFocus();
+        }
+    }
+
+    @Override
+    public View getLastItem() {
+        int total = getShortcutsAndWidgets().getChildCount();
+        return getShortcutsAndWidgets().getChildAt(total % getCountX(), total / getCountX());
+    }
+
+    @Override
     public void realTimeReorder(int empty, int target) {
         boolean wrap;
         int startX;