Merge "Enabling accessible drag and drop" into ub-launcher3-burnaby
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ed8d43e..74cdf11 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!--
+     Copyright (C) 2008 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,42 +15,50 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.Folder
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.Folder xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:background="@drawable/quantum_panel">
+    android:background="@drawable/quantum_panel"
+    android:orientation="vertical" >
 
-    <ScrollView
-        android:id="@+id/scroll_view"
+    <FrameLayout
+        android:id="@+id/folder_content_wrapper"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
-      <com.android.launcher3.CellLayout
-          android:id="@+id/folder_content"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:cacheColorHint="#ff333333"
-          android:hapticFeedbackEnabled="false" />
-    </ScrollView>
+        android:layout_height="match_parent" >
+
+        <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size -->
+
+        <com.android.launcher3.FocusIndicatorView
+            android:id="@+id/focus_indicator"
+            android:layout_width="20dp"
+            android:layout_height="20dp" />
+
+        <com.android.launcher3.FolderCellLayout
+            android:id="@+id/folder_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:cacheColorHint="#ff333333"
+            android:hapticFeedbackEnabled="false" />
+    </FrameLayout>
 
     <com.android.launcher3.FolderEditText
         android:id="@+id/folder_name"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_centerHorizontal="true"
-        android:paddingTop="@dimen/folder_name_padding"
-        android:paddingBottom="@dimen/folder_name_padding"
         android:background="#00000000"
-        android:hint="@string/folder_hint_text"
-        android:textSize="14sp"
-        android:textColor="#ff777777"
-        android:textColorHint="#ff808080"
-        android:textColorHighlight="#ffCCCCCC"
-        android:textCursorDrawable="@null"
+        android:fontFamily="sans-serif-condensed"
         android:gravity="center_horizontal"
-        android:singleLine="true"
+        android:hint="@string/folder_hint_text"
         android:imeOptions="flagNoExtractUi"
-        android:fontFamily="sans-serif-condensed"/>
-</com.android.launcher3.Folder>
+        android:paddingBottom="@dimen/folder_name_padding"
+        android:paddingTop="@dimen/folder_name_padding"
+        android:singleLine="true"
+        android:textColor="#ff777777"
+        android:textColorHighlight="#ffCCCCCC"
+        android:textColorHint="#ff808080"
+        android:textCursorDrawable="@null"
+        android:textSize="14sp" />
+
+</com.android.launcher3.Folder>
\ No newline at end of file
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b97f0f2..7d02e10 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -766,8 +766,7 @@
             lp.gravity = Gravity.BOTTOM;
             lp.width = LayoutParams.MATCH_PARENT;
             lp.height = hotseatBarHeightPx;
-            hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
-                    2 * edgeMarginPx, 0);
+            hotseat.setPadding(2 * edgeMarginPx, 0, 2 * edgeMarginPx, 0);
         }
         hotseat.setLayoutParams(lp);
 
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index bdfd7b2..737f6cc 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,7 +22,6 @@
 import android.view.SoundEffectConstants;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ScrollView;
 
 import com.android.launcher3.util.FocusLogic;
 
@@ -89,6 +88,7 @@
             countX = ((CellLayout) parentLayout).getCountX();
             countY = ((CellLayout) parentLayout).getCountY();
         } else if (v.getParent() instanceof ViewGroup) {
+            //TODO(hyunyoungs): figure out when this needs to be called.
             itemContainer = parentLayout = (ViewGroup) v.getParent();
             countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
             countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
@@ -102,6 +102,7 @@
         final int pageCount = container.getChildCount();
         ViewGroup newParent = null;
         View child = null;
+        // TODO(hyunyoungs): this matrix is not applicable on the last page.
         int[][] matrix = FocusLogic.createFullMatrix(countX, countY, true);
 
         // Process focus.
@@ -111,12 +112,22 @@
             return consume;
         }
         switch (newIconIndex) {
+            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+                newParent = getAppsCustomizePage(container, pageIndex -1);
+                if (newParent != null) {
+                    int row = FocusLogic.findRow(matrix, iconIndex);
+                    container.snapToPage(pageIndex - 1);
+                    // no need to create a new matrix.
+                    child = newParent.getChildAt(matrix[countX-1][row]);
+                }
+                break;
             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
                 newParent = getAppsCustomizePage(container, pageIndex - 1);
                 if (newParent != null) {
                     container.snapToPage(pageIndex - 1);
                     child = newParent.getChildAt(0);
                 }
+                break;
             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
                 newParent = getAppsCustomizePage(container, pageIndex - 1);
                 if (newParent != null) {
@@ -131,6 +142,14 @@
                     child = newParent.getChildAt(0);
                 }
                 break;
+            case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+                newParent = getAppsCustomizePage(container, pageIndex + 1);
+                if (newParent != null) {
+                    container.snapToPage(pageIndex + 1);
+                    int row = FocusLogic.findRow(matrix, iconIndex);
+                    child = newParent.getChildAt(matrix[0][row]);
+                }
+                break;
             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
                 child = container.getChildAt(0);
                 break;
@@ -256,7 +275,7 @@
 
         // Initialize the variables.
         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
-        final CellLayout iconLayout = (CellLayout) parent.getParent();
+        CellLayout iconLayout = (CellLayout) parent.getParent();
         final Workspace workspace = (Workspace) iconLayout.getParent();
         final ViewGroup launcher = (ViewGroup) workspace.getParent();
         final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
@@ -301,10 +320,23 @@
                     newIcon = tabs;
                 }
                 break;
+            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+                int row = FocusLogic.findRow(matrix, iconIndex);
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                if (parent != null) {
+                    iconLayout = (CellLayout) parent.getParent();
+                    matrix = FocusLogic.createSparseMatrix(iconLayout,
+                        iconLayout.getCountX(), row);
+                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+                        FocusLogic.PIVOT, pageIndex - 1, pageCount);
+                    newIcon = parent.getChildAt(newIconIndex);
+                }
+                break;
             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
                 newIcon = parent.getChildAt(0);
                 workspace.snapToPage(pageIndex - 1);
+                break;
             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
                 newIcon = parent.getChildAt(parent.getChildCount() - 1);
@@ -315,6 +347,17 @@
                 newIcon = parent.getChildAt(0);
                 workspace.snapToPage(pageIndex + 1);
                 break;
+            case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+                row = FocusLogic.findRow(matrix, iconIndex);
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+                if (parent != null) {
+                    iconLayout = (CellLayout) parent.getParent();
+                    matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
+                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+                        FocusLogic.PIVOT, pageIndex, pageCount);
+                    newIcon = parent.getChildAt(newIconIndex);
+                }
+                break;
             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
                 newIcon = parent.getChildAt(0);
                 break;
@@ -354,8 +397,7 @@
         // Initialize the variables.
         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
         final CellLayout layout = (CellLayout) parent.getParent();
-        final ScrollView scrollView = (ScrollView) layout.getParent();
-        final Folder folder = (Folder) scrollView.getParent();
+        final Folder folder = (Folder) layout.getParent().getParent();
         View title = folder.mFolderName;
         Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
         final int countX = layout.getCountX();
diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java
index 7d4664a..af3b976 100644
--- a/src/com/android/launcher3/FocusIndicatorView.java
+++ b/src/com/android/launcher3/FocusIndicatorView.java
@@ -23,7 +23,6 @@
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.View;
-import android.view.ViewParent;
 
 public class FocusIndicatorView extends View implements View.OnFocusChangeListener {
 
@@ -32,9 +31,6 @@
     private static final float MIN_VISIBLE_ALPHA = 0.2f;
     private static final long ANIM_DURATION = 150;
 
-    private static final int[] sTempPos = new int[2];
-    private static final int[] sTempShift = new int[2];
-
     private final int[] mIndicatorPos = new int[2];
     private final int[] mTargetViewPos = new int[2];
 
@@ -80,7 +76,8 @@
         }
 
         if (!mInitiated) {
-            getLocationRelativeToParentPagedView(this, mIndicatorPos);
+            // The parent view should always the a parent of the target view.
+            computeLocationRelativeToParent(this, (View) getParent(), mIndicatorPos);
             mInitiated = true;
         }
 
@@ -93,7 +90,7 @@
             nextState.scaleX = v.getScaleX() * v.getWidth() / indicatorWidth;
             nextState.scaleY = v.getScaleY() * v.getHeight() / indicatorHeight;
 
-            getLocationRelativeToParentPagedView(v, mTargetViewPos);
+            computeLocationRelativeToParent(v, (View) getParent(), mTargetViewPos);
             nextState.x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - nextState.scaleX) * indicatorWidth / 2;
             nextState.y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - nextState.scaleY) * indicatorHeight / 2;
 
@@ -150,27 +147,32 @@
     }
 
     /**
-     * Gets the location of a view relative in the window, off-setting any shift due to
-     * page view scroll
+     * Computes the location of a view relative to {@link #mCommonParent}, off-setting
+     * any shift due to page view scroll.
+     * @param pos an array of two integers in which to hold the coordinates
      */
-    private static void getLocationRelativeToParentPagedView(View v, int[] pos) {
-        getPagedViewScrollShift(v, sTempShift);
-        v.getLocationInWindow(sTempPos);
-        pos[0] = sTempPos[0] + sTempShift[0];
-        pos[1] = sTempPos[1] + sTempShift[1];
+    private static void computeLocationRelativeToParent(View v, View parent, int[] pos) {
+        pos[0] = pos[1] = 0;
+        computeLocationRelativeToParentHelper(v, parent, pos);
+
+        // If a view is scaled, its position will also shift accordingly. For optimization, only
+        // consider this for the last node.
+        pos[0] += (1 - v.getScaleX()) * v.getWidth() / 2;
+        pos[1] += (1 - v.getScaleY()) * v.getHeight() / 2;
     }
 
-    private static void getPagedViewScrollShift(View child, int[] shift) {
-        ViewParent parent = child.getParent();
+    private static void computeLocationRelativeToParentHelper(View child,
+            View commonParent, int[] shift) {
+        View parent = (View) child.getParent();
         if (parent instanceof PagedView) {
-            View parentView = (View) parent;
-            child.getLocationInWindow(sTempPos);
-            shift[0] = parentView.getPaddingLeft() - sTempPos[0];
-            shift[1] = -(int) child.getTranslationY();
-        } else if (parent instanceof View) {
-            getPagedViewScrollShift((View) parent, shift);
-        } else {
-            shift[0] = shift[1] = 0;
+            child = ((PagedView) parent).getPageAt(0);
+        }
+
+        shift[0] += child.getLeft();
+        shift[1] += child.getTop();
+
+        if (parent != commonParent) {
+            computeLocationRelativeToParentHelper(parent, commonParent, shift);
         }
     }
 
diff --git a/src/com/android/launcher3/FocusOnlyTabWidget.java b/src/com/android/launcher3/FocusOnlyTabWidget.java
deleted file mode 100644
index 08fc311..0000000
--- a/src/com/android/launcher3/FocusOnlyTabWidget.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TabWidget;
-
-public class FocusOnlyTabWidget extends TabWidget {
-    public FocusOnlyTabWidget(Context context) {
-        super(context);
-    }
-
-    public FocusOnlyTabWidget(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public View getSelectedTab() {
-        final int count = getTabCount();
-        for (int i = 0; i < count; ++i) {
-            View v = getChildTabViewAt(i);
-            if (v.isSelected()) {
-                return v;
-            }
-        }
-        return null;
-    }
-
-    public int getChildTabIndex(View v) {
-        final int tabCount = getTabCount();
-        for (int i = 0; i < tabCount; ++i) {
-            if (getChildTabViewAt(i) == v) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    public void setCurrentTabToFocusedTab() {
-        View tab = null;
-        int index = -1;
-        final int count = getTabCount();
-        for (int i = 0; i < count; ++i) {
-            View v = getChildTabViewAt(i);
-            if (v.hasFocus()) {
-                tab = v;
-                index = i;
-                break;
-            }
-        }
-        if (index > -1) {
-            super.setCurrentTab(index);
-            super.onFocusChange(tab, true);
-        }
-    }
-    public void superOnFocusChange(View v, boolean hasFocus) {
-        super.onFocusChange(v, hasFocus);
-    }
-
-    @Override
-    public void onFocusChange(android.view.View v, boolean hasFocus) {
-        if (v == this && hasFocus && getTabCount() > 0) {
-            getSelectedTab().requestFocus();
-            return;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 11e9835..4414672 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -45,10 +45,10 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.LinearLayout;
-import android.widget.ScrollView;
 import android.widget.TextView;
 
 import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.Workspace.ItemOperator;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -61,64 +61,65 @@
         View.OnFocusChangeListener {
     private static final String TAG = "Launcher.Folder";
 
-    protected DragController mDragController;
-    protected Launcher mLauncher;
-    protected FolderInfo mInfo;
+    /**
+     * We avoid measuring {@link #mContentWrapper} with a 0 width or height, as this
+     * results in CellLayout being measured as UNSPECIFIED, which it does not support.
+     */
+    private static final int MIN_CONTENT_DIMEN = 5;
 
     static final int STATE_NONE = -1;
     static final int STATE_SMALL = 0;
     static final int STATE_ANIMATING = 1;
     static final int STATE_OPEN = 2;
 
-    private int mExpandDuration;
-    private int mMaterialExpandDuration;
-    private int mMaterialExpandStagger;
-    protected CellLayout mContent;
-    private ScrollView mScrollView;
-    private final LayoutInflater mInflater;
-    private final IconCache mIconCache;
-    private int mState = STATE_NONE;
-    private static final int REORDER_ANIMATION_DURATION = 230;
     private static final int REORDER_DELAY = 250;
     private static final int ON_EXIT_CLOSE_DELAY = 400;
-    private boolean mRearrangeOnClose = false;
+    private static final Rect sTempRect = new Rect();
+
+    private static String sDefaultFolderName;
+    private static String sHintText;
+
+    private final Alarm mReorderAlarm = new Alarm();
+    private final Alarm mOnExitAlarm = new Alarm();
+
+    private final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+
+    private final int mExpandDuration;
+    private final int mMaterialExpandDuration;
+    private final int mMaterialExpandStagger;
+
+    private final InputMethodManager mInputMethodManager;
+
+    protected final Launcher mLauncher;
+    protected DragController mDragController;
+    protected FolderInfo mInfo;
+
     private FolderIcon mFolderIcon;
-    private int mMaxCountX;
-    private int mMaxCountY;
-    private int mMaxNumItems;
-    private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+
+    private FolderContent mContent;
+    private View mContentWrapper;
+    FolderEditText mFolderName;
+
+    private View mBottomContent;
+    private int mBottomContentHeight;
+
+    // Cell ranks used for drag and drop
+    private int mTargetRank, mPrevTargetRank, mEmptyCellRank;
+
+    private int mState = STATE_NONE;
+    private boolean mRearrangeOnClose = false;
     boolean mItemsInvalidated = false;
     private ShortcutInfo mCurrentDragInfo;
     private View mCurrentDragView;
     private boolean mIsExternalDrag;
     boolean mSuppressOnAdd = false;
-    private int[] mTargetCell = new int[2];
-    private int[] mPreviousTargetCell = new int[2];
-    private int[] mEmptyCell = new int[2];
-    private Alarm mReorderAlarm = new Alarm();
-    private Alarm mOnExitAlarm = new Alarm();
-    private int mFolderNameHeight;
-    private Rect mTempRect = new Rect();
     private boolean mDragInProgress = false;
     private boolean mDeleteFolderOnDropCompleted = false;
     private boolean mSuppressFolderDeletion = false;
     private boolean mItemAddedBackToSelfViaIcon = false;
-    FolderEditText mFolderName;
     private float mFolderIconPivotX;
     private float mFolderIconPivotY;
-
     private boolean mIsEditingName = false;
-    private InputMethodManager mInputMethodManager;
-
-    private static String sDefaultFolderName;
-    private static String sHintText;
-
-    private FocusIndicatorView mFocusIndicatorHandler;
-
-    // We avoid measuring the scroll view with a 0 width or height, as this
-    // results in CellLayout being measured as UNSPECIFIED, which it does
-    // not support.
-    private static final int MIN_CONTENT_DIMEN = 5;
 
     private boolean mDestroyed;
 
@@ -130,25 +131,15 @@
      * Used to inflate the Workspace from XML.
      *
      * @param context The application's context.
-     * @param attrs The attribtues set containing the Workspace's customization values.
+     * @param attrs The attributes set containing the Workspace's customization values.
      */
     public Folder(Context context, AttributeSet attrs) {
         super(context, attrs);
-
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
         setAlwaysDrawnWithCacheEnabled(false);
-        mInflater = LayoutInflater.from(context);
-        mIconCache = app.getIconCache();
-
-        Resources res = getResources();
-        mMaxCountX = (int) grid.numColumns;
-        mMaxCountY = (int) grid.numRows;
-        mMaxNumItems = mMaxCountX * mMaxCountY;
-
         mInputMethodManager = (InputMethodManager)
                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
 
+        Resources res = getResources();
         mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
         mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
         mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger);
@@ -162,44 +153,35 @@
         mLauncher = (Launcher) context;
         // We need this view to be focusable in touch mode so that when text editing of the folder
         // name is complete, we have something to focus on, thus hiding the cursor and giving
-        // reliable behvior when clicking the text field (since it will always gain focus on click).
+        // reliable behavior when clicking the text field (since it will always gain focus on click).
         setFocusableInTouchMode(true);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mScrollView = (ScrollView) findViewById(R.id.scroll_view);
-        mContent = (CellLayout) findViewById(R.id.folder_content);
+        mContentWrapper = findViewById(R.id.folder_content_wrapper);
+        mContent = (FolderContent) findViewById(R.id.folder_content);
+        mContent.setFolder(this);
 
-        mFocusIndicatorHandler = new FocusIndicatorView(getContext());
-        mContent.addView(mFocusIndicatorHandler, 0);
-        mFocusIndicatorHandler.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
-        mFocusIndicatorHandler.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
-
-        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);
 
-        // We find out how tall the text view wants to be (it is set to wrap_content), so that
-        // we can allocate the appropriate amount of space for it.
-        int measureSpec = MeasureSpec.UNSPECIFIED;
-        mFolderName.measure(measureSpec, measureSpec);
-        mFolderNameHeight = mFolderName.getMeasuredHeight();
-
         // We disable action mode for now since it messes up the view on phones
         mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
         mFolderName.setOnEditorActionListener(this);
         mFolderName.setSelectAllOnFocus(true);
         mFolderName.setInputType(mFolderName.getInputType() |
                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+        // We only have the folder name at the bottom for now
+        mBottomContent = mFolderName;
+        // We find out how tall the bottom content wants to be (it is set to wrap_content), so that
+        // we can allocate the appropriate amount of space for it.
+        int measureSpec = MeasureSpec.UNSPECIFIED;
+        mBottomContent.measure(measureSpec, measureSpec);
+        mBottomContentHeight = mBottomContent.getMeasuredHeight();
     }
 
     private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -240,8 +222,7 @@
             mLauncher.getWorkspace().beginDragShared(v, this);
 
             mCurrentDragInfo = item;
-            mEmptyCell[0] = item.cellX;
-            mEmptyCell[1] = item.cellY;
+            mEmptyCellRank = item.rank;
             mCurrentDragView = v;
 
             mContent.removeView(mCurrentDragView);
@@ -298,10 +279,6 @@
         return mFolderName;
     }
 
-    public CellLayout getContent() {
-        return mContent;
-    }
-
     /**
      * We need to handle touch events to prevent them from falling through to the workspace below.
      */
@@ -314,7 +291,7 @@
         mDragController = dragController;
     }
 
-    void setFolderIcon(FolderIcon icon) {
+    public void setFolderIcon(FolderIcon icon) {
         mFolderIcon = icon;
     }
 
@@ -334,30 +311,9 @@
     void bind(FolderInfo info) {
         mInfo = info;
         ArrayList<ShortcutInfo> children = info.contents;
-        ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
-
-        final int totalChildren = children.size();
-        setupContentForNumItems(totalChildren);
-
-        // Arrange children in the grid based on the rank.
         Collections.sort(children, Utilities.RANK_COMPARATOR);
-        final int countX = mContent.getCountX();
 
-        int visibleChildren = 0;
-        for (int i = 0; i < children.size(); i++) {
-            ShortcutInfo child = children.get(i);
-            child.cellX = i % countX;
-            child.cellY = i / countX;
-
-            if (createAndAddShortcut(child) == null) {
-                overflow.add(child);
-            } else {
-                visibleChildren++;
-            }
-        }
-
-        // We rearrange the items in case there are any empty gaps
-        setupContentForNumItems(visibleChildren);
+        ArrayList<ShortcutInfo> overflow = mContent.bindItems(children);
 
         // If our folder has too many items we prune them from the list. This is an issue
         // when upgrading from the old Folders implementation which could contain an unlimited
@@ -367,6 +323,14 @@
             LauncherModel.deleteItemFromDatabase(mLauncher, item);
         }
 
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        if (lp == null) {
+            lp = new DragLayer.LayoutParams(0, 0);
+            lp.customPosition = true;
+            setLayoutParams(lp);
+        }
+        centerAboutIcon();
+
         mItemsInvalidated = true;
         updateTextViewFocus();
         mInfo.addListener(this);
@@ -464,14 +428,14 @@
             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));
 
-            mFolderName.setAlpha(0f);
-            Animator textAlpha = LauncherAnimUtils.ofFloat(mFolderName, "alpha", 0f, 1f);
+            mBottomContent.setAlpha(0f);
+            Animator textAlpha = LauncherAnimUtils.ofFloat(mBottomContent, "alpha", 0f, 1f);
             textAlpha.setDuration(mMaterialExpandDuration);
             textAlpha.setStartDelay(mMaterialExpandStagger);
             textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
@@ -488,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);
                 }
             };
         }
@@ -500,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
@@ -512,7 +475,7 @@
                     onCompleteRunnable.run();
                 }
 
-                setFocusOnFirstChild();
+                mContent.setFocusOnFirstChild();
             }
         });
         openFolderAnim.start();
@@ -524,14 +487,9 @@
     }
 
     public void beginExternalDrag(ShortcutInfo item) {
-        setupContentForNumItems(getItemCount() + 1);
-        findAndSetEmptyCells(item);
-
         mCurrentDragInfo = item;
-        mEmptyCell[0] = item.cellX;
-        mEmptyCell[1] = item.cellY;
+        mEmptyCellRank = mContent.allocateNewLastItemRank();
         mIsExternalDrag = true;
-
         mDragInProgress = true;
     }
 
@@ -546,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);
@@ -588,132 +539,34 @@
                     !isFull());
     }
 
-    protected boolean findAndSetEmptyCells(ShortcutInfo item) {
-        int[] emptyCell = new int[2];
-        if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
-            item.cellX = emptyCell[0];
-            item.cellY = emptyCell[1];
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    protected View createAndAddShortcut(ShortcutInfo item) {
-        final BubbleTextView textView =
-            (BubbleTextView) mInflater.inflate(R.layout.folder_application, this, false);
-        textView.applyFromShortcutInfo(item, mIconCache, false);
-
-        textView.setOnClickListener(this);
-        textView.setOnLongClickListener(this);
-        textView.setOnFocusChangeListener(mFocusIndicatorHandler);
-
-        // We need to check here to verify that the given item's location isn't already occupied
-        // by another item.
-        if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
-                || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
-            // This shouldn't happen, log it.
-            Log.e(TAG, "Folder order not properly persisted during bind");
-            if (!findAndSetEmptyCells(item)) {
-                return null;
-            }
-        }
-
-        CellLayout.LayoutParams lp =
-            new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
-        boolean insert = false;
-        textView.setOnKeyListener(new FolderKeyEventListener());
-        mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
-        return textView;
-    }
-
     public void onDragEnter(DragObject d) {
-        mPreviousTargetCell[0] = -1;
-        mPreviousTargetCell[1] = -1;
+        mPrevTargetRank = -1;
         mOnExitAlarm.cancelAlarm();
     }
 
     OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
         public void onAlarm(Alarm alarm) {
-            realTimeReorder(mEmptyCell, mTargetCell);
+            mContent.realTimeReorder(mEmptyCellRank, mTargetRank);
+            mEmptyCellRank = mTargetRank;
         }
     };
 
-    boolean readingOrderGreaterThan(int[] v1, int[] v2) {
-        if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private void realTimeReorder(int[] empty, int[] target) {
-        boolean wrap;
-        int startX;
-        int endX;
-        int startY;
-        int delay = 0;
-        float delayAmount = 30;
-        if (readingOrderGreaterThan(target, empty)) {
-            wrap = empty[0] >= mContent.getCountX() - 1;
-            startY = wrap ? empty[1] + 1 : empty[1];
-            for (int y = startY; y <= target[1]; y++) {
-                startX = y == empty[1] ? empty[0] + 1 : 0;
-                endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
-                for (int x = startX; x <= endX; x++) {
-                    View v = mContent.getChildAt(x,y);
-                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
-                            REORDER_ANIMATION_DURATION, delay, true, true)) {
-                        empty[0] = x;
-                        empty[1] = y;
-                        delay += delayAmount;
-                        delayAmount *= 0.9;
-                    }
-                }
-            }
-        } else {
-            wrap = empty[0] == 0;
-            startY = wrap ? empty[1] - 1 : empty[1];
-            for (int y = startY; y >= target[1]; y--) {
-                startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
-                endX = y > target[1] ? 0 : target[0];
-                for (int x = startX; x >= endX; x--) {
-                    View v = mContent.getChildAt(x,y);
-                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
-                            REORDER_ANIMATION_DURATION, delay, true, true)) {
-                        empty[0] = x;
-                        empty[1] = y;
-                        delay += delayAmount;
-                        delayAmount *= 0.9;
-                    }
-                }
-            }
-        }
-    }
-
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public boolean isLayoutRtl() {
         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
     }
 
     public void onDragOver(DragObject d) {
-        final int scrollOffset = mScrollView.getScrollY();
         final float[] r = d.getVisualCenter(null);
         r[0] -= getPaddingLeft();
         r[1] -= getPaddingTop();
 
-        mTargetCell = mContent.findNearestArea(
-                (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
-        if (isLayoutRtl()) {
-            mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
-        }
-        if (mTargetCell[0] != mPreviousTargetCell[0]
-                || mTargetCell[1] != mPreviousTargetCell[1]) {
+        mTargetRank = mContent.findNearestArea((int) r[0], (int) r[1]);
+        if (mTargetRank != mPrevTargetRank) {
             mReorderAlarm.cancelAlarm();
             mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
             mReorderAlarm.setAlarm(REORDER_DELAY);
-            mPreviousTargetCell[0] = mTargetCell[0];
-            mPreviousTargetCell[1] = mTargetCell[1];
+            mPrevTargetRank = mTargetRank;
         }
     }
 
@@ -764,7 +617,7 @@
                 replaceFolderWithFinalItem();
             }
         } else {
-            setupContentForNumItems(getItemCount());
+            rearrangeChildren();
             // The drag failed, we need to return the item to the folder
             mFolderIcon.onDrop(d);
         }
@@ -865,37 +718,8 @@
         return true;
     }
 
-    private void setupContentDimensions(int count) {
-        ArrayList<View> list = getItemsInReadingOrder();
-
-        int countX = mContent.getCountX();
-        int countY = mContent.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;
-        }
-        mContent.setGridSize(countX, countY);
-        arrangeChildren(list);
-    }
-
     public boolean isFull() {
-        return getItemCount() >= mMaxNumItems;
+        return mContent.isFull();
     }
 
     private void centerAboutIcon() {
@@ -905,13 +729,13 @@
         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
         int height = getFolderHeight();
 
-        float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
+        float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
 
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
 
-        int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
-        int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
+        int centerX = (int) (sTempRect.left + sTempRect.width() * scale / 2);
+        int centerY = (int) (sTempRect.top + sTempRect.height() * scale / 2);
         int centeredLeft = centerX - width / 2;
         int centeredTop = centerY - height / 2;
         int currentPage = mLauncher.getWorkspace().getNextPage();
@@ -964,18 +788,6 @@
         return mFolderIconPivotY;
     }
 
-    private void setupContentForNumItems(int count) {
-        setupContentDimensions(count);
-
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        if (lp == null) {
-            lp = new DragLayer.LayoutParams(0, 0);
-            lp.customPosition = true;
-            setLayoutParams(lp);
-        }
-        centerAboutIcon();
-    }
-
     private int getContentAreaHeight() {
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
@@ -983,7 +795,7 @@
                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
         int maxContentAreaHeight = grid.availableHeightPx -
                 workspacePadding.top - workspacePadding.bottom -
-                mFolderNameHeight;
+                mBottomContentHeight;
         int height = Math.min(maxContentAreaHeight,
                 mContent.getDesiredHeight());
         return Math.max(height, MIN_CONTENT_DIMEN);
@@ -994,58 +806,52 @@
     }
 
     private int getFolderHeight() {
-        int height = getPaddingTop() + getPaddingBottom()
-                + getContentAreaHeight() + mFolderNameHeight;
-        return height;
+        return getFolderHeight(getContentAreaHeight());
+    }
+
+    private int getFolderHeight(int contentAreaHeight) {
+        return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mBottomContentHeight;
     }
 
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
-        int height = getFolderHeight();
-        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
-                MeasureSpec.EXACTLY);
-        int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
-                MeasureSpec.EXACTLY);
+        int contentWidth = getContentAreaWidth();
+        int contentHeight = getContentAreaHeight();
 
-        mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
-        mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
-        mFolderName.measure(contentAreaWidthSpec,
-                MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
-        setMeasuredDimension(width, height);
+        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
+        int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
+
+        mContent.setFixedSize(contentWidth, contentHeight);
+        mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec);
+
+        // Move the bottom content below mContent
+        mBottomContent.measure(contentAreaWidthSpec,
+                MeasureSpec.makeMeasureSpec(mBottomContentHeight, MeasureSpec.EXACTLY));
+
+        int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth;
+        int folderHeight = getFolderHeight(contentHeight);
+        setMeasuredDimension(folderWidth, folderHeight);
     }
 
-    private void arrangeChildren(ArrayList<View> list) {
-        int[] vacant = new int[2];
-        if (list == null) {
-            list = getItemsInReadingOrder();
-        }
-        mContent.removeAllViews();
+    /**
+     * Rearranges the children based on their rank.
+     */
+    public void rearrangeChildren() {
+        rearrangeChildren(-1);
+    }
 
-        for (int i = 0; i < list.size(); i++) {
-            View v = list.get(i);
-            mContent.getVacantCell(vacant, 1, 1);
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
-            lp.cellX = vacant[0];
-            lp.cellY = vacant[1];
-            ItemInfo info = (ItemInfo) v.getTag();
-            if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
-                info.cellX = vacant[0];
-                info.cellY = vacant[1];
-                LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
-                        info.cellX, info.cellY);
-            }
-            boolean insert = false;
-            mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
-        }
+    /**
+     * Rearranges the children based on their rank.
+     * @param itemCount if greater than the total children count, empty spaces are left at the end,
+     * otherwise it is ignored.
+     */
+    public void rearrangeChildren(int itemCount) {
+        ArrayList<View> views = getItemsInReadingOrder();
+        mContent.arrangeChildren(views, Math.max(itemCount, views.size()));
         mItemsInvalidated = true;
     }
 
     public int getItemCount() {
-        return mContent.getShortcutsAndWidgets().getChildCount();
-    }
-
-    public View getItemAt(int index) {
-        return mContent.getShortcutsAndWidgets().getChildAt(index);
+        return mContent.getItemCount();
     }
 
     private void onCloseComplete() {
@@ -1058,7 +864,7 @@
         mFolderIcon.requestFocus();
 
         if (mRearrangeOnClose) {
-            setupContentForNumItems(getItemCount());
+            rearrangeChildren();
             mRearrangeOnClose = false;
         }
         if (getItemCount() <= 1) {
@@ -1108,7 +914,7 @@
                 }
             }
         };
-        View finalChild = getItemAt(0);
+        View finalChild = mContent.getLastItem();
         if (finalChild != null) {
             mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
         } else {
@@ -1124,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());
@@ -1153,9 +958,7 @@
         View currentDragView;
         ShortcutInfo si = mCurrentDragInfo;
         if (mIsExternalDrag) {
-            si.cellX = mEmptyCell[0];
-            si.cellY = mEmptyCell[1];
-
+            currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
             // Actually move the item in the database if it was an external drag. Call this
             // before creating the view, so that ShortcutInfo is updated appropriately.
             LauncherModel.addOrMoveItemInDatabase(
@@ -1166,14 +969,9 @@
                 updateItemLocationsInDatabaseBatch();
             }
             mIsExternalDrag = false;
-
-            currentDragView = createAndAddShortcut(si);
         } else {
             currentDragView = mCurrentDragView;
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams();
-            si.cellX = lp.cellX = mEmptyCell[0];
-            si.cellX = lp.cellY = mEmptyCell[1];
-            mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true);
+            mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
         }
 
         if (d.dragView.hasDrawn()) {
@@ -1192,7 +990,7 @@
             currentDragView.setVisibility(VISIBLE);
         }
         mItemsInvalidated = true;
-        setupContentDimensions(getItemCount());
+        rearrangeChildren();
 
         // Temporarily suppress the listener, as we did all the work already here.
         mSuppressOnAdd = true;
@@ -1219,12 +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;
-        if (!findAndSetEmptyCells(item)) {
-            // The current layout is full, can we expand it?
-            setupContentForNumItems(getItemCount() + 1);
-            findAndSetEmptyCells(item);
-        }
-        createAndAddShortcut(item);
+        mContent.createAndAddViewForRank(item, mContent.allocateNewLastItemRank());
         LauncherModel.addOrMoveItemInDatabase(
                 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
     }
@@ -1239,23 +1032,21 @@
         if (mState == STATE_ANIMATING) {
             mRearrangeOnClose = true;
         } else {
-            setupContentForNumItems(getItemCount());
+            rearrangeChildren();
         }
         if (getItemCount() <= 1) {
             replaceFolderWithFinalItem();
         }
     }
 
-    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() {
@@ -1268,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;
@@ -1295,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
new file mode 100644
index 0000000..b354ec7
--- /dev/null
+++ b/src/com/android/launcher3/FolderCellLayout.java
@@ -0,0 +1,309 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.Workspace.ItemOperator;
+
+import java.util.ArrayList;
+
+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;
+    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();
+
+        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);
+    }
+
+    /**
+     * 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);
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    public int allocateNewLastItemRank() {
+        int rank = getItemCount();
+        mFolder.rearrangeChildren(rank + 1);
+        return rank;
+    }
+
+    @Override
+    public View createAndAddViewForRank(ShortcutInfo item, int rank) {
+        updateItemXY(item, rank);
+        return addNewView(item);
+    }
+
+    @Override
+    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, mFolder.mLauncher.getViewIdForItem(item), 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, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+        return textView;
+    }
+
+    /**
+     * Refer {@link #findNearestArea(int, int, int, int, View, boolean, int[])}
+     */
+    @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;
+        }
+
+        // Convert this position to rank.
+        return Math.min(mAllocatedContentSize - 1,
+                sTempPosArray[1] * getCountX() + sTempPosArray[0]);
+    }
+
+    @Override
+    public boolean isFull() {
+        return getItemCount() >= mMaxNumItems;
+    }
+
+    @Override
+    public int getItemCount() {
+        return getShortcutsAndWidgets().getChildCount();
+    }
+
+    @Override
+    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, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+        }
+    }
+
+    @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 lastRank = getShortcutsAndWidgets().getChildCount() - 1;
+        // count can be zero if the folder is not yet laid out.
+        int count = getCountX();
+        if (count > 0) {
+            return getShortcutsAndWidgets().getChildAt(lastRank % count, lastRank / count);
+        } else {
+            return getShortcutsAndWidgets().getChildAt(lastRank);
+        }
+    }
+
+    @Override
+    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;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e58d0da..442bad1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3210,7 +3210,7 @@
     /**
      * Returns the CellLayout of the specified container at the specified screen.
      */
-    CellLayout getCellLayout(long container, long screenId) {
+    public CellLayout getCellLayout(long container, long screenId) {
         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
             if (mHotseat != null) {
                 return mHotseat.getLayout();
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index b618444..b7a271e 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -297,6 +297,10 @@
             if (loader == null) {
                 loader = getDefaultLayoutParser();
             }
+
+            // There might be some partially restored DB items, due to buggy restore logic in
+            // previous versions of launcher.
+            createEmptyDB();
             // Populate favorites table with initial favorites
             if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                     && usingExternallyProvidedLayout) {
@@ -733,23 +737,7 @@
         }
 
         private long initializeMaxItemId(SQLiteDatabase db) {
-            Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
-
-            // get the result
-            final int maxIdIndex = 0;
-            long id = -1;
-            if (c != null && c.moveToNext()) {
-                id = c.getLong(maxIdIndex);
-            }
-            if (c != null) {
-                c.close();
-            }
-
-            if (id == -1) {
-                throw new RuntimeException("Error: could not query max item id");
-            }
-
-            return id;
+            return getMaxId(db, TABLE_FAVORITES);
         }
 
         // Generates a new ID to use for an workspace screen in your database. This method
@@ -768,25 +756,7 @@
         }
 
         private long initializeMaxScreenId(SQLiteDatabase db) {
-            Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
-
-            // get the result
-            final int maxIdIndex = 0;
-            long id = -1;
-            if (c != null && c.moveToNext()) {
-                id = c.getLong(maxIdIndex);
-            }
-            if (c != null) {
-                c.close();
-            }
-
-            if (id == -1) {
-                throw new RuntimeException("Error: could not query max screen id");
-            }
-
-            // Log to disk
-            Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
-            return id;
+            return getMaxId(db, TABLE_WORKSPACE_SCREENS);
         }
 
         private boolean initializeExternalAdd(ContentValues values) {
@@ -1199,6 +1169,27 @@
         }
     }
 
+    /**
+     * @return the max _id in the provided table.
+     */
+    private static long getMaxId(SQLiteDatabase db, String table) {
+        Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
+        // get the result
+        long id = -1;
+        if (c != null && c.moveToNext()) {
+            id = c.getLong(0);
+        }
+        if (c != null) {
+            c.close();
+        }
+
+        if (id == -1) {
+            throw new RuntimeException("Error: could not query max id in " + table);
+        }
+
+        return id;
+    }
+
     static class SqlArguments {
         public final String table;
         public final String where;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 125a2ed..b2b502d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -4210,19 +4210,14 @@
                 removeWorkspaceItem(mDragInfo.cell);
             }
         } else if (mDragInfo != null) {
-            CellLayout cellLayout;
-            if (mLauncher.isHotseatLayout(target)) {
-                cellLayout = mLauncher.getHotseat().getLayout();
-            } else {
-                cellLayout = getScreenWithId(mDragInfo.screenId);
-            }
-            if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
-                throw new RuntimeException("Invalid state: cellLayout == null in "
-                        + "Workspace#onDropCompleted. Please file a bug. ");
-            }
+            final CellLayout cellLayout = mLauncher.getCellLayout(
+                    mDragInfo.container, mDragInfo.screenId);
             if (cellLayout != null) {
                 cellLayout.onDropChild(mDragInfo.cell);
-            }
+            } else if (LauncherAppState.isDogfoodBuild()) {
+                throw new RuntimeException("Invalid state: cellLayout == null in "
+                        + "Workspace#onDropCompleted. Please file a bug. ");
+            };
         }
         if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
                 && mDragInfo.cell != null) {
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index c0730d9..0c6bfbf 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -48,16 +48,19 @@
     // Item and page index related constant used by {@link #handleKeyEvent}.
     public static final int NOOP = -1;
 
-    public static final int PREVIOUS_PAGE_FIRST_ITEM    = -2;
-    public static final int PREVIOUS_PAGE_LAST_ITEM     = -3;
+    public static final int PREVIOUS_PAGE_RIGHT_COLUMN  = -2;
+    public static final int PREVIOUS_PAGE_FIRST_ITEM    = -3;
+    public static final int PREVIOUS_PAGE_LAST_ITEM     = -4;
 
-    public static final int CURRENT_PAGE_FIRST_ITEM     = -4;
-    public static final int CURRENT_PAGE_LAST_ITEM      = -5;
+    public static final int CURRENT_PAGE_FIRST_ITEM     = -5;
+    public static final int CURRENT_PAGE_LAST_ITEM      = -6;
 
-    public static final int NEXT_PAGE_FIRST_ITEM        = -6;
+    public static final int NEXT_PAGE_FIRST_ITEM        = -7;
+    public static final int NEXT_PAGE_LEFT_COLUMN       = -8;
 
     // Matrix related constant.
     public static final int EMPTY = -1;
+    public static final int PIVOT = 100;
 
     /**
      * Returns true only if this utility class handles the key code.
@@ -87,13 +90,13 @@
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/);
                 if (newIndex == NOOP && pageIndex > 0) {
-                    return PREVIOUS_PAGE_LAST_ITEM;
+                    newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/);
                 if (newIndex == NOOP && pageIndex < pageCount - 1) {
-                    return NEXT_PAGE_FIRST_ITEM;
+                    newIndex = NEXT_PAGE_LEFT_COLUMN;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -169,7 +172,7 @@
             matrix[cx][cy] = i;
         }
         if (DEBUG) {
-            printMatrix(matrix, m, n);
+            printMatrix(matrix);
         }
         return matrix;
     }
@@ -231,7 +234,47 @@
             }
         }
         if (DEBUG) {
-            printMatrix(matrix, m, n);
+            printMatrix(matrix);
+        }
+        return matrix;
+    }
+
+    /**
+     * Creates a sparse matrix that merges the icon of previous/next page and last column of
+     * current page. When left key is triggered on the leftmost column, sparse matrix is created
+     * that combines previous page matrix and an extra column on the right. Likewise, when right
+     * key is triggered on the rightmost column, sparse matrix is created that combines this column
+     * on the 0th column and the next page matrix.
+     *
+     * @param pivotX    x coordinate of the focused item in the current page
+     * @param pivotY    y coordinate of the focused item in the current page
+     */
+    // TODO: get rid of the dynamic matrix creation
+    public static int[][] createSparseMatrix(CellLayout iconLayout, int pivotX, int pivotY) {
+
+        ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+        int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY(),
+                false /* set all cell to empty */);
+
+        // Iterate thru the children of the top parent.
+        for (int i = 0; i < iconParent.getChildCount(); i++) {
+            int cx = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellX;
+            int cy = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellY;
+            if (pivotX < 0) {
+                matrix[cx - pivotX][cy] = i;
+            } else {
+                matrix[cx][cy] = i;
+            }
+        }
+
+        if (pivotX < 0) {
+            matrix[0][pivotY] = PIVOT;
+        } else {
+            matrix[pivotX][pivotY] = PIVOT;
+        }
+        if (DEBUG) {
+            printMatrix(matrix);
         }
         return matrix;
     }
@@ -407,21 +450,32 @@
         return newIconIndex;
     }
 
+    /**
+     * Only used for debugging.
+     */
     private static String getStringIndex(int index) {
         switch(index) {
             case NOOP: return "NOOP";
             case PREVIOUS_PAGE_FIRST_ITEM:  return "PREVIOUS_PAGE_FIRST";
             case PREVIOUS_PAGE_LAST_ITEM:   return "PREVIOUS_PAGE_LAST";
+            case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
             case CURRENT_PAGE_FIRST_ITEM:   return "CURRENT_PAGE_FIRST";
             case CURRENT_PAGE_LAST_ITEM:    return "CURRENT_PAGE_LAST";
             case NEXT_PAGE_FIRST_ITEM:      return "NEXT_PAGE_FIRST";
+            case NEXT_PAGE_LEFT_COLUMN:     return "NEXT_PAGE_LEFT_COLUMN";
             default:
                 return Integer.toString(index);
         }
     }
 
-    private static void printMatrix(int[][] matrix, int m, int n) {
+    /**
+     * Only used for debugging.
+     */
+    private static void printMatrix(int[][] matrix) {
         Log.v(TAG, "\tprintMap:");
+        int m = matrix.length;
+        int n = matrix[0].length;
+
         for (int j=0; j < n; j++) {
             String colY = "\t\t";
             for (int i=0; i < m; i++) {
@@ -430,4 +484,24 @@
             Log.v(TAG, colY);
         }
     }
+
+    /**
+     * Figure out the location of the icon.
+     *
+     */
+    //TODO(hyunyoungs): this helper method should move to CellLayout class while removing the
+    // dynamic matrix creation all together.
+    public static int findRow(int[][] matrix, int iconIndex) {
+        int cntX = matrix.length;
+        int cntY = matrix[0].length;
+
+        for (int i = 0; i < cntX; i++) {
+            for (int j = 0; j < cntY; j++) {
+                if (matrix[i][j] == iconIndex) {
+                    return j;
+                }
+            }
+        }
+        return -1;
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index fd2e2a8..ea87014 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -18,6 +18,9 @@
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import com.android.launcher3.util.FocusLogic;
 
 /**
  * Tests the {@link FocusLogic} class that handles key event based focus handling.
@@ -37,6 +40,21 @@
     }
 
     public void testShouldConsume() {
-        // write tests.
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
+    }
+
+    public void testCreateSparseMatrix() {
+         // Either, 1) create a helper method to generate/instantiate all possible cell layout that
+         // may get created in real world to test this method. OR 2) Move all the matrix
+         // management routine to celllayout and write tests for them.
     }
 }