Merge "Stop animating highlighted page border in spring-loaded mode." into ub-launcher3-calgary
diff --git a/Android.mk b/Android.mk
index 0773869..e42a463 100644
--- a/Android.mk
+++ b/Android.mk
@@ -26,7 +26,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     libWallpaperPicker \
     android-support-v4 \
-    android-support-v7-recyclerview
+    android-support-v7-recyclerview \
+    android-support-v7-palette
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-proto-files-under, protos)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 418b1d2..80ffa43 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -141,6 +141,11 @@
             </intent-filter>
         </receiver>
 
+        <service android:name=".dynamicui.ColorExtractionService"
+            android:exported="false"
+            android:process=":wallpaper_chooser">
+        </service>
+
         <!-- The settings provider contains Home's data, like the workspace favorites -->
         <provider
             android:name="com.android.launcher3.LauncherProvider"
diff --git a/build.gradle b/build.gradle
index 6620c10..2306744 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,12 +52,14 @@
 dependencies {
     compile 'com.android.support:support-v4:23.1.1'
     compile 'com.android.support:recyclerview-v7:23.1.1'
+    compile 'com.android.support:palette-v7:23.2.0'
     compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
     compile project(":WallpaperPicker-Lib")
 
     testCompile 'junit:junit:4.12'
     androidTestCompile 'com.android.support.test:runner:0.5'
     androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
+    androidTestCompile 'com.android.support:support-annotations:23.2.0'
 }
 
 protobuf {
diff --git a/res/drawable-hdpi/ic_setting_pressed.png b/res/drawable-hdpi/ic_setting_pressed.png
deleted file mode 100644
index b86fce1..0000000
--- a/res/drawable-hdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed.png b/res/drawable-hdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index 4bb1958..0000000
--- a/res/drawable-hdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed.png b/res/drawable-hdpi/ic_widget_pressed.png
deleted file mode 100644
index 7f31ab3..0000000
--- a/res/drawable-hdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed.png b/res/drawable-mdpi/ic_setting_pressed.png
deleted file mode 100644
index 018bea3..0000000
--- a/res/drawable-mdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed.png b/res/drawable-mdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index 08794d9..0000000
--- a/res/drawable-mdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed.png b/res/drawable-mdpi/ic_widget_pressed.png
deleted file mode 100644
index 634b415..0000000
--- a/res/drawable-mdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed.png b/res/drawable-xhdpi/ic_setting_pressed.png
deleted file mode 100644
index 949373f..0000000
--- a/res/drawable-xhdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed.png b/res/drawable-xhdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index e1e291d..0000000
--- a/res/drawable-xhdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed.png b/res/drawable-xhdpi/ic_widget_pressed.png
deleted file mode 100644
index 1dcaf37..0000000
--- a/res/drawable-xhdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed.png b/res/drawable-xxhdpi/ic_setting_pressed.png
deleted file mode 100644
index d78cad6..0000000
--- a/res/drawable-xxhdpi/ic_setting_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed.png b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
deleted file mode 100644
index 52c92cb..0000000
--- a/res/drawable-xxhdpi/ic_wallpaper_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed.png b/res/drawable-xxhdpi/ic_widget_pressed.png
deleted file mode 100644
index 0c9b02a..0000000
--- a/res/drawable-xxhdpi/ic_widget_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index f9ca01b..be16c89 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -18,4 +18,7 @@
 <!-- QSB -->
     <dimen name="toolbar_button_vertical_padding">12dip</dimen>
     <dimen name="toolbar_button_horizontal_padding">20dip</dimen>
+
+<!-- Container -->
+    <dimen name="container_max_width">736dp</dimen>
 </resources>
diff --git a/res/values-sw768dp-port/dimens.xml b/res/values-sw768dp-port/dimens.xml
new file mode 100644
index 0000000..6fb2bf6
--- /dev/null
+++ b/res/values-sw768dp-port/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<resources>
+<!-- Container -->
+    <dimen name="container_max_width">736dp</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 7d8f6f6..281de08 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -69,6 +69,7 @@
 
     <item name="container_margin" format="fraction" type="fraction">0%</item>
     <dimen name="container_min_margin">8dp</dimen>
+    <dimen name="container_max_width">0dp</dimen>
 
 <!-- All Apps -->
     <dimen name="all_apps_button_scale_down">0dp</dimen>
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index 51a97b9..d0cacd3 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -18,16 +18,12 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.widget.FrameLayout;
 
-import com.android.launcher3.config.ProviderConfig;
-
 /**
  * A base container view, which supports resizing.
  */
@@ -52,9 +48,7 @@
         super(context, attrs, defStyleAttr);
 
         int width = ((Launcher) context).getDeviceProfile().availableWidthPx;
-        mHorizontalPadding = Math.max(
-                getResources().getDimensionPixelSize(R.dimen.container_min_margin),
-                (int) getResources().getFraction(R.fraction.container_margin, width, 1));
+        mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BaseContainerView, defStyleAttr, 0);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 6426ec2..baf5e1f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -511,6 +511,9 @@
             canvas.save();
             canvas.translate(mTempLocation[0], mTempLocation[1]);
             bg.drawBackground(canvas, mFolderBgPaint);
+            if (!bg.isClipping) {
+                bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+            }
             canvas.restore();
         }
 
@@ -530,11 +533,13 @@
 
         for (int i = 0; i < mFolderBackgrounds.size(); i++) {
             FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
-            cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
-            canvas.save();
-            canvas.translate(mTempLocation[0], mTempLocation[1]);
-            bg.drawBackgroundStroke(canvas, mFolderBgPaint);
-            canvas.restore();
+            if (bg.isClipping) {
+                cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
+                canvas.save();
+                canvas.translate(mTempLocation[0], mTempLocation[1]);
+                bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+                canvas.restore();
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7c6f39e..9ab5611 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -594,4 +594,19 @@
                 ? Math.min(widthPx, heightPx)
                 : Math.max(widthPx, heightPx);
     }
+
+
+    public static final int getContainerPadding(Context context, int availableWidth) {
+        Resources res = context.getResources();
+
+        int maxSize = res.getDimensionPixelSize(R.dimen.container_max_width);
+        int minMargin = res.getDimensionPixelSize(R.dimen.container_min_margin);
+
+        if (maxSize > 0) {
+            return Math.max(minMargin, (availableWidth - maxSize) / 2);
+        } else {
+            return Math.max(minMargin,
+                    (int) res.getFraction(R.fraction.container_margin, availableWidth, 1));
+        }
+    }
 }
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 861a935..6c9d969 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -69,12 +69,12 @@
      *
      * @param item
      */
-    public void add(ShortcutInfo item) {
+    public void add(ShortcutInfo item, boolean animate) {
         contents.add(item);
         for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onAdd(item);
         }
-        itemsChanged();
+        itemsChanged(animate);
     }
 
     /**
@@ -82,12 +82,12 @@
      *
      * @param item
      */
-    public void remove(ShortcutInfo item) {
+    public void remove(ShortcutInfo item, boolean animate) {
         contents.remove(item);
         for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onRemove(item);
         }
-        itemsChanged();
+        itemsChanged(animate);
     }
 
     public void setTitle(CharSequence title) {
@@ -115,9 +115,9 @@
         }
     }
 
-    void itemsChanged() {
+    public void itemsChanged(boolean animate) {
         for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onItemsChanged();
+            listeners.get(i).onItemsChanged(animate);
         }
     }
 
@@ -131,7 +131,7 @@
         public void onAdd(ShortcutInfo item);
         public void onRemove(ShortcutInfo item);
         public void onTitleChanged(CharSequence title);
-        public void onItemsChanged();
+        public void onItemsChanged(boolean animate);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b16a650..445831c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -84,6 +84,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.OvershootInterpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Advanceable;
@@ -105,6 +106,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logging.LoggerUtils;
@@ -137,7 +139,8 @@
  */
 public class Launcher extends Activity
         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
-                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
+                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
+                   AccessibilityManager.AccessibilityStateChangeListener {
     public static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
@@ -279,6 +282,7 @@
 
     private LauncherModel mModel;
     private IconCache mIconCache;
+    private ExtractedColors mExtractedColors;
     @Thunk boolean mUserPresent = true;
     private boolean mVisible = false;
     private boolean mHasFocus = false;
@@ -286,8 +290,6 @@
 
     private LauncherClings mClings;
 
-    private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
-
     private View.OnTouchListener mHapticFeedbackTouchListener;
 
     // Related to the auto-advancing of widgets
@@ -447,6 +449,11 @@
         app.getInvariantDeviceProfile().portraitProfile.setSearchBarHeight(getSearchBarHeight());
         setupViews();
         mDeviceProfile.layout(this);
+        mExtractedColors = new ExtractedColors();
+        loadExtractedColorsAndColorItems();
+
+        ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
+                .addAccessibilityStateChangeListener(this);
 
         lockAllApps();
 
@@ -509,6 +516,18 @@
         }
     }
 
+    @Override
+    public void onExtractedColorsChanged() {
+        loadExtractedColorsAndColorItems();
+    }
+
+    private void loadExtractedColorsAndColorItems() {
+        if (mExtractedColors != null) {
+            mExtractedColors.load(this);
+            // TODO: pass mExtractedColors to interested items such as hotseat.
+        }
+    }
+
     private LauncherCallbacks mLauncherCallbacks;
 
     public void onPostCreate(Bundle savedInstanceState) {
@@ -997,7 +1016,7 @@
         mPaused = false;
         if (mRestoring || mOnResumeNeedsLoad) {
             setWorkspaceLoading(true);
-            mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
+            mModel.startLoader(getCurrentWorkspaceScreen());
             mRestoring = false;
             mOnResumeNeedsLoad = false;
         }
@@ -1995,6 +2014,9 @@
 
         TextKeyListener.getInstance().release();
 
+        ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
+                .removeAccessibilityStateChangeListener(this);
+
         unregisterReceiver(mCloseSystemDialogsReceiver);
 
         LauncherAnimUtils.onDestroyActivity();
@@ -2333,7 +2355,6 @@
         // Update the model
         LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
                 cellX, cellY);
-        sFolders.put(folderInfo.id, folderInfo);
 
         // Create the view
         FolderIcon newFolder =
@@ -2356,9 +2377,9 @@
     public boolean removeItem(View v, ItemInfo itemInfo, boolean deleteFromDb) {
         if (itemInfo instanceof ShortcutInfo) {
             // Remove the shortcut from the folder before removing it from launcher
-            FolderInfo folderInfo = sFolders.get(itemInfo.container);
-            if (folderInfo != null) {
-                folderInfo.remove((ShortcutInfo) itemInfo);
+            View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
+            if (folderIcon instanceof FolderIcon) {
+                ((FolderInfo) folderIcon.getTag()).remove((ShortcutInfo) itemInfo, true);
             } else {
                 mWorkspace.removeWorkspaceItem(v);
             }
@@ -2367,7 +2388,6 @@
             }
         } else if (itemInfo instanceof FolderInfo) {
             final FolderInfo folderInfo = (FolderInfo) itemInfo;
-            unbindFolder(folderInfo);
             mWorkspace.removeWorkspaceItem(v);
             if (deleteFromDb) {
                 LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
@@ -2388,13 +2408,6 @@
     }
 
     /**
-     * Unbinds any launcher references to the folder.
-     */
-    private void unbindFolder(FolderInfo folder) {
-        sFolders.remove(folder.id);
-    }
-
-    /**
      * Deletes the widget info and the widget id.
      */
     private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) {
@@ -2778,6 +2791,11 @@
         return mHapticFeedbackTouchListener;
     }
 
+    @Override
+    public void onAccessibilityStateChanged(boolean enabled) {
+        mDragLayer.onAccessibilityStateChanged(enabled);
+    }
+
     public void onDragStarted(View view) {
         if (isOnCustomContent()) {
             // Custom content screen doesn't participate in drag and drop. If on custom
@@ -3664,6 +3682,7 @@
      * @return true if we are currently paused.  The caller might be able to
      * skip some work in that case since we will come back again.
      */
+    @Override
     public boolean setLoadOnResume() {
         if (mPaused) {
             if (LOGD) Log.d(TAG, "setLoadOnResume");
@@ -3677,6 +3696,7 @@
     /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
+    @Override
     public int getCurrentWorkspaceScreen() {
         if (mWorkspace != null) {
             return mWorkspace.getCurrentPage();
@@ -3887,21 +3907,6 @@
         workspace.requestLayout();
     }
 
-    /**
-     * Implementation of the method from LauncherModel.Callbacks.
-     */
-    public void bindFolders(final LongArrayMap<FolderInfo> folders) {
-        Runnable r = new Runnable() {
-            public void run() {
-                bindFolders(folders);
-            }
-        };
-        if (waitUntilResume(r)) {
-            return;
-        }
-        sFolders = folders.clone();
-    }
-
     private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
         PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
         view.updateIcon(mIconCache);
@@ -4046,7 +4051,6 @@
      * Restores a pending widget.
      *
      * @param appWidgetId The app widget id
-     * @param cellInfo The position on screen where to create the widget.
      */
     private void completeRestoreAppWidget(final int appWidgetId) {
         LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
@@ -4681,7 +4685,6 @@
         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
         Log.d(TAG, "mRestoring=" + mRestoring);
         Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
-        Log.d(TAG, "sFolders.size=" + sFolders.size());
         mModel.dumpState();
         // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
 
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index e58652b..f84e4b5 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -107,7 +108,13 @@
         // For handling managed profiles
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED);
+        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE);
+        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        // For extracting colors from the wallpaper
+        if (Utilities.isNycOrAbove()) {
+            // TODO: add a broadcast entry to the manifest for pre-N.
+            filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
+        }
 
         sContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(sContext).enableAndResetCache();
@@ -121,6 +128,8 @@
             }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
         }
         new ConfigMonitor(sContext).register();
+
+        ExtractionUtils.startColorExtractionServiceIfNecessary(sContext);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3c7366c..f2b307b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -54,6 +54,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.GridSizeMigrationTask;
@@ -102,7 +103,6 @@
     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
     private static final long INVALID_SCREEN_ID = -1L;
 
-    @Thunk final boolean mAppsCanBeOnRemoveableStorage;
     private final boolean mOldContentProviderExists;
 
     @Thunk final LauncherAppState mApp;
@@ -184,7 +184,6 @@
                               boolean forceAnimateIcons);
         public void bindScreens(ArrayList<Long> orderedScreenIds);
         public void bindAddScreens(ArrayList<Long> orderedScreenIds);
-        public void bindFolders(LongArrayMap<FolderInfo> folders);
         public void finishBindingItems();
         public void bindAppWidget(LauncherAppWidgetInfo info);
         public void bindAllApplications(ArrayList<AppInfo> apps);
@@ -216,7 +215,6 @@
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         Context context = app.getContext();
 
-        mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
         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.
@@ -1169,20 +1167,8 @@
     @Override
     public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
-        if (!replacing) {
-            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
-                    user));
-            if (mAppsCanBeOnRemoveableStorage) {
-                // Only rebind if we support removable storage. It catches the
-                // case where
-                // apps on the external sd card need to be reloaded
-                startLoaderFromBackground();
-            }
-        } else {
-            // If we are replacing then just update the packages in the list
-            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
-                    packageNames, user));
-        }
+        enqueuePackageUpdated(
+                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
     }
 
     @Override
@@ -1230,13 +1216,16 @@
                 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
             UserManagerCompat.getInstance(context).enableAndResetCache();
             forceReload();
-        } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED.equals(action)) {
+        } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
+                LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
             UserHandleCompat user = UserHandleCompat.fromIntent(intent);
             if (user != null) {
                 enqueuePackageUpdated(new PackageUpdatedTask(
                         PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
                         new String[0], user));
             }
+        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
+            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
         }
     }
 
@@ -1266,17 +1255,13 @@
      * of doing it now.
      */
     public void startLoaderFromBackground() {
-        boolean runLoader = false;
         Callbacks callbacks = getCallback();
         if (callbacks != null) {
             // Only actually run the loader if they're not paused.
             if (!callbacks.setLoadOnResume()) {
-                runLoader = true;
+                startLoader(callbacks.getCurrentWorkspaceScreen());
             }
         }
-        if (runLoader) {
-            startLoader(PagedView.INVALID_RESTORE_PAGE);
-        }
     }
 
     /**
@@ -1313,7 +1298,7 @@
 
                 // If there is already one running, tell it to stop.
                 stopLoaderLocked();
-                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
+                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags, synchronousBindPage);
                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                         && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
@@ -1367,14 +1352,17 @@
      */
     private class LoaderTask implements Runnable {
         private Context mContext;
+        private int mPageToBindFirst;
+
         @Thunk boolean mIsLoadingAndBindingWorkspace;
         private boolean mStopped;
         @Thunk boolean mLoadAndBindStepFinished;
         private int mFlags;
 
-        LoaderTask(Context context, int flags) {
+        LoaderTask(Context context, int flags, int pageToBindFirst) {
             mContext = context;
             mFlags = flags;
+            mPageToBindFirst = pageToBindFirst;
         }
 
         private void loadAndBindWorkspace() {
@@ -1396,7 +1384,7 @@
             }
 
             // Bind the workspace
-            bindWorkspace(-1);
+            bindWorkspace(mPageToBindFirst);
         }
 
         private void waitForIdle() {
@@ -1998,7 +1986,7 @@
                                         // Item is in a user folder
                                         FolderInfo folderInfo =
                                                 findOrMakeFolder(sBgFolders, container);
-                                        folderInfo.add(info);
+                                        folderInfo.add(info, false);
                                         break;
                                     }
                                     sBgItemsIdMap.put(info.id, info);
@@ -2360,29 +2348,6 @@
             }
         }
 
-        /** Filters the set of folders which are on the specified screen. */
-        private void filterCurrentFolders(long currentScreenId,
-                LongArrayMap<ItemInfo> itemsIdMap,
-                LongArrayMap<FolderInfo> folders,
-                LongArrayMap<FolderInfo> currentScreenFolders,
-                LongArrayMap<FolderInfo> otherScreenFolders) {
-
-            int total = folders.size();
-            for (int i = 0; i < total; i++) {
-                long id = folders.keyAt(i);
-                FolderInfo folder = folders.valueAt(i);
-
-                ItemInfo info = itemsIdMap.get(id);
-                if (info == null || folder == null) continue;
-                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
-                        info.screenId == currentScreenId) {
-                    currentScreenFolders.put(id, folder);
-                } else {
-                    otherScreenFolders.put(id, folder);
-                }
-            }
-        }
-
         /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
          * right) */
         private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
@@ -2439,7 +2404,6 @@
         private void bindWorkspaceItems(final Callbacks oldCallbacks,
                 final ArrayList<ItemInfo> workspaceItems,
                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
-                final LongArrayMap<FolderInfo> folders,
                 final Executor executor) {
 
             // Bind the workspace items
@@ -2460,19 +2424,6 @@
                 executor.execute(r);
             }
 
-            // Bind the folders
-            if (!folders.isEmpty()) {
-                final Runnable r = new Runnable() {
-                    public void run() {
-                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
-                        if (callbacks != null) {
-                            callbacks.bindFolders(folders);
-                        }
-                    }
-                };
-                executor.execute(r);
-            }
-
             // Bind the widgets, one at a time
             N = appWidgets.size();
             for (int i = 0; i < N; i++) {
@@ -2506,21 +2457,14 @@
             }
 
             // Save a copy of all the bg-thread collections
-            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
-            ArrayList<LauncherAppWidgetInfo> appWidgets =
-                    new ArrayList<LauncherAppWidgetInfo>();
-            ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
-
-            final LongArrayMap<FolderInfo> folders;
-            final LongArrayMap<ItemInfo> itemsIdMap;
+            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+            ArrayList<Long> orderedScreenIds = new ArrayList<>();
 
             synchronized (sBgLock) {
                 workspaceItems.addAll(sBgWorkspaceItems);
                 appWidgets.addAll(sBgAppWidgets);
                 orderedScreenIds.addAll(sBgWorkspaceScreens);
-
-                folders = sBgFolders.clone();
-                itemsIdMap = sBgItemsIdMap.clone();
             }
 
             final boolean isLoadingSynchronously =
@@ -2540,21 +2484,15 @@
             unbindWorkspaceItemsOnMainThread();
 
             // Separate the items that are on the current screen, and all the other remaining items
-            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
-            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
-            ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
-                    new ArrayList<LauncherAppWidgetInfo>();
-            ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
-                    new ArrayList<LauncherAppWidgetInfo>();
-            LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
-            LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
+            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
 
             filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                     otherWorkspaceItems);
             filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
                     otherAppWidgets);
-            filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
-                    otherFolders);
             sortWorkspaceItemsSpatially(currentWorkspaceItems);
             sortWorkspaceItemsSpatially(otherWorkspaceItems);
 
@@ -2574,8 +2512,7 @@
 
             Executor mainExecutor = new DeferredMainThreadExecutor();
             // Load items on the current page.
-            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
-                    currentFolders, mainExecutor);
+            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
 
             // In case of isLoadingSynchronously, only bind the first screen, and defer binding the
             // remaining screens after first onDraw is called. This ensures that the first screen
@@ -2584,8 +2521,7 @@
             final Executor deferredExecutor = isLoadingSynchronously ?
                     new ViewOnDrawExecutor(mHandler) : mainExecutor;
 
-            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets,
-                    otherFolders, deferredExecutor);
+            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
 
             // Tell the workspace that we're done binding items
             r = new Runnable() {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 207121b..f076094 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.NoLocaleSqliteContext;
 import com.android.launcher3.util.Thunk;
@@ -257,7 +258,7 @@
     }
 
     @Override
-    public Bundle call(String method, String arg, Bundle extras) {
+    public Bundle call(String method, final String arg, final Bundle extras) {
         if (Binder.getCallingUid() != Process.myUid()) {
             return null;
         }
@@ -277,13 +278,19 @@
                 return result;
             }
             case LauncherSettings.Settings.METHOD_SET_BOOLEAN: {
-                boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
+                final boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
                 Utilities.getPrefs(getContext()).edit().putBoolean(arg, value).apply();
-                synchronized (LISTENER_LOCK) {
-                    if (mListener != null) {
-                        mListener.onSettingsChanged(arg, value);
+                new MainThreadExecutor().execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (LISTENER_LOCK) {
+                            if (mListener != null) {
+                                mListener.onSettingsChanged(arg, value);
+                            }
+                        }
+
                     }
-                }
+                });
                 if (extras.getBoolean(LauncherSettings.Settings.NOTIFY_BACKUP)) {
                     LauncherBackupAgentHelper.dataChanged(getContext());
                 }
@@ -291,6 +298,29 @@
                 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);
+                int wallpaperId = extras.getInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID);
+                Utilities.getPrefs(getContext()).edit()
+                        .putString(ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, extractedColors)
+                        .putInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, wallpaperId)
+                        .apply();
+                new MainThreadExecutor().execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (LISTENER_LOCK) {
+                            if (mListener != null) {
+                                mListener.onExtractedColorsChanged();
+                            }
+                        }
+
+                    }
+                });
+                Bundle result = new Bundle();
+                result.putString(LauncherSettings.Settings.EXTRA_VALUE, extractedColors);
+                return result;
+            }
             case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
                 clearFlagEmptyDbCreated();
                 return null;
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
index 1b78e9c..2d2da6e 100644
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ b/src/com/android/launcher3/LauncherProviderChangeListener.java
@@ -11,5 +11,7 @@
 
     public void onSettingsChanged(String settings, boolean value);
 
+    public void onExtractedColorsChanged();
+
     public void onAppWidgetHostReset();
 }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index fb53068..0c16287 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -339,6 +339,11 @@
         public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
         public static final String METHOD_MIGRATE_LAUNCHER2_SHORTCUTS = "migrate_l2_shortcuts";
 
+        public static final String METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID =
+                "set_extracted_colors_and_wallpaper_id_setting";
+        public static final String EXTRA_EXTRACTED_COLORS = "extra_extractedColors";
+        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.
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 74e6b39..f32c845 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -33,6 +33,11 @@
 public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
     private static final float OVERVIEW_PROGRESS = 0f;
     private static final float WORKSPACE_PROGRESS = 1f;
+    /**
+     * The velocity threshold at which a pinch will be completed instead of canceled,
+     * even if the first threshold has not been passed. Measured in progress / millisecond
+     */
+    private static final float FLING_VELOCITY = 0.003f;
 
     private ScaleGestureDetector mPinchDetector;
     private Launcher mLauncher;
@@ -110,17 +115,20 @@
     public void onScaleEnd(ScaleGestureDetector detector) {
         super.onScaleEnd(detector);
 
+        float progressVelocity = mProgressDelta / mTimeDelta;
         float passedThreshold = mThresholdManager.getPassedThreshold();
+        boolean isFling = mWorkspace.isInOverviewMode() && progressVelocity >= FLING_VELOCITY
+                || !mWorkspace.isInOverviewMode() && progressVelocity <= -FLING_VELOCITY;
+        boolean shouldCancelPinch = !isFling && passedThreshold < PinchThresholdManager.THRESHOLD_ONE;
         // If we are going towards overview, mPreviousProgress is how much further we need to
         // go, since it is going from 1 to 0. If we are going to workspace, we want
         // 1 - mPreviousProgress.
         float remainingProgress = mPreviousProgress;
-        if (mWorkspace.isInOverviewMode() || passedThreshold < PinchThresholdManager.THRESHOLD_ONE) {
+        if (mWorkspace.isInOverviewMode() || shouldCancelPinch) {
             remainingProgress = 1f - mPreviousProgress;
         }
-        int duration = computeDuration(remainingProgress, mProgressDelta, mTimeDelta);
-
-        if (passedThreshold < PinchThresholdManager.THRESHOLD_ONE) {
+        int duration = computeDuration(remainingProgress, progressVelocity);
+        if (shouldCancelPinch) {
             cancelPinch(mPreviousProgress, duration);
         } else if (passedThreshold < PinchThresholdManager.THRESHOLD_THREE) {
             float toProgress = mWorkspace.isInOverviewMode() ?
@@ -138,8 +146,8 @@
      * Compute the amount of time required to complete the transition based on the current pinch
      * speed. If this time is too long, instead return the normal duration, ignoring the speed.
      */
-    private int computeDuration(float remainingProgress, float progressDelta, long timeDelta) {
-        float progressSpeed = Math.abs(progressDelta) / timeDelta;
+    private int computeDuration(float remainingProgress, float progressVelocity) {
+        float progressSpeed = Math.abs(progressVelocity);
         int remainingMillis = (int) (remainingProgress / progressSpeed);
         return Math.min(remainingMillis, mAnimationManager.getNormalOverviewTransitionDuration());
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index bd5cd8a..f04244f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -219,7 +219,7 @@
     public static final int REORDER_TIMEOUT = 350;
     private final Alarm mFolderCreationAlarm = new Alarm();
     private final Alarm mReorderAlarm = new Alarm();
-    private FolderIcon.PreviewBackground mFolderCreateBg = new FolderIcon.PreviewBackground();
+    private FolderIcon.PreviewBackground mFolderCreateBg;
     private FolderIcon mDragOverFolderIcon = null;
     private boolean mCreateUserFolderOnDrop = false;
     private boolean mAddToExistingFolderOnDrop = false;
@@ -2878,7 +2878,9 @@
     }
 
     private void cleanupFolderCreation() {
-        mFolderCreateBg.animateToRest();
+        if (mFolderCreateBg != null) {
+            mFolderCreateBg.animateToRest();
+        }
         mFolderCreationAlarm.setOnAlarmListener(null);
         mFolderCreationAlarm.cancelAlarm();
     }
@@ -3182,6 +3184,8 @@
         int cellX;
         int cellY;
 
+        FolderIcon.PreviewBackground bg = new FolderIcon.PreviewBackground();
+
         public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
             this.layout = layout;
             this.cellX = cellX;
@@ -3190,11 +3194,15 @@
             DeviceProfile grid = mLauncher.getDeviceProfile();
             BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
 
-            mFolderCreateBg.setup(getResources().getDisplayMetrics(), grid, null,
+            bg.setup(getResources().getDisplayMetrics(), grid, null,
                     cell.getMeasuredWidth(), cell.getPaddingTop());
+
+            // The full preview background should appear behind the icon
+            bg.isClipping = false;
         }
 
         public void onAlarm(Alarm alarm) {
+            mFolderCreateBg = bg;
             mFolderCreateBg.animateToAccept(layout, cellX, cellY);
             layout.clearDragOutlines();
             setDragMode(DRAG_MODE_CREATE_FOLDER);
@@ -3865,7 +3873,7 @@
         return getFirstMatch(new ItemOperator() {
 
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
+            public boolean evaluate(ItemInfo info, View v) {
                 return info != null && info.id == id;
             }
         });
@@ -3875,7 +3883,7 @@
         return getFirstMatch(new ItemOperator() {
 
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
+            public boolean evaluate(ItemInfo info, View v) {
                 return info == tag;
             }
         });
@@ -3885,7 +3893,7 @@
         return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
 
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
+            public boolean evaluate(ItemInfo info, View v) {
                 return (info instanceof LauncherAppWidgetInfo) &&
                         ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
             }
@@ -3896,8 +3904,8 @@
         final View[] value = new View[1];
         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
-                if (operator.evaluate(info, v, parent)) {
+            public boolean evaluate(ItemInfo info, View v) {
+                if (operator.evaluate(info, v)) {
                     value[0] = v;
                     return true;
                 }
@@ -3910,7 +3918,7 @@
     void clearDropTargets() {
         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
+            public boolean evaluate(ItemInfo info, View v) {
                 if (v instanceof DropTarget) {
                     mDragController.removeDropTarget((DropTarget) v);
                 }
@@ -4006,7 +4014,7 @@
             for (FolderInfo folder : folderAppsToRemove.keySet()) {
                 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
                 for (ShortcutInfo info : appsToRemove) {
-                    folder.remove(info);
+                    folder.remove(info, false);
                 }
             }
 
@@ -4036,10 +4044,9 @@
          *
          * @param info info for the shortcut
          * @param view view for the shortcut
-         * @param parent containing folder, or null
          * @return true if done, false to continue the map
          */
-        public boolean evaluate(ItemInfo info, View view, View parent);
+        public boolean evaluate(ItemInfo info, View view);
     }
 
     /**
@@ -4066,12 +4073,12 @@
                     for (int childIdx = 0; childIdx < childCount; childIdx++) {
                         View child = folderChildren.get(childIdx);
                         info = (ItemInfo) child.getTag();
-                        if (op.evaluate(info, child, folder)) {
+                        if (op.evaluate(info, child)) {
                             return;
                         }
                     }
                 } else {
-                    if (op.evaluate(info, item, null)) {
+                    if (op.evaluate(info, item)) {
                         return;
                     }
                 }
@@ -4080,10 +4087,19 @@
     }
 
     void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) {
-        final HashSet<ShortcutInfo> updates = new HashSet<ShortcutInfo>(shortcuts);
+        int total  = shortcuts.size();
+        final HashSet<ShortcutInfo> updates = new HashSet<ShortcutInfo>(total);
+        final HashSet<Long> folderIds = new HashSet<>();
+
+        for (int i = 0; i < total; i++) {
+            ShortcutInfo s = shortcuts.get(i);
+            updates.add(s);
+            folderIds.add(s.container);
+        }
+
         mapOverItems(MAP_RECURSE, new ItemOperator() {
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
+            public boolean evaluate(ItemInfo info, View v) {
                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView &&
                         updates.contains(info)) {
                     ShortcutInfo si = (ShortcutInfo) info;
@@ -4093,10 +4109,18 @@
                             && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
                     shortcut.applyFromShortcutInfo(si, mIconCache,
                             si.isPromise() != oldPromiseState);
+                }
+                // process all the shortcuts
+                return false;
+            }
+        });
 
-                    if (parent != null) {
-                        parent.invalidate();
-                    }
+        // Update folder icons
+        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View v) {
+                if (info instanceof FolderInfo && folderIds.contains(info.id)) {
+                    ((FolderInfo) info).itemsChanged(false);
                 }
                 // process all the shortcuts
                 return false;
@@ -4114,7 +4138,7 @@
     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
         mapOverItems(MAP_RECURSE, new ItemOperator() {
             @Override
-            public boolean evaluate(ItemInfo info, View v, View parent) {
+            public boolean evaluate(ItemInfo info, View v) {
                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView
                         && updates.contains(info)) {
                     ((BubbleTextView) v).applyState(false);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index c699479..aa6e08e 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -174,7 +174,7 @@
             Folder folder = mLauncher.getWorkspace().getOpenFolder();
             mLauncher.closeFolder(folder, true);
             ShortcutInfo info = (ShortcutInfo) item;
-            folder.getInfo().remove(info);
+            folder.getInfo().remove(info, false);
 
             final int[] coordinates = new int[2];
             final long screenId = findSpaceOnWorkspace(item, coordinates);
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index db5b89e..bc900bc 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -35,8 +35,10 @@
             "android.intent.action.MANAGED_PROFILE_ADDED";
     public static final String ACTION_MANAGED_PROFILE_REMOVED =
             "android.intent.action.MANAGED_PROFILE_REMOVED";
-    public static final String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED =
-            "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED";
+    public static final String ACTION_MANAGED_PROFILE_AVAILABLE =
+            "android.intent.action.MANAGED_PROFILE_AVAILABLE";
+    public static final String ACTION_MANAGED_PROFILE_UNAVAILABLE =
+            "android.intent.action.MANAGED_PROFILE_UNAVAILABLE";
 
     public interface OnAppsChangedCallbackCompat {
         void onPackageRemoved(String packageName, UserHandleCompat user);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 58f98bf..33ce683 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -29,6 +29,7 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -43,7 +44,6 @@
 
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.Hotseat;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -142,9 +142,9 @@
         mLauncher = launcher;
         mDragController = controller;
 
-        if (!FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW) {
-            mPinchListener = new PinchToOverviewListener(mLauncher);
-        }
+        boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
+                Context.ACCESSIBILITY_SERVICE)).isEnabled();
+        onAccessibilityStateChanged(isAccessibilityEnabled);
     }
 
     @Override
@@ -152,6 +152,11 @@
         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
     }
 
+    public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
+        mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
+                ? null : new PinchToOverviewListener(mLauncher);
+    }
+
     public void showOverlayView(View overlayView) {
         LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
         mOverlayView = overlayView;
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
new file mode 100644
index 0000000..95a62b9
--- /dev/null
+++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.dynamicui;
+
+import android.app.IntentService;
+import android.app.WallpaperManager;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.support.v7.graphics.Palette;
+
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+
+/**
+ * Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}.
+ */
+public class ColorExtractionService extends IntentService {
+
+    public ColorExtractionService() {
+        super("ColorExtractionService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+        int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager);
+        ExtractedColors extractedColors = new ExtractedColors();
+        if (wallpaperManager.getWallpaperInfo() != null) {
+            // We can't extract colors from live wallpapers, so just use the default color always.
+            extractedColors.updatePalette(null);
+        } else {
+            Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
+            Palette palette = Palette.from(wallpaper).generate();
+            extractedColors.updatePalette(palette);
+        }
+
+        // Save the extracted colors and wallpaper id to LauncherProvider.
+        String colorsString = extractedColors.encodeAsString();
+        Bundle extras = new Bundle();
+        extras.putInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID, wallpaperId);
+        extras.putString(LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS, colorsString);
+        getContentResolver().call(
+                LauncherSettings.Settings.CONTENT_URI,
+                LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID,
+                null, extras);
+    }
+}
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
new file mode 100644
index 0000000..4d17ff7
--- /dev/null
+++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016 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.dynamicui;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v7.graphics.Palette;
+import android.util.Log;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Saves and loads colors extracted from the wallpaper, as well as the associated wallpaper id.
+ */
+public class ExtractedColors {
+    private static final String TAG = "ExtractedColors";
+
+    public static final int DEFAULT_LIGHT = Color.WHITE;
+    public static final int DEFAULT_DARK = Color.BLACK;
+    public static final int DEFAULT_COLOR = DEFAULT_LIGHT;
+
+    // These color profile indices should NOT be changed, since they are used when saving and
+    // loading extracted colors. New colors should always be added at the end.
+    public static final int DEFAULT_INDEX = 0;
+    public static final int VIBRANT_INDEX = 1;
+    public static final int VIBRANT_DARK_INDEX = 2;
+    public static final int VIBRANT_LIGHT_INDEX = 3;
+    public static final int MUTED_INDEX = 4;
+    public static final int MUTED_DARK_INDEX = 5;
+    public static final int MUTED_LIGHT_INDEX = 6;
+
+    public static final int NUM_COLOR_PROFILES = 7;
+
+    private static final String COLOR_SEPARATOR = ",";
+
+    private int[] mColors;
+
+    public ExtractedColors() {
+        mColors = new int[NUM_COLOR_PROFILES];
+    }
+
+    public void setColorAtIndex(int index, int color) {
+        if (index >= 0 && index < mColors.length) {
+            mColors[index] = color;
+        } else {
+            Log.e(TAG, "Attempted to set a color at an invalid index " + index);
+        }
+    }
+
+    /**
+     * Encodes {@link #mColors} as a comma-separated String.
+     */
+    String encodeAsString() {
+        StringBuilder colorsStringBuilder = new StringBuilder();
+        for (int color : mColors) {
+            colorsStringBuilder.append(color).append(COLOR_SEPARATOR);
+        }
+        return colorsStringBuilder.toString();
+    }
+
+    /**
+     * Decodes a comma-separated String into {@link #mColors}.
+     */
+    void decodeFromString(String colorsString) {
+        String[] splitColorsString = colorsString.split(COLOR_SEPARATOR);
+        mColors = new int[splitColorsString.length];
+        for (int i = 0; i < mColors.length; i++) {
+            mColors[i] = Integer.parseInt(splitColorsString[i]);
+        }
+    }
+
+    /**
+     * Loads colors and wallpaper id from {@link Utilities#getPrefs(Context)}.
+     * These were saved there in {@link ColorExtractionService}.
+     */
+    public void load(Context context) {
+        String encodedString = Utilities.getPrefs(context).getString(
+                ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, DEFAULT_COLOR + "");
+
+        decodeFromString(encodedString);
+    }
+
+    /** @param index must be one of the index values defined at the top of this class. */
+    public int getColor(int index) {
+        if (index >= 0 && index < mColors.length) {
+            return mColors[index];
+        }
+        return DEFAULT_COLOR;
+    }
+
+    /**
+     * Updates colors based on the palette.
+     * If the palette is null, the default color is used in all cases.
+     */
+    public void updatePalette(Palette palette) {
+        if (palette == null) {
+            for (int i = 0; i < NUM_COLOR_PROFILES; i++) {
+                setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR);
+            }
+        } else {
+            setColorAtIndex(ExtractedColors.DEFAULT_INDEX,
+                    ExtractedColors.DEFAULT_COLOR);
+            setColorAtIndex(ExtractedColors.VIBRANT_INDEX,
+                    palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR));
+            setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX,
+                    palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK));
+            setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX,
+                    palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
+            setColorAtIndex(ExtractedColors.MUTED_INDEX,
+                    palette.getMutedColor(ExtractedColors.DEFAULT_COLOR));
+            setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX,
+                    palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK));
+            setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX,
+                    palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
+        }
+    }
+}
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
new file mode 100644
index 0000000..0b28ba6
--- /dev/null
+++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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.dynamicui;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import com.android.launcher3.Utilities;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Contains helper fields and methods related to extracting colors from the wallpaper.
+ */
+public class ExtractionUtils {
+    public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
+    public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
+
+    private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
+
+    /**
+     * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed.
+     * When the new colors are saved in the LauncherProvider,
+     * Launcher will be notified in Launcher#onSettingsChanged(String, String).
+     */
+    public static void startColorExtractionServiceIfNecessary(final Context context) {
+        // Run on a background thread, since the service is asynchronous anyway.
+        Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+            @Override
+            public void run() {
+                if (hasWallpaperIdChanged(context)) {
+                    context.startService(new Intent(context, ColorExtractionService.class));
+                }
+            }
+        });
+    }
+
+    private static boolean hasWallpaperIdChanged(Context context) {
+        if (!Utilities.isNycOrAbove()) {
+            // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here.
+            return false;
+        }
+        final SharedPreferences sharedPrefs = Utilities.getPrefs(context);
+        int wallpaperId = getWallpaperId(WallpaperManager.getInstance(context));
+        int savedWallpaperId = sharedPrefs.getInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, -1);
+        return wallpaperId != savedWallpaperId;
+    }
+
+    public static int getWallpaperId(WallpaperManager wallpaperManager) {
+        // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly.
+        try {
+            Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class);
+            return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM);
+        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
+            return -1;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 087670e..09a92a9 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -300,7 +300,7 @@
             mCurrentDragView = v;
 
             mContent.removeItem(mCurrentDragView);
-            mInfo.remove(mCurrentDragInfo);
+            mInfo.remove(mCurrentDragInfo, true);
             mDragInProgress = true;
             mItemAddedBackToSelfViaIcon = false;
         }
@@ -426,8 +426,9 @@
         // If our folder has too many items we prune them from the list. This is an issue
         // when upgrading from the old Folders implementation which could contain an unlimited
         // number of items.
+        // TODO: Remove this, as with multi-page folders, there will never be any overflow
         for (ShortcutInfo item: overflow) {
-            mInfo.remove(item);
+            mInfo.remove(item, false);
             LauncherModel.deleteItemFromDatabase(mLauncher, item);
         }
 
@@ -1330,7 +1331,7 @@
 
         // Temporarily suppress the listener, as we did all the work already here.
         mSuppressOnAdd = true;
-        mInfo.add(si);
+        mInfo.add(si, false);
         mSuppressOnAdd = false;
         // Clear the drag info, as it is no longer being dragged.
         mCurrentDragInfo = null;
@@ -1390,13 +1391,14 @@
         return mContent.iterateOverItems(new ItemOperator() {
 
             @Override
-            public boolean evaluate(ItemInfo info, View view, View parent) {
+            public boolean evaluate(ItemInfo info, View view) {
                 return info == item;
             }
         });
     }
 
-    public void onItemsChanged() {
+    @Override
+    public void onItemsChanged(boolean animate) {
         updateTextViewFocus();
     }
 
@@ -1409,7 +1411,7 @@
             mContent.iterateOverItems(new ItemOperator() {
 
                 @Override
-                public boolean evaluate(ItemInfo info, View view, View parent) {
+                public boolean evaluate(ItemInfo info, View view) {
                     mItemsInReadingOrder.add(view);
                     return false;
                 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index a9b707f..d76608a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -224,7 +224,7 @@
     }
 
     public void addItem(ShortcutInfo item) {
-        mInfo.add(item);
+        mInfo.add(item, true);
     }
 
     public void onDragEnter(ItemInfo dragInfo) {
@@ -492,12 +492,16 @@
         canvas.restore();
     }
 
+    /**
+     * This object represents a FolderIcon preview background. It stores drawing / measurement
+     * information, handles drawing, and animation (accept state <--> rest state).
+     */
     public static class PreviewBackground {
         private float mScale = 1f;
         private float mColorMultiplier = 1f;
         private Path mClipPath = new Path();
         private int mStrokeWidth;
-        private View mInvalidateDeligate;
+        private View mInvalidateDelegate;
 
         public int previewSize;
         private int basePreviewOffsetX;
@@ -507,6 +511,10 @@
         public int delegateCellX;
         public int delegateCellY;
 
+        // When the PreviewBackground is drawn under an icon (for creating a folder) the border
+        // should not occlude the icon
+        public boolean isClipping = true;
+
         // Drawing / animation configurations
         private static final float ACCEPT_SCALE_FACTOR = 1.25f;
         private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
@@ -519,9 +527,9 @@
 
         ValueAnimator mScaleAnimator;
 
-        public void setup(DisplayMetrics dm, DeviceProfile grid, View invalidateDeligate,
+        public void setup(DisplayMetrics dm, DeviceProfile grid, View invalidateDelegate,
                    int availableSpace, int topPadding) {
-            mInvalidateDeligate = invalidateDeligate;
+            mInvalidateDelegate = invalidateDelegate;
 
             final int previewSize = grid.folderIconSizePx;
             final int previewPadding = grid.folderIconPreviewPadding;
@@ -557,8 +565,8 @@
             mClipPath.reset();
             mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
 
-            if (mInvalidateDeligate != null) {
-                mInvalidateDeligate.invalidate();
+            if (mInvalidateDelegate != null) {
+                mInvalidateDelegate.invalidate();
             }
 
             if (mDrawingDelegate != null) {
@@ -566,8 +574,8 @@
             }
         }
 
-        void setInvalidateDeligate(View invalidateDeligate) {
-            mInvalidateDeligate = invalidateDeligate;
+        void setInvalidateDelegate(View invalidateDelegate) {
+            mInvalidateDelegate = invalidateDelegate;
             invalidate();
         }
 
@@ -742,7 +750,7 @@
 
     public void setFolderBackground(PreviewBackground bg) {
         mBackground = bg;
-        mBackground.setInvalidateDeligate(this);
+        mBackground.setInvalidateDelegate(this);
     }
 
     @Override
@@ -929,8 +937,9 @@
         }
     }
 
-    public void onItemsChanged() {
-        updateItemDrawingParams(true);
+    @Override
+    public void onItemsChanged(boolean animate) {
+        updateItemDrawingParams(animate);
         invalidate();
         requestLayout();
     }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index c25444e..1af1485 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -454,7 +454,7 @@
             for (int j = 0; j < page.getCountY(); j++) {
                 for (int i = 0; i < page.getCountX(); i++) {
                     View v = page.getChildAt(i, j);
-                    if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) {
+                    if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v)) {
                         return v;
                     }
                 }
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index fb9bbb2..3925c40 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -188,7 +188,7 @@
                 @Override
                 public void run() {
                     for (ShortcutInfo info : shortcuts) {
-                        workFolder.add(info);
+                        workFolder.add(info, false);
                     }
                 }
             });
@@ -200,7 +200,7 @@
 
             // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
             for (ShortcutInfo info : mWorkFolderApps) {
-                workFolder.add(info);
+                workFolder.add(info, false);
             }
 
             // Add the item to home screen and DB. This also generates an item id synchronously.