Merge "Ensuring setQsbSearchBar is called whenever the QSB is created" into ub-launcher3-burnaby
diff --git a/Android.mk b/Android.mk
index 632dd09..5267469 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,6 +23,8 @@
 
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-java-files-under, WallpaperPicker/src) \
     $(call all-renderscript-files-under, src) \
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current_dark.png b/res/drawable-xxhdpi/ic_pageindicator_current_dark.png
new file mode 100644
index 0000000..d5c4c8d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_current_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default_dark.png b/res/drawable-xxhdpi/ic_pageindicator_default_dark.png
new file mode 100644
index 0000000..79d307b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_default_dark.png
Binary files differ
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ed8d43e..7a4d5e8 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,49 @@
      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/res/layout/user_folder_scroll.xml b/res/layout/user_folder_scroll.xml
new file mode 100644
index 0000000..421e426
--- /dev/null
+++ b/res/layout/user_folder_scroll.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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.
+-->
+
+<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:background="@drawable/quantum_panel"
+    android:orientation="vertical" >
+
+    <FrameLayout
+        android:id="@+id/folder_content_wrapper"
+        android:layout_width="match_parent"
+        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.FolderPagedView
+            android:id="@+id/folder_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            launcher:pageIndicator="@+id/folder_page_indicator" />
+    </FrameLayout>
+
+    <LinearLayout
+        android:id="@+id/folder_footer"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <com.android.launcher3.FolderEditText
+            android:id="@+id/folder_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:background="#00000000"
+            android:fontFamily="sans-serif-condensed"
+            android:gravity="center_horizontal"
+            android:hint="@string/folder_hint_text"
+            android:imeOptions="flagNoExtractUi"
+            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" />
+
+        <include
+            android:id="@+id/folder_page_indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="12dp"
+            android:layout_gravity="center_vertical"
+            layout="@layout/page_indicator" />
+    </LinearLayout>
+
+</com.android.launcher3.Folder>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index cbec512..1acace6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -104,4 +104,5 @@
     <item type="id" name="action_uninstall" />
     <item type="id" name="action_info" />
     <item type="id" name="action_add_to_workspace" />
+    <item type="id" name="action_move" />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8b7e6c1..74b8814 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -305,4 +305,32 @@
 <!-- Strings for accessibility actions -->
     <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
     <string name="action_add_to_workspace">Add To Workspace</string>
+
+    <!-- Accessibility confirmation for item added to workspace -->
+    <string name="item_added_to_workspace">Item added to workspace</string>
+
+    <!-- Accessibility confirmation for item removed-->
+    <string name="item_removed_from_workspace">Item removed from workspace</string>
+
+    <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] -->
+    <string name="action_move">Move Item</string>
+
+    <!-- Accessibility description to move item to empty cell. -->
+    <string name="move_to_empty_cell">Move to empty cell <xliff:g id="number" example="1">%1$s</xliff:g>, <xliff:g id="number" example="1">%2$s</xliff:g></string>
+
+    <!-- Accessibility confirmation for item move -->
+    <string name="item_moved">Item moved</string>
+
+    <!-- Accessibility description to move item into an existing folder. -->
+    <string name="add_to_folder">Add to folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+
+    <!-- Accessibility confirmation for item added to folder-->
+    <string name="added_to_folder">Item added to folder</string>
+
+    <!-- Accessibility description to create folder with another item. -->
+    <string name="create_folder_with">Create folder with: <xliff:g id="name" example="Game">%1$s</xliff:g></string>
+
+    <!-- Accessibility confirmation for folder created -->
+    <string name="folder_created">Folder created</string>
+
 </resources>
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index c1aa19a..9f8d499 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -36,7 +36,6 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -45,6 +44,7 @@
 import android.widget.Toast;
 
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.FocusHelper.PagedViewKeyListener;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 import java.util.ArrayList;
@@ -139,7 +139,7 @@
  * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
  */
 public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
-        View.OnClickListener, View.OnKeyListener, DragSource,
+        View.OnClickListener, DragSource,
         PagedViewWidget.ShortPressListener, LauncherTransitionable {
     static final String TAG = "AppsCustomizePagedView";
 
@@ -182,6 +182,8 @@
     ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
     private static final int sPageSleepDelay = 200;
 
+    private final PagedViewKeyListener mKeyListener = new PagedViewKeyListener();
+
     private Runnable mInflateWidgetRunnable = null;
     private Runnable mBindWidgetRunnable = null;
     static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
@@ -449,10 +451,6 @@
         mWidgetInstructionToast.show();
     }
 
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
-    }
-
     /*
      * PagedViewWithDraggableItems implementation
      */
@@ -959,7 +957,7 @@
             icon.setOnClickListener(mLauncher);
             icon.setOnLongClickListener(this);
             icon.setOnTouchListener(this);
-            icon.setOnKeyListener(this);
+            icon.setOnKeyListener(mKeyListener);
             icon.setOnFocusChangeListener(layout.mFocusHandlerView);
 
             int index = i - startIndex;
@@ -1141,7 +1139,7 @@
             widget.setOnClickListener(this);
             widget.setOnLongClickListener(this);
             widget.setOnTouchListener(this);
-            widget.setOnKeyListener(this);
+            widget.setOnKeyListener(mKeyListener);
 
             // Layout each widget
             int ix = i % mWidgetCountX;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a3500aa..c57090d 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -22,6 +22,7 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -33,25 +34,34 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Animation;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LayoutAnimationController;
 
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.LauncherAccessibilityDelegate.DragType;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Stack;
 
 public class CellLayout extends ViewGroup {
@@ -169,6 +179,14 @@
 
     private final static Paint sPaint = new Paint();
 
+    // Related to accessible drag and drop
+    DragAndDropAccessibilityDelegate mTouchHelper = new DragAndDropAccessibilityDelegate(this);
+    private boolean mUseTouchHelper = false;
+    OnClickListener mOldClickListener = null;
+    OnClickListener mOldWorkspaceListener = null;
+    private int mDownX = 0;
+    private int mDownY = 0;
+
     public CellLayout(Context context) {
         this(context, null);
     }
@@ -294,6 +312,282 @@
         addView(mShortcutsAndWidgets);
     }
 
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public void enableAccessibleDrag(boolean enable) {
+        mUseTouchHelper = enable;
+        if (!enable) {
+            ViewCompat.setAccessibilityDelegate(this, null);
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            setOnClickListener(mLauncher);
+        } else {
+            ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            setOnClickListener(mTouchHelper);
+        }
+
+        // Invalidate the accessibility hierarchy
+        if (getParent() != null) {
+            getParent().notifySubtreeAccessibilityStateChanged(
+                    this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+        }
+    }
+
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // Always attempt to dispatch hover events to accessibility first.
+        if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
+            return true;
+        }
+        return super.dispatchHoverEvent(event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mDownX = (int) event.getX();
+            mDownY = (int) event.getY();
+        }
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mUseTouchHelper ||
+                (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
+            return true;
+        }
+        return false;
+    }
+
+    class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper implements OnClickListener {
+        private final Rect mTempRect = new Rect();
+
+        public DragAndDropAccessibilityDelegate(View forView) {
+            super(forView);
+        }
+
+        private int getViewIdAt(float x, float y) {
+            if (x < 0 || y < 0 || x > getMeasuredWidth() || y > getMeasuredHeight()) {
+                return ExploreByTouchHelper.INVALID_ID;
+            }
+
+            // Map coords to cell
+            int cellX = (int) Math.floor(x / (mCellWidth + mWidthGap));
+            int cellY = (int) Math.floor(y / (mCellHeight + mHeightGap));
+
+            // Map cell to id
+            int id = cellX * mCountY + cellY;
+            return id;
+        }
+
+        @Override
+        protected int getVirtualViewAt(float x, float y) {
+            return nearestDropLocation(getViewIdAt(x, y));
+        }
+
+        protected int nearestDropLocation(int id) {
+            int count = mCountX * mCountY;
+            for (int delta = 0; delta < count; delta++) {
+                if (id + delta <= (count - 1)) {
+                    int target = intersectsValidDropTarget(id + delta);
+                    if (target >= 0) {
+                        return target;
+                    }
+                } else if (id - delta >= 0) {
+                    int target = intersectsValidDropTarget(id - delta);
+                    if (target >= 0) {
+                        return target;
+                    }
+                }
+            }
+            return ExploreByTouchHelper.INVALID_ID;
+        }
+
+        /**
+         * Find the virtual view id corresponding to the top left corner of any drop region by which
+         * the passed id is contained. For an icon, this is simply
+         *
+         * @param id the id we're interested examining (ie. does it fit there?)
+         * @return the view id of the top left corner of a valid drop region or -1 if there is no
+         *         such valid region. For the icon, this can just be -1 or id.
+         */
+        protected int intersectsValidDropTarget(int id) {
+            LauncherAccessibilityDelegate delegate =
+                    LauncherAppState.getInstance().getAccessibilityDelegate();
+            LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+            int y = id % mCountY;
+            int x = id / mCountY;
+
+            if (dragInfo.dragType == DragType.WIDGET) {
+                // For a widget, every cell must be vacant. In addition, we will return any valid
+                // drop target by which the passed id is contained.
+                boolean fits = false;
+
+                // These represent the amount that we can back off if we hit a problem. They
+                // get consumed as we move up and to the right, trying new regions.
+                int spanX = dragInfo.info.spanX;
+                int spanY = dragInfo.info.spanY;
+
+                for (int m = 0; m < spanX; m++) {
+                    for (int n = 0; n < spanY; n++) {
+
+                        fits = true;
+                        int x0 = x - m;
+                        int y0 = y - n;
+
+                        if (x0 < 0 || y0 < 0) continue;
+
+                        for (int i = x0; i < x0 + spanX; i++) {
+                            if (!fits) break;
+                            for (int j = y0; j < y0 + spanY; j++) {
+                                if (i >= mCountX || j >= mCountY || mOccupied[i][j]) {
+                                    fits = false;
+                                    break;
+                                }
+                            }
+                        }
+                        if (fits) {
+                            return x0 * mCountY + y0;
+                        }
+                    }
+                }
+                return -1;
+            } else {
+                // For an icon, we simply check the view directly below
+                View child = getChildAt(x, y);
+                if (child == null || child == dragInfo.item) {
+                    // Empty cell. Good for an icon or folder.
+                    return id;
+                } else if (dragInfo.dragType != DragType.FOLDER) {
+                    // For icons, we can consider cells that have another icon or a folder.
+                    ItemInfo info = (ItemInfo) child.getTag();
+                    if (info instanceof AppInfo || info instanceof FolderInfo ||
+                            info instanceof ShortcutInfo) {
+                        return id;
+                    }
+                }
+                return -1;
+            }
+        }
+
+        @Override
+        protected void getVisibleVirtualViews(List<Integer> virtualViews) {
+            // We create a virtual view for each cell of the grid
+            // The cell ids correspond to cells in reading order.
+            int nCells = mCountX * mCountY;
+
+            for (int i = 0; i < nCells; i++) {
+                if (intersectsValidDropTarget(i) >= 0) {
+                    virtualViews.add(i);
+                }
+            }
+        }
+
+        @Override
+        protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
+            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+                String confirmation = getConfirmationForIconDrop(viewId);
+                LauncherAppState.getInstance().getAccessibilityDelegate()
+                        .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onClick(View arg0) {
+            int viewId = getViewIdAt(mDownX, mDownY);
+
+            String confirmation = getConfirmationForIconDrop(viewId);
+            LauncherAppState.getInstance().getAccessibilityDelegate()
+                    .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
+        }
+
+        @Override
+        protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) {
+            if (id == ExploreByTouchHelper.INVALID_ID) {
+                throw new IllegalArgumentException("Invalid virtual view id");
+            }
+            // We're required to set something here.
+            event.setContentDescription("");
+        }
+
+        @Override
+        protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
+            if (id == ExploreByTouchHelper.INVALID_ID) {
+                throw new IllegalArgumentException("Invalid virtual view id");
+            }
+
+            node.setContentDescription(getLocationDescriptionForIconDrop(id));
+            node.setBoundsInParent(getItemBounds(id));
+
+            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+            node.setClickable(true);
+            node.setFocusable(true);
+        }
+
+        private String getLocationDescriptionForIconDrop(int id) {
+            LauncherAccessibilityDelegate delegate =
+                    LauncherAppState.getInstance().getAccessibilityDelegate();
+            LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+            int y = id % mCountY;
+            int x = id / mCountY;
+
+            Resources res = getContext().getResources();
+            View child = getChildAt(x, y);
+            if (child == null || child == dragInfo.item) {
+                return res.getString(R.string.move_to_empty_cell, x, y);
+            } else {
+                ItemInfo info = (ItemInfo) child.getTag();
+                if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+                    return res.getString(R.string.create_folder_with, info.title);
+                } else if (info instanceof FolderInfo) {
+                    return res.getString(R.string.add_to_folder, info.title);
+                }
+            }
+            return "";
+        }
+
+        private String getConfirmationForIconDrop(int id) {
+            LauncherAccessibilityDelegate delegate =
+                    LauncherAppState.getInstance().getAccessibilityDelegate();
+            LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+            int y = id % mCountY;
+            int x = id / mCountY;
+
+            Resources res = getContext().getResources();
+            View child = getChildAt(x, y);
+            if (child == null || child == dragInfo.item) {
+                return res.getString(R.string.item_moved);
+            } else {
+                ItemInfo info = (ItemInfo) child.getTag();
+                if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+                    return res.getString(R.string.folder_created);
+
+                } else if (info instanceof FolderInfo) {
+                    return res.getString(R.string.added_to_folder);
+                }
+            }
+            return "";
+        }
+
+        private Rect getItemBounds(int id) {
+            int cellY = id % mCountY;
+            int cellX = id / mCountY;
+            int x = getPaddingLeft() + (int) (cellX * (mCellWidth + mWidthGap));
+            int y = getPaddingTop() + (int) (cellY * (mCellHeight + mHeightGap));
+
+            Rect bounds = mTempRect;
+            bounds.set(x, y, x + mCellWidth, y + mCellHeight);
+            return bounds;
+        }
+    }
+
     public void enableHardwareLayer(boolean hasLayer) {
         mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
     }
@@ -679,18 +973,6 @@
         mShortcutsAndWidgets.removeViewsInLayout(start, count);
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // First we clear the tag to ensure that on every touch down we start with a fresh slate,
-        // even in the case where we return early. Not clearing here was causing bugs whereby on
-        // long-press we'd end up picking up an item from a previous drag operation.
-        if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
-            return true;
-        }
-
-        return false;
-    }
-
     /**
      * Given a point, return the cell that strictly encloses that point
      * @param x X coordinate of the point
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/DragController.java b/src/com/android/launcher3/DragController.java
index 480dce9..09c59a0 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -73,6 +73,9 @@
     /** Whether or not we're dragging. */
     private boolean mDragging;
 
+    /** Whether or not this is an accessible drag operation */
+    private boolean mIsAccessibleDrag;
+
     /** X coordinate of the down event. */
     private int mMotionDownX;
 
@@ -182,7 +185,7 @@
                 (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
 
         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
-                null, initialDragViewScale);
+                null, initialDragViewScale, false);
 
         if (dragAction == DRAG_ACTION_MOVE) {
             v.setVisibility(View.GONE);
@@ -202,10 +205,11 @@
      *        {@link #DRAG_ACTION_COPY}
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
+     * @param accessible whether this drag should occur in accessibility mode
      */
     public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
             DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
-            float initialDragViewScale) {
+            float initialDragViewScale, boolean accessible) {
         if (PROFILE_DRAWING_DURING_DRAG) {
             android.os.Debug.startMethodTracing("Launcher");
         }
@@ -228,12 +232,21 @@
         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
 
         mDragging = true;
+        mIsAccessibleDrag = accessible;
 
         mDragObject = new DropTarget.DragObject();
 
         mDragObject.dragComplete = false;
-        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
-        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+        if (mIsAccessibleDrag) {
+            // For an accessible drag, we assume the view is being dragged from the center.
+            mDragObject.xOffset = b.getWidth() / 2;
+            mDragObject.yOffset = b.getHeight() / 2;
+            mDragObject.accessibleDrag = true;
+        } else {
+            mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
+            mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+        }
+
         mDragObject.dragSource = source;
         mDragObject.dragInfo = dragInfo;
 
@@ -349,6 +362,7 @@
     private void endDrag() {
         if (mDragging) {
             mDragging = false;
+            mIsAccessibleDrag = false;
             clearScrollRunnable();
             boolean isDeferred = false;
             if (mDragObject.dragView != null) {
@@ -421,6 +435,10 @@
                     + mDragging);
         }
 
+        if (mIsAccessibleDrag) {
+            return false;
+        }
+
         // Update the velocity tracker
         acquireVelocityTrackerAndAddMovement(ev);
 
@@ -560,7 +578,7 @@
      * Call this from a drag source view.
      */
     public boolean onTouchEvent(MotionEvent ev) {
-        if (!mDragging) {
+        if (!mDragging || mIsAccessibleDrag) {
             return false;
         }
 
@@ -617,6 +635,34 @@
     }
 
     /**
+     * Since accessible drag and drop won't cause the same sequence of touch events, we manually
+     * inject the appropriate state.
+     */
+    public void prepareAccessibleDrag(int x, int y) {
+        mMotionDownX = x;
+        mMotionDownY = y;
+        mLastDropTarget = null;
+    }
+
+    /**
+     * As above, since accessible drag and drop won't cause the same sequence of touch events,
+     * we manually ensure appropriate drag and drop events get emulated for accessible drag.
+     */
+    public void completeAccessibleDrag(int[] location) {
+        final int[] coordinates = mCoordinatesTemp;
+
+        // We make sure that we prime the target for drop.
+        DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
+        mDragObject.x = coordinates[0];
+        mDragObject.y = coordinates[1];
+        checkTouchMove(dropTarget);
+
+        // Perform the drop
+        drop(location[0], location[1]);
+        endDrag();
+    }
+
+    /**
      * Determines whether the user flung the current item to delete it.
      *
      * @return the vector at which the item was flung, or null if no fling was detected.
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 7ede427..94ae82b 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -54,6 +54,9 @@
         /** Where the drag originated */
         public DragSource dragSource = null;
 
+        /** The object is part of an accessible drag operation */
+        public boolean accessibleDrag;
+
         /** Post drag animation runnable */
         public Runnable postAnimationRunnable = null;
 
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index bdfd7b2..b090a7c 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,8 +22,8 @@
 import android.view.SoundEffectConstants;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ScrollView;
 
+import com.android.launcher3.FocusHelper.PagedViewKeyListener;
 import com.android.launcher3.util.FocusLogic;
 
 /**
@@ -66,9 +66,32 @@
     //
 
     /**
+     * A keyboard listener for scrollable folders
+     */
+    public static class PagedFolderKeyEventListener extends PagedViewKeyListener {
+
+        private final Folder mFolder;
+
+        public PagedFolderKeyEventListener(Folder folder) {
+            mFolder = folder;
+        }
+
+        @Override
+        public void handleNoopKey(int keyCode, View v) {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                mFolder.mFolderName.requestFocus();
+                playSoundEffect(keyCode, v);
+            }
+        }
+    }
+
+    /**
      * Handles key events in the all apps screen.
      */
-    static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
+    public static class PagedViewKeyListener implements View.OnKeyListener {
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent e) {
         boolean consume = FocusLogic.shouldConsume(keyCode);
         if (e.getAction() == KeyEvent.ACTION_UP) {
             return consume;
@@ -88,35 +111,47 @@
             parentLayout = (ViewGroup) itemContainer.getParent();
             countX = ((CellLayout) parentLayout).getCountX();
             countY = ((CellLayout) parentLayout).getCountY();
-        } else if (v.getParent() instanceof ViewGroup) {
-            itemContainer = parentLayout = (ViewGroup) v.getParent();
-            countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
-            countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
         } else {
-            throw new IllegalStateException(
-                    "Parent of the focused item inside all apps screen is not a supported type.");
+            if (LauncherAppState.isDogfoodBuild()) {
+                throw new IllegalStateException("Parent of the focused item is not supported.");
+            } else {
+                return false;
+            }
         }
+
         final int iconIndex = itemContainer.indexOfChild(v);
         final PagedView container = (PagedView) parentLayout.getParent();
         final int pageIndex = container.indexToPage(container.indexOfChild(parentLayout));
         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.
         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
                 iconIndex, pageIndex, pageCount);
         if (newIconIndex == FocusLogic.NOOP) {
+            handleNoopKey(keyCode, v);
             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 +166,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;
@@ -144,10 +187,15 @@
         if (child != null) {
             child.requestFocus();
             playSoundEffect(keyCode, v);
+        } else {
+            handleNoopKey(keyCode, v);
         }
         return consume;
     }
 
+    public void handleNoopKey(int keyCode, View v) { }
+    }
+
     /**
      * Handles key events in the workspace hot seat (bottom of the screen).
      * <p>Currently we don't special case for the phone UI in different orientations, even though
@@ -256,7 +304,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 +349,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 +376,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 +426,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..0a65579 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,66 @@
         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;
+    private static final boolean ALLOW_FOLDER_SCROLL = true;
 
     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 mFooter;
+    private int mFooterHeight;
+
+    // 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 +132,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 +154,34 @@
         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);
+
+        mFooter = ALLOW_FOLDER_SCROLL ? findViewById(R.id.folder_footer) : mFolderName;
+        // We find out how tall footer 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;
+        mFooter.measure(measureSpec, measureSpec);
+        mFooterHeight = mFooter.getMeasuredHeight();
     }
 
     private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -240,11 +222,10 @@
             mLauncher.getWorkspace().beginDragShared(v, this);
 
             mCurrentDragInfo = item;
-            mEmptyCell[0] = item.cellX;
-            mEmptyCell[1] = item.cellY;
+            mEmptyCellRank = item.rank;
             mCurrentDragView = v;
 
-            mContent.removeView(mCurrentDragView);
+            mContent.removeItem(mCurrentDragView);
             mInfo.remove(mCurrentDragInfo);
             mDragInProgress = true;
             mItemAddedBackToSelfViaIcon = false;
@@ -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);
@@ -395,7 +359,8 @@
      * @return A new UserFolder.
      */
     static Folder fromXml(Context context) {
-        return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
+        return (Folder) LayoutInflater.from(context).inflate(
+                ALLOW_FOLDER_SCROLL ? R.layout.user_folder_scroll : R.layout.user_folder, null);
     }
 
     /**
@@ -464,14 +429,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);
+            mFooter.setAlpha(0f);
+            Animator textAlpha = LauncherAnimUtils.ofFloat(mFooter, "alpha", 0f, 1f);
             textAlpha.setDuration(mMaterialExpandDuration);
             textAlpha.setStartDelay(mMaterialExpandStagger);
             textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
@@ -488,11 +453,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 +465,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 +476,7 @@
                     onCompleteRunnable.run();
                 }
 
-                setFocusOnFirstChild();
+                mContent.setFocusOnFirstChild();
             }
         });
         openFolderAnim.start();
@@ -524,14 +488,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 +505,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 +540,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 +618,7 @@
                 replaceFolderWithFinalItem();
             }
         } else {
-            setupContentForNumItems(getItemCount());
+            rearrangeChildren();
             // The drag failed, we need to return the item to the folder
             mFolderIcon.onDrop(d);
         }
@@ -865,37 +719,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 +730,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 +789,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 +796,7 @@
                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
         int maxContentAreaHeight = grid.availableHeightPx -
                 workspacePadding.top - workspacePadding.bottom -
-                mFolderNameHeight;
+                mFooterHeight;
         int height = Math.min(maxContentAreaHeight,
                 mContent.getDesiredHeight());
         return Math.max(height, MIN_CONTENT_DIMEN);
@@ -994,58 +807,50 @@
     }
 
     private int getFolderHeight() {
-        int height = getPaddingTop() + getPaddingBottom()
-                + getContentAreaHeight() + mFolderNameHeight;
-        return height;
+        return getFolderHeight(getContentAreaHeight());
+    }
+
+    private int getFolderHeight(int contentAreaHeight) {
+        return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight;
     }
 
     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);
+        mFooter.measure(contentAreaWidthSpec,
+                MeasureSpec.makeMeasureSpec(mFooterHeight, 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 +863,7 @@
         mFolderIcon.requestFocus();
 
         if (mRearrangeOnClose) {
-            setupContentForNumItems(getItemCount());
+            rearrangeChildren();
             mRearrangeOnClose = false;
         }
         if (getItemCount() <= 1) {
@@ -1108,7 +913,7 @@
                 }
             }
         };
-        View finalChild = getItemAt(0);
+        View finalChild = mContent.getLastItem();
         if (finalChild != null) {
             mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
         } else {
@@ -1123,9 +928,8 @@
 
     // 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);
+    public void updateTextViewFocus() {
+        View lastChild = mContent.getLastItem();
         if (lastChild != null) {
             mFolderName.setNextFocusDownId(lastChild.getId());
             mFolderName.setNextFocusRightId(lastChild.getId());
@@ -1153,9 +957,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 +968,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 +989,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 +1016,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);
     }
@@ -1235,27 +1027,25 @@
         // the work associated with removing the item, so we don't have to do anything here.
         if (item == mCurrentDragInfo) return;
         View v = getViewForInfo(item);
-        mContent.removeView(v);
+        mContent.removeItem(v);
         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 +1058,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 +1085,69 @@
     public void getHitRectRelativeToDragLayer(Rect outRect) {
         getHitRect(outRect);
     }
+
+    public static interface FolderContent {
+        void setFolder(Folder f);
+
+        void removeItem(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..1566912
--- /dev/null
+++ b/src/com/android/launcher3/FolderCellLayout.java
@@ -0,0 +1,330 @@
+/**
+ * Copyright (C) 2015 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.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);
+    }
+
+    @Override
+    public void removeItem(View v) {
+        removeView(v);
+    }
+
+    /**
+     * 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/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
new file mode 100644
index 0000000..60c94e0
--- /dev/null
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -0,0 +1,581 @@
+/**
+ * Copyright (C) 2015 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.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
+import com.android.launcher3.PageIndicator.PageMarkerResources;
+import com.android.launcher3.Workspace.ItemOperator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+public class FolderPagedView extends PagedView implements Folder.FolderContent {
+
+    private static final String TAG = "FolderPagedView";
+
+    private static final int REORDER_ANIMATION_DURATION = 230;
+    private static final int[] sTempPosArray = new int[2];
+
+    // TODO: Remove this restriction
+    private static final int MAX_ITEMS_PER_PAGE = 3;
+
+    private final LayoutInflater mInflater;
+    private final IconCache mIconCache;
+    private final HashMap<View, Runnable> mPageChangingViews = new HashMap<>();
+
+    private final CellLayout mFirstPage;
+
+    final int mMaxCountX;
+    final int mMaxCountY;
+    final int mMaxItemsPerPage;
+
+    private int mAllocatedContentSize;
+    private int mGridCountX;
+    private int mGridCountY;
+
+    private Folder mFolder;
+    private FocusIndicatorView mFocusIndicatorView;
+    private PagedFolderKeyEventListener mKeyListener;
+
+    public FolderPagedView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        LauncherAppState app = LauncherAppState.getInstance();
+
+        mFirstPage = newCellLayout();
+        addFullScreenPage(mFirstPage);
+        setCurrentPage(0);
+        setDataIsReady();
+
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        mMaxCountX = Math.min((int) grid.numColumns, MAX_ITEMS_PER_PAGE);
+        mMaxCountY = Math.min((int) grid.numRows, MAX_ITEMS_PER_PAGE);
+        mMaxItemsPerPage = mMaxCountX * mMaxCountY;
+
+        mInflater = LayoutInflater.from(context);
+        mIconCache = app.getIconCache();
+    }
+
+    @Override
+    public void setFolder(Folder folder) {
+        mFolder = folder;
+        mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
+        mKeyListener = new PagedFolderKeyEventListener(folder);
+    }
+
+    /**
+     * 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;
+        boolean done;
+        if (count >= mMaxItemsPerPage) {
+            mGridCountX = mMaxCountX;
+            mGridCountY = mMaxCountY;
+            done = true;
+        } else {
+            mGridCountX = mFirstPage.getCountX();
+            mGridCountY = mFirstPage.getCountY();
+            done = false;
+        }
+
+        while (!done) {
+            int oldCountX = mGridCountX;
+            int oldCountY = mGridCountY;
+            if (mGridCountX * mGridCountY < count) {
+                // Current grid is too small, expand it
+                if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) {
+                    mGridCountX++;
+                } else if (mGridCountY < mMaxCountY) {
+                    mGridCountY++;
+                }
+                if (mGridCountY == 0) mGridCountY++;
+            } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) {
+                mGridCountY = Math.max(0, mGridCountY - 1);
+            } else if ((mGridCountX - 1) * mGridCountY >= count) {
+                mGridCountX = Math.max(0, mGridCountX - 1);
+            }
+            done = mGridCountX == oldCountX && mGridCountY == oldCountY;
+        }
+
+        setGridSize(mGridCountX, mGridCountY);
+    }
+
+    public void setGridSize(int countX, int countY) {
+        mGridCountX = countX;
+        mGridCountY = countY;
+        mFirstPage.setGridSize(mGridCountX, mGridCountY);
+        for (int i = getPageCount() - 1; i > 0; i--) {
+            getPageAt(i).setGridSize(mGridCountX, mGridCountY);
+        }
+    }
+
+    @Override
+    public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
+        final int count = items.size();
+
+        if (getPageCount() > 1) {
+            Log.d(TAG, "Binding items to an non-empty view");
+            removeAllViews();
+            addView(mFirstPage);
+            mFirstPage.removeAllViews();
+        }
+
+        setupContentDimensions(count);
+        CellLayout page = mFirstPage;
+        int pagePosition = 0;
+        int rank = 0;
+
+        for (ShortcutInfo item : items) {
+            if (pagePosition >= mMaxItemsPerPage) {
+                // This page is full, add a new page.
+                pagePosition = 0;
+                page = newCellLayout();
+                addFullScreenPage(page);
+            }
+
+            item.cellX = pagePosition % mGridCountX;
+            item.cellY = pagePosition / mGridCountX;
+            item.rank = rank;
+            addNewView(item, page);
+
+            rank++;
+            pagePosition++;
+        }
+        return new ArrayList<ShortcutInfo>();
+    }
+
+    /**
+     * Create space for a new item at the end, and returns the rank for that item.
+     * Also sets the current page to the last page.
+     */
+    @Override
+    public int allocateNewLastItemRank() {
+        int rank = getItemCount();
+        int total = rank + 1;
+        if (rank < mMaxItemsPerPage) {
+            // Rearrange the items as the grid size might change.
+            mFolder.rearrangeChildren(total);
+        } else {
+            setupContentDimensions(total);
+        }
+
+        // Add a new page if last page is full
+        if (getPageAt(getChildCount() - 1).getShortcutsAndWidgets().getChildCount()
+                >= mMaxItemsPerPage) {
+            addFullScreenPage(newCellLayout());
+        }
+        setCurrentPage(getChildCount() - 1);
+        return rank;
+    }
+
+    @Override
+    public View createAndAddViewForRank(ShortcutInfo item, int rank) {
+        int pageNo = updateItemXY(item, rank);
+        CellLayout page = getPageAt(pageNo);
+        return addNewView(item, page);
+    }
+
+    @Override
+    public void addViewForRank(View view, ShortcutInfo item, int rank) {
+        int pageNo = updateItemXY(item, rank);
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+        lp.cellX = item.cellX;
+        lp.cellY = item.cellY;
+        getPageAt(pageNo).addViewToCellLayout(
+                view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+    }
+
+    /**
+     * Updates the item cellX and cellY position and return the page number for that item.
+     */
+    private int updateItemXY(ShortcutInfo item, int rank) {
+        item.rank = rank;
+
+        int pagePos = item.rank % mMaxItemsPerPage;
+        item.cellX = pagePos % mGridCountX;
+        item.cellY = pagePos / mGridCountX;
+
+        return item.rank / mMaxItemsPerPage;
+    }
+
+    private View addNewView(ShortcutInfo item, CellLayout target) {
+        final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
+                R.layout.folder_application, target.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);
+        target.addViewToCellLayout(
+                textView, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+        return textView;
+    }
+
+    @Override
+    public CellLayout getPageAt(int index) {
+        return (CellLayout) getChildAt(index);
+    }
+
+    public void removeCellLayoutView(View view) {
+        for (int i = getChildCount() - 1; i >= 0; i --) {
+            getPageAt(i).removeView(view);
+        }
+    }
+
+    public CellLayout getCurrentCellLayout() {
+        return getPageAt(getNextPage());
+    }
+
+    @Override
+    public void addFullScreenPage(View page) {
+        LayoutParams lp = generateDefaultLayoutParams();
+        lp.isFullScreenPage = true;
+        super.addView(page, -1, lp);
+    }
+
+    private CellLayout newCellLayout() {
+        DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+
+        CellLayout layout = new CellLayout(getContext());
+        layout.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+        layout.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+        layout.setInvertIfRtl(true);
+
+        if (mFirstPage != null) {
+            layout.setGridSize(mFirstPage.getCountX(), mFirstPage.getCountY());
+        }
+
+        return layout;
+    }
+
+    @Override
+    public void setFixedSize(int width, int height) {
+        for (int i = getChildCount() - 1; i >= 0; i --) {
+            ((CellLayout) getChildAt(i)).setFixedSize(width, height);
+        }
+    }
+
+    @Override
+    public void removeItem(View v) {
+        for (int i = getChildCount() - 1; i >= 0; i --) {
+            getPageAt(i).removeView(v);
+        }
+    }
+
+    /**
+     * Updates position and rank of all the children in the view.
+     * It essentially removes all views from all the pages and then adds them again in appropriate
+     * page.
+     *
+     * @param list the ordered list of children.
+     * @param itemCount if greater than the total children count, empty spaces are left
+     * at the end, otherwise it is ignored.
+     *
+     */
+    @Override
+    public void arrangeChildren(ArrayList<View> list, int itemCount) {
+        ArrayList<CellLayout> pages = new ArrayList<CellLayout>();
+        for (int i = 0; i < getChildCount(); i++) {
+            CellLayout page = (CellLayout) getChildAt(i);
+            page.removeAllViews();
+            pages.add(page);
+        }
+        setupContentDimensions(itemCount);
+
+        Iterator<CellLayout> pageItr = pages.iterator();
+        CellLayout currentPage = null;
+
+        int position = 0;
+        int newX, newY, rank;
+
+        rank = 0;
+        for (View v : list) {
+            if (currentPage == null || position >= mMaxItemsPerPage) {
+                // Next page
+                if (pageItr.hasNext()) {
+                    currentPage = pageItr.next();
+                } else {
+                    currentPage = newCellLayout();
+                    addFullScreenPage(currentPage);
+                }
+                position = 0;
+            }
+
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+            newX = position % mGridCountX;
+            newY = position / mGridCountX;
+            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 ++;
+            position++;
+            currentPage.addViewToCellLayout(
+                    v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+        }
+
+        boolean removed = false;
+        while (pageItr.hasNext()) {
+            CellLayout layout = pageItr.next();
+            if (layout != mFirstPage) {
+                removeView(layout);
+                removed = true;
+            }
+        }
+        if (removed) {
+            setCurrentPage(0);
+        }
+    }
+
+    @Override
+    protected void loadAssociatedPages(int page, boolean immediateAndOnly) { }
+
+    @Override
+    public void syncPages() { }
+
+    @Override
+    public void syncPageItems(int page, boolean immediate) { }
+
+    public int getDesiredWidth() {
+        return mFirstPage.getDesiredWidth();
+    }
+
+    public int getDesiredHeight()  {
+        return mFirstPage.getDesiredHeight();
+    }
+
+    @Override
+    public int getItemCount() {
+        int lastPage = getChildCount() - 1;
+        return getPageAt(lastPage).getShortcutsAndWidgets().getChildCount()
+                + lastPage * mMaxItemsPerPage;
+    }
+
+    @Override
+    public int findNearestArea(int pixelX, int pixelY) {
+        int pageIndex = getNextPage();
+        CellLayout page = getPageAt(pageIndex);
+        page.findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray);
+        if (mFolder.isLayoutRtl()) {
+            sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1;
+        }
+        return Math.min(mAllocatedContentSize - 1,
+                pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]);
+    }
+
+    @Override
+    protected PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+        return new PageMarkerResources(R.drawable.ic_pageindicator_current_dark, R.drawable.ic_pageindicator_default_dark);
+    }
+
+    @Override
+    public boolean isFull() {
+        return false;
+    }
+
+    @Override
+    public View getLastItem() {
+        if (getChildCount() < 1) {
+            return null;
+        }
+        ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+        int lastRank = lastContainer.getChildCount() - 1;
+        if (mGridCountX > 0) {
+            return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+        } else {
+            return lastContainer.getChildAt(lastRank);
+        }
+    }
+
+    @Override
+    public View iterateOverItems(ItemOperator op) {
+        for (int k = 0 ; k < getChildCount(); k++) {
+            CellLayout page = getPageAt(k);
+            for (int j = 0; j < page.getCountY(); j++) {
+                for (int i = 0; i < page.getCountX(); i++) {
+                    View v = page.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),
+                mGridCountX, mGridCountY);
+    }
+
+    @Override
+    public void setFocusOnFirstChild() {
+        View firstChild = getCurrentCellLayout().getChildAt(0, 0);
+        if (firstChild != null) {
+            firstChild.requestFocus();
+        }
+    }
+
+    @Override
+    protected void notifyPageSwitchListener() {
+        super.notifyPageSwitchListener();
+        if (mFolder != null) {
+            mFolder.updateTextViewFocus();
+        }
+    }
+
+    @Override
+    public void realTimeReorder(int empty, int target) {
+        int delay = 0;
+        float delayAmount = 30;
+
+        // Animation only happens on the current page.
+        int pageToAnimate = getNextPage();
+
+        int pageT = target / mMaxItemsPerPage;
+        int pagePosT = target % mMaxItemsPerPage;
+
+        if (pageT != pageToAnimate) {
+            Log.e(TAG, "Cannot animate when the target cell is invisible");
+        }
+        int pagePosE = empty % mMaxItemsPerPage;
+        int pageE = empty / mMaxItemsPerPage;
+
+        int startPos, endPos;
+        int moveStart, moveEnd;
+        int direction;
+
+        if (target == empty) {
+            // No animation
+            return;
+        } else if (target > empty) {
+            // Items will move backwards to make room for the empty cell.
+            direction = 1;
+
+            // If empty cell is in a different page, move them instantly.
+            if (pageE < pageToAnimate) {
+                moveStart = empty;
+                // Instantly move the first item in the current page.
+                moveEnd = pageToAnimate * mMaxItemsPerPage;
+                // Animate the 2nd item in the current page, as the first item was already moved to
+                // the last page.
+                startPos = 0;
+            } else {
+                moveStart = moveEnd = -1;
+                startPos = pagePosE;
+            }
+
+            endPos = pagePosT;
+        } else {
+            // The items will move forward.
+            direction = -1;
+
+            if (pageE > pageToAnimate) {
+                // Move the items immediately.
+                moveStart = empty;
+                // Instantly move the last item in the current page.
+                moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
+
+                // Animations start with the second last item in the page
+                startPos = mMaxItemsPerPage - 1;
+            } else {
+                moveStart = moveEnd = -1;
+                startPos = pagePosE;
+            }
+
+            endPos = pagePosT;
+        }
+
+        // Instant moving views.
+        while (moveStart != moveEnd) {
+            int rankToMove = moveStart + direction;
+            int p = rankToMove / mMaxItemsPerPage;
+            int pagePos = rankToMove % mMaxItemsPerPage;
+            int x = pagePos % mGridCountX;
+            int y = pagePos / mGridCountX;
+
+            final CellLayout page = getPageAt(p);
+            final View v = page.getChildAt(x, y);
+            if (v != null) {
+                if (pageToAnimate != p) {
+                    page.removeView(v);
+                    addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
+                } else {
+                    // Do a fake animation before removing it.
+                    final int newRank = moveStart;
+                    final float oldTranslateX = v.getTranslationX();
+
+                    Runnable endAction = new Runnable() {
+
+                        @Override
+                        public void run() {
+                            mPageChangingViews.remove(v);
+                            v.setTranslationX(oldTranslateX);
+                            ((CellLayout) v.getParent().getParent()).removeView(v);
+                            addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
+                        }
+                    };
+                    v.animate()
+                        .translationXBy(direction > 0 ? -v.getWidth() : v.getWidth())
+                        .setDuration(REORDER_ANIMATION_DURATION)
+                        .setStartDelay(0)
+                        .withEndAction(endAction);
+                    mPageChangingViews.put(v, endAction);
+                }
+            }
+            moveStart = rankToMove;
+        }
+
+        if ((endPos - startPos) * direction <= 0) {
+            // No animation
+            return;
+        }
+
+        CellLayout page = getPageAt(pageToAnimate);
+        for (int i = startPos; i != endPos; i += direction) {
+            int nextPos = i + direction;
+            View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
+            if (v != null) {
+                ((ItemInfo) v.getTag()).rank -= direction;
+            }
+            if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
+                    REORDER_ANIMATION_DURATION, delay, true, true)) {
+                delay += delayAmount;
+                delayAmount *= 0.9;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e3a2d7c..984d536 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2448,6 +2448,10 @@
             return;
         }
 
+        if (LauncherAppState.getInstance().getAccessibilityDelegate().onBackPressed()) {
+            return;
+        }
+
         if (isAllAppsVisible()) {
             if (mAppsCustomizeContent.getContentType() ==
                     AppsCustomizePagedView.ContentType.Applications) {
@@ -3166,7 +3170,7 @@
         View itemUnderLongClick = null;
         if (v.getTag() instanceof ItemInfo) {
             ItemInfo info = (ItemInfo) v.getTag();
-            longClickCellInfo = new CellLayout.CellInfo(v, info);;
+            longClickCellInfo = new CellLayout.CellInfo(v, info);
             itemUnderLongClick = longClickCellInfo.cell;
             resetAddInfo();
         }
@@ -3207,7 +3211,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/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index c9e277e..0ae1c0e 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -1,11 +1,16 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -20,6 +25,21 @@
     public static final int INFO = R.id.action_info;
     public static final int UNINSTALL = R.id.action_uninstall;
     public static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
+    public static final int MOVE = R.id.action_move;
+
+    enum DragType {
+        ICON,
+        FOLDER,
+        WIDGET
+    }
+
+    public static class DragInfo {
+        DragType dragType;
+        ItemInfo info;
+        View item;
+    }
+
+    private DragInfo mDragInfo = null;
 
     private final SparseArray<AccessibilityAction> mActions =
             new SparseArray<AccessibilityAction>();
@@ -36,6 +56,9 @@
                 launcher.getText(R.string.delete_target_uninstall_label)));
         mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
                 launcher.getText(R.string.action_add_to_workspace)));
+        mActions.put(MOVE, new AccessibilityAction(MOVE,
+                launcher.getText(R.string.action_move)));
+
     }
 
     @Override
@@ -49,6 +72,7 @@
                 || (item instanceof FolderInfo)) {
             // Workspace shortcut / widget
             info.addAction(mActions.get(REMOVE));
+            info.addAction(mActions.get(MOVE));
         } else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
             // App or Widget from customization tray
             if (item instanceof AppInfo) {
@@ -69,14 +93,21 @@
     }
 
     public boolean performAction(View host, ItemInfo item, int action) {
+        Resources res = mLauncher.getResources();
         if (action == REMOVE) {
-            return DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
+            if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) {
+                announceConfirmation(R.string.item_removed_from_workspace);
+                return true;
+            }
+            return false;
         } else if (action == INFO) {
             InfoDropTarget.startDetailsActivityForInfo(item, mLauncher);
             return true;
         } else if (action == UNINSTALL) {
             DeleteDropTarget.uninstallApp(mLauncher, (AppInfo) item);
             return true;
+        } else if (action == MOVE) {
+            beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
             final int preferredPage = mLauncher.getWorkspace().getCurrentPage();
             final ScreenPosProvider screenProvider = new ScreenPosProvider() {
@@ -90,20 +121,92 @@
                 final ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
                 addShortcuts.add(((AppInfo) item).makeShortcut());
                 mLauncher.showWorkspace(true, new Runnable() {
-
                     @Override
                     public void run() {
                         mLauncher.getModel().addAndBindAddedWorkspaceApps(
                                 mLauncher, addShortcuts, screenProvider, 0, true);
+                        announceConfirmation(R.string.item_added_to_workspace);
                     }
                 });
                 return true;
             } else if (item instanceof PendingAddItemInfo) {
                 mLauncher.getModel().addAndBindPendingItem(
                         mLauncher, (PendingAddItemInfo) item, screenProvider, 0);
+                announceConfirmation(R.string.item_added_to_workspace);
                 return true;
             }
         }
         return false;
     }
+
+    private void announceConfirmation(int resId) {
+        announceConfirmation(mLauncher.getResources().getString(resId));
+    }
+
+    private void announceConfirmation(String confirmation) {
+        mLauncher.getDragLayer().announceForAccessibility(confirmation);
+
+    }
+
+    public boolean isInAccessibleDrag() {
+        return mDragInfo != null;
+    }
+
+    public DragInfo getDragInfo() {
+        return mDragInfo;
+    }
+
+    public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation,
+            String confirmation) {
+        if (!isInAccessibleDrag()) return;
+
+        int[] loc = new int[2];
+        loc[0] = dropLocation.centerX();
+        loc[1] = dropLocation.centerY();
+
+        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc);
+        mLauncher.getDragController().completeAccessibleDrag(loc);
+
+        endAccessibleDrag();
+        announceConfirmation(confirmation);
+    }
+
+    public void beginAccessibleDrag(View item, ItemInfo info) {
+        mDragInfo = new DragInfo();
+        mDragInfo.info = info;
+        mDragInfo.item = item;
+        mDragInfo.dragType = DragType.ICON;
+        if (info instanceof FolderInfo) {
+            mDragInfo.dragType = DragType.FOLDER;
+        } else if (info instanceof LauncherAppWidgetInfo) {
+            mDragInfo.dragType = DragType.WIDGET;
+        }
+
+        CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
+
+        Rect pos = new Rect();
+        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
+
+        mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
+        mLauncher.getWorkspace().enableAccessibleDrag(true);
+        mLauncher.getWorkspace().startDrag(cellInfo, true);
+    }
+
+    public boolean onBackPressed() {
+        if (isInAccessibleDrag()) {
+            cancelAccessibleDrag();
+            return true;
+        }
+        return false;
+    }
+
+    private void cancelAccessibleDrag() {
+        mLauncher.getDragController().cancelDrag();
+        endAccessibleDrag();
+    }
+
+    private void endAccessibleDrag() {
+        mDragInfo = null;
+        mLauncher.getWorkspace().enableAccessibleDrag(false);
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 8e6557f..d8896cc 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -62,7 +62,7 @@
     private static LauncherAppState INSTANCE;
 
     private DynamicGrid mDynamicGrid;
-    private AccessibilityDelegate mAccessibilityDelegate;
+    private LauncherAccessibilityDelegate mAccessibilityDelegate;
 
     public static LauncherAppState getInstance() {
         if (INSTANCE == null) {
@@ -168,7 +168,7 @@
         return mModel;
     }
 
-    AccessibilityDelegate getAccessibilityDelegate() {
+    LauncherAccessibilityDelegate getAccessibilityDelegate() {
         return mAccessibilityDelegate;
     }
 
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/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 99c2e08..cc17820 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -197,6 +197,10 @@
      */
     @Override
     public void onDragStart(DragSource source, Object info, int dragAction) {
+        showDeleteTarget();
+    }
+
+    public void showDeleteTarget() {
         // Animate out the QSB search bar, and animate in the drop target bar
         prepareStartAnimation(mDropTargetBar);
         mDropTargetBarAnim.start();
@@ -206,6 +210,16 @@
         }
     }
 
+    public void hideDeleteTarget() {
+        // Restore the QSB search bar, and animate out the drop target bar
+        prepareStartAnimation(mDropTargetBar);
+        mDropTargetBarAnim.reverse();
+        if (!mIsSearchBarHidden) {
+            prepareStartAnimation(mQSBSearchBar);
+            mQSBSearchBarAnim.reverse();
+        }
+    }
+
     public void deferOnDragEnd() {
         mDeferOnDragEnd = true;
     }
@@ -213,13 +227,7 @@
     @Override
     public void onDragEnd() {
         if (!mDeferOnDragEnd) {
-            // Restore the QSB search bar, and animate out the drop target bar
-            prepareStartAnimation(mDropTargetBar);
-            mDropTargetBarAnim.reverse();
-            if (!mIsSearchBarHidden) {
-                prepareStartAnimation(mQSBSearchBar);
-                mQSBSearchBarAnim.reverse();
-            }
+            hideDeleteTarget();
         } else {
             mDeferOnDragEnd = false;
         }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index da893b6..3b293f9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -26,6 +26,7 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
@@ -44,6 +45,7 @@
 import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcelable;
@@ -572,6 +574,10 @@
         mWorkspaceScreens.put(screenId, newScreen);
         mScreenOrder.add(insertIndex, screenId);
         addView(newScreen, insertIndex);
+
+        if (LauncherAppState.getInstance().getAccessibilityDelegate().isInAccessibleDrag()) {
+            newScreen.enableAccessibleDrag(true);
+        }
         return screenId;
     }
 
@@ -1621,7 +1627,6 @@
                     float scrollProgress = getScrollProgress(screenCenter, child, i);
                     float alpha = 1 - Math.abs(scrollProgress);
                     child.getShortcutsAndWidgets().setAlpha(alpha);
-                    //child.setBackgroundAlphaMultiplier(1 - alpha);
                 }
             }
         }
@@ -1634,6 +1639,23 @@
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public void enableAccessibleDrag(boolean enable) {
+        for (int i = 0; i < getChildCount(); i++) {
+            CellLayout child = (CellLayout) getChildAt(i);
+            child.enableAccessibleDrag(enable);
+        }
+
+        if (enable) {
+            // We need to allow our individual children to become click handlers in this case
+            setOnClickListener(null);
+        } else {
+            // Reset our click listener
+            setOnClickListener(mLauncher);
+        }
+        mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable);
+    }
+
     public boolean hasCustomContent() {
         return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
     }
@@ -2184,7 +2206,7 @@
 
     private void updateAccessibilityFlags() {
         int accessible = mState == State.NORMAL ?
-                IMPORTANT_FOR_ACCESSIBILITY_YES :
+                IMPORTANT_FOR_ACCESSIBILITY_NO :
                 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
         setImportantForAccessibility(accessible);
     }
@@ -2674,7 +2696,11 @@
         return b;
     }
 
-    void startDrag(CellLayout.CellInfo cellInfo) {
+    public void startDrag(CellLayout.CellInfo cellInfo) {
+        startDrag(cellInfo, false);
+    }
+
+    public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
         View child = cellInfo.cell;
 
         // Make sure the drag was started by a long press as opposed to a long click.
@@ -2687,10 +2713,14 @@
         CellLayout layout = (CellLayout) child.getParent().getParent();
         layout.prepareChildForDrag(child);
 
-        beginDragShared(child, this);
+        beginDragShared(child, this, accessible);
     }
 
     public void beginDragShared(View child, DragSource source) {
+        beginDragShared(child, source, false);
+    }
+
+    public void beginDragShared(View child, DragSource source, boolean accessible) {
         child.clearFocus();
         child.setPressed(false);
 
@@ -2744,7 +2774,7 @@
         }
 
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
-                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
@@ -2794,7 +2824,7 @@
 
         // Start the drag
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
-                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
         // Recycle temporary bitmaps
@@ -3149,7 +3179,8 @@
 
                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
-                        if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+                        if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
+                                && !d.accessibleDrag) {
                             final Runnable addResizeFrame = new Runnable() {
                                 public void run() {
                                     DragLayer dragLayer = mLauncher.getDragLayer();
@@ -3638,7 +3669,7 @@
                     mTargetCell[1]);
 
             manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
-                    targetCellDistance, dragOverView);
+                    targetCellDistance, dragOverView, d.accessibleDrag);
 
             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
@@ -3676,15 +3707,21 @@
     }
 
     private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
-            int[] targetCell, float distance, View dragOverView) {
+            int[] targetCell, float distance, View dragOverView, boolean accessibleDrag) {
         boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
                 false);
-
         if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
                 !mFolderCreationAlarm.alarmPending()) {
-            mFolderCreationAlarm.setOnAlarmListener(new
-                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
-            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+
+            FolderCreationAlarmListener listener = new
+                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
+
+            if (!accessibleDrag) {
+                mFolderCreationAlarm.setOnAlarmListener(listener);
+                mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+            } else {
+                listener.onAlarm(mFolderCreationAlarm);
+            }
             return;
         }
 
@@ -4173,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.
     }
 }