Merge "Import translations. DO NOT MERGE" into ub-launcher3-calgary
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index f711274..4576e4d 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -57,12 +57,10 @@
 
         <!-- Keep these behind the workspace so that they are not visible when
              we go into AllApps -->
-        <include
+        <com.android.launcher3.pageindicators.PageIndicatorLine
             android:id="@+id/page_indicator"
-            layout="@layout/page_indicator"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_horizontal" />
+            android:layout_width="match_parent"
+            android:layout_height="1dp" />
 
         <include
             android:id="@+id/app_info_drop_target_bar"
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 780d645..0f755d8 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -64,11 +64,10 @@
 
         <!-- Keep these behind the workspace so that they are not visible when
              we go into AllApps -->
-        <include android:id="@+id/page_indicator"
-            layout="@layout/page_indicator"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_horizontal" />
+        <com.android.launcher3.pageindicators.PageIndicatorLine
+            android:id="@+id/page_indicator"
+            android:layout_width="match_parent"
+            android:layout_height="1dp" />
 
         <include layout="@layout/widgets_view"
             android:id="@+id/widgets_view"
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
index 68fe3dd..5655159 100644
--- a/res/layout/page_indicator.xml
+++ b/res/layout/page_indicator.xml
@@ -13,9 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.PageIndicator
+<com.android.launcher3.pageindicators.PageIndicatorDots
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:animateLayoutChanges="true"
     launcher:windowSize="@integer/config_maxNumberOfPageIndicatorsToShow">
-</com.android.launcher3.PageIndicator>
+</com.android.launcher3.pageindicators.PageIndicatorDots>
diff --git a/res/layout/page_indicator_marker.xml b/res/layout/page_indicator_marker.xml
index 564a958..357a761 100644
--- a/res/layout/page_indicator_marker.xml
+++ b/res/layout/page_indicator_marker.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.PageIndicatorMarker
+<com.android.launcher3.pageindicators.PageIndicatorDot
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="12dp"
@@ -36,4 +36,4 @@
         android:scaleX="0.5"
         android:scaleY="0.5"
         />
-</com.android.launcher3.PageIndicatorMarker>
+</com.android.launcher3.pageindicators.PageIndicatorDot>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 6d8efaa..2eb9b91 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -32,7 +32,7 @@
     </declare-styleable>
 
     <!-- Page Indicator specific attributes. -->
-    <declare-styleable name="PageIndicator">
+    <declare-styleable name="PageIndicatorDots">
         <attr name="windowSize" format="integer"  />
     </declare-styleable>
 
diff --git a/res/values/config.xml b/res/values/config.xml
index d689f1b..88aa7fd 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -75,6 +75,9 @@
     <!-- Name of an icon provider class. -->
     <string name="icon_provider_class" translatable="false"></string>
 
+    <!-- Package name of the default wallpaper picker. -->
+    <string name="wallpaper_picker_package" translatable="false"></string>
+
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 813953e..4c9d0b5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -169,7 +169,11 @@
 
     <!-- Strings for settings -->
     <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] -->
-    <string name="allow_rotation_title">Allow rotation</string>
+    <string name="allow_rotation_title">Allow homescreen rotation</string>
+    <!-- Text explaining when the home screen will get rotated. [CHAR LIMIT=100] -->
+    <string name="allow_rotation_desc">When device is rotated</string>
+    <!-- Text explaining that rotation is disabled in Display settings. 'Display' refers to the Display section in system settings [CHAR LIMIT=100] -->
+    <string name="allow_rotation_blocked_desc">Current Display setting doesn\'t permit rotation</string>
 
     <!-- Label on an icon that references an uninstalled package, for which we have no information about when it might be installed. [CHAR_LIMIT=15] -->
     <string name="package_state_unknown">Unknown</string>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9ab5611..8d11aaa 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -537,7 +537,6 @@
                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
                 lp.width = LayoutParams.WRAP_CONTENT;
-                lp.height = LayoutParams.WRAP_CONTENT;
                 lp.bottomMargin = hotseatBarHeightPx;
                 pageIndicator.setLayoutParams(lp);
             }
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 6c9d969..9a99852 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -109,10 +109,8 @@
         listeners.add(listener);
     }
 
-    void removeListener(FolderListener listener) {
-        if (listeners.contains(listener)) {
-            listeners.remove(listener);
-        }
+    public void removeListener(FolderListener listener) {
+        listeners.remove(listener);
     }
 
     public void itemsChanged(boolean animate) {
@@ -121,12 +119,6 @@
         }
     }
 
-    @Override
-    void unbind() {
-        super.unbind();
-        listeners.clear();
-    }
-
     public interface FolderListener {
         public void onAdd(ShortcutInfo item);
         public void onRemove(ShortcutInfo item);
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 191becf..259370c 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -18,7 +18,9 @@
 
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.widget.Toast;
@@ -92,7 +94,12 @@
     }
 
     public static boolean supportsDrop(ItemInfo info) {
-        return info instanceof AppInfo || info instanceof ShortcutInfo
-                || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo;
+        // Only show the App Info drop target if developer settings are enabled.
+        ContentResolver resolver = LauncherAppState.getInstance().getContext().getContentResolver();
+        boolean developmentSettingsEnabled = Settings.Global.getInt(resolver,
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
+        return developmentSettingsEnabled
+                && (info instanceof AppInfo || info instanceof ShortcutInfo
+                || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo);
     }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 1ba09e1..286a7f1 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -183,15 +183,6 @@
         }
     }
 
-    /**
-     * It is very important that sub-classes implement this if they contain any references
-     * to the activity (anything in the view hierarchy etc.). If not, leaks can result since
-     * ItemInfo objects persist across rotation and can hence leak by holding stale references
-     * to the old view hierarchy / activity.
-     */
-    void unbind() {
-    }
-
     @Override
     public String toString() {
         return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index eacf72a..a5ebb52 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -58,6 +58,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
@@ -112,9 +113,12 @@
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.pageindicators.PageIndicatorLine;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -198,6 +202,7 @@
     private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
 
     public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
+    private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
 
     /** The different states that Launcher can be in. */
     enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
@@ -223,7 +228,7 @@
 
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
-    private View mPageIndicators;
+    private PageIndicatorLine mPageIndicator;
     @Thunk DragLayer mDragLayer;
     private DragController mDragController;
 
@@ -500,6 +505,7 @@
         if (mExtractedColors != null && Utilities.isNycOrAbove()) {
             mExtractedColors.load(this);
             mHotseat.updateColor(mExtractedColors, !mPaused);
+            mPageIndicator.updateColor(mExtractedColors);
         }
     }
 
@@ -1326,7 +1332,7 @@
         mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
-        mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
+        mPageIndicator = (PageIndicatorLine) mDragLayer.findViewById(R.id.page_indicator);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -1567,31 +1573,28 @@
         if (!mRestoring) {
             if (hostView == null) {
                 // Perform actual inflation because we're live
-                launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
-                        appWidgetInfo);
-            } else {
-                // The AppWidgetHostView has already been inflated and instantiated
-                launcherInfo.hostView = hostView;
+                hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
             }
-            launcherInfo.hostView.setVisibility(View.VISIBLE);
-            addAppWidgetToWorkspace(launcherInfo, appWidgetInfo, isWorkspaceLocked());
+            hostView.setVisibility(View.VISIBLE);
+            addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
         }
         resetAddInfo();
     }
 
-    private void addAppWidgetToWorkspace(LauncherAppWidgetInfo item,
+    private void addAppWidgetToWorkspace(
+            AppWidgetHostView hostView, LauncherAppWidgetInfo item,
             LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
-        item.hostView.setTag(item);
-        item.onBindAppWidget(this);
+        hostView.setTag(item);
+        item.onBindAppWidget(this, hostView);
 
-        item.hostView.setFocusable(true);
-        item.hostView.setOnFocusChangeListener(mFocusHandler);
+        hostView.setFocusable(true);
+        hostView.setOnFocusChangeListener(mFocusHandler);
 
-        mWorkspace.addInScreen(item.hostView, item.container, item.screenId,
+        mWorkspace.addInScreen(hostView, item.container, item.screenId,
                 item.cellX, item.cellY, item.spanX, item.spanY, insert);
 
         if (!item.isCustomWidget()) {
-            addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
+            addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
         }
     }
 
@@ -1969,6 +1972,7 @@
         mHandler.removeMessages(ADVANCE_MSG);
         mHandler.removeMessages(0);
         mWorkspace.removeCallbacks(mBuildLayersRunnable);
+        mWorkspace.removeFolderListeners();
 
         // Stop callbacks from LauncherModel
         // It's possible to receive onDestroy after a new Launcher activity has
@@ -2367,6 +2371,9 @@
             }
         } else if (itemInfo instanceof FolderInfo) {
             final FolderInfo folderInfo = (FolderInfo) itemInfo;
+            if (v instanceof FolderIcon) {
+                ((FolderIcon) v).removeListeners();
+            }
             mWorkspace.removeWorkspaceItem(v);
             if (deleteFromDb) {
                 LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
@@ -2374,8 +2381,7 @@
         } else if (itemInfo instanceof LauncherAppWidgetInfo) {
             final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
             mWorkspace.removeWorkspaceItem(v);
-            removeWidgetToAutoAdvance(widgetInfo.hostView);
-            widgetInfo.hostView = null;
+            removeWidgetToAutoAdvance(v);
             if (deleteFromDb) {
                 deleteWidgetInfo(widgetInfo);
             }
@@ -2719,10 +2725,17 @@
             return;
         }
 
-        if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
+        String pickerPackage = getString(R.string.wallpaper_picker_package);
+        if (TextUtils.isEmpty(pickerPackage)) {
+            pickerPackage =  PackageManagerHelper.getWallpaperPickerPackage(getPackageManager());
+        }
+
         int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
         float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
-        // TODO: Start the system wallpaper picker
+        startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER)
+                .setPackage(pickerPackage)
+                .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset),
+                REQUEST_PICK_WALLPAPER);
     }
 
     /**
@@ -3860,10 +3873,9 @@
     private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
         PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
         view.updateIcon(mIconCache);
-        item.hostView = view;
-        item.hostView.updateAppWidget(null);
-        item.hostView.setOnClickListener(this);
-        addAppWidgetToWorkspace(item, null, false);
+        view.updateAppWidget(null);
+        view.setOnClickListener(this);
+        addAppWidgetToWorkspace(view, item, null, false);
         mWorkspace.requestLayout();
     }
 
@@ -3976,18 +3988,17 @@
                 return;
             }
 
-            item.hostView = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
             item.minSpanX = appWidgetInfo.minSpanX;
             item.minSpanY = appWidgetInfo.minSpanY;
-            addAppWidgetToWorkspace(item, appWidgetInfo, false);
+            addAppWidgetToWorkspace(
+                    mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
+                    item, appWidgetInfo, false);
         } else {
-            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
-                    mIsSafeModeEnabled);
+            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
             view.updateIcon(mIconCache);
-            item.hostView = view;
-            item.hostView.updateAppWidget(null);
-            item.hostView.setOnClickListener(this);
-            addAppWidgetToWorkspace(item, null, false);
+            view.updateAppWidget(null);
+            view.setOnClickListener(this);
+            addAppWidgetToWorkspace(view, item, null, false);
         }
         mWorkspace.requestLayout();
 
@@ -4379,21 +4390,6 @@
         }
     }
 
-    protected boolean isLauncherPreinstalled() {
-        PackageManager pm = getPackageManager();
-        try {
-            ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
-            if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                return true;
-            } else {
-                return false;
-            }
-        } catch (NameNotFoundException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
-
     /**
      * To be overridden by subclasses to indicate that there is an activity to launch
      * before showing the standard launcher experience.
@@ -4513,7 +4509,7 @@
         LauncherClings launcherClings = new LauncherClings(this);
         if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
             mClings = launcherClings;
-            if (mModel.canMigrateFromOldLauncherDb(this)) {
+            if (canMigrateFromOldLauncherDb()) {
                 launcherClings.showMigrationCling();
             } else {
                 launcherClings.showLongPressCling(true);
@@ -4521,10 +4517,23 @@
         }
     }
 
+    private boolean canMigrateFromOldLauncherDb() {
+        // Return true if launcher was not preinstalled and and old content provider exists.
+        return ((getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) &&
+                providerExists(MIGRATE_AUTHORITY) &&
+                providerExists(Uri.parse(getString(R.string.old_launcher_provider_uri)).getAuthority());
+
+    }
+
+    private boolean providerExists(String authority) {
+        return getPackageManager().resolveContentProvider(authority, 0) != null;
+    }
+
+
     void showWorkspaceSearchAndHotseat() {
         if (mWorkspace != null) mWorkspace.setAlpha(1f);
         if (mHotseat != null) mHotseat.setAlpha(1f);
-        if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
+        if (mPageIndicator != null) mPageIndicator.setAlpha(1f);
         if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
                 SearchDropTargetBar.State.SEARCH_BAR, 0);
     }
@@ -4532,7 +4541,7 @@
     void hideWorkspaceSearchAndHotseat() {
         if (mWorkspace != null) mWorkspace.setAlpha(0f);
         if (mHotseat != null) mHotseat.setAlpha(0f);
-        if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
+        if (mPageIndicator != null) mPageIndicator.setAlpha(0f);
         if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
                 SearchDropTargetBar.State.INVISIBLE, 0);
     }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 55edf45..42d6468 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -80,12 +80,6 @@
 
     private boolean mHasNotifiedInitialWidgetSizeChanged;
 
-    /**
-     * View that holds this widget after it's been created.  This view isn't created
-     * until Launcher knows it's needed.
-     */
-    AppWidgetHostView hostView = null;
-
     LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
         if (appWidgetId == CUSTOM_WIDGET_ID) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
@@ -121,25 +115,18 @@
      * When we bind the widget, we should notify the widget that the size has changed if we have not
      * done so already (only really for default workspace widgets).
      */
-    void onBindAppWidget(Launcher launcher) {
+    void onBindAppWidget(Launcher launcher, AppWidgetHostView hostView) {
         if (!mHasNotifiedInitialWidgetSizeChanged) {
             AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY);
             mHasNotifiedInitialWidgetSizeChanged = true;
         }
     }
 
-
     @Override
     public String toString() {
         return "AppWidget(id=" + Integer.toString(appWidgetId) + ")";
     }
 
-    @Override
-    void unbind() {
-        super.unbind();
-        hostView = null;
-    }
-
     public final boolean isWidgetIdValid() {
         return (restoreStatus & FLAG_ID_NOT_VALID) == 0;
     }
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 9b8e894..1cfa3f7 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -22,7 +22,9 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserManager;
@@ -48,9 +50,6 @@
     private static final int SHOW_CLING_DURATION = 250;
     private static final int DISMISS_CLING_DURATION = 200;
 
-    // New Secure Setting in L
-    private static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
-
     @Thunk Launcher mLauncher;
     private LayoutInflater mInflater;
     @Thunk boolean mIsVisible;
@@ -262,8 +261,8 @@
                 return false;
             }
         }
-        if (Settings.Secure.getInt(mLauncher.getContentResolver(), SKIP_FIRST_USE_HINTS, 0)
-                == 1) {
+        if (Settings.Secure.getInt(mLauncher.getContentResolver(),
+                Settings.Secure.SKIP_FIRST_USE_HINTS, 0) == 1) {
             return false;
         }
         return true;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index eaeb1ac..fec96ca 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -28,7 +28,6 @@
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
@@ -56,15 +55,16 @@
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
-import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.StringFilter;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -104,8 +104,6 @@
     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
     private static final long INVALID_SCREEN_ID = -1L;
 
-    private final boolean mOldContentProviderExists;
-
     @Thunk final LauncherAppState mApp;
     @Thunk final Object mLock = new Object();
     @Thunk DeferredHandler mHandler = new DeferredHandler();
@@ -113,8 +111,6 @@
     @Thunk boolean mIsLoaderTaskRunning;
     @Thunk boolean mHasLoaderCompletedOnce;
 
-    private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
-
     @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
     static {
         sWorkerThread.start();
@@ -215,25 +211,6 @@
 
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         Context context = app.getContext();
-
-        String oldProvider = context.getString(R.string.old_launcher_provider_uri);
-        // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
-        // resource string.
-        String redirectAuthority = Uri.parse(oldProvider).getAuthority();
-        ProviderInfo providerInfo =
-                context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
-        ProviderInfo redirectProvider =
-                context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
-
-        Log.d(TAG, "Old launcher provider: " + oldProvider);
-        mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
-
-        if (mOldContentProviderExists) {
-            Log.d(TAG, "Old launcher provider exists.");
-        } else {
-            Log.d(TAG, "Old launcher provider does not exist.");
-        }
-
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
@@ -265,10 +242,6 @@
         }
     }
 
-    boolean canMigrateFromOldLauncherDb(Launcher launcher) {
-        return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
-    }
-
     public void setPackageState(final PackageInstallInfo installInfo) {
         Runnable updateRunnable = new Runnable() {
 
@@ -578,38 +551,6 @@
         runOnWorkerThread(r);
     }
 
-    private void unbindItemInfosAndClearQueuedBindRunnables() {
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
-                    "main thread");
-        }
-
-        // Remove any queued UI runnables
-        mHandler.cancelAll();
-        // Unbind all the workspace items
-        unbindWorkspaceItemsOnMainThread();
-    }
-
-    /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
-    void unbindWorkspaceItemsOnMainThread() {
-        // Ensure that we don't use the same workspace items data structure on the main thread
-        // by making a copy of workspace items first.
-        final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
-        synchronized (sBgLock) {
-            tmpItems.addAll(sBgWorkspaceItems);
-            tmpItems.addAll(sBgAppWidgets);
-        }
-        Runnable r = new Runnable() {
-                @Override
-                public void run() {
-                   for (ItemInfo item : tmpItems) {
-                       item.unbind();
-                   }
-                }
-            };
-        runOnMainThread(r);
-    }
-
     /**
      * Adds an item to the DB if it was not created previously, or move it to a new
      * <container, screen, cellX, cellY>
@@ -1137,10 +1078,10 @@
      */
     public void initialize(Callbacks callbacks) {
         synchronized (mLock) {
-            // Disconnect any of the callbacks and drawables associated with ItemInfos on the
-            // workspace to prevent leaking Launcher activities on orientation change.
-            unbindItemInfosAndClearQueuedBindRunnables();
-            mCallbacks = new WeakReference<Callbacks>(callbacks);
+            Preconditions.assertUIThread();
+            // Remove any queued UI runnables
+            mHandler.cancelAll();
+            mCallbacks = new WeakReference<>(callbacks);
         }
     }
 
@@ -2482,10 +2423,6 @@
             final long currentScreenId = currentScreen < 0
                     ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
 
-            // Load all the items that are on the current page first (and in the process, unbind
-            // all the existing workspace items before we call startBinding() below.
-            unbindWorkspaceItemsOnMainThread();
-
             // Separate the items that are on the current screen, and all the other remaining items
             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 5cc5aa6..7ebee31 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -323,28 +323,6 @@
         createDbIfNotExists();
 
         switch (method) {
-            case LauncherSettings.Settings.METHOD_GET_BOOLEAN: {
-                Bundle result = new Bundle();
-                if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(arg)) {
-                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                            Utilities.isAllowRotationPrefEnabled(getContext()));
-                } else {
-                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                            Utilities.getPrefs(getContext()).getBoolean(arg, extras.getBoolean(
-                                    LauncherSettings.Settings.EXTRA_DEFAULT_VALUE)));
-                }
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_SET_BOOLEAN: {
-                final boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
-                Utilities.getPrefs(getContext()).edit().putBoolean(arg, value).apply();
-                if (extras.getBoolean(LauncherSettings.Settings.NOTIFY_BACKUP)) {
-                    LauncherBackupAgentHelper.dataChanged(getContext());
-                }
-                Bundle result = new Bundle();
-                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, value);
-                return result;
-            }
             case LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID: {
                 String extractedColors = extras.getString(
                         LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 095670d..45a87cc 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -277,8 +277,6 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" +
                 ProviderConfig.AUTHORITY + "/settings");
 
-        public static final String METHOD_GET_BOOLEAN = "get_boolean_setting";
-        public static final String METHOD_SET_BOOLEAN = "set_boolean_setting";
         public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag";
 
         public static final String METHOD_DELETE_EMPTY_FOLDERS = "delete_empty_folders";
@@ -301,9 +299,6 @@
         public static final String EXTRA_WALLPAPER_ID = "extra_wallpaperId";
 
         public static final String EXTRA_VALUE = "value";
-        public static final String EXTRA_DEFAULT_VALUE = "default_value";
-        // Extra for set_boolean method to also notify the backup manager of the change.
-        public static final String NOTIFY_BACKUP = "notify_backup";
 
         public static Bundle call(ContentResolver cr, String method) {
             return cr.call(CONTENT_URI, method, null, null);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e1cb082..02e894b 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -49,8 +49,11 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
+
+import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.LauncherEdgeEffect;
 import com.android.launcher3.util.Thunk;
+
 import java.util.ArrayList;
 
 /**
@@ -254,8 +257,7 @@
             mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId);
             mPageIndicator.removeAllMarkers(true);
 
-            ArrayList<PageIndicator.PageMarkerResources> markers =
-                    new ArrayList<PageIndicator.PageMarkerResources>();
+            ArrayList<PageIndicator.PageMarkerResources> markers = new ArrayList<>();
             for (int i = 0; i < getChildCount(); ++i) {
                 markers.add(getPageIndicatorMarker(i));
             }
@@ -264,9 +266,9 @@
 
             OnClickListener listener = getPageIndicatorClickListener();
             if (listener != null) {
-                mPageIndicator.setOnClickListener(listener);
+                mPageIndicator.getView().setOnClickListener(listener);
             }
-            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+            mPageIndicator.getView().setContentDescription(getPageIndicatorDescription());
         }
     }
 
@@ -355,7 +357,8 @@
         return mPageIndicator;
     }
     protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
-        return new PageIndicator.PageMarkerResources();
+        return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
+                R.drawable.ic_pageindicator_default);
     }
 
     /**
@@ -430,7 +433,7 @@
                     Math.min(newPage, mTempVisiblePagesRange[1]));
         }
         // Ensure that it is clamped by the actual set of children in all cases
-        validatedPage = Utilities.boundInRange(validatedPage, 0, getPageCount() - 1);
+        validatedPage = Utilities.boundToRange(validatedPage, 0, getPageCount() - 1);
         return validatedPage;
     }
 
@@ -475,7 +478,7 @@
     private void updatePageIndicator() {
         // Update the page indicator (when we aren't reordering)
         if (mPageIndicator != null) {
-            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+            mPageIndicator.getView().setContentDescription(getPageIndicatorDescription());
             if (!isReordering(false)) {
                 mPageIndicator.setActiveMarker(getNextPage());
             }
@@ -931,12 +934,16 @@
     }
 
     @Thunk void updateMaxScrollX() {
+        mMaxScrollX = computeMaxScrollX();
+    }
+
+    protected int computeMaxScrollX() {
         int childCount = getChildCount();
         if (childCount > 0) {
             final int index = mIsRtl ? 0 : childCount - 1;
-            mMaxScrollX = getScrollForPage(index);
+            return getScrollForPage(index);
         } else {
-            mMaxScrollX = 0;
+            return 0;
         }
     }
 
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index c8c8fa4..477b92c 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -194,7 +194,7 @@
         animateShowHideView(INDEX_HOTSEAT, mLauncher.getHotseat(), show);
         if (mWorkspace.getPageIndicator() != null) {
             // There aren't page indicators in landscape mode on phones, hence the null check.
-            animateShowHideView(INDEX_PAGE_INDICATOR, mWorkspace.getPageIndicator(), show);
+            animateShowHideView(INDEX_PAGE_INDICATOR, mWorkspace.getPageIndicator().getView(), show);
         }
     }
 
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 4135d5b..5ef6dd5 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -17,13 +17,14 @@
 package com.android.launcher3;
 
 import android.app.Activity;
-import android.os.AsyncTask;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
 import android.os.Bundle;
+import android.os.Handler;
 import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.preference.TwoStatePreference;
+import android.provider.Settings;
+import android.provider.Settings.System;
 
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -42,56 +43,70 @@
     /**
      * This fragment shows the launcher preferences.
      */
-    public static class LauncherSettingsFragment extends PreferenceFragment
-            implements OnPreferenceChangeListener {
+    public static class LauncherSettingsFragment extends PreferenceFragment {
+
+        private SystemDisplayRotationLockObserver mRotationLockObserver;
+
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
+            getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
             addPreferencesFromResource(R.xml.launcher_preferences);
 
-            PreferenceScreen screen = getPreferenceScreen();
-            for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
-                Preference pref = screen.getPreference(i);
-                if (pref instanceof TwoStatePreference) {
-                    setBooleanPrefUsingContentProvider((TwoStatePreference) pref);
-                }
+            // Setup allow rotation preference
+            Preference rotationPref = findPreference(Utilities.ALLOW_ROTATION_PREFERENCE_KEY);
+            if (getResources().getBoolean(R.bool.allow_rotation)) {
+                // Launcher supports rotation by default. No need to show this setting.
+                getPreferenceScreen().removePreference(rotationPref);
+            } else {
+                ContentResolver resolver = getContext().getContentResolver();
+                mRotationLockObserver = new SystemDisplayRotationLockObserver(rotationPref, resolver);
+
+                // Register a content observer to listen for system setting changes while
+                // this UI is active.
+                resolver.registerContentObserver(
+                        Settings.System.getUriFor(System.ACCELEROMETER_ROTATION),
+                        false, mRotationLockObserver);
+
+                // Initialize the UI once
+                mRotationLockObserver.onChange(true);
+                rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getContext()));
             }
         }
 
         @Override
-        public boolean onPreferenceChange(Preference preference, Object newValue) {
-            Bundle extras = new Bundle();
-            extras.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, (Boolean) newValue);
-            getActivity().getContentResolver().call(
-                    LauncherSettings.Settings.CONTENT_URI,
-                    LauncherSettings.Settings.METHOD_SET_BOOLEAN,
-                    preference.getKey(), extras);
-            return true;
+        public void onDestroy() {
+            if (mRotationLockObserver != null) {
+                getContext().getContentResolver().unregisterContentObserver(mRotationLockObserver);
+                mRotationLockObserver = null;
+            }
+            super.onDestroy();
+        }
+    }
+
+    /**
+     * Content observer which listens for system auto-rotate setting changes, and enables/disables
+     * the launcher rotation setting accordingly.
+     */
+    private static class SystemDisplayRotationLockObserver extends ContentObserver {
+
+        private final Preference mRotationPref;
+        private final ContentResolver mResolver;
+
+        public SystemDisplayRotationLockObserver(
+                Preference rotationPref, ContentResolver resolver) {
+            super(new Handler());
+            mRotationPref = rotationPref;
+            mResolver = resolver;
         }
 
-        private void setBooleanPrefUsingContentProvider(final TwoStatePreference pref) {
-            pref.setPersistent(false);
-            pref.setEnabled(false);
-
-            new AsyncTask<Void, Void, Boolean>() {
-                @Override
-                protected Boolean doInBackground(Void... params) {
-                    Bundle extras = new Bundle();
-                    extras.putBoolean(LauncherSettings.Settings.EXTRA_DEFAULT_VALUE, true);
-                    Bundle value = pref.getContext().getContentResolver().call(
-                            LauncherSettings.Settings.CONTENT_URI,
-                            LauncherSettings.Settings.METHOD_GET_BOOLEAN,
-                            pref.getKey(), extras);
-                    return value.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
-                }
-
-                @Override
-                protected void onPostExecute(Boolean aBoolean) {
-                    pref.setChecked(aBoolean);
-                    pref.setEnabled(true);
-                    pref.setOnPreferenceChangeListener(LauncherSettingsFragment.this);
-                }
-            }.execute();
+        @Override
+        public void onChange(boolean selfChange) {
+            boolean enabled = Settings.System.getInt(mResolver,
+                    Settings.System.ACCELEROMETER_ROTATION, 1) == 1;
+            mRotationPref.setEnabled(enabled);
+            mRotationPref.setSummary(enabled
+                    ? R.string.allow_rotation_desc : R.string.allow_rotation_blocked_desc);
         }
     }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index c5f601d..e3b959b 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -142,7 +142,11 @@
     }
 
     public static boolean isAllowRotationPrefEnabled(Context context) {
-        boolean allowRotationPref = false;
+        return getPrefs(context).getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+                getAllowRotationDefaultValue(context));
+    }
+
+    public static boolean getAllowRotationDefaultValue(Context context) {
         if (isNycOrAbove()) {
             // If the device was scaled, used the original dimensions to determine if rotation
             // is allowed of not.
@@ -153,13 +157,12 @@
                 Resources res = context.getResources();
                 int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
                         * res.getDisplayMetrics().densityDpi / originalDensity;
-                allowRotationPref = originalSmallestWidth >= 600;
+                return originalSmallestWidth >= 600;
             } catch (Exception e) {
                 // Ignore
             }
         }
-
-        return getPrefs(context).getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, allowRotationPref);
+        return false;
     }
 
     public static boolean isNycOrAbove() {
@@ -795,7 +798,14 @@
      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
      * return upperBound; else return value unchanged.
      */
-    public static int boundInRange(int value, int lowerBound, int upperBound) {
+    public static int boundToRange(int value, int lowerBound, int upperBound) {
+        return Math.max(lowerBound, Math.min(value, upperBound));
+    }
+
+    /**
+     * @see #boundToRange(int, int, int).
+     */
+    public static float boundToRange(float value, float lowerBound, float upperBound) {
         return Math.max(lowerBound, Math.min(value, upperBound));
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 88e5251..386e016 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -73,6 +73,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.LongArrayMap;
@@ -1096,10 +1097,11 @@
             for (int j = 0; j < itemCount; j++) {
                 View v = swc.getChildAt(j);
 
-                if (v != null  && v.getTag() instanceof LauncherAppWidgetInfo) {
+                if (v instanceof LauncherAppWidgetHostView
+                        && v.getTag() instanceof LauncherAppWidgetInfo) {
                     LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
-                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
-                    if (lahv != null && lahv.isReinflateRequired()) {
+                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) v;
+                    if (lahv.isReinflateRequired()) {
                         // Remove and rebind the current widget (which was inflated in the wrong
                         // orientation), but don't delete it from the database
                         mLauncher.removeItem(lahv, info, false  /* deleteFromDb */);
@@ -1272,6 +1274,22 @@
     }
 
     @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+
+        // Update the page indicator progress.
+        boolean isTransitioning = mIsSwitchingState
+                || (getLayoutTransition() != null && getLayoutTransition().isRunning());
+        if (mPageIndicator != null && !isTransitioning) {
+            showPageIndicatorAtCurrentScroll();
+        }
+    }
+
+    private void showPageIndicatorAtCurrentScroll() {
+        mPageIndicator.setProgress((float) getScrollX() / computeMaxScrollX());
+    }
+
+    @Override
     protected void overScroll(float amount) {
         boolean shouldOverScroll = (amount <= 0 && (!hasCustomContent() || mIsRtl)) ||
                 (amount >= 0 && (!hasCustomContent() || !mIsRtl));
@@ -1324,7 +1342,7 @@
         // different effects based on device performance. On at least one relatively high-end
         // device I've tried, translating the launcher causes things to get quite laggy.
         setTranslationAndAlpha(mLauncher.getSearchDropTargetBar(), transX, alpha);
-        setTranslationAndAlpha(getPageIndicator(), transX, alpha);
+        setTranslationAndAlpha(getPageIndicator().getView(), transX, alpha);
         setTranslationAndAlpha(getChildAt(getCurrentPage()), transX, alpha);
         setTranslationAndAlpha(mLauncher.getHotseat(), transX, alpha);
 
@@ -1537,7 +1555,7 @@
         }
 
         if (getPageIndicator() != null) {
-            getPageIndicator().setTranslationX(translationX);
+            getPageIndicator().getView().setTranslationX(translationX);
         }
 
         if (mCustomContentCallbacks != null) {
@@ -1586,8 +1604,10 @@
             // attach to window
             OnClickListener listener = getPageIndicatorClickListener();
             if (listener != null) {
-                getPageIndicator().setOnClickListener(listener);
+                getPageIndicator().getView().setOnClickListener(listener);
             }
+
+            showPageIndicatorAtCurrentScroll();
         }
 
         // Update wallpaper dimensions if they were changed since last onResume
@@ -1738,8 +1758,8 @@
         super.getVisiblePages(range);
         if (mForceDrawAdjacentPages) {
             // In overview mode, make sure that the two side pages are visible.
-            range[0] = Utilities.boundInRange(getCurrentPage() - 1, numCustomPages(), range[1]);
-            range[1] = Utilities.boundInRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
+            range[0] = Utilities.boundToRange(getCurrentPage() - 1, numCustomPages(), range[1]);
+            range[1] = Utilities.boundToRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
         }
     }
 
@@ -2005,6 +2025,9 @@
         updateChildrenLayersEnabled(false);
         showCustomContentIfNecessary();
         mForceDrawAdjacentPages = false;
+        if (mState == State.NORMAL || mState == State.SPRING_LOADED) {
+            showPageIndicatorAtCurrentScroll();
+        }
     }
 
     void updateCustomContentVisibility() {
@@ -3668,6 +3691,21 @@
         }
     }
 
+    /**
+     * Removes all folder listeners
+     */
+    public void removeFolderListeners() {
+        mapOverItems(false, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                if (view instanceof FolderIcon) {
+                    ((FolderIcon) view).removeListeners();
+                }
+                return false;
+            }
+        });
+    }
+
     @Override
     public void deferCompleteDropAfterUninstallActivity() {
         mDeferDropAfterUninstall = true;
@@ -4156,7 +4194,7 @@
         });
     }
 
-    public void widgetsRestored(ArrayList<LauncherAppWidgetInfo> changedInfo) {
+    public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
         if (!changedInfo.isEmpty()) {
             DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
                     mLauncher.getAppWidgetHost());
@@ -4177,12 +4215,18 @@
             } else {
                 // widgetRefresh will automatically run when the packages are updated.
                 // For now just update the progress bars
-                for (LauncherAppWidgetInfo info : changedInfo) {
-                    if (info.hostView instanceof PendingAppWidgetHostView) {
-                        info.installProgress = 100;
-                        ((PendingAppWidgetHostView) info.hostView).applyState();
+                mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+                    @Override
+                    public boolean evaluate(ItemInfo info, View view) {
+                        if (view instanceof PendingAppWidgetHostView
+                                && changedInfo.contains(info)) {
+                            ((LauncherAppWidgetInfo) info).installProgress = 100;
+                            ((PendingAppWidgetHostView) view).applyState();
+                        }
+                        // process all the shortcuts
+                        return false;
                     }
-                }
+                });
             }
         }
     }
@@ -4310,14 +4354,18 @@
 
             mRefreshPending = false;
 
-            for (LauncherAppWidgetInfo info : mInfos) {
-                if (info.hostView instanceof PendingAppWidgetHostView) {
-                    // Remove and rebind the current widget, but don't delete it from the database
-                    PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
-                    mLauncher.removeItem(view, info, false /* deleteFromDb */);
-                    mLauncher.bindAppWidget(info);
+            mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+                @Override
+                public boolean evaluate(ItemInfo info, View view) {
+                    if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
+                        PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view;
+                        mLauncher.removeItem(view, info, false /* deleteFromDb */);
+                        mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
+                    }
+                    // process all the shortcuts
+                    return false;
                 }
-            }
+            });
         }
     }
 }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index c0eb7ed..60070a8 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -288,7 +288,7 @@
         float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
                 1.0f : 0f;
         float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ? 1f : 0f;
-        float finalPageIndicatorAlpha = states.stateIsNormal ? 1f : 0f;
+        float finalPageIndicatorAlpha = finalHotseatAlpha;
         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
 
         float finalWorkspaceTranslationY = 0;
@@ -357,7 +357,7 @@
 
         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
         final View hotseat = mLauncher.getHotseat();
-        final View pageIndicator = mWorkspace.getPageIndicator();
+        final View pageIndicator = mWorkspace.getPageIndicator().getView();
         if (animated) {
             LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
             scale.scaleX(mNewScale)
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 1e4eb7f..157a970 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -1000,6 +1000,11 @@
         mLongPressHelper.cancelLongPress();
     }
 
+    public void removeListeners() {
+        mInfo.removeListener(this);
+        mInfo.removeListener(mFolder);
+    }
+
     public interface PreviewLayoutRule {
         public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 1af1485..e1a1431 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -39,8 +39,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.PageIndicator;
-import com.android.launcher3.PageIndicator.PageMarkerResources;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.pageindicators.PageIndicator.PageMarkerResources;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -103,7 +103,7 @@
     private FocusIndicatorView mFocusIndicatorView;
     private PagedFolderKeyEventListener mKeyListener;
 
-    private PageIndicator mPageIndicator;
+    private PageIndicatorDots mPageIndicator;
 
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -128,7 +128,7 @@
         mFolder = folder;
         mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
         mKeyListener = new PagedFolderKeyEventListener(folder);
-        mPageIndicator = (PageIndicator) folder.findViewById(R.id.folder_page_indicator);
+        mPageIndicator = (PageIndicatorDots) folder.findViewById(R.id.folder_page_indicator);
     }
 
     /**
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
new file mode 100644
index 0000000..6348b12
--- /dev/null
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -0,0 +1,31 @@
+package com.android.launcher3.pageindicators;
+
+import android.view.View;
+
+import java.util.ArrayList;
+
+public interface PageIndicator {
+    View getView();
+    void setProgress(float progress);
+
+    void removeAllMarkers(boolean allowAnimations);
+    void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations);
+    void setActiveMarker(int activePage);
+    void addMarker(int pageIndex, PageMarkerResources pageIndicatorMarker, boolean allowAnimations);
+    void removeMarker(int pageIndex, boolean allowAnimations);
+    void updateMarker(int pageIndex, PageMarkerResources pageIndicatorMarker);
+
+    /**
+     * Contains two resource ids for each page indicator marker (e.g. dots):
+     * one for when the page is active and one for when the page is inactive.
+     */
+    class PageMarkerResources {
+        int activeId;
+        int inactiveId;
+
+        public PageMarkerResources(int aId, int iaId) {
+            activeId = aId;
+            inactiveId = iaId;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/PageIndicatorMarker.java b/src/com/android/launcher3/pageindicators/PageIndicatorDot.java
similarity index 89%
rename from src/com/android/launcher3/PageIndicatorMarker.java
rename to src/com/android/launcher3/pageindicators/PageIndicatorDot.java
index 7bf21dd..5ed3426 100644
--- a/src/com/android/launcher3/PageIndicatorMarker.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDot.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.pageindicators;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -22,7 +22,9 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
-public class PageIndicatorMarker extends FrameLayout {
+import com.android.launcher3.R;
+
+public class PageIndicatorDot extends FrameLayout {
     @SuppressWarnings("unused")
     private static final String TAG = "PageIndicator";
 
@@ -32,15 +34,15 @@
     private ImageView mInactiveMarker;
     private boolean mIsActive = false;
 
-    public PageIndicatorMarker(Context context) {
+    public PageIndicatorDot(Context context) {
         this(context, null);
     }
 
-    public PageIndicatorMarker(Context context, AttributeSet attrs) {
+    public PageIndicatorDot(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public PageIndicatorMarker(Context context, AttributeSet attrs, int defStyle) {
+    public PageIndicatorDot(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
 
diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
similarity index 77%
rename from src/com/android/launcher3/PageIndicator.java
rename to src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 8adbf8d..a488f02 100644
--- a/src/com/android/launcher3/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -14,19 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.pageindicators;
 
 import android.animation.LayoutTransition;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewDebug;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.R;
+
 import java.util.ArrayList;
 
-public class PageIndicator extends LinearLayout {
+public class PageIndicatorDots extends LinearLayout implements PageIndicator {
     @SuppressWarnings("unused")
     private static final String TAG = "PageIndicator";
     // Want this to look good? Keep it odd
@@ -36,38 +39,23 @@
     private int[] mWindowRange = new int[2];
     private int mMaxWindowSize;
 
-    private ArrayList<PageIndicatorMarker> mMarkers =
-            new ArrayList<PageIndicatorMarker>();
+    private ArrayList<PageIndicatorDot> mMarkers = new ArrayList<>();
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mActiveMarkerIndex;
 
-    public static class PageMarkerResources {
-        int activeId;
-        int inactiveId;
-
-        public PageMarkerResources() {
-            activeId = R.drawable.ic_pageindicator_current;
-            inactiveId = R.drawable.ic_pageindicator_default;
-        }
-        public PageMarkerResources(int aId, int iaId) {
-            activeId = aId;
-            inactiveId = iaId;
-        }
-    }
-
-    public PageIndicator(Context context) {
+    public PageIndicatorDots(Context context) {
         this(context, null);
     }
 
-    public PageIndicator(Context context, AttributeSet attrs) {
+    public PageIndicatorDots(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public PageIndicator(Context context, AttributeSet attrs, int defStyle) {
+    public PageIndicatorDots(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         TypedArray a = context.obtainStyledAttributes(attrs,
-                R.styleable.PageIndicator, defStyle, 0);
-        mMaxWindowSize = a.getInteger(R.styleable.PageIndicator_windowSize, 15);
+                R.styleable.PageIndicatorDots, defStyle, 0);
+        mMaxWindowSize = a.getInteger(R.styleable.PageIndicatorDots_windowSize, 15);
         mWindowRange[0] = 0;
         mWindowRange[1] = 0;
         mLayoutInflater = LayoutInflater.from(context);
@@ -94,7 +82,7 @@
         transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
     }
 
-    void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) {
+    public void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) {
         if (activeIndex < 0) {
             new Throwable().printStackTrace();
         }
@@ -116,7 +104,7 @@
 
         // Remove all the previous children that are no longer in the window
         for (int i = getChildCount() - 1; i >= 0; --i) {
-            PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i);
+            PageIndicatorDot marker = (PageIndicatorDot) getChildAt(i);
             int markerIndex = mMarkers.indexOf(marker);
             if (markerIndex < windowStart || markerIndex >= windowEnd) {
                 removeView(marker);
@@ -125,7 +113,7 @@
 
         // Add all the new children that belong in the window
         for (int i = 0; i < mMarkers.size(); ++i) {
-            PageIndicatorMarker marker = (PageIndicatorMarker) mMarkers.get(i);
+            PageIndicatorDot marker = (PageIndicatorDot) mMarkers.get(i);
             if (windowStart <= i && i < windowEnd) {
                 if (indexOfChild(marker) < 0) {
                     addView(marker, i - windowStart);
@@ -161,58 +149,75 @@
         mWindowRange[1] = windowEnd;
     }
 
-    void addMarker(int index, PageMarkerResources marker, boolean allowAnimations) {
+    @Override
+    public void addMarker(int index, PageMarkerResources marker, boolean allowAnimations) {
         index = Math.max(0, Math.min(index, mMarkers.size()));
 
-        PageIndicatorMarker m =
-            (PageIndicatorMarker) mLayoutInflater.inflate(R.layout.page_indicator_marker,
+        PageIndicatorDot m =
+            (PageIndicatorDot) mLayoutInflater.inflate(R.layout.page_indicator_marker,
                     this, false);
         m.setMarkerDrawables(marker.activeId, marker.inactiveId);
 
         mMarkers.add(index, m);
         offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
     }
-    void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) {
+
+    @Override
+    public void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) {
         for (int i = 0; i < markers.size(); ++i) {
             addMarker(Integer.MAX_VALUE, markers.get(i), allowAnimations);
         }
     }
 
-    void updateMarker(int index, PageMarkerResources marker) {
-        PageIndicatorMarker m = mMarkers.get(index);
+    @Override
+    public void updateMarker(int index, PageMarkerResources marker) {
+        PageIndicatorDot m = mMarkers.get(index);
         m.setMarkerDrawables(marker.activeId, marker.inactiveId);
     }
 
-    void removeMarker(int index, boolean allowAnimations) {
+    @Override
+    public void removeMarker(int index, boolean allowAnimations) {
         if (mMarkers.size() > 0) {
             index = Math.max(0, Math.min(mMarkers.size() - 1, index));
             mMarkers.remove(index);
             offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
         }
     }
-    void removeAllMarkers(boolean allowAnimations) {
+
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public void setProgress(float progress) {
+    }
+
+    @Override
+    public void removeAllMarkers(boolean allowAnimations) {
         while (mMarkers.size() > 0) {
             removeMarker(Integer.MAX_VALUE, allowAnimations);
         }
     }
 
-    void setActiveMarker(int index) {
+    @Override
+    public void setActiveMarker(int index) {
         // Center the active marker
         mActiveMarkerIndex = index;
         offsetWindowCenterTo(index, false);
     }
 
-    void dumpState(String txt) {
+    private void dumpState(String txt) {
         System.out.println(txt);
         System.out.println("\tmMarkers: " + mMarkers.size());
         for (int i = 0; i < mMarkers.size(); ++i) {
-            PageIndicatorMarker m = mMarkers.get(i);
+            PageIndicatorDot m = mMarkers.get(i);
             System.out.println("\t\t(" + i + ") " + m);
         }
         System.out.println("\twindow: [" + mWindowRange[0] + ", " + mWindowRange[1] + "]");
         System.out.println("\tchildren: " + getChildCount());
         for (int i = 0; i < getChildCount(); ++i) {
-            PageIndicatorMarker m = (PageIndicatorMarker) getChildAt(i);
+            PageIndicatorDot m = (PageIndicatorDot) getChildAt(i);
             System.out.println("\t\t(" + i + ") " + m);
         }
         System.out.println("\tactive: " + mActiveMarkerIndex);
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLine.java b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
new file mode 100644
index 0000000..449bf06
--- /dev/null
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
@@ -0,0 +1,187 @@
+package com.android.launcher3.pageindicators;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.v4.graphics.ColorUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dynamicui.ExtractedColors;
+
+import java.util.ArrayList;
+
+/**
+ * A PageIndicator that briefly shows a fraction of a line when moving between pages.
+ *
+ * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
+ */
+public class PageIndicatorLine extends View implements PageIndicator {
+    private static final String TAG = "PageIndicatorLine";
+
+    private static final int LINE_FADE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
+    private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
+    public static final int WHITE_ALPHA = (int) (0.70f * 255);
+    public static final int BLACK_ALPHA = (int) (0.65f * 255);
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private ValueAnimator mLineAlphaAnimator;
+    private int mAlpha = 0;
+    private float mProgress = 0f;
+    private int mNumPages = 1;
+    private Paint mLinePaint;
+
+    private Property<Paint, Integer> mPaintAlphaProperty
+            = new Property<Paint, Integer>(Integer.class, "paint_alpha") {
+        @Override
+        public Integer get(Paint paint) {
+            return paint.getAlpha();
+        }
+
+        @Override
+        public void set(Paint paint, Integer alpha) {
+            paint.setAlpha(alpha);
+            invalidate();
+        }
+    };
+
+    private Runnable mHideLineRunnable = new Runnable() {
+        @Override
+        public void run() {
+            animateLineToAlpha(0);
+        }
+    };
+
+    public PageIndicatorLine(Context context) {
+        this(context, null);
+    }
+
+    public PageIndicatorLine(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PageIndicatorLine(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLinePaint = new Paint();
+        mLinePaint.setAlpha(0);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mNumPages == 0) {
+            return;
+        }
+
+        int availableWidth = canvas.getWidth();
+        int lineWidth = availableWidth / mNumPages;
+        int lineLeft = (int) (mProgress * (availableWidth - lineWidth));
+        int lineRight = lineLeft + lineWidth;
+        canvas.drawRect(lineLeft, 0, lineRight, canvas.getHeight(), mLinePaint);
+    }
+
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public void setProgress(float progress) {
+        if (getAlpha() == 0) {
+            return;
+        }
+        progress = Utilities.boundToRange(progress, 0f, 1f);
+        animateLineToAlpha(mAlpha);
+        mProgress = progress;
+        invalidate();
+
+        // Hide after a brief period.
+        mHandler.removeCallbacksAndMessages(null);
+        mHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY);
+    }
+
+    @Override
+    public void removeAllMarkers(boolean allowAnimations) {
+        mNumPages = 0;
+    }
+
+    @Override
+    public void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) {
+        mNumPages += markers.size();
+    }
+
+    @Override
+    public void setActiveMarker(int activePage) {
+    }
+
+    @Override
+    public void addMarker(int pageIndex, PageMarkerResources pageIndicatorMarker,
+            boolean allowAnimations) {
+        mNumPages++;
+    }
+
+    @Override
+    public void removeMarker(int pageIndex, boolean allowAnimations) {
+        mNumPages--;
+    }
+
+    @Override
+    public void updateMarker(int pageIndex, PageMarkerResources pageIndicatorMarker) {
+    }
+
+    /**
+     * The line's color will be:
+     * - mostly opaque white if the hotseat is white (ignoring alpha)
+     * - mostly opaque black if the hotseat is black (ignoring alpha)
+     */
+    public void updateColor(ExtractedColors extractedColors) {
+        int originalLineAlpha = mLinePaint.getAlpha();
+        int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT);
+        if (color != Color.TRANSPARENT) {
+            color = ColorUtils.setAlphaComponent(color, 255);
+            if (color == Color.BLACK) {
+                mAlpha = BLACK_ALPHA;
+            } else if (color == Color.WHITE) {
+                mAlpha = WHITE_ALPHA;
+            } else {
+                Log.e(TAG, "Setting workspace page indicators to an unsupported color: #"
+                        + Integer.toHexString(color));
+            }
+            mLinePaint.setColor(color);
+            mLinePaint.setAlpha(originalLineAlpha);
+        }
+    }
+
+    private void animateLineToAlpha(int alpha) {
+        if (mLineAlphaAnimator != null) {
+            // An animation is already running, so ignore the new animation request unless we are
+            // trying to hide the line, in which case we always allow the animation.
+            if (alpha != 0) {
+                return;
+            }
+            mLineAlphaAnimator.cancel();
+        }
+        mLineAlphaAnimator = ObjectAnimator.ofInt(mLinePaint, mPaintAlphaProperty, alpha);
+        mLineAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mLineAlphaAnimator = null;
+            }
+        });
+        mLineAlphaAnimator.setDuration(LINE_FADE_DURATION);
+        mLineAlphaAnimator.start();
+    }
+}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 08e8e86..3c4c79a 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,17 +16,22 @@
 
 package com.android.launcher3.util;
 
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 
 import com.android.launcher3.Utilities;
 
+import java.util.ArrayList;
+
 /**
  * Utility methods using package manager
  */
 public class PackageManagerHelper {
 
     private static final int FLAG_SUSPENDED = 1<<30;
+    private static final String LIVE_WALLPAPER_PICKER_PKG = "com.android.wallpaper.livepicker";
 
     /**
      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
@@ -68,4 +73,27 @@
             return false;
         }
     }
+
+    /**
+     * Returns the package for a wallpaper picker system app giving preference to a app which
+     * is not as image picker.
+     */
+    public static String getWallpaperPickerPackage(PackageManager pm) {
+        ArrayList<String> excludePackages = new ArrayList<>();
+        // Exclude packages which contain an image picker
+        for (ResolveInfo info : pm.queryIntentActivities(
+                new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), 0)) {
+            excludePackages.add(info.activityInfo.packageName);
+        }
+        excludePackages.add(LIVE_WALLPAPER_PICKER_PKG);
+
+        for (ResolveInfo info : pm.queryIntentActivities(
+                new Intent(Intent.ACTION_SET_WALLPAPER), 0)) {
+            if (!excludePackages.contains(info.activityInfo.packageName) &&
+                    (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                return info.activityInfo.packageName;
+            }
+        }
+        return excludePackages.get(0);
+    }
 }