Merge "Fixing regression in AllApps being aggressively dismissed after returning to Launcher." into ub-launcher3-burnaby
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f43106f..b61b90c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -192,15 +192,6 @@
             </intent-filter>
         </receiver>
 
-        <!-- New user initialization; set up initial wallpaper -->
-        <receiver
-            android:name="com.android.launcher3.UserInitializeReceiver"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="android.intent.action.USER_INITIALIZE" />
-            </intent-filter>
-        </receiver>
-
         <receiver android:name="com.android.launcher3.StartupReceiver" >
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/WallpaperPicker/src/com/android/launcher3/CropView.java b/WallpaperPicker/src/com/android/launcher3/CropView.java
index 578b8ea..50f779a 100644
--- a/WallpaperPicker/src/com/android/launcher3/CropView.java
+++ b/WallpaperPicker/src/com/android/launcher3/CropView.java
@@ -21,7 +21,6 @@
 import android.graphics.Point;
 import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.util.FloatMath;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
@@ -300,12 +299,12 @@
                     adjustment[0] = (edges.right - getWidth()) / scale;
                 }
                 if (edges.top > 0) {
-                    adjustment[1] = FloatMath.ceil(edges.top / scale);
+                    adjustment[1] = (float) Math.ceil(edges.top / scale);
                 } else if (edges.bottom < getHeight()) {
                     adjustment[1] = (edges.bottom - getHeight()) / scale;
                 }
                 for (int dim = 0; dim <= 1; dim++) {
-                    if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
+                    if (coef[dim] > 0) adjustment[dim] = (float) Math.ceil(adjustment[dim]);
                 }
 
                 mInverseRotateMatrix.mapPoints(adjustment);
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index c49286a..9332091 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -137,39 +137,32 @@
 
     public static class UriWallpaperInfo extends WallpaperTileInfo {
         private Uri mUri;
-        private boolean mFirstClick = true;
-        @Thunk BitmapRegionTileSource.UriBitmapSource mBitmapSource;
         public UriWallpaperInfo(Uri uri) {
             mUri = uri;
         }
         @Override
         public void onClick(final WallpaperPickerActivity a) {
-            final Runnable onLoad;
-            if (!mFirstClick) {
-                onLoad = null;
-            } else {
-                mFirstClick = false;
-                a.mSetWallpaperButton.setEnabled(false);
-                onLoad = new Runnable() {
-                    public void run() {
-                        if (mBitmapSource != null &&
-                                mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
-                            a.selectTile(mView);
-                            a.mSetWallpaperButton.setEnabled(true);
-                        } else {
-                            ViewGroup parent = (ViewGroup) mView.getParent();
-                            if (parent != null) {
-                                parent.removeView(mView);
-                                Toast.makeText(a.getContext(), R.string.image_load_fail,
-                                        Toast.LENGTH_SHORT).show();
-                            }
+            a.setWallpaperButtonEnabled(false);
+            final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+                    new BitmapRegionTileSource.UriBitmapSource(
+                            a.getContext(), mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+            a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
+
+                @Override
+                public void run() {
+                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                        a.selectTile(mView);
+                        a.setWallpaperButtonEnabled(true);
+                    } else {
+                        ViewGroup parent = (ViewGroup) mView.getParent();
+                        if (parent != null) {
+                            parent.removeView(mView);
+                            Toast.makeText(a.getContext(), R.string.image_load_fail,
+                                    Toast.LENGTH_SHORT).show();
                         }
                     }
-                };
-            }
-            mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
-                    a.getContext(), mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
-            a.setCropViewTileSource(mBitmapSource, true, false, null, onLoad);
+                }
+            });
         }
         @Override
         public void onSave(final WallpaperPickerActivity a) {
@@ -203,11 +196,18 @@
             mThumb = thumb;
         }
         @Override
-        public void onClick(WallpaperPickerActivity a) {
+        public void onClick(final WallpaperPickerActivity a) {
+            a.setWallpaperButtonEnabled(false);
             BitmapRegionTileSource.UriBitmapSource bitmapSource =
                     new BitmapRegionTileSource.UriBitmapSource(a.getContext(),
                             Uri.fromFile(mFile), 1024);
-            a.setCropViewTileSource(bitmapSource, false, true, null, null);
+            a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
+
+                @Override
+                public void run() {
+                    a.setWallpaperButtonEnabled(true);
+                }
+            });
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
@@ -234,6 +234,7 @@
         }
         @Override
         public void onClick(final WallpaperPickerActivity a) {
+            a.setWallpaperButtonEnabled(false);
             BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
                     new BitmapRegionTileSource.ResourceBitmapSource(
                             mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
@@ -248,7 +249,13 @@
                             wallpaperSize.x, wallpaperSize.y, false);
                     return wallpaperSize.x / crop.width();
                 }
-            }, null);
+            }, new Runnable() {
+
+                @Override
+                public void run() {
+                    a.setWallpaperButtonEnabled(true);
+                }
+            });
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
@@ -420,7 +427,7 @@
                     }
                     return;
                 }
-                mSetWallpaperButton.setEnabled(true);
+                setWallpaperButtonEnabled(true);
                 WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
                 if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
                     selectTile(v);
@@ -639,6 +646,10 @@
         };
     }
 
+    public void setWallpaperButtonEnabled(boolean enabled) {
+        mSetWallpaperButton.setEnabled(enabled);
+    }
+
     @Thunk void selectTile(View v) {
         if (mSelectedTile != null) {
             mSelectedTile.setSelected(false);
diff --git a/res/layout/page_indicator_marker.xml b/res/layout/page_indicator_marker.xml
index 686d275..564a958 100644
--- a/res/layout/page_indicator_marker.xml
+++ b/res/layout/page_indicator_marker.xml
@@ -16,8 +16,8 @@
 <com.android.launcher3.PageIndicatorMarker
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="16dp"
-    android:layout_height="16dp"
+    android:layout_width="12dp"
+    android:layout_height="12dp"
     android:layout_gravity="center_vertical">
     <ImageView
         android:id="@+id/inactive"
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 7a4d5e8..cd3a051 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2008 The Android Open Source Project
+     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.
@@ -34,30 +34,44 @@
             android:layout_width="20dp"
             android:layout_height="20dp" />
 
-        <com.android.launcher3.FolderCellLayout
+        <com.android.launcher3.FolderPagedView
             android:id="@+id/folder_content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:cacheColorHint="#ff333333"
-            android:hapticFeedbackEnabled="false" />
+            launcher:pageIndicator="@+id/folder_page_indicator" />
     </FrameLayout>
 
-    <com.android.launcher3.FolderEditText
-        android:id="@+id/folder_name"
+    <LinearLayout
+        android:id="@+id/folder_footer"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        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" />
+        android:orientation="vertical" >
+
+        <include
+            android:id="@+id/folder_page_indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="12dp"
+            android:layout_gravity="center_horizontal"
+            layout="@layout/page_indicator" />
+
+        <com.android.launcher3.FolderEditText
+            android:id="@+id/folder_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            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" />
+    </LinearLayout>
 
 </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
deleted file mode 100644
index 12e5097..0000000
--- a/res/layout/user_folder_scroll.xml
+++ /dev/null
@@ -1,106 +0,0 @@
-<?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"
-        android:paddingStart="12dp"
-        android:paddingEnd="8dp" >
-
-        <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="start"
-            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="top"
-            android:layout_marginTop="5dp"
-            layout="@layout/page_indicator" />
-
-        <LinearLayout
-            android:id="@+id/folder_sort"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:layout_gravity="center_vertical"
-            android:gravity="end|center_vertical" >
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="8dp"
-                android:text="@string/sort_alphabetical"
-                android:textColor="#ff777777"
-                android:textSize="14sp" />
-
-            <Switch
-                android:id="@+id/folder_sort_switch"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:clickable="false"
-                android:duplicateParentState="true"
-                android:focusable="false" />
-        </LinearLayout>
-    </LinearLayout>
-
-</com.android.launcher3.Folder>
\ No newline at end of file
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 1286a62..a5b25aa 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -41,7 +41,7 @@
             android:layout_weight="1"
             android:gravity="start"
             android:singleLine="true"
-            android:ellipsize="marquee"
+            android:ellipsize="end"
             android:fadingEdge="horizontal"
 
             android:textColor="#FFFFFFFF"
@@ -57,7 +57,6 @@
             android:id="@+id/widget_dims"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center"
             android:layout_marginStart="5dp"
             android:layout_marginLeft="5dp"
             android:layout_weight="0"
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index c7863c7..017b450 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -52,6 +52,8 @@
             android:paddingTop="8dp"
             android:paddingLeft="16dp"
             android:paddingRight="16dp"
+            android:singleLine="true"
+            android:ellipsize="end"
             android:gravity="start|center_vertical"
             android:textColor="@color/widgets_view_section_text_color"
             android:background="@color/widget_text_panel"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1b58d75..52306e3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,6 +38,8 @@
     <string name="uid_name">Android Core Apps</string>
     <!-- Default folder name -->
     <string name="folder_name"></string>
+    <!-- Work folder name -->
+    <string name="work_folder_name">Work</string>
     <!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
     <string name="activity_not_found">App isn\'t installed.</string>
     <!-- Displayed when user selects a shortcut for an app that is current not available [CHAR_LIMIT=none]-->
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 786f2ce..22fb6a0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -82,7 +82,7 @@
     boolean isLandscape;
     boolean isTablet;
     boolean isLargeTablet;
-    boolean isLayoutRtl;
+    public boolean isLayoutRtl;
 
     boolean transposeLayoutWithOrientation;
 
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 327fac4..8791c89 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -38,16 +38,6 @@
 }
 
 /**
- * A keyboard listener we set on all the workspace icons.
- */
-class FolderKeyEventListener implements View.OnKeyListener {
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
-    }
-}
-
-/**
  * A keyboard listener we set on all the hotseat buttons.
  */
 class HotseatIconKeyEventListener implements View.OnKeyListener {
@@ -91,110 +81,120 @@
      */
     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;
-        }
-        if (DEBUG) {
-            Log.v(TAG, String.format("Handle ALL APPS keyevent=[%s].",
-                    KeyEvent.keyCodeToString(keyCode)));
-        }
-
-        // Initialize variables.
-        ViewGroup parentLayout;
-        ViewGroup itemContainer;
-        int countX;
-        int countY;
-        if (v.getParent() instanceof ShortcutAndWidgetContainer) {
-            itemContainer = (ViewGroup) v.getParent();
-            parentLayout = (ViewGroup) itemContainer.getParent();
-            countX = ((CellLayout) parentLayout).getCountX();
-            countY = ((CellLayout) parentLayout).getCountY();
-        } else {
-            if (LauncherAppState.isDogfoodBuild()) {
-                throw new IllegalStateException("Parent of the focused item is not supported.");
-            } else {
-                return false;
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent e) {
+            boolean consume = FocusLogic.shouldConsume(keyCode);
+            if (e.getAction() == KeyEvent.ACTION_UP) {
+                return consume;
             }
-        }
+            if (DEBUG) {
+                Log.v(TAG, String.format("Handle ALL APPS and Folders keyevent=[%s].",
+                        KeyEvent.keyCodeToString(keyCode)));
+            }
 
-        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);
+            // Initialize variables.
+            ViewGroup parentLayout;
+            ViewGroup itemContainer;
+            int countX;
+            int countY;
+            if (v.getParent() instanceof ShortcutAndWidgetContainer) {
+                itemContainer = (ViewGroup) v.getParent();
+                parentLayout = (ViewGroup) itemContainer.getParent();
+                countX = ((CellLayout) parentLayout).getCountX();
+                countY = ((CellLayout) parentLayout).getCountY();
+            } else {
+                if (LauncherAppState.isDogfoodBuild()) {
+                    throw new IllegalStateException("Parent of the focused item is not supported.");
+                } else {
+                    return false;
+                }
+            }
 
-        // Process focus.
-        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
-                iconIndex, pageIndex, pageCount);
-        if (newIconIndex == FocusLogic.NOOP) {
-            handleNoopKey(keyCode, v);
+            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:
+                case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
+                    int newPageIndex = pageIndex - 1;
+                    if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
+                        newPageIndex = pageIndex + 1;
+                    }
+                    newParent = getAppsCustomizePage(container, newPageIndex);
+                    if (newParent != null) {
+                        int row = FocusLogic.findRow(matrix, iconIndex);
+                        container.snapToPage(newPageIndex);
+                        // 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) {
+                        container.snapToPage(pageIndex - 1);
+                        child = newParent.getChildAt(newParent.getChildCount() - 1);
+                    }
+                    break;
+                case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+                    newParent = getAppsCustomizePage(container, pageIndex + 1);
+                    if (newParent != null) {
+                        container.snapToPage(pageIndex + 1);
+                        child = newParent.getChildAt(0);
+                    }
+                    break;
+                case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+                case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
+                    newPageIndex = pageIndex + 1;
+                    if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
+                        newPageIndex = pageIndex -1;
+                    }
+                    newParent = getAppsCustomizePage(container, newPageIndex);
+                    if (newParent != null) {
+                        container.snapToPage(newPageIndex);
+                        int row = FocusLogic.findRow(matrix, iconIndex);
+                        child = newParent.getChildAt(matrix[0][row]);
+                    }
+                    break;
+                case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+                    child = container.getChildAt(0);
+                    break;
+                case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+                    child = itemContainer.getChildAt(itemContainer.getChildCount() - 1);
+                    break;
+                default: // Go to some item on the current page.
+                    child = itemContainer.getChildAt(newIconIndex);
+                    break;
+            }
+            if (child != null) {
+                child.requestFocus();
+                playSoundEffect(keyCode, v);
+            } else {
+                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) {
-                    container.snapToPage(pageIndex - 1);
-                    child = newParent.getChildAt(newParent.getChildCount() - 1);
-                }
-                break;
-            case FocusLogic.NEXT_PAGE_FIRST_ITEM:
-                newParent = getAppsCustomizePage(container, pageIndex + 1);
-                if (newParent != null) {
-                    container.snapToPage(pageIndex + 1);
-                    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;
-            case FocusLogic.CURRENT_PAGE_LAST_ITEM:
-                child = itemContainer.getChildAt(itemContainer.getChildCount() - 1);
-                break;
-            default: // Go to some item on the current page.
-                child = itemContainer.getChildAt(newIconIndex);
-                break;
-        }
-        if (child != null) {
-            child.requestFocus();
-            playSoundEffect(keyCode, v);
-        } else {
-            handleNoopKey(keyCode, v);
-        }
-        return consume;
-    }
 
-    public void handleNoopKey(int keyCode, View v) { }
+        public void handleNoopKey(int keyCode, View v) { }
     }
 
     /**
@@ -209,8 +209,7 @@
             return consume;
         }
 
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile profile = app.getDynamicGrid().getDeviceProfile();
+        DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
         if (DEBUG) {
             Log.v(TAG, String.format(
                     "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
@@ -358,14 +357,19 @@
                 }
                 break;
             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+            case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
+                int newPageIndex = pageIndex - 1;
+                if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
+                    newPageIndex = pageIndex + 1;
+                }
                 int row = FocusLogic.findRow(matrix, iconIndex);
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
                 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);
+                        FocusLogic.PIVOT, newPageIndex, pageCount);
                     newIcon = parent.getChildAt(newIconIndex);
                 }
                 break;
@@ -385,13 +389,18 @@
                 workspace.snapToPage(pageIndex + 1);
                 break;
             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+            case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
+                newPageIndex = pageIndex + 1;
+                if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
+                    newPageIndex = pageIndex - 1;
+                }
                 row = FocusLogic.findRow(matrix, iconIndex);
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+                parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
                 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);
+                        FocusLogic.PIVOT, newPageIndex, pageCount);
                     newIcon = parent.getChildAt(newIconIndex);
                 }
                 break;
@@ -418,62 +427,6 @@
         return consume;
     }
 
-    /**
-     * Handles key events for items in a Folder.
-     */
-    static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
-        boolean consume = FocusLogic.shouldConsume(keyCode);
-        if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
-            return consume;
-        }
-        if (DEBUG) {
-            Log.v(TAG, String.format("Handle FOLDER keyevent=[%s].",
-                    KeyEvent.keyCodeToString(keyCode)));
-        }
-
-        // Initialize the variables.
-        ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
-        final CellLayout layout = (CellLayout) parent.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();
-        final int countY = layout.getCountY();
-        final int iconIndex = findIndexOfView(parent, v);
-        int pageIndex = workspace.indexOfChild(layout);
-        int pageCount = workspace.getChildCount();
-        int[][] map = FocusLogic.createFullMatrix(countX, countY, true /* incremental order */);
-
-        // Process the focus.
-        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, map, iconIndex,
-                pageIndex, pageCount);
-        View newIcon = null;
-        switch (newIconIndex) {
-            case FocusLogic.NOOP:
-                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
-                    newIcon = title;
-                }
-                break;
-            case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
-            case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
-            case FocusLogic.NEXT_PAGE_FIRST_ITEM:
-            case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
-            case FocusLogic.CURRENT_PAGE_LAST_ITEM:
-                if (DEBUG) {
-                    Log.v(TAG, "Page advance handling not supported on folder icons.");
-                }
-                break;
-            default: // current page some item.
-                newIcon = parent.getChildAt(newIconIndex);
-                break;
-        }
-        if (newIcon != null) {
-            newIcon.requestFocus();
-            playSoundEffect(keyCode, v);
-        }
-        return consume;
-    }
-
     //
     // Helper methods.
     //
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 1e1d1ee..0eb1fd8 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -69,7 +69,6 @@
      * 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;
@@ -101,6 +100,8 @@
 
     private final Alarm mReorderAlarm = new Alarm();
     private final Alarm mOnExitAlarm = new Alarm();
+    private final Alarm mOnScrollHintAlarm = new Alarm();
+    @Thunk final Alarm mScrollPauseAlarm = new Alarm();
 
     @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
 
@@ -116,7 +117,7 @@
 
     @Thunk FolderIcon mFolderIcon;
 
-    @Thunk FolderContent mContent;
+    @Thunk FolderPagedView mContent;
     @Thunk View mContentWrapper;
     FolderEditText mFolderName;
 
@@ -149,11 +150,6 @@
 
     // Folder scrolling
     private int mScrollAreaOffset;
-    private Alarm mOnScrollHintAlarm;
-    @Thunk Alarm mScrollPauseAlarm;
-
-    // TODO: Use {@link #mContent} once {@link #ALLOW_FOLDER_SCROLL} is removed.
-    @Thunk FolderPagedView mPagedView;
 
     @Thunk int mScrollHintDir = DragController.SCROLL_NONE;
     @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE;
@@ -186,18 +182,13 @@
         // name is complete, we have something to focus on, thus hiding the cursor and giving
         // reliable behavior when clicking the text field (since it will always gain focus on click).
         setFocusableInTouchMode(true);
-
-        if (ALLOW_FOLDER_SCROLL) {
-            mOnScrollHintAlarm = new Alarm();
-            mScrollPauseAlarm = new Alarm();
-        }
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mContentWrapper = findViewById(R.id.folder_content_wrapper);
-        mContent = (FolderContent) findViewById(R.id.folder_content);
+        mContent = (FolderPagedView) findViewById(R.id.folder_content);
         mContent.setFolder(this);
 
         mFolderName = (FolderEditText) findViewById(R.id.folder_name);
@@ -211,16 +202,16 @@
         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;
+        mFooter = findViewById(R.id.folder_footer);
+        updateFooterHeight();
+    }
+
+    public void updateFooterHeight() {
         // 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();
-
-        if (ALLOW_FOLDER_SCROLL) {
-            mPagedView = (FolderPagedView) mContent;
-        }
     }
 
     private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -398,8 +389,7 @@
      * @return A new UserFolder.
      */
     static Folder fromXml(Context context) {
-        return (Folder) LayoutInflater.from(context).inflate(
-                ALLOW_FOLDER_SCROLL ? R.layout.user_folder_scroll : R.layout.user_folder, null);
+        return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
     }
 
     /**
@@ -424,12 +414,10 @@
     public void animateOpen() {
         if (!(getParent() instanceof DragLayer)) return;
 
-        if (ALLOW_FOLDER_SCROLL) {
-            mPagedView.completePendingPageChanges();
-            if (!(mDragInProgress && mPagedView.mIsSorted)) {
-                // Open on the first page.
-                mPagedView.snapToPageImmediately(0);
-            }
+        mContent.completePendingPageChanges();
+        if (!(mDragInProgress && mContent.mIsSorted)) {
+            // Open on the first page.
+            mContent.snapToPageImmediately(0);
         }
 
         Animator openFolderAnim = null;
@@ -533,10 +521,8 @@
             mDragController.forceTouchMove();
         }
 
-        if (ALLOW_FOLDER_SCROLL) {
-            FolderPagedView pages = (FolderPagedView) mContent;
-            pages.verifyVisibleHighResIcons(pages.getNextPage());
-        }
+        FolderPagedView pages = (FolderPagedView) mContent;
+        pages.verifyVisibleHighResIcons(pages.getNextPage());
     }
 
     public void beginExternalDrag(ShortcutInfo item) {
@@ -544,7 +530,8 @@
         mEmptyCellRank = mContent.allocateRankForNewItem(item);
         mIsExternalDrag = true;
         mDragInProgress = true;
-        if (ALLOW_FOLDER_SCROLL && mPagedView.mIsSorted) {
+
+        if (mContent.mIsSorted) {
             mScrollPauseAlarm.setOnAlarmListener(null);
             mScrollPauseAlarm.cancelAlarm();
             mScrollPauseAlarm.setAlarm(SORTED_STICKY_REORDER_DELAY);
@@ -601,11 +588,9 @@
     public void onDragEnter(DragObject d) {
         mPrevTargetRank = -1;
         mOnExitAlarm.cancelAlarm();
-        if (ALLOW_FOLDER_SCROLL) {
-            // Get the area offset such that the folder only closes if half the drag icon width
-            // is outside the folder area
-            mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset;
-        }
+        // Get the area offset such that the folder only closes if half the drag icon width
+        // is outside the folder area
+        mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset;
     }
 
     OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
@@ -632,7 +617,7 @@
     }
 
     @Thunk void onDragOver(DragObject d, int reorderDelay) {
-        if (ALLOW_FOLDER_SCROLL && mScrollPauseAlarm.alarmPending()) {
+        if (mScrollPauseAlarm.alarmPending()) {
             return;
         }
         final float[] r = new float[2];
@@ -645,27 +630,23 @@
             mPrevTargetRank = mTargetRank;
         }
 
-        if (!ALLOW_FOLDER_SCROLL) {
-            return;
-        }
-
         float x = r[0];
-        int currentPage = mPagedView.getNextPage();
+        int currentPage = mContent.getNextPage();
 
-        float cellOverlap = mPagedView.getCurrentCellLayout().getCellWidth()
+        float cellOverlap = mContent.getCurrentCellLayout().getCellWidth()
                 * ICON_OVERSCROLL_WIDTH_FACTOR;
         boolean isOutsideLeftEdge = x < cellOverlap;
         boolean isOutsideRightEdge = x > (getWidth() - cellOverlap);
 
-        if (currentPage > 0 && (mPagedView.rtlLayout ? isOutsideRightEdge : isOutsideLeftEdge)) {
+        if (currentPage > 0 && (mContent.rtlLayout ? isOutsideRightEdge : isOutsideLeftEdge)) {
             showScrollHint(DragController.SCROLL_LEFT, d);
-        } else if (currentPage < (mPagedView.getPageCount() - 1)
-                && (mPagedView.rtlLayout ? isOutsideLeftEdge : isOutsideRightEdge)) {
+        } else if (currentPage < (mContent.getPageCount() - 1)
+                && (mContent.rtlLayout ? isOutsideLeftEdge : isOutsideRightEdge)) {
             showScrollHint(DragController.SCROLL_RIGHT, d);
         } else {
             mOnScrollHintAlarm.cancelAlarm();
             if (mScrollHintDir != DragController.SCROLL_NONE) {
-                mPagedView.clearScrollHint();
+                mContent.clearScrollHint();
                 mScrollHintDir = DragController.SCROLL_NONE;
             }
         }
@@ -674,7 +655,7 @@
     private void showScrollHint(int direction, DragObject d) {
         // Show scroll hint on the right
         if (mScrollHintDir != direction) {
-            mPagedView.showScrollHint(direction);
+            mContent.showScrollHint(direction);
             mScrollHintDir = direction;
         }
 
@@ -714,13 +695,11 @@
         }
         mReorderAlarm.cancelAlarm();
 
-        if (ALLOW_FOLDER_SCROLL) {
-            mOnScrollHintAlarm.cancelAlarm();
-            mScrollPauseAlarm.cancelAlarm();
-            if (mScrollHintDir != DragController.SCROLL_NONE) {
-                mPagedView.clearScrollHint();
-                mScrollHintDir = DragController.SCROLL_NONE;
-            }
+        mOnScrollHintAlarm.cancelAlarm();
+        mScrollPauseAlarm.cancelAlarm();
+        if (mScrollHintDir != DragController.SCROLL_NONE) {
+            mContent.clearScrollHint();
+            mScrollHintDir = DragController.SCROLL_NONE;
         }
     }
 
@@ -1088,21 +1067,19 @@
             };
         }
 
-        if (ALLOW_FOLDER_SCROLL) {
-            // If the icon was dropped while the page was being scrolled, we need to compute
-            // the target location again such that the icon is placed of the final page.
-            if (!mPagedView.rankOnCurrentPage(mEmptyCellRank)) {
-                // Reorder again.
-                mTargetRank = getTargetRank(d, null);
+        // If the icon was dropped while the page was being scrolled, we need to compute
+        // the target location again such that the icon is placed of the final page.
+        if (!mContent.rankOnCurrentPage(mEmptyCellRank)) {
+            // Reorder again.
+            mTargetRank = getTargetRank(d, null);
 
-                // Rearrange items immediately.
-                mReorderAlarmListener.onAlarm(mReorderAlarm);
+            // Rearrange items immediately.
+            mReorderAlarmListener.onAlarm(mReorderAlarm);
 
-                mOnScrollHintAlarm.cancelAlarm();
-                mScrollPauseAlarm.cancelAlarm();
-            }
-            mPagedView.completePendingPageChanges();
+            mOnScrollHintAlarm.cancelAlarm();
+            mScrollPauseAlarm.cancelAlarm();
         }
+        mContent.completePendingPageChanges();
 
         View currentDragView;
         ShortcutInfo si = mCurrentDragInfo;
@@ -1252,10 +1229,10 @@
         @Override
         public void onAlarm(Alarm alarm) {
             if (mCurrentScrollDir == DragController.SCROLL_LEFT) {
-                mPagedView.scrollLeft();
+                mContent.scrollLeft();
                 mScrollHintDir = DragController.SCROLL_NONE;
             } else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) {
-                mPagedView.scrollRight();
+                mContent.scrollRight();
                 mScrollHintDir = DragController.SCROLL_NONE;
             } else {
                 // This should not happen
@@ -1286,69 +1263,4 @@
             onDragOver(mDragObject, 1);
         }
     }
-
-    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, and returns the rank for that item.
-         * Resizes the content if necessary.
-         */
-        int allocateRankForNewItem(ShortcutInfo info);
-
-        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
deleted file mode 100644
index 8585add..0000000
--- a/src/com/android/launcher3/FolderCellLayout.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/**
- * 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 allocateRankForNewItem(ShortcutInfo info) {
-        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/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 3240cbf..80b1564 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -37,6 +37,11 @@
     public static final int FLAG_ITEMS_SORTED = 0x00000001;
 
     /**
+     * It is a work folder
+     */
+    public static final int FLAG_WORK_FOLDER = 0x00000002;
+
+    /**
      * Whether this folder has been opened
      */
     boolean opened;
@@ -46,11 +51,11 @@
     /**
      * The apps and shortcuts
      */
-    ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
+    public ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
 
     ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
 
-    FolderInfo() {
+    public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
         user = UserHandleCompat.myUserHandle();
     }
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index 1c42d25..6174892 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
-import android.util.LayoutDirection;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -42,10 +41,15 @@
 import java.util.Iterator;
 import java.util.Map;
 
-public class FolderPagedView extends PagedView implements Folder.FolderContent {
+public class FolderPagedView extends PagedView {
 
     private static final String TAG = "FolderPagedView";
 
+    private static final boolean ALLOW_FOLDER_SCROLL = true;
+
+    // To enable this flag, user_folder.xml needs to be modified to add sort button.
+    private static final boolean ALLOW_ITEM_SORTING = false;
+
     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;
@@ -96,34 +100,36 @@
         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);
+        if (ALLOW_FOLDER_SCROLL) {
+            mMaxCountX = Math.min((int) grid.numColumns, MAX_ITEMS_PER_PAGE);
+            mMaxCountY = Math.min((int) grid.numRows, MAX_ITEMS_PER_PAGE);
+        } else {
+            mMaxCountX = (int) grid.numColumns;
+            mMaxCountY = (int) grid.numRows;
+        }
+
         mMaxItemsPerPage = mMaxCountX * mMaxCountY;
 
         mInflater = LayoutInflater.from(context);
         mIconCache = app.getIconCache();
 
-        rtlLayout = getResources().getConfiguration().getLayoutDirection() == LayoutDirection.RTL;
+        rtlLayout = getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL;
     }
 
-    @Override
     public void setFolder(Folder folder) {
         mFolder = folder;
         mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
         mKeyListener = new PagedFolderKeyEventListener(folder);
-
-        mSortButton = folder.findViewById(R.id.folder_sort);
-        mSortButton.setOnClickListener(new OnClickListener() {
-
-            @Override
-            public void onClick(View v) {
-                onSortClicked();
-            }
-        });
         mPageIndicator = folder.findViewById(R.id.folder_page_indicator);
-        mSortSwitch = (Switch) folder.findViewById(R.id.folder_sort_switch);
+
+        if (ALLOW_ITEM_SORTING) {
+            // Initialize {@link #mSortSwitch} and {@link #mSortButton}.
+        }
     }
 
+    /**
+     * Called when sort button is clicked.
+     */
     private void onSortClicked() {
         if (mSortOperationPending) {
             return;
@@ -138,9 +144,11 @@
 
     private void setIsSorted(boolean isSorted, boolean saveChanges) {
         mIsSorted = isSorted;
-        mSortSwitch.setChecked(isSorted);
-        mFolder.mInfo.setOption(FolderInfo.FLAG_ITEMS_SORTED, isSorted,
-                saveChanges ? mFolder.mLauncher : null);
+        if (ALLOW_ITEM_SORTING) {
+            mSortSwitch.setChecked(isSorted);
+            mFolder.mInfo.setOption(FolderInfo.FLAG_ITEMS_SORTED, isSorted,
+                    saveChanges ? mFolder.mLauncher : null);
+        }
     }
 
     /**
@@ -282,26 +290,34 @@
         }
     }
 
-    @Override
+    /**
+     * Binds items to the layout.
+     * @return list of items that could not be bound, probably because we hit the max size limit.
+     */
     public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
-        mIsSorted = mFolder.mInfo.hasOption(FolderInfo.FLAG_ITEMS_SORTED);
+        mIsSorted = ALLOW_ITEM_SORTING && mFolder.mInfo.hasOption(FolderInfo.FLAG_ITEMS_SORTED);
         ArrayList<View> icons = new ArrayList<View>();
+        ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
+
         for (ShortcutInfo item : items) {
-            icons.add(createNewView(item));
+            if (!ALLOW_FOLDER_SCROLL && icons.size() >= mMaxItemsPerPage) {
+                extra.add(item);
+            } else {
+                icons.add(createNewView(item));
+            }
         }
         arrangeChildren(icons, icons.size(), false);
-        return new ArrayList<ShortcutInfo>();
+        return extra;
     }
 
     /**
      * 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 allocateRankForNewItem(ShortcutInfo info) {
         int rank = getItemCount();
         ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder());
-        if (mIsSorted) {
+        if (ALLOW_ITEM_SORTING && mIsSorted) {
             View tmp = new View(getContext());
             tmp.setTag(info);
             int index = Collections.binarySearch(views, tmp, new ViewComparator());
@@ -321,14 +337,16 @@
         return rank;
     }
 
-    @Override
     public View createAndAddViewForRank(ShortcutInfo item, int rank) {
         View icon = createNewView(item);
         addViewForRank(icon, item, rank);
         return icon;
     }
 
-    @Override
+    /**
+     * Adds the {@param view} to the layout based on {@param rank} and updated the position
+     * related attributes. It assumes that {@param item} is already attached to the view.
+     */
     public void addViewForRank(View view, ShortcutInfo item, int rank) {
         int pagePos = rank % mMaxItemsPerPage;
         int pageNo = rank / mMaxItemsPerPage;
@@ -388,14 +406,12 @@
         return page;
     }
 
-    @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);
@@ -412,7 +428,6 @@
      * at the end, otherwise it is ignored.
      *
      */
-    @Override
     public void arrangeChildren(ArrayList<View> list, int itemCount) {
         arrangeChildren(list, itemCount, true);
     }
@@ -488,19 +503,26 @@
             setCurrentPage(0);
         }
 
-        setIsSorted(isSorted, saveChanges);
+        setEnableOverscroll(getPageCount() > 1);
 
         // Update footer
-        if (getPageCount() > 1) {
-            mPageIndicator.setVisibility(View.VISIBLE);
-            mSortButton.setVisibility(View.VISIBLE);
-            mFolder.mFolderName.setGravity(rtlLayout ? Gravity.RIGHT : Gravity.LEFT);
-            setEnableOverscroll(true);
+        if (ALLOW_ITEM_SORTING) {
+            setIsSorted(isSorted, saveChanges);
+            if (getPageCount() > 1) {
+                mPageIndicator.setVisibility(View.VISIBLE);
+                mSortButton.setVisibility(View.VISIBLE);
+                mFolder.mFolderName.setGravity(rtlLayout ? Gravity.RIGHT : Gravity.LEFT);
+            } else {
+                mPageIndicator.setVisibility(View.GONE);
+                mSortButton.setVisibility(View.GONE);
+                mFolder.mFolderName.setGravity(Gravity.CENTER_HORIZONTAL);
+            }
         } else {
-            mPageIndicator.setVisibility(View.GONE);
-            mSortButton.setVisibility(View.GONE);
-            mFolder.mFolderName.setGravity(Gravity.CENTER_HORIZONTAL);
-            setEnableOverscroll(false);
+            int indicatorVisibility = mPageIndicator.getVisibility();
+            mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
+            if (indicatorVisibility != mPageIndicator.getVisibility()) {
+                mFolder.updateFooterHeight();
+            }
         }
     }
 
@@ -521,7 +543,6 @@
         return  getPageCount() > 0 ? getPageAt(0).getDesiredHeight() : 0;
     }
 
-    @Override
     public int getItemCount() {
         int lastPageIndex = getChildCount() - 1;
         if (lastPageIndex < 0) {
@@ -532,7 +553,9 @@
                 + lastPageIndex * mMaxItemsPerPage;
     }
 
-    @Override
+    /**
+     * @return the rank of the cell nearest to the provided pixel position.
+     */
     public int findNearestArea(int pixelX, int pixelY) {
         int pageIndex = getNextPage();
         CellLayout page = getPageAt(pageIndex);
@@ -550,12 +573,10 @@
                 R.drawable.ic_pageindicator_default_folder);
     }
 
-    @Override
     public boolean isFull() {
-        return false;
+        return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
     }
 
-    @Override
     public View getLastItem() {
         if (getChildCount() < 1) {
             return null;
@@ -569,7 +590,10 @@
         }
     }
 
-    @Override
+    /**
+     * Iterates over all its items in a reading order.
+     * @return the view for which the operator returned true.
+     */
     public View iterateOverItems(ItemOperator op) {
         for (int k = 0 ; k < getChildCount(); k++) {
             CellLayout page = getPageAt(k);
@@ -585,13 +609,14 @@
         return null;
     }
 
-    @Override
     public String getAccessibilityDescription() {
         return String.format(getContext().getString(R.string.folder_opened),
                 mGridCountX, mGridCountY);
     }
 
-    @Override
+    /**
+     * Sets the focus on the first visible child.
+     */
     public void setFocusOnFirstChild() {
         View firstChild = getCurrentCellLayout().getChildAt(0, 0);
         if (firstChild != null) {
@@ -605,7 +630,7 @@
         if (mFolder != null) {
             mFolder.updateTextViewFocus();
         }
-        if (mSortOperationPending && getNextPage() == 0) {
+        if (ALLOW_ITEM_SORTING && mSortOperationPending && getNextPage() == 0) {
             post(new Runnable() {
 
                 @Override
@@ -680,7 +705,9 @@
         }
     }
 
-    @Override
+    /**
+     * Reorders the items such that the {@param empty} spot moves to {@param target}
+     */
     public void realTimeReorder(int empty, int target) {
         completePendingPageChanges();
         int delay = 0;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index f6238da..48b38f1 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -283,8 +283,8 @@
             }
             ContentValues values = updateCacheAndGetContentValues(app);
             mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
-                    IconDB.COLUMN_COMPONENT + " = ?",
-                    new String[] { cn });
+                    IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+                    new String[] {cn, Long.toString(userSerial)});
 
             updatedPackages.add(component.getPackageName());
         }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 0db22a4..0c69154 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -22,6 +22,7 @@
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.text.TextUtils;
@@ -147,6 +148,7 @@
 
         if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+        info = convertToLauncherActivityIfPossible(info);
 
         queuePendingShortcutInfo(info, context);
     }
@@ -208,7 +210,7 @@
             // Add the new apps to the model and bind them
             if (!addShortcuts.isEmpty()) {
                 LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceApps(context, addShortcuts);
+                app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
             }
         }
     }
@@ -350,16 +352,7 @@
 
         public ShortcutInfo getShortcutInfo() {
             if (activityInfo != null) {
-                final ShortcutInfo info = new ShortcutInfo();
-                info.user = user;
-                info.title = label;
-                info.contentDescription = label;
-                info.customIcon = false;
-                info.intent = launchIntent;
-                info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-                info.flags = AppInfo.initFlags(activityInfo);
-                info.firstInstallTime = activityInfo.getFirstInstallTime();
-                return info;
+                return ShortcutInfo.fromActivityInfo(activityInfo, mContext);
             } else {
                 return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
             }
@@ -373,6 +366,10 @@
             }
             return packageName;
         }
+
+        public boolean isLuncherActivity() {
+            return activityInfo != null;
+        }
     }
 
     private static PendingInstallShortcutInfo decode(String encoded, Context context) {
@@ -420,4 +417,40 @@
         }
         return null;
     }
+
+    /**
+     * Tries to create a new PendingInstallShortcutInfo which represents the same target,
+     * but is an app target and not a shortcut.
+     * @return the newly created info or the original one.
+     */
+    private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
+            PendingInstallShortcutInfo original) {
+        if (original.isLuncherActivity()) {
+            // Already an activity target
+            return original;
+        }
+        if (isValidShortcutLaunchIntent(original.launchIntent)
+                || !original.user.equals(UserHandleCompat.myUserHandle())) {
+            // We can only convert shortcuts which point to a main activity in the current user.
+            return original;
+        }
+
+        PackageManager pm = original.mContext.getPackageManager();
+        ResolveInfo info = pm.resolveActivity(original.launchIntent, 0);
+
+        if (info == null) {
+            return original;
+        }
+
+        // Ignore any conflicts in the label name, as that can change based on locale.
+        LauncherActivityInfoCompat launcherInfo = LauncherActivityInfoCompat
+                .fromResolveInfo(info, original.mContext);
+        return new PendingInstallShortcutInfo(launcherInfo, original.mContext);
+    }
+
+    public static boolean isLauncherActivity(Intent intent, Context context) {
+        Intent data = new Intent().putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
+        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+        return convertToLauncherActivityIfPossible(info).isLuncherActivity();
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 10d804a..5679dfc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -132,7 +132,7 @@
         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                    View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
                    LauncherStateTransitionAnimation.Callbacks {
-    static final String TAG = "Launcher - MERONG";
+    static final String TAG = "Launcher";
     static final boolean LOGD = true;
 
     static final boolean PROFILE_STARTUP = false;
diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index 42f1914..cfc1bd9 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -124,7 +124,7 @@
                 mLauncher.showWorkspace(true, new Runnable() {
                     @Override
                     public void run() {
-                        mLauncher.getModel().addAndBindAddedWorkspaceApps(
+                        mLauncher.getModel().addAndBindAddedWorkspaceItems(
                                 mLauncher, addShortcuts, screenProvider, 0, true);
                         announceConfirmation(R.string.item_added_to_workspace);
                     }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3bd3850..6e77d06 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -149,7 +149,7 @@
         return mIconCache;
     }
 
-    LauncherModel getModel() {
+    public LauncherModel getModel() {
         return mModel;
     }
 
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 699cb37..9dd8dc5 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -21,6 +21,7 @@
     public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
     public static final String WALLPAPER_CROP_PREFERENCES_KEY =
             "com.android.launcher3.WallpaperCropActivity";
+    public static final String MANAGED_USER_PREFERENCES_KEY = "com.android.launcher3.managedusers.prefs";
 
     public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db";
     public static final String WIDGET_PREVIEWS_DB = "widgetpreviews.db";
@@ -35,6 +36,7 @@
             WALLPAPER_CROP_PREFERENCES_KEY + XML,
             WALLPAPER_IMAGES_DB,
             WIDGET_PREVIEWS_DB,
+            MANAGED_USER_PREFERENCES_KEY,
             APP_ICONS_DB));
 
     // TODO: Delete these files on upgrade
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 98ba09b..37f1ea8 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.Thunk;
 
 import java.lang.ref.WeakReference;
@@ -87,7 +88,6 @@
     static final boolean DEBUG_LOADERS = false;
     private static final boolean DEBUG_RECEIVER = false;
     private static final boolean REMOVE_UNRESTORED_ICONS = true;
-    private static final boolean ADD_MANAGED_PROFILE_SHORTCUTS = false;
 
     static final String TAG = "Launcher.Model";
 
@@ -107,11 +107,6 @@
     @Thunk LoaderTask mLoaderTask;
     @Thunk boolean mIsLoaderTaskRunning;
 
-    /**
-     * Maintain a set of packages per user, for which we added a shortcut on the workspace.
-     */
-    private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
-
     // Specific runnable types that are run on the main thread deferred handler, this allows us to
     // clear all queued binding runnables when the Launcher activity is destroyed.
     private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
@@ -338,9 +333,9 @@
         runOnWorkerThread(r);
     }
 
-    public void addAndBindAddedWorkspaceApps(final Context context,
+    public void addAndBindAddedWorkspaceItems(final Context context,
             final ArrayList<ItemInfo> workspaceApps) {
-        addAndBindAddedWorkspaceApps(context, workspaceApps,
+        addAndBindAddedWorkspaceItems(context, workspaceApps,
                 new ScreenPosProvider() {
 
                     @Override
@@ -518,7 +513,7 @@
      * @param fallbackStartScreen the screen to start search for empty space if
      * preferredScreen is not available.
      */
-    public void addAndBindAddedWorkspaceApps(final Context context,
+    public void addAndBindAddedWorkspaceItems(final Context context,
             final ArrayList<ItemInfo> workspaceApps,
             final ScreenPosProvider preferredScreen,
             final int fallbackStartScreen,
@@ -539,7 +534,7 @@
                 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
                 synchronized(sBgLock) {
                     for (ItemInfo item : workspaceApps) {
-                        if (!allowDuplicate) {
+                        if (!allowDuplicate && item instanceof ShortcutInfo) {
                             // Short-circuit this logic if the icon exists somewhere on the workspace
                             if (shortcutExists(context, item.title.toString(),
                                     item.getIntent(), item.user)) {
@@ -554,21 +549,21 @@
                         long screenId = coords.first;
                         int[] cordinates = coords.second;
 
-                        ShortcutInfo shortcutInfo;
-                        if (item instanceof ShortcutInfo) {
-                            shortcutInfo = (ShortcutInfo) item;
+                        ItemInfo itemInfo;
+                        if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+                            itemInfo = item;
                         } else if (item instanceof AppInfo) {
-                            shortcutInfo = ((AppInfo) item).makeShortcut();
+                            itemInfo = ((AppInfo) item).makeShortcut();
                         } else {
                             throw new RuntimeException("Unexpected info type");
                         }
 
                         // Add the shortcut to the db
-                        addItemToDatabase(context, shortcutInfo,
+                        addItemToDatabase(context, itemInfo,
                                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
                                 screenId, cordinates[0], cordinates[1]);
                         // Save the ShortcutInfo for binding in the workspace
-                        addedShortcutsFinal.add(shortcutInfo);
+                        addedShortcutsFinal.add(itemInfo);
                     }
                 }
 
@@ -993,7 +988,7 @@
      * Add an item to the database in a specified container. Sets the container, screen, cellX and
      * cellY fields of the item. Also assigns an ID to the item.
      */
-    static void addItemToDatabase(Context context, final ItemInfo item, final long container,
+    public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
             final long screenId, final int cellX, final int cellY) {
         item.container = container;
         item.cellX = cellX;
@@ -1097,7 +1092,6 @@
      */
     static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
-
         Runnable r = new Runnable() {
             public void run() {
                 for (ItemInfo item : items) {
@@ -2845,23 +2839,11 @@
                     mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                 }
 
-                if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
-                    // Add shortcuts for packages which were installed while launcher was dead.
-                    String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
-                            + mUserManager.getSerialNumberForUser(user);
-                    Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
-                    HashSet<String> newPackageSet = new HashSet<String>();
-
-                    for (LauncherActivityInfoCompat info : apps) {
-                        String packageName = info.getComponentName().getPackageName();
-                        if (!packagesAdded.contains(packageName)
-                                && !newPackageSet.contains(packageName)) {
-                            InstallShortcutReceiver.queueInstallShortcut(info, mContext);
-                        }
-                        newPackageSet.add(packageName);
+                if (!user.equals(UserHandleCompat.myUserHandle())) {
+                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
+                    if (heuristic != null) {
+                        heuristic.processUserApps(apps);
                     }
-
-                    prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
                 }
             }
             // Huh? Shouldn't this be inside the Runnable below?
@@ -2884,6 +2866,8 @@
                     }
                 }
             });
+            // Cleanup any data stored for a deleted user.
+            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
 
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "Icons processed in "
@@ -2971,38 +2955,19 @@
             final String[] packages = mPackages;
             final int N = packages.length;
             switch (mOp) {
-                case OP_ADD:
+                case OP_ADD: {
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
                         mIconCache.updateIconsForPkg(packages[i], mUser);
                         mBgAllAppsList.addPackage(context, packages[i], mUser);
                     }
 
-                    // Auto add shortcuts for added packages.
-                    if (ADD_MANAGED_PROFILE_SHORTCUTS
-                            && !UserHandleCompat.myUserHandle().equals(mUser)) {
-                        SharedPreferences prefs = context.getSharedPreferences(
-                                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
-                        String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
-                                + mUserManager.getSerialNumberForUser(mUser);
-                        Set<String> shortcutSet = new HashSet<String>(
-                                prefs.getStringSet(shortcutsSetKey,Collections.EMPTY_SET));
-
-                        for (int i=0; i<N; i++) {
-                            if (!shortcutSet.contains(packages[i])) {
-                                shortcutSet.add(packages[i]);
-                                List<LauncherActivityInfoCompat> activities =
-                                        mLauncherApps.getActivityList(packages[i], mUser);
-                                if (activities != null && !activities.isEmpty()) {
-                                    InstallShortcutReceiver.queueInstallShortcut(
-                                            activities.get(0), context);
-                                }
-                            }
-                        }
-
-                        prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                    if (heuristic != null) {
+                        heuristic.processPackageAdd(mPackages);
                     }
                     break;
+                }
                 case OP_UPDATE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
@@ -3011,25 +2976,17 @@
                         mApp.getWidgetCache().removePackage(packages[i], mUser);
                     }
                     break;
-                case OP_REMOVE:
-                    // Remove the packageName for the set of auto-installed shortcuts. This
-                    // will ensure that the shortcut when the app is installed again.
-                    if (ADD_MANAGED_PROFILE_SHORTCUTS
-                            && !UserHandleCompat.myUserHandle().equals(mUser)) {
-                        SharedPreferences prefs = context.getSharedPreferences(
-                                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
-                        String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
-                                + mUserManager.getSerialNumberForUser(mUser);
-                        HashSet<String> shortcutSet = new HashSet<String>(
-                                prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET));
-                        shortcutSet.removeAll(Arrays.asList(mPackages));
-                        prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+                case OP_REMOVE: {
+                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                    if (heuristic != null) {
+                        heuristic.processPackageRemoved(mPackages);
                     }
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
                         mIconCache.removeIconsForPkg(packages[i], mUser);
                     }
                     // Fall through
+                }
                 case OP_UNAVAILABLE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
@@ -3677,4 +3634,13 @@
     public Callbacks getCallback() {
         return mCallbacks != null ? mCallbacks.get() : null;
     }
+
+    /**
+     * @return {@link FolderInfo} if its already loaded.
+     */
+    public FolderInfo findFolderById(Long folderId) {
+        synchronized (sBgLock) {
+            return sBgFolders.get(folderId);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 1f59533..d75ef73 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -37,6 +37,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -63,7 +64,7 @@
     private static final String TAG = "Launcher.LauncherProvider";
     private static final boolean LOGD = false;
 
-    private static final int DATABASE_VERSION = 23;
+    private static final int DATABASE_VERSION = 24;
 
     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -617,7 +618,9 @@
                         break;
                     }
                 }
-                case 23: {
+                case 23:
+                    convertShortcutsToLauncherActivities(db);
+                case 24: {
                     // DB Upgraded successfully
                     return;
                 }
@@ -636,7 +639,6 @@
             createEmptyDB(db);
         }
 
-
         /**
          * Clears all the data for a fresh start.
          */
@@ -647,6 +649,63 @@
         }
 
         /**
+         * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
+         * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
+         */
+        private void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
+            db.beginTransaction();
+            Cursor c = null;
+            SQLiteStatement updateStmt = null;
+
+            try {
+                // Only consider the primary user as other users can't have a shortcut.
+                long userSerial = UserManagerCompat.getInstance(mContext)
+                        .getSerialNumberForUser(UserHandleCompat.myUserHandle());
+                c = db.query(TABLE_FAVORITES, new String[] {
+                        Favorites._ID,
+                        Favorites.INTENT,
+                    }, "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + " AND profileId=" + userSerial,
+                    null, null, null, null);
+
+                updateStmt = db.compileStatement("UPDATE favorites SET itemType="
+                        + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?");
+
+                final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
+                final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
+
+                while (c.moveToNext()) {
+                    String intentDescription = c.getString(intentIndex);
+                    Intent intent;
+                    try {
+                        intent = Intent.parseUri(intentDescription, 0);
+                    } catch (URISyntaxException e) {
+                        Log.e(TAG, "Unable to parse intent", e);
+                        continue;
+                    }
+
+                    if (!InstallShortcutReceiver.isLauncherActivity(intent, mContext)) {
+                        continue;
+                    }
+
+                    long id = c.getLong(idIndex);
+                    updateStmt.bindLong(1, id);
+                    updateStmt.execute();
+                }
+                db.setTransactionSuccessful();
+            } catch (SQLException ex) {
+                Log.w(TAG, "Error deduping shortcuts", ex);
+            } finally {
+                db.endTransaction();
+                if (c != null) {
+                    c.close();
+                }
+                if (updateStmt != null) {
+                    updateStmt.close();
+                }
+            }
+        }
+
+        /**
          * Recreates workspace table and migrates data to the new table.
          */
         public boolean recreateWorkspaceTable(SQLiteDatabase db) {
diff --git a/src/com/android/launcher3/LauncherScroller.java b/src/com/android/launcher3/LauncherScroller.java
index 3bd0a78..a9b4955 100644
--- a/src/com/android/launcher3/LauncherScroller.java
+++ b/src/com/android/launcher3/LauncherScroller.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.SensorManager;
 import android.os.Build;
-import android.util.FloatMath;
 import android.view.ViewConfiguration;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -409,7 +408,7 @@
 
             float dx = (float) (mFinalX - mStartX);
             float dy = (float) (mFinalY - mStartY);
-            float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+            float hyp = (float) Math.hypot(dx, dy);
 
             float ndx = dx / hyp;
             float ndy = dy / hyp;
@@ -426,7 +425,7 @@
         mMode = FLING_MODE;
         mFinished = false;
 
-        float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+        float velocity = (float) Math.hypot(velocityX, velocityY);
 
         mVelocity = velocity;
         mDuration = getSplineFlingDuration(velocity);
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 57bd5b2..78272a8 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -184,11 +184,6 @@
         final WidgetsContainerView toView = mLauncher.getWidgetsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
-            public void onRevealViewVisible(View revealView, View contentView,
-                    View allAppsButtonView) {
-                revealView.setBackground(mLauncher.getDrawable(R.drawable.quantum_panel_dark));
-            }
-            @Override
             public float getMaterialRevealViewFinalAlpha(View revealView) {
                 return 0.3f;
             }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 9f7da6c..5bef845 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -24,7 +24,9 @@
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -274,5 +276,19 @@
     public boolean shouldUseLowResIcon() {
         return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
     }
+
+    public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
+        final ShortcutInfo shortcut = new ShortcutInfo();
+        shortcut.user = info.getUser();
+        shortcut.title = info.getLabel().toString();
+        shortcut.contentDescription = UserManagerCompat.getInstance(context)
+                .getBadgedLabelForUser(info.getLabel(), info.getUser());
+        shortcut.customIcon = false;
+        shortcut.intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
+        shortcut.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        shortcut.flags = AppInfo.initFlags(info);
+        shortcut.firstInstallTime = info.getFirstInstallTime();
+        return shortcut;
+    }
 }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9173971..043ecb0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1410,7 +1410,22 @@
         }
 
         private float wallpaperOffsetForCurrentScroll() {
+            // TODO: do different behavior if it's  a live wallpaper?
+            // Don't use up all the wallpaper parallax until you have at least
+            // MIN_PARALLAX_PAGE_SPAN pages
+            int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
+            int parallaxPageSpan;
+            if (mWallpaperIsLiveWallpaper) {
+                parallaxPageSpan = numScrollingPages - 1;
+            } else {
+                parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
+            }
+            mNumPagesForWallpaperParallax = parallaxPageSpan;
+
             if (getChildCount() <= 1) {
+                if (isLayoutRtl()) {
+                    return 1 - 1.0f/mNumPagesForWallpaperParallax;
+                }
                 return 0;
             }
 
@@ -1430,28 +1445,20 @@
             if (scrollRange == 0) {
                 return 0;
             } else {
-                // TODO: do different behavior if it's  a live wallpaper?
                 // Sometimes the left parameter of the pages is animated during a layout transition;
                 // this parameter offsets it to keep the wallpaper from animating as well
                 int adjustedScroll =
                         getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
                 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
                 offset = Math.max(0, offset);
-                // Don't use up all the wallpaper parallax until you have at least
-                // MIN_PARALLAX_PAGE_SPAN pages
-                int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
-                int parallaxPageSpan;
-                if (mWallpaperIsLiveWallpaper) {
-                    parallaxPageSpan = numScrollingPages - 1;
-                } else {
-                    parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
-                }
-                mNumPagesForWallpaperParallax = parallaxPageSpan;
 
                 // On RTL devices, push the wallpaper offset to the right if we don't have enough
                 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
-                int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
-                return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
+                if (!mWallpaperIsLiveWallpaper && numScrollingPages < MIN_PARALLAX_PAGE_SPAN
+                        && isLayoutRtl()) {
+                    return offset * (parallaxPageSpan - numScrollingPages + 1) / parallaxPageSpan;
+                }
+                return offset * (numScrollingPages - 1) / parallaxPageSpan;
             }
         }
 
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
index 90a4d1a..07ef0ef 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -17,7 +17,9 @@
 package com.android.launcher3.compat;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 
 public abstract class LauncherActivityInfoCompat {
@@ -32,4 +34,11 @@
     public abstract ApplicationInfo getApplicationInfo();
     public abstract long getFirstInstallTime();
     public abstract Drawable getBadgedIcon(int density);
+
+    /**
+     * Creates a LauncherActivityInfoCompat for the primary user.
+     */
+    public static LauncherActivityInfoCompat fromResolveInfo(ResolveInfo info, Context context) {
+        return new LauncherActivityInfoCompatV16(context, info);
+    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 1374b4e..a79d946 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -43,4 +43,5 @@
     public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
     public abstract Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user);
     public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
+    public abstract long getUserCreationTime(UserHandleCompat user);
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index 32f972e..ffe698c 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -48,4 +48,9 @@
     public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
         return label;
     }
+
+    @Override
+    public long getUserCreationTime(UserHandleCompat user) {
+        return 0;
+    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 19eeabd..884d6fe 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -18,21 +18,27 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
-import android.os.UserManager;
+
+import com.android.launcher3.LauncherAppState;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 public class UserManagerCompatVL extends UserManagerCompatV17 {
+    private static final String USER_CREATION_TIME_KEY = "user_creation_time_";
+
     private final PackageManager mPm;
+    private final Context mContext;
 
     UserManagerCompatVL(Context context) {
         super(context);
         mPm = context.getPackageManager();
+        mContext = context;
     }
 
     @Override
@@ -61,5 +67,17 @@
         }
         return mPm.getUserBadgedLabel(label, user.getUser());
     }
+
+    @Override
+    public long getUserCreationTime(UserHandleCompat user) {
+        // TODO: Use system API once available.
+        SharedPreferences prefs = mContext.getSharedPreferences(
+                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
+        String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
+        if (!prefs.contains(key)) {
+            prefs.edit().putLong(key, System.currentTimeMillis()).apply();
+        }
+        return prefs.getLong(key, 0);
+    }
 }
 
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 6e80c2f..8a08a4e 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -16,12 +16,13 @@
 
 package com.android.launcher3.util;
 
-import android.content.res.Configuration;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.ViewGroup;
 
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
 
 /**
  * Calculates the next item that a {@link KeyEvent} should change the focus to.
@@ -42,7 +43,7 @@
  */
 public class FocusLogic {
 
-    private static final String TAG = "Focus";
+    private static final String TAG = "FocusLogic";
     private static final boolean DEBUG = false;
 
     // Item and page index related constant used by {@link #handleKeyEvent}.
@@ -51,12 +52,14 @@
     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 PREVIOUS_PAGE_LEFT_COLUMN   = -5;
 
-    public static final int CURRENT_PAGE_FIRST_ITEM     = -5;
-    public static final int CURRENT_PAGE_LAST_ITEM      = -6;
+    public static final int CURRENT_PAGE_FIRST_ITEM     = -6;
+    public static final int CURRENT_PAGE_LAST_ITEM      = -7;
 
-    public static final int NEXT_PAGE_FIRST_ITEM        = -7;
-    public static final int NEXT_PAGE_LEFT_COLUMN       = -8;
+    public static final int NEXT_PAGE_FIRST_ITEM        = -8;
+    public static final int NEXT_PAGE_LEFT_COLUMN       = -9;
+    public static final int NEXT_PAGE_RIGHT_COLUMN      = -10;
 
     // Matrix related constant.
     public static final int EMPTY = -1;
@@ -85,18 +88,24 @@
                     cntX, cntY, iconIdx, pageIndex, pageCount));
         }
 
+        DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid()
+                .getDeviceProfile();
         int newIndex = NOOP;
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/);
-                if (newIndex == NOOP && pageIndex > 0) {
+                if (!profile.isLayoutRtl && newIndex == NOOP && pageIndex > 0) {
                     newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
+                } else if (profile.isLayoutRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
+                    newIndex = NEXT_PAGE_RIGHT_COLUMN;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/);
-                if (newIndex == NOOP && pageIndex < pageCount - 1) {
+                if (!profile.isLayoutRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
                     newIndex = NEXT_PAGE_LEFT_COLUMN;
+                } else if (profile.isLayoutRtl && newIndex == NOOP && pageIndex > 0) {
+                    newIndex = PREVIOUS_PAGE_LEFT_COLUMN;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -140,11 +149,18 @@
      */
     // TODO: get rid of dynamic matrix creation.
     public static int[][] createFullMatrix(int m, int n, boolean incrementOrder) {
+        DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid()
+                .getDeviceProfile();
         int[][] matrix = new int [m][n];
+
         for (int i=0; i < m;i++) {
             for (int j=0; j < n; j++) {
                 if (incrementOrder) {
-                    matrix[i][j] = j * m + i;
+                    if (!profile.isLayoutRtl) {
+                        matrix[i][j] = j * m + i;
+                    } else {
+                        matrix[i][j] = j * m + m - i -1;
+                    }
                 } else {
                     matrix[i][j] = EMPTY;
                 }
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
new file mode 100644
index 0000000..cefa71c
--- /dev/null
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -0,0 +1,277 @@
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Handles addition of app shortcuts for managed profiles.
+ * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
+ */
+public class ManagedProfileHeuristic {
+
+    private static final String TAG = "ManagedProfileHeuristic";
+
+    /**
+     * Maintain a set of packages installed per user.
+     */
+    private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";
+
+    private static final String USER_FOLDER_ID_PREFIX = "user_folder_";
+
+    /**
+     * Duration (in milliseconds) for which app shortcuts will be added to work folder.
+     */
+    private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
+
+    public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
+        if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+            return new ManagedProfileHeuristic(context, user);
+        }
+        return null;
+    }
+
+    private final Context mContext;
+    private final UserHandleCompat mUser;
+    private final LauncherModel mModel;
+
+    private final SharedPreferences mPrefs;
+    private final long mUserSerial;
+    private final long mUserCreationTime;
+    private final String mPackageSetKey;
+
+    private ArrayList<ItemInfo> mHomescreenApps;
+    private ArrayList<ItemInfo> mWorkFolderApps;
+
+    private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
+        mContext = context;
+        mUser = user;
+        mModel = LauncherAppState.getInstance().getModel();
+
+        UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+        mUserSerial = userManager.getSerialNumberForUser(user);
+        mUserCreationTime = userManager.getUserCreationTime(user);
+        mPackageSetKey = INSTALLED_PACKAGES_PREFIX + mUserSerial;
+
+        mPrefs = mContext.getSharedPreferences(LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
+                Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Checks the list of user apps and adds icons for newly installed apps on the homescreen or
+     * workfolder.
+     */
+    public void processUserApps(List<LauncherActivityInfoCompat> apps) {
+        mHomescreenApps = new ArrayList<ItemInfo>();
+        mWorkFolderApps = new ArrayList<ItemInfo>();
+        HashSet<String> packageSet = getPackageSet();
+        boolean newPackageAdded = false;
+
+        for (LauncherActivityInfoCompat info : apps) {
+            String packageName = info.getComponentName().getPackageName();
+            if (!packageSet.contains(packageName)) {
+                packageSet.add(packageName);
+                newPackageAdded = true;
+
+                try {
+                    PackageInfo pkgInfo = mContext.getPackageManager()
+                            .getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+                    markForAddition(info, pkgInfo.firstInstallTime);
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "Unknown package " + packageName, e);
+                }
+            }
+        }
+
+        if (newPackageAdded) {
+            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+            finalizeAdditions();
+        }
+    }
+
+    private void markForAddition(LauncherActivityInfoCompat info, long installTime) {
+        ArrayList<ItemInfo> targetList =
+                (installTime <= mUserCreationTime + AUTO_ADD_TO_FOLDER_DURATION) ?
+                        mWorkFolderApps : mHomescreenApps;
+        targetList.add(ShortcutInfo.fromActivityInfo(info, mContext));
+    }
+
+    /**
+     * Adds and binds shortcuts marked to be added to the work folder.
+     */
+    private void finalizeWorkFolder() {
+        if (mWorkFolderApps.isEmpty()) {
+            return;
+        }
+
+        // Try to get a work folder.
+        String folderIdKey = USER_FOLDER_ID_PREFIX + mUserSerial;
+        if (mPrefs.contains(folderIdKey)) {
+            long folderId = mPrefs.getLong(folderIdKey, 0);
+            final FolderInfo workFolder = mModel.findFolderById(folderId);
+
+            if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
+                // Could not get a work folder. Add all the icons to homescreen.
+                mHomescreenApps.addAll(mWorkFolderApps);
+                return;
+            }
+            saveWorkFolderShortcuts(folderId, workFolder.contents.size());
+
+            final ArrayList<ItemInfo> shortcuts = mWorkFolderApps;
+            // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
+            new MainThreadExecutor().execute(new Runnable() {
+
+                @Override
+                public void run() {
+                    for (ItemInfo info : shortcuts) {
+                        workFolder.add((ShortcutInfo) info);
+                    }
+                }
+            });
+        } else {
+            // Create a new folder.
+            final FolderInfo workFolder = new FolderInfo();
+            workFolder.title = mContext.getText(R.string.work_folder_name);
+            workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
+
+            // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
+            for (ItemInfo info : mWorkFolderApps) {
+                workFolder.add((ShortcutInfo) info);
+            }
+
+            // Add the item to home screen and DB. This also generates an item id synchronously.
+            ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+            itemList.add(workFolder);
+            mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+            mPrefs.edit().putLong(USER_FOLDER_ID_PREFIX + mUserSerial, workFolder.id).apply();
+
+            saveWorkFolderShortcuts(workFolder.id, 0);
+        }
+    }
+
+    /**
+     * Add work folder shortcuts to the DB.
+     */
+    private void saveWorkFolderShortcuts(long workFolderId, int startingRank) {
+        for (ItemInfo info : mWorkFolderApps) {
+            info.rank = startingRank++;
+            LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
+        }
+    }
+
+    /**
+     * Adds and binds all shortcuts marked for addition.
+     */
+    private void finalizeAdditions() {
+        finalizeWorkFolder();
+
+        if (!mHomescreenApps.isEmpty()) {
+            mModel.addAndBindAddedWorkspaceItems(mContext, mHomescreenApps);
+        }
+    }
+
+    /**
+     * Updates the list of installed apps and adds any new icons on homescreen or work folder.
+     */
+    public void processPackageAdd(String[] packages) {
+        mHomescreenApps = new ArrayList<ItemInfo>();
+        mWorkFolderApps = new ArrayList<ItemInfo>();
+        HashSet<String> packageSet = getPackageSet();
+        boolean newPackageAdded = false;
+        long installTime = System.currentTimeMillis();
+        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
+
+        for (String packageName : packages) {
+            if (!packageSet.contains(packageName)) {
+                packageSet.add(packageName);
+                newPackageAdded = true;
+
+                List<LauncherActivityInfoCompat> activities =
+                        launcherApps.getActivityList(packageName, mUser);
+                if (!activities.isEmpty()) {
+                    markForAddition(activities.get(0), installTime);
+                }
+            }
+        }
+
+        if (newPackageAdded) {
+            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+            finalizeAdditions();
+        }
+    }
+
+    /**
+     * Updates the list of installed packages for the user.
+     */
+    public void processPackageRemoved(String[] packages) {
+        HashSet<String> packageSet = getPackageSet();
+        boolean packageRemoved = false;
+
+        for (String packageName : packages) {
+            if (packageSet.remove(packageName)) {
+                packageRemoved = true;
+            }
+        }
+
+        if (packageRemoved) {
+            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private HashSet<String> getPackageSet() {
+        return new HashSet<String>(mPrefs.getStringSet(mPackageSetKey, Collections.EMPTY_SET));
+    }
+
+    /**
+     * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
+     */
+    public static void processAllUsers(List<UserHandleCompat> users, Context context) {
+        if (!Utilities.isLmpOrAbove()) {
+            return;
+        }
+        UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+        HashSet<String> validKeys = new HashSet<String>();
+        for (UserHandleCompat user : users) {
+            addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
+        }
+
+        SharedPreferences prefs = context.getSharedPreferences(
+                LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = prefs.edit();
+        for (String key : prefs.getAll().keySet()) {
+            if (!validKeys.contains(key)) {
+                editor.remove(key);
+            }
+        }
+        editor.apply();
+    }
+
+    private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) {
+        keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial);
+        keysOut.add(USER_FOLDER_ID_PREFIX + userSerial);
+    }
+}
diff --git a/src/com/android/launcher3/widget/PackageItemInfo.java b/src/com/android/launcher3/widget/PackageItemInfo.java
index d7edf22..1a1de55 100644
--- a/src/com/android/launcher3/widget/PackageItemInfo.java
+++ b/src/com/android/launcher3/widget/PackageItemInfo.java
@@ -39,11 +39,12 @@
      */
     public boolean usingLowResIcon;
 
-    public ComponentName componentName;
+    public String packageName;
 
     int flags = 0;
 
-    PackageItemInfo() {
+    PackageItemInfo(String packageName) {
+        this.packageName = packageName;
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index ccd67ce..d10c304 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -47,7 +47,7 @@
  */
 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
 
-    private static final String TAG = "PagedViewWidget";
+    private static final String TAG = "WidgetCell";
     private static final boolean DEBUG = false;
 
     // Temporary preset width and height of the image to keep them aligned.
@@ -123,6 +123,15 @@
         deletePreview(false);
     }
 
+    public void reset() {
+        ImageView image = (ImageView) findViewById(R.id.widget_preview);
+        final TextView name = (TextView) findViewById(R.id.widget_name);
+        final TextView dims = (TextView) findViewById(R.id.widget_dims);
+        image.setImageDrawable(null);
+        name.setText(null);
+        dims.setText(null);
+    }
+
     public void deletePreview(boolean recycleImage) {
         if (recycleImage) {
             final ImageView image = (ImageView) findViewById(R.id.widget_preview);
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index d0d1e60..afeb2d3 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -88,14 +88,13 @@
 
     @Override
     public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
-        String packageName = mWidgetsModel.getPackageName(pos);
-        List<Object> infoList = mWidgetsModel.getSortedWidgets(packageName);
+        List<Object> infoList = mWidgetsModel.getSortedWidgets(pos);
 
         ViewGroup row = ((ViewGroup) holder.getContent().findViewById(R.id.widgets_cell_list));
         if (DEBUG) {
             Log.d(TAG, String.format(
-                    "onBindViewHolder [pos=%d, packageName=%s, widget#=%d, row.getChildCount=%d]",
-                    pos, packageName, infoList.size(), row.getChildCount()));
+                    "onBindViewHolder [pos=%d, widget#=%d, row.getChildCount=%d]",
+                    pos, infoList.size(), row.getChildCount()));
         }
 
         // Add more views.
@@ -120,10 +119,11 @@
         }
 
         // Bind the views in the application info section.
-        PackageItemInfo infoOut = mWidgetsModel.getPackageItemInfo(packageName);
+        PackageItemInfo infoOut = mWidgetsModel.getPackageItemInfo(pos);
         if (infoOut.usingLowResIcon) {
-            mIconCache.getTitleAndIconForApp(packageName, UserHandleCompat.myUserHandle(),
-                false /* useLowResIcon */, infoOut);
+            // TODO(hyunyoungs): call this in none UI thread in the same way as BubbleTextView.
+            mIconCache.getTitleAndIconForApp(infoOut.packageName,
+                    UserHandleCompat.myUserHandle(), false /* useLowResIcon */, infoOut);
         }
         ((TextView) holder.getContent().findViewById(R.id.section)).setText(infoOut.title);
         ImageView iv = (ImageView) holder.getContent().findViewById(R.id.section_image);
@@ -132,7 +132,8 @@
         // Bind the view in the widget horizontal tray region.
         for (int i=0; i < infoList.size(); i++) {
             WidgetCell widget = (WidgetCell) row.getChildAt(i);
-            if (getWidgetPreviewLoader() == null || widget == null) {
+            widget.reset();
+            if (getWidgetPreviewLoader() == null) {
                 return;
             }
             if (infoList.get(i) instanceof LauncherAppWidgetProviderInfo) {
@@ -149,7 +150,6 @@
             widget.setVisibility(View.VISIBLE);
             widget.ensurePreview();
         }
-        // TODO(hyunyoungs): Draw the scrollable indicator.
     }
 
     @Override
@@ -174,15 +174,4 @@
         }
         return mWidgetPreviewLoader;
     }
-
-    /**
-     * TODO(hyunyoungs): this is temporary. Figure out the width of each widget cell
-     * and then check if the total sum is longer than the parent width.
-     */
-    private void addScrollableIndicator(int contentSize, ViewGroup parent) {
-        if (contentSize > 2) {
-            ViewGroup indicator = (ViewGroup) parent.findViewById(R.id.scrollable_indicator);
-            indicator.setVisibility(View.VISIBLE);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsModel.java b/src/com/android/launcher3/widget/WidgetsModel.java
index c400d63..71a7b94 100644
--- a/src/com/android/launcher3/widget/WidgetsModel.java
+++ b/src/com/android/launcher3/widget/WidgetsModel.java
@@ -31,12 +31,10 @@
     private static final boolean DEBUG = false;
 
     /* List of packages that is tracked by this model. */
-    private List<String> mPackageNames = new ArrayList<>();
-
-    private Map<String, PackageItemInfo> mPackageItemInfoList = new HashMap<>();
+    private List<PackageItemInfo> mPackageItemInfos = new ArrayList<>();
 
     /* Map of widgets and shortcuts that are tracked per package. */
-    private Map<String, ArrayList<Object>> mWidgetsList = new HashMap<>();
+    private Map<PackageItemInfo, ArrayList<Object>> mWidgetsList = new HashMap<>();
 
     /* Notifies the adapter when data changes. */
     private RecyclerView.Adapter mAdapter;
@@ -53,20 +51,16 @@
 
     // Access methods that may be deleted if the private fields are made package-private.
     public int getPackageSize() {
-        return mPackageNames.size();
+        return mPackageItemInfos.size();
     }
 
     // Access methods that may be deleted if the private fields are made package-private.
-    public String getPackageName(int pos) {
-        return mPackageNames.get(pos);
+    public PackageItemInfo getPackageItemInfo(int pos) {
+        return mPackageItemInfos.get(pos);
     }
 
-    public PackageItemInfo getPackageItemInfo(String packageName) {
-        return mPackageItemInfoList.get(packageName);
-    }
-
-    public List<Object> getSortedWidgets(String packageName) {
-        return mWidgetsList.get(packageName);
+    public List<Object> getSortedWidgets(int pos) {
+        return mWidgetsList.get(mPackageItemInfos.get(pos));
     }
 
     public void addWidgetsAndShortcuts(ArrayList<Object> widgetsShortcuts, PackageManager pm) {
@@ -74,9 +68,13 @@
             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + widgetsShortcuts.size());
         }
 
+        // Temporary list for {@link PackageItemInfos} to avoid having to go through
+        // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
+        HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
+
         // clear the lists.
-        mPackageNames.clear();
         mWidgetsList.clear();
+        mPackageItemInfos.clear();
 
         // add and update.
         for (Object o: widgetsShortcuts) {
@@ -90,47 +88,41 @@
             } else {
                 Log.e(TAG, String.format("addWidgetsAndShortcuts, nothing added for class=%s",
                         o.getClass().toString()));
-                
             }
 
-            ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(packageName);
+            PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
+            ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(pInfo);
             if (widgetsShortcutsList != null) {
                 widgetsShortcutsList.add(o);
             } else {
                 widgetsShortcutsList = new ArrayList<Object>();
                 widgetsShortcutsList.add(o);
-                mWidgetsList.put(packageName, widgetsShortcutsList);
-                mPackageNames.add(packageName);
-            }
-        }
-        for (String packageName: mPackageNames) {
-            PackageItemInfo pInfo = mPackageItemInfoList.get(packageName);
-            if (pInfo == null) {
-                pInfo = new PackageItemInfo();
+
+                pInfo = new PackageItemInfo(packageName);
                 mIconCache.getTitleAndIconForApp(packageName, UserHandleCompat.myUserHandle(),
                         true /* useLowResIcon */, pInfo);
-                mPackageItemInfoList.put(packageName, pInfo);
+                mWidgetsList.put(pInfo, widgetsShortcutsList);
+                tmpPackageItemInfos.put(packageName,  pInfo);
+                mPackageItemInfos.add(pInfo);
             }
         }
 
         // sort.
-        sortPackageList();
-        for (String packageName: mPackageNames) {
-            Collections.sort(mWidgetsList.get(packageName), mWidgetAndShortcutNameComparator);
+        sortPackageItemInfos();
+        for (PackageItemInfo p: mPackageItemInfos) {
+            Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator);
         }
 
         // notify.
         mAdapter.notifyDataSetChanged();
     }
 
-    private void sortPackageList() {
-        Collections.sort(mPackageNames, new Comparator<String>() {
+    private void sortPackageItemInfos() {
+        Collections.sort(mPackageItemInfos, new Comparator<PackageItemInfo>() {
             @Override
-            public int compare(String lhs, String rhs) {
-                String lhsTitle = mPackageItemInfoList.get(lhs).title.toString();
-                String rhsTitle = mPackageItemInfoList.get(rhs).title.toString();
-                return lhsTitle.compareTo(rhsTitle);
+            public int compare(PackageItemInfo lhs, PackageItemInfo rhs) {
+                return lhs.title.toString().compareTo(rhs.title.toString());
             }
         });
     }
-}
+}
\ No newline at end of file