Merge "Fixed mw bug where resize frame is off when resizing." into ub-launcher3-master
diff --git a/res/xml/app_target_browser.xml b/res/xml/app_target_browser.xml
deleted file mode 100644
index d7c3ed5..0000000
--- a/res/xml/app_target_browser.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-    <favorite launcher:uri="http://www.example.com/" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_camera.xml b/res/xml/app_target_camera.xml
deleted file mode 100644
index f65a2b1..0000000
--- a/res/xml/app_target_camera.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
-    <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_email.xml b/res/xml/app_target_email.xml
deleted file mode 100644
index 44f0a40..0000000
--- a/res/xml/app_target_email.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
-    <favorite launcher:uri="mailto:" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_gallery.xml b/res/xml/app_target_gallery.xml
deleted file mode 100644
index c9d3492..0000000
--- a/res/xml/app_target_gallery.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
-    <favorite launcher:uri="#Intent;type=images/*;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_messenger.xml b/res/xml/app_target_messenger.xml
deleted file mode 100644
index 278eb5c..0000000
--- a/res/xml/app_target_messenger.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-    <favorite launcher:uri="sms:" />
-    <favorite launcher:uri="smsto:" />
-    <favorite launcher:uri="mms:" />
-    <favorite launcher:uri="mmsto:" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_phone.xml b/res/xml/app_target_phone.xml
deleted file mode 100644
index 5d6ca31..0000000
--- a/res/xml/app_target_phone.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-    <favorite launcher:uri="tel:123" />
-    <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index f7c5184..9c9dcc5 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -19,7 +19,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 import android.os.UserHandle;
 import android.util.Log;
 
@@ -33,23 +32,13 @@
 /**
  * Represents an app in AllAppsView.
  */
-public class AppInfo extends ItemInfo {
+public class AppInfo extends ItemInfoWithIcon {
 
     /**
      * The intent used to start the application.
      */
     public Intent intent;
 
-    /**
-     * A bitmap version of the application icon.
-     */
-    public Bitmap iconBitmap;
-
-    /**
-     * Indicates whether we're using a low res icon
-     */
-    boolean usingLowResIcon;
-
     public ComponentName componentName;
 
     /**
@@ -66,10 +55,6 @@
         return intent;
     }
 
-    protected Intent getRestoredIntent() {
-        return null;
-    }
-
     /**
      * Must not hold the Context.
      */
@@ -96,8 +81,8 @@
             isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
         }
 
-        iconCache.getTitleAndIcon(this, info, useLowResIcon);
         intent = makeLaunchIntent(context, info, user);
+        iconCache.getTitleAndIcon(this, info, useLowResIcon);
     }
 
     public AppInfo(AppInfo info) {
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 9b9d6e6..92da9b7 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -552,10 +552,10 @@
         public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
                 int minSize, int maxEnd, IntRange out) {
             applyDelta(moveStart, moveEnd, delta, out);
-            if (start < 0) {
+            if (out.start < 0) {
                 out.start = 0;
             }
-            if (end > maxEnd) {
+            if (out.end > maxEnd) {
                 out.end = maxEnd;
             }
             if (out.size() < minSize) {
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 40c5ed6..4fecc3d 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -28,6 +28,8 @@
 import android.view.ViewConfiguration;
 import android.widget.TextView;
 
+import com.android.launcher3.config.FeatureFlags;
+
 /**
  * The track and scrollbar that shows when you scroll the list.
  */
@@ -198,6 +200,11 @@
             case MotionEvent.ACTION_DOWN:
                 if (isNearThumb(downX, downY)) {
                     mTouchOffsetY = downY - mThumbOffsetY;
+                } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
+                        && mRv.supportsFastScrolling()
+                        && isNearScrollBar(downX)) {
+                    calcTouchOffsetAndPrepToFastScroll(downY, lastY);
+                    updateFastScrollSectionNameAndThumbOffset(lastY, y);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -207,28 +214,10 @@
                 if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
                         isNearThumb(downX, lastY) &&
                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
-                    mRv.getParent().requestDisallowInterceptTouchEvent(true);
-                    mIsDragging = true;
-                    if (mCanThumbDetach) {
-                        mIsThumbDetached = true;
-                    }
-                    mTouchOffsetY += (lastY - downY);
-                    animatePopupVisibility(true);
-                    showActiveScrollbar(true);
+                    calcTouchOffsetAndPrepToFastScroll(downY, lastY);
                 }
                 if (mIsDragging) {
-                    // Update the fastscroller section name at this touch position
-                    int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
-                    float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
-                    String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
-                    if (!sectionName.equals(mPopupSectionName)) {
-                        mPopupSectionName = sectionName;
-                        mPopupView.setText(sectionName);
-                    }
-                    animatePopupVisibility(!sectionName.isEmpty());
-                    updatePopupY(lastY);
-                    mLastTouchY = boundedY;
-                    setThumbOffsetY((int) mLastTouchY);
+                    updateFastScrollSectionNameAndThumbOffset(lastY, y);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -245,6 +234,32 @@
         }
     }
 
+    private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
+        mRv.getParent().requestDisallowInterceptTouchEvent(true);
+        mIsDragging = true;
+        if (mCanThumbDetach) {
+            mIsThumbDetached = true;
+        }
+        mTouchOffsetY += (lastY - downY);
+        animatePopupVisibility(true);
+        showActiveScrollbar(true);
+    }
+
+    private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+        // Update the fastscroller section name at this touch position
+        int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
+        float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
+        String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+        if (!sectionName.equals(mPopupSectionName)) {
+            mPopupSectionName = sectionName;
+            mPopupView.setText(sectionName);
+        }
+        animatePopupVisibility(!sectionName.isEmpty());
+        updatePopupY(lastY);
+        mLastTouchY = boundedY;
+        setThumbOffsetY((int) mLastTouchY);
+    }
+
     public void draw(Canvas canvas) {
         if (mThumbOffsetY < 0) {
             return;
@@ -277,7 +292,7 @@
     }
 
     /**
-     * Returns whether the specified points are near the scroll bar bounds.
+     * Returns whether the specified point is inside the thumb bounds.
      */
     public boolean isNearThumb(int x, int y) {
         int left = getDrawLeft();
@@ -286,6 +301,14 @@
         return mTmpRect.contains(x, y);
     }
 
+    /**
+     * Returns whether the specified x position is near the scroll bar.
+     */
+    public boolean isNearScrollBar(int x) {
+        int left = getDrawLeft();
+        return x >= left && x <= left + mMaxWidth;
+    }
+
     private void animatePopupVisibility(boolean visible) {
         if (mPopupVisible != visible) {
             mPopupVisible = visible;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5e9e7e2..b8b43c9 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -38,6 +38,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
+import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
@@ -51,7 +52,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView
-        implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView {
+        implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView, ItemInfoUpdateReceiver {
 
     private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
 
@@ -540,7 +541,8 @@
     /**
      * Applies the item info if it is same as what the view is pointing to currently.
      */
-    public void reapplyItemInfo(final ItemInfo info) {
+    @Override
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
         if (getTag() == info) {
             FastBitmapDrawable.State prevState = FastBitmapDrawable.State.NORMAL;
             if (mIcon instanceof FastBitmapDrawable) {
@@ -582,20 +584,8 @@
             mIconLoadRequest.cancel();
             mIconLoadRequest = null;
         }
-        if (getTag() instanceof AppInfo) {
-            AppInfo info = (AppInfo) getTag();
-            if (info.usingLowResIcon) {
-                mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
-                        .updateIconInBackground(BubbleTextView.this, info);
-            }
-        } else if (getTag() instanceof ShortcutInfo) {
-            ShortcutInfo info = (ShortcutInfo) getTag();
-            if (info.usingLowResIcon) {
-                mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
-                        .updateIconInBackground(BubbleTextView.this, info);
-            }
-        } else if (getTag() instanceof PackageItemInfo) {
-            PackageItemInfo info = (PackageItemInfo) getTag();
+        if (getTag() instanceof ItemInfoWithIcon) {
+            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
             if (info.usingLowResIcon) {
                 mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
                         .updateIconInBackground(BubbleTextView.this, info);
diff --git a/src/com/android/launcher3/CommonAppTypeParser.java b/src/com/android/launcher3/CommonAppTypeParser.java
deleted file mode 100644
index c2bd883..0000000
--- a/src/com/android/launcher3/CommonAppTypeParser.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.XmlResourceParser;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.Log;
-
-import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.util.Thunk;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * A class that parses content values corresponding to some common app types.
- */
-public class CommonAppTypeParser implements LayoutParserCallback {
-    private static final String TAG = "CommonAppTypeParser";
-
-    // Including TARGET_NONE
-    public static final int SUPPORTED_TYPE_COUNT = 7;
-
-    private static final int RESTORE_FLAG_BIT_SHIFT = 4;
-
-    public static final int TARGET_PHONE = 1;
-    public static final int TARGET_MESSENGER = 2;
-    public static final int TARGET_EMAIL = 3;
-    public static final int TARGET_BROWSER = 4;
-    public static final int TARGET_GALLERY = 5;
-    public static final int TARGET_CAMERA = 6;
-
-    private final long mItemId;
-    @Thunk final int mResId;
-    @Thunk final Context mContext;
-
-    ContentValues parsedValues;
-    Intent parsedIntent;
-    String parsedTitle;
-
-    public CommonAppTypeParser(long itemId, int itemType, Context context) {
-        mItemId = itemId;
-        mContext = context;
-        mResId = getResourceForItemType(itemType);
-    }
-
-    @Override
-    public long generateNewItemId() {
-        return mItemId;
-    }
-
-    @Override
-    public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
-        parsedValues = values;
-
-        // Remove unwanted values
-        values.put(Favorites.ICON_PACKAGE, (String) null);
-        values.put(Favorites.ICON_RESOURCE, (String) null);
-        values.put(Favorites.ICON, (byte[]) null);
-        return 1;
-    }
-
-    /**
-     * Tries to find a suitable app to the provided app type.
-     */
-    public boolean findDefaultApp() {
-        if (mResId == 0) {
-            return false;
-        }
-
-        parsedIntent = null;
-        parsedValues = null;
-        new MyLayoutParser().parseValues();
-        return (parsedValues != null) && (parsedIntent != null);
-    }
-
-    private class MyLayoutParser extends DefaultLayoutParser {
-
-        public MyLayoutParser() {
-            super(CommonAppTypeParser.this.mContext, null, CommonAppTypeParser.this,
-                    CommonAppTypeParser.this.mContext.getResources(), mResId, TAG_RESOLVE);
-        }
-
-        @Override
-        protected long addShortcut(String title, Intent intent, int type) {
-            if (type == Favorites.ITEM_TYPE_APPLICATION) {
-                parsedIntent = intent;
-                parsedTitle = title;
-            }
-            return super.addShortcut(title, intent, type);
-        }
-
-        public void parseValues() {
-            XmlResourceParser parser = mSourceRes.getXml(mLayoutId);
-            try {
-                beginDocument(parser, mRootTag);
-                new ResolveParser().parseAndAdd(parser);
-            } catch (IOException | XmlPullParserException e) {
-                Log.e(TAG, "Unable to parse default app info", e);
-            }
-            parser.close();
-        }
-    }
-
-    public static int getResourceForItemType(int type) {
-        switch (type) {
-            case TARGET_PHONE:
-                return R.xml.app_target_phone;
-
-            case TARGET_MESSENGER:
-                return R.xml.app_target_messenger;
-
-            case TARGET_EMAIL:
-                return R.xml.app_target_email;
-
-            case TARGET_BROWSER:
-                return R.xml.app_target_browser;
-
-            case TARGET_GALLERY:
-                return R.xml.app_target_gallery;
-
-            case TARGET_CAMERA:
-                return R.xml.app_target_camera;
-
-            default:
-                return 0;
-        }
-    }
-
-    public static int encodeItemTypeToFlag(int itemType) {
-        return itemType << RESTORE_FLAG_BIT_SHIFT;
-    }
-
-    public static int decodeItemTypeFromFlag(int flag) {
-        return (flag & ShortcutInfo.FLAG_RESTORED_APP_TYPE) >> RESTORE_FLAG_BIT_SHIFT;
-    }
-
-}
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index ef28d1e..05911ab 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -54,11 +54,6 @@
         super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
     }
 
-    public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
-            LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag) {
-        super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag);
-    }
-
     @Override
     protected HashMap<String, TagParser> getFolderElementsMap() {
         return getFolderElementsMap(mSourceRes);
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 3557447..7c50a5c 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -41,6 +41,7 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -51,6 +52,7 @@
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.SQLiteCacheHelper;
 import com.android.launcher3.util.Thunk;
 
@@ -404,21 +406,16 @@
      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
      * @return a request ID that can be used to cancel the request.
      */
-    public IconLoadRequest updateIconInBackground(final BubbleTextView caller, final ItemInfo info) {
+    public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
+            final ItemInfoWithIcon info) {
         Runnable request = new Runnable() {
 
             @Override
             public void run() {
-                if (info instanceof AppInfo) {
-                    getTitleAndIcon((AppInfo) info, null, false);
-                } else if (info instanceof ShortcutInfo) {
-                    ShortcutInfo st = (ShortcutInfo) info;
-                    getTitleAndIcon(st,
-                            st.promisedIntent != null ? st.promisedIntent : st.intent,
-                            st.user, false);
+                if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+                    getTitleAndIcon(info, false);
                 } else if (info instanceof PackageItemInfo) {
-                    PackageItemInfo pti = (PackageItemInfo) info;
-                    getTitleAndIconForApp(pti, false);
+                    getTitleAndIconForApp((PackageItemInfo) info, false);
                 }
                 mMainThreadExecutor.execute(new Runnable() {
 
@@ -433,85 +430,55 @@
         return new IconLoadRequest(request, mWorkerHandler);
     }
 
-    private Bitmap getNonNullIcon(CacheEntry entry, UserHandle user) {
-        return entry.icon == null ? getDefaultIcon(user) : entry.icon;
-    }
-
-    /**
-     * Fill in "application" with the icon and label for "info."
-     */
-    public synchronized void getTitleAndIcon(AppInfo application,
-            LauncherActivityInfoCompat info, boolean useLowResIcon) {
-        UserHandle user = info == null ? application.user : info.getUser();
-        CacheEntry entry = cacheLocked(application.componentName, info, user,
-                false, useLowResIcon);
-        application.title = Utilities.trim(entry.title);
-        application.contentDescription = entry.contentDescription;
-        application.iconBitmap = getNonNullIcon(entry, user);
-        application.usingLowResIcon = entry.isLowResIcon;
-    }
-
     /**
      * Updates {@param application} only if a valid entry is found.
      */
     public synchronized void updateTitleAndIcon(AppInfo application) {
-        CacheEntry entry = cacheLocked(application.componentName, null, application.user,
-                false, application.usingLowResIcon);
+        CacheEntry entry = cacheLocked(application.componentName,
+                Provider.<LauncherActivityInfoCompat>of(null),
+                application.user, false, application.usingLowResIcon);
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
-            application.title = Utilities.trim(entry.title);
-            application.contentDescription = entry.contentDescription;
-            application.iconBitmap = entry.icon;
-            application.usingLowResIcon = entry.isLowResIcon;
+            applyCacheEntry(entry, application);
         }
     }
 
     /**
-     * Returns a high res icon for the given intent and user
+     * Fill in {@param info} with the icon and label for {@param activityInfo}
      */
-    public synchronized Bitmap getIcon(Intent intent, UserHandle user) {
-        ComponentName component = intent.getComponent();
-        // null info means not installed, but if we have a component from the intent then
-        // we should still look in the cache for restored app icons.
-        if (component == null) {
-            return getDefaultIcon(user);
-        }
-
-        LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
-        CacheEntry entry = cacheLocked(component, launcherActInfo, user, true, false /* useLowRes */);
-        return entry.icon;
+    public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
+            LauncherActivityInfoCompat activityInfo, boolean useLowResIcon) {
+        // If we already have activity info, no need to use package icon
+        getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
     }
 
     /**
-     * Fill in {@param shortcutInfo} with the icon and label for {@param intent}. If the
+     * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
-    public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent,
-            UserHandle user, boolean useLowResIcon) {
-        ComponentName component = intent.getComponent();
+    public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
-        if (component == null) {
-            shortcutInfo.iconBitmap = getDefaultIcon(user);
-            shortcutInfo.title = "";
-            shortcutInfo.contentDescription = "";
-            shortcutInfo.usingLowResIcon = false;
+        if (info.getTargetComponent() == null) {
+            info.iconBitmap = getDefaultIcon(info.user);
+            info.title = "";
+            info.contentDescription = "";
+            info.usingLowResIcon = false;
         } else {
-            LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
-            getTitleAndIcon(shortcutInfo, component, info, user, true, useLowResIcon);
+            getTitleAndIcon(info, new ActivityInfoProvider(info.getIntent(), info.user),
+                    true, useLowResIcon);
         }
     }
 
     /**
      * Fill in {@param shortcutInfo} with the icon and label for {@param info}
      */
-    public synchronized void getTitleAndIcon(
-            ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
-            UserHandle user, boolean usePkgIcon, boolean useLowResIcon) {
-        CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
-        shortcutInfo.iconBitmap = getNonNullIcon(entry, user);
-        shortcutInfo.title = Utilities.trim(entry.title);
-        shortcutInfo.contentDescription = entry.contentDescription;
-        shortcutInfo.usingLowResIcon = entry.isLowResIcon;
+    private synchronized void getTitleAndIcon(
+            @NonNull ItemInfoWithIcon infoInOut,
+            @NonNull Provider<LauncherActivityInfoCompat> activityInfoProvider,
+            boolean usePkgIcon, boolean useLowResIcon) {
+        CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
+                infoInOut.user, usePkgIcon, useLowResIcon);
+        applyCacheEntry(entry, infoInOut);
     }
 
     /**
@@ -521,10 +488,14 @@
             PackageItemInfo infoInOut, boolean useLowResIcon) {
         CacheEntry entry = getEntryForPackageLocked(
                 infoInOut.packageName, infoInOut.user, useLowResIcon);
-        infoInOut.title = Utilities.trim(entry.title);
-        infoInOut.contentDescription = entry.contentDescription;
-        infoInOut.iconBitmap = getNonNullIcon(entry, infoInOut.user);
-        infoInOut.usingLowResIcon = entry.isLowResIcon;
+        applyCacheEntry(entry, infoInOut);
+    }
+
+    private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
+        info.title = Utilities.trim(entry.title);
+        info.contentDescription = entry.contentDescription;
+        info.iconBitmap = entry.icon == null ? getDefaultIcon(info.user) : entry.icon;
+        info.usingLowResIcon = entry.isLowResIcon;
     }
 
     public synchronized Bitmap getDefaultIcon(UserHandle user) {
@@ -542,7 +513,9 @@
      * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
      * This method is not thread safe, it must be called from a synchronized method.
      */
-    protected CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
+    protected CacheEntry cacheLocked(
+            @NonNull ComponentName componentName,
+            @NonNull Provider<LauncherActivityInfoCompat> infoProvider,
             UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
@@ -551,7 +524,13 @@
             mCache.put(cacheKey, entry);
 
             // Check the DB first.
+            LauncherActivityInfoCompat info = null;
+            boolean providerFetchedOnce = false;
+
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
+                info = infoProvider.get();
+                providerFetchedOnce = true;
+
                 if (info != null) {
                     entry.icon = LauncherIcons.createBadgedIconBitmap(
                             mIconProvider.getIcon(info, mIconDpi), info.getUser(),
@@ -576,9 +555,15 @@
                 }
             }
 
-            if (TextUtils.isEmpty(entry.title) && info != null) {
-                entry.title = info.getLabel();
-                entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+            if (TextUtils.isEmpty(entry.title)) {
+                if (info == null && !providerFetchedOnce) {
+                    info = infoProvider.get();
+                    providerFetchedOnce = true;
+                }
+                if (info != null) {
+                    entry.title = info.getLabel();
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+                }
             }
         }
         return entry;
@@ -668,37 +653,6 @@
         return entry;
     }
 
-    /**
-     * Pre-load an icon into the persistent cache.
-     *
-     * <P>Queries for a component that does not exist in the package manager
-     * will be answered by the persistent cache.
-     *
-     * @param componentName the icon should be returned for this component
-     * @param icon the icon to be persisted
-     * @param dpi the native density of the icon
-     */
-    public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
-            long userSerial, InvariantDeviceProfile idp) {
-        // TODO rescale to the correct native DPI
-        try {
-            PackageManager packageManager = mContext.getPackageManager();
-            packageManager.getActivityIcon(componentName);
-            // component is present on the system already, do nothing
-            return;
-        } catch (PackageManager.NameNotFoundException e) {
-            // pass
-        }
-
-        icon = Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true);
-        Bitmap lowResIcon = generateLowResIcon(icon, Color.TRANSPARENT);
-        ContentValues values = newContentValues(icon, lowResIcon, label,
-                componentName.getPackageName());
-        values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
-        values.put(IconDB.COLUMN_USER, userSerial);
-        mIconDb.insertOrReplace(values);
-    }
-
     private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
         Cursor c = null;
         try {
@@ -885,4 +839,28 @@
             return null;
         }
     }
+
+    private class ActivityInfoProvider extends Provider<LauncherActivityInfoCompat> {
+
+        private final Intent mIntent;
+        private final UserHandle mUser;
+
+        public ActivityInfoProvider(Intent intent, UserHandle user) {
+            mIntent = intent;
+            mUser = user;
+        }
+
+        @Override
+        public LauncherActivityInfoCompat get() {
+            return mLauncherApps.resolveActivity(mIntent, mUser);
+        }
+    }
+
+    /**
+     * Interface for receiving itemInfo with high-res icon.
+     */
+    public interface ItemInfoUpdateReceiver {
+
+        void reapplyItemInfo(ItemInfoWithIcon info);
+    }
 }
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
new file mode 100644
index 0000000..a3d8c6a
--- /dev/null
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.graphics.Bitmap;
+
+/**
+ * Represents an ItemInfo which also holds an icon.
+ */
+public abstract class ItemInfoWithIcon extends ItemInfo {
+
+    /**
+     * A bitmap version of the application icon.
+     */
+    public Bitmap iconBitmap;
+
+    /**
+     * Indicates whether we're using a low res icon
+     */
+    public boolean usingLowResIcon;
+
+    protected ItemInfoWithIcon() { }
+
+    protected ItemInfoWithIcon(ItemInfo info) {
+        super(info);
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7dd8450..8e28912 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3799,7 +3799,7 @@
 
             for (ShortcutInfo si : removed) {
                 if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                    removedDeepShortcuts.add(ShortcutKey.fromShortcutInfo(si));
+                    removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si));
                 } else {
                     removedComponents.add(si.getTargetComponent());
                 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 0dc91e3..daa910f 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -1338,7 +1338,6 @@
                                 user = allUsers.get(serialNumber);
                                 int promiseType = c.getInt(restoredIndex);
                                 int disabledState = 0;
-                                boolean itemReplaced = false;
                                 targetPackage = null;
                                 if (user == null) {
                                     // User has been deleted remove the item.
@@ -1406,25 +1405,6 @@
                                                 values.put(LauncherSettings.Favorites.RESTORED,
                                                         promiseType);
                                                 updateItem(id, values);
-                                            } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
-                                                // This is a common app. Try to replace this.
-                                                int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
-                                                CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
-                                                if (parser.findDefaultApp()) {
-                                                    // Default app found. Replace it.
-                                                    intent = parser.parsedIntent;
-                                                    cn = intent.getComponent();
-                                                    ContentValues values = parser.parsedValues;
-                                                    values.put(LauncherSettings.Favorites.RESTORED, 0);
-                                                    updateItem(id, values);
-                                                    restored = false;
-                                                    itemReplaced = true;
-
-                                                } else {
-                                                    FileLog.d(TAG, "Unrestored package removed: " + cn);
-                                                    itemsToRemove.add(id);
-                                                    continue;
-                                                }
                                             } else {
                                                 FileLog.d(TAG, "Unrestored package removed: " + cn);
                                                 itemsToRemove.add(id);
@@ -1464,16 +1444,7 @@
                                 boolean useLowResIcon = container >= 0 &&
                                         c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
 
-                                if (itemReplaced) {
-                                    if (user.equals(Process.myUserHandle())) {
-                                        info = getAppShortcutInfo(intent, user, null,
-                                                cursorIconInfo, false, useLowResIcon);
-                                    } else {
-                                        // Don't replace items for other profiles.
-                                        itemsToRemove.add(id);
-                                        continue;
-                                    }
-                                } else if (restored) {
+                                if (restored) {
                                     if (user.equals(Process.myUserHandle())) {
                                         info = getRestoredItemInfo(c, intent,
                                                 promiseType, itemType, cursorIconInfo);
@@ -1794,8 +1765,7 @@
                     for (ShortcutInfo info : folder.contents) {
                         if (info.usingLowResIcon &&
                                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                            mIconCache.getTitleAndIcon(
-                                    info, info.getPromisedIntent(), info.user, false);
+                            mIconCache.getTitleAndIcon(info, false);
                         }
                         pos ++;
                         if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
@@ -2520,10 +2490,12 @@
             int promiseType, int itemType, CursorIconInfo iconInfo) {
         final ShortcutInfo info = new ShortcutInfo();
         info.user = Process.myUserHandle();
+        info.promisedIntent = intent;
+
         info.iconBitmap = iconInfo.loadIcon(c, info);
         // the fallback icon
         if (info.iconBitmap == null) {
-            mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
+            mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
         }
 
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
@@ -2541,7 +2513,6 @@
 
         info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         info.itemType = itemType;
-        info.promisedIntent = intent;
         info.status = promiseType;
         return info;
     }
@@ -2592,7 +2563,11 @@
         }
 
         final ShortcutInfo info = new ShortcutInfo();
-        mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
+        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        info.user = user;
+        info.intent = newIntent;
+
+        mIconCache.getTitleAndIcon(info, lai, useLowResIcon);
         if (mIconCache.isDefaultIcon(info.iconBitmap, user) && c != null) {
             Bitmap icon = iconInfo.loadIcon(c);
             info.iconBitmap = icon != null ? icon : mIconCache.getDefaultIcon(user);
@@ -2612,8 +2587,6 @@
             info.title = componentName.getClassName();
         }
 
-        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-        info.user = user;
         info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         return info;
     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 7aed155..76e2073 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -563,7 +563,7 @@
     protected boolean computeScrollHelper(boolean shouldInvalidate) {
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
-            if (getScrollX() != mScroller.getCurrX()
+            if (getUnboundedScrollX() != mScroller.getCurrX()
                     || getScrollY() != mScroller.getCurrY()) {
                 float scaleX = mFreeScroll ? getScaleX() : 1f;
                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
@@ -1945,6 +1945,7 @@
         // Trigger a compute() to finish switching pages if necessary
         if (immediate) {
             computeScroll();
+            pageEndTransition();
         }
 
         invalidate();
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 2976807..3256df6 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -35,8 +34,11 @@
 import android.view.View.OnClickListener;
 
 import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.model.PackageItemInfo;
 
-public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
+        implements OnClickListener, ItemInfoUpdateReceiver {
     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     private static final float MIN_SATUNATION = 0.7f;
 
@@ -47,7 +49,6 @@
     private OnClickListener mClickListener;
     private final LauncherAppWidgetInfo mInfo;
     private final int mStartState;
-    private final Intent mIconLookupIntent;
     private final boolean mDisabledForSafeMode;
     private Launcher mLauncher;
 
@@ -68,7 +69,6 @@
         mLauncher = Launcher.getLauncher(context);
         mInfo = info;
         mStartState = info.restoreStatus;
-        mIconLookupIntent = new Intent().setComponent(info.providerName);
         mDisabledForSafeMode = disabledForSafeMode;
 
         mPaint = new TextPaint();
@@ -79,9 +79,13 @@
         setWillNotDraw(false);
 
         setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
-        updateIcon(cache);
         updateAppWidget(null);
         setOnClickListener(mLauncher);
+
+        // Load icon
+        PackageItemInfo item = new PackageItemInfo(info.providerName.getPackageName());
+        item.user = info.user;
+        cache.updateIconInBackground(this, item);
     }
 
     @Override
@@ -117,8 +121,9 @@
         mDrawableSizeChanged = true;
     }
 
-    private void updateIcon(IconCache cache) {
-        Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user);
+    @Override
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
+        Bitmap icon = info.iconBitmap;
         if (mIcon == icon) {
             return;
         }
@@ -157,6 +162,7 @@
             }
             mDrawableSizeChanged = true;
         }
+        invalidate();
     }
 
     private void updateSettingColor() {
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index b9010c7..7f9bc2ba 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -37,7 +36,7 @@
 /**
  * Represents a launchable icon on the workspaces and in folders.
  */
-public class ShortcutInfo extends ItemInfo {
+public class ShortcutInfo extends ItemInfoWithIcon {
 
     public static final int DEFAULT = 0;
 
@@ -69,6 +68,7 @@
      * Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
      * Upto 15 different types supported.
      */
+    @Deprecated
     public static final int FLAG_RESTORED_APP_TYPE = 0B0011110000;
 
     /**
@@ -77,22 +77,12 @@
     public Intent intent;
 
     /**
-     * Indicates whether we're using a low res icon
-     */
-    public boolean usingLowResIcon;
-
-    /**
      * If isShortcut=true and customIcon=false, this contains a reference to the
      * shortcut icon as an application's resource.
      */
     public Intent.ShortcutIconResource iconResource;
 
     /**
-     * The application icon.
-     */
-    public Bitmap iconBitmap;
-
-    /**
      * Indicates that the icon is disabled due to safe mode restrictions.
      */
     public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
@@ -152,16 +142,6 @@
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
 
-    @Override
-    public Intent getIntent() {
-        return intent;
-    }
-
-    /** Returns {@link #promisedIntent}, or {@link #intent} if promisedIntent is null. */
-    public Intent getPromisedIntent() {
-        return promisedIntent != null ? promisedIntent : intent;
-    }
-
     public ShortcutInfo(ShortcutInfo info) {
         super(info);
         title = info.title;
@@ -197,7 +177,7 @@
     void onAddToDatabase(ContentWriter writer) {
         super.onAddToDatabase(writer);
         writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
-                .put(LauncherSettings.BaseLauncherColumns.INTENT, getPromisedIntent())
+                .put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent())
                 .put(LauncherSettings.Favorites.RESTORED, status);
 
         if (!usingLowResIcon) {
@@ -210,8 +190,12 @@
         }
     }
 
-    public ComponentName getTargetComponent() {
-        return getPromisedIntent().getComponent();
+    /**
+     *  Returns {@link #promisedIntent}, or {@link #intent} if promisedIntent is null.
+     */
+    @Override
+    public Intent getIntent() {
+        return promisedIntent != null ? promisedIntent : intent;
     }
 
     public boolean hasStatusFlag(int flag) {
@@ -270,19 +254,14 @@
         AppInfo appInfo = new AppInfo();
         appInfo.user = user;
         appInfo.componentName = shortcutInfo.getActivity();
-        try {
-            cache.getTitleAndIcon(appInfo, shortcutInfo.getActivityInfo(context), false);
-        } catch (NullPointerException e) {
-            // This may happen when we fail to load the activity info. Worst case ignore badging.
-            return LauncherIcons.badgeIconForUser(unbadgedBitmap, user, context);
-        }
+        cache.getTitleAndIcon(appInfo, false);
         return LauncherIcons.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
     }
 
     /** Returns the ShortcutInfo id associated with the deep shortcut. */
     public String getDeepShortcutId() {
         return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
-                getPromisedIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
+                getIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
     }
 
     @Override
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fccff6c..cf6b025 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1386,12 +1386,10 @@
         // it's own settling, and every gesture to the overlay should be self-contained and start
         // from 0, so we zero it out here.
         if (isScrollingOverlay()) {
-            int finalScroll = mIsRtl ? mMaxScrollX : 0;
-
             // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll
-            // interaction when we call scrollTo.
+            // interaction when we call snapToPageImmediately.
             mWasInOverscroll = false;
-            scrollTo(finalScroll, getScrollY());
+            snapToPageImmediately(0);
         } else {
             super.snapToDestination();
         }
diff --git a/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java b/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java
deleted file mode 100644
index 4dd05bb..0000000
--- a/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-
-/**
- * {@link LauncherActivityInfoCompat} which loads its data only when needed.
- */
-public class DeferredLauncherActivityInfo extends LauncherActivityInfoCompat {
-
-    private final ComponentName mComponent;
-    private final UserHandle mUser;
-    private final Context mContext;
-
-    private LauncherActivityInfoCompat mActualInfo;
-
-    public DeferredLauncherActivityInfo(
-            ComponentName component, UserHandle user, Context context) {
-        mComponent = component;
-        mUser = user;
-        mContext = context;
-    }
-
-    @Override
-    public ComponentName getComponentName() {
-        return mComponent;
-    }
-
-    @Override
-    public UserHandle getUser() {
-        return mUser;
-    }
-
-    private synchronized LauncherActivityInfoCompat getActualInfo() {
-        if (mActualInfo == null) {
-            Intent intent = new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_LAUNCHER)
-                    .setComponent(mComponent);
-            mActualInfo = LauncherAppsCompat.getInstance(mContext).resolveActivity(intent, mUser);
-        }
-        return mActualInfo;
-    }
-
-    @Override
-    public CharSequence getLabel() {
-        return getActualInfo().getLabel();
-    }
-
-    @Override
-    public Drawable getIcon(int density) {
-        return getActualInfo().getIcon(density);
-    }
-
-    @Override
-    public ApplicationInfo getApplicationInfo() {
-        return getActualInfo().getApplicationInfo();
-    }
-
-    @Override
-    public long getFirstInstallTime() {
-        return getActualInfo().getFirstInstallTime();
-    }
-}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 05f43af..6dc5a36 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -123,7 +123,7 @@
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                     // Decrement pinned shortcut count
-                    ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+                    ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
                     MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
                     Context context = LauncherAppState.getInstance().getContext();
                     if ((count == null || --count.value == 0)
@@ -155,7 +155,7 @@
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                 // Increment the count for the given shortcut
-                ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+                ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
                 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
                 if (count == null) {
                     count = new MutableInt(1);
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 9d693a4..46130fc 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -65,7 +65,7 @@
                     if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                             && isValidShortcut(si) && cn != null
                             && mPackages.contains(cn.getPackageName())) {
-                        iconCache.getTitleAndIcon(si, si.getPromisedIntent(), si.user, si.usingLowResIcon);
+                        iconCache.getTitleAndIcon(si, si.usingLowResIcon);
                         updatedShortcuts.add(si);
                     }
                 }
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 00470e1..baeaa94 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -16,31 +16,19 @@
 
 package com.android.launcher3.model;
 
-import android.graphics.Bitmap;
-
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 
 /**
  * Represents a {@link Package} in the widget tray section.
  */
-public class PackageItemInfo extends ItemInfo {
+public class PackageItemInfo extends ItemInfoWithIcon {
 
     /**
-     * A bitmap version of the application icon.
-     */
-    public Bitmap iconBitmap;
-
-    /**
-     * Indicates whether we're using a low res icon.
-     */
-    public boolean usingLowResIcon;
-
-    /**
-     * Package name of the {@link ItemInfo}.
+     * Package name of the {@link PackageItemInfo}.
      */
     public String packageName;
 
-    PackageItemInfo(String packageName) {
+    public PackageItemInfo(String packageName) {
         this.packageName = packageName;
     }
 
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index bafa95b..f5de650 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -251,15 +251,13 @@
                                 si.status = ShortcutInfo.DEFAULT;
                                 infoUpdated = true;
                                 if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
-                                    iconCache.getTitleAndIcon(si, si.getPromisedIntent(),
-                                            si.user, si.usingLowResIcon);
+                                    iconCache.getTitleAndIcon(si, si.usingLowResIcon);
                                 }
                             }
 
                             if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
                                     && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                iconCache.getTitleAndIcon(
-                                        si, si.getPromisedIntent(), si.user, si.usingLowResIcon);
+                                iconCache.getTitleAndIcon(si, si.usingLowResIcon);
                                 infoUpdated = true;
                             }
 
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 3750a7e..67bec64 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -61,7 +61,7 @@
         for (ItemInfo itemInfo : dataModel.itemsIdMap) {
             if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 ShortcutInfo si = (ShortcutInfo) itemInfo;
-                if (si.getPromisedIntent().getPackage().equals(mPackageName)
+                if (si.getIntent().getPackage().equals(mPackageName)
                         && si.user.equals(mUser)) {
                     idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
                 }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 15496b8..a214a29 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -76,8 +76,7 @@
                     && mUser.equals(itemInfo.user)) {
                 ShortcutInfo si = (ShortcutInfo) itemInfo;
                 if (isUserUnlocked) {
-                    ShortcutInfoCompat shortcut =
-                            pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
+                    ShortcutInfoCompat shortcut = pinnedShortcuts.get(ShortcutKey.fromItemInfo(si));
                     // We couldn't verify the shortcut during loader. If its no longer available
                     // (probably due to clear data), delete the workspace item as well
                     if (shortcut == null) {
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index acc632c..dac2160 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -25,8 +25,6 @@
 import android.os.UserHandle;
 
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.compat.DeferredLauncherActivityInfo;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
 /**
@@ -122,8 +120,4 @@
     public String toString() {
         return mShortcutInfo.toString();
     }
-
-    public LauncherActivityInfoCompat getActivityInfo(Context context) {
-        return new DeferredLauncherActivityInfo(getActivity(), getUserHandle(), context);
-    }
 }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 8f72666..e86bfb2 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -4,7 +4,7 @@
 import android.content.Intent;
 import android.os.UserHandle;
 
-import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.util.ComponentKey;
 
 /**
@@ -32,7 +32,7 @@
         return new ShortcutKey(intent.getPackage(), user, shortcutId);
     }
 
-    public static ShortcutKey fromShortcutInfo(ShortcutInfo info) {
-        return fromIntent(info.getPromisedIntent(), info.user);
+    public static ShortcutKey fromItemInfo(ItemInfo info) {
+        return fromIntent(info.getIntent(), info.user);
     }
 }
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index b6e0e6e..42de284 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -100,7 +100,7 @@
             @Override
             public boolean matches(ItemInfo info, ComponentName cn) {
                 return info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
-                        keys.contains(ShortcutKey.fromShortcutInfo((ShortcutInfo) info));
+                        keys.contains(ShortcutKey.fromItemInfo(info));
             }
         };
     }
diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index 99d2654..4cad836 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -37,4 +37,6 @@
     public static final boolean PULLDOWN_SEARCH = false;
     // When enabled the status bar may show dark icons based on the top of the wallpaper.
     public static final boolean LIGHT_STATUS_BAR = false;
+    // When enabled allows to use any point on the fast scrollbar to start dragging.
+    public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index 466146e..5103ced 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -17,9 +17,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-#LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
 
-#LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 21
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index f23a574..1d092ab 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -8,6 +8,7 @@
 import android.graphics.Bitmap.Config;
 import android.os.Process;
 import android.os.UserHandle;
+import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.test.ProviderTestCase2;
 
@@ -24,6 +25,7 @@
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.TestLauncherProvider;
 
 import org.mockito.ArgumentCaptor;
@@ -184,9 +186,10 @@
         }
 
         @Override
-        protected CacheEntry cacheLocked(ComponentName componentName,
-                LauncherActivityInfoCompat info, UserHandle user,
-                boolean usePackageIcon, boolean useLowResIcon) {
+        protected CacheEntry cacheLocked(
+                @NonNull ComponentName componentName,
+                @NonNull Provider<LauncherActivityInfoCompat> infoProvider,
+                UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
             CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
             if (entry == null) {
                 entry = new CacheEntry();