Merge "Removing all dependencies of launcher code on wallpaper-picker" into ub-launcher3-burnaby
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 82ee8f2..8c837cc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -216,15 +216,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.launcher3.PackageChangedReceiver" >
-            <intent-filter>
-                <action android:name="android.intent.action.PACKAGE_CHANGED"/>
-                <action android:name="android.intent.action.PACKAGE_REPLACED"/>
-                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
-                <data android:scheme="package"></data>
-            </intent-filter>
-        </receiver>
-
         <receiver android:name="com.android.launcher3.StartupReceiver" >
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 1955547..58bcf1d 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -29,10 +29,8 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Process;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
@@ -48,90 +46,6 @@
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * A simple callback interface which also provides the results of the task.
- */
-interface AsyncTaskCallback {
-    void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
-}
-
-/**
- * The data needed to perform either of the custom AsyncTasks.
- */
-class AsyncTaskPageData {
-    enum Type {
-        LoadWidgetPreviewData
-    }
-
-    AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
-            AsyncTaskCallback postR, WidgetPreviewLoader w) {
-        page = p;
-        items = l;
-        generatedImages = new ArrayList<Bitmap>();
-        maxImageWidth = cw;
-        maxImageHeight = ch;
-        doInBackgroundCallback = bgR;
-        postExecuteCallback = postR;
-        widgetPreviewLoader = w;
-    }
-    void cleanup(boolean cancelled) {
-        // Clean up any references to source/generated bitmaps
-        if (generatedImages != null) {
-            if (cancelled) {
-                for (int i = 0; i < generatedImages.size(); i++) {
-                    widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i));
-                }
-            }
-            generatedImages.clear();
-        }
-    }
-    int page;
-    ArrayList<Object> items;
-    ArrayList<Bitmap> sourceImages;
-    ArrayList<Bitmap> generatedImages;
-    int maxImageWidth;
-    int maxImageHeight;
-    AsyncTaskCallback doInBackgroundCallback;
-    AsyncTaskCallback postExecuteCallback;
-    WidgetPreviewLoader widgetPreviewLoader;
-}
-
-/**
- * A generic template for an async task used in AppsCustomize.
- */
-class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
-    AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
-        page = p;
-        threadPriority = Process.THREAD_PRIORITY_DEFAULT;
-        dataType = ty;
-    }
-    @Override
-    protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
-        if (params.length != 1) return null;
-        // Load each of the widget previews in the background
-        params[0].doInBackgroundCallback.run(this, params[0]);
-        return params[0];
-    }
-    @Override
-    protected void onPostExecute(AsyncTaskPageData result) {
-        // All the widget previews are loaded, so we can just callback to inflate the page
-        result.postExecuteCallback.run(this, result);
-    }
-
-    void setThreadPriority(int p) {
-        threadPriority = p;
-    }
-    void syncThreadPriority() {
-        Process.setThreadPriority(threadPriority);
-    }
-
-    // The page that this async task is associated with
-    AsyncTaskPageData.Type dataType;
-    int page;
-    int threadPriority;
-}
 
 /**
  * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
@@ -142,6 +56,7 @@
     static final String TAG = "AppsCustomizePagedView";
 
     private static Rect sTmpRect = new Rect();
+    private static final int[] sTempPosArray = new int[2];
 
     /**
      * The different content types that this paged view can show.
@@ -171,10 +86,6 @@
     @Thunk int mWidgetCountX, mWidgetCountY;
     private int mNumWidgetPages;
 
-    // Previews & outlines
-    ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
-    private static final int sPageSleepDelay = 200;
-
     private final PagedViewKeyListener mKeyListener = new PagedViewKeyListener();
 
     private Runnable mInflateWidgetRunnable = null;
@@ -192,11 +103,7 @@
     private Toast mWidgetInstructionToast;
 
     // Deferral of loading widget previews during launcher transitions
-    @Thunk boolean mInTransition;
-    private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
-        new ArrayList<AsyncTaskPageData>();
-    @Thunk ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
-        new ArrayList<Runnable>();
+    private boolean mInTransition;
 
     WidgetPreviewLoader mWidgetPreviewLoader;
 
@@ -209,7 +116,6 @@
         mPackageManager = context.getPackageManager();
         mWidgets = new ArrayList<>();
         mIconCache = (LauncherAppState.getInstance()).getIconCache();
-        mRunningTasks = new ArrayList<>();
 
         // Save the default widget preview background
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
@@ -253,7 +159,7 @@
 
     WidgetPreviewLoader getWidgetPreviewLoader() {
         if (mWidgetPreviewLoader == null) {
-            mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
+            mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
         }
         return mWidgetPreviewLoader;
     }
@@ -484,8 +390,7 @@
                 info.boundWidget = hostView;
                 mWidgetCleanupState = WIDGET_INFLATED;
                 hostView.setVisibility(INVISIBLE);
-                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
-                        info.spanY, info, false);
+                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false);
 
                 // We want the first widget layout to be the correct size. This will be important
                 // for width size reporting to the AppWidgetManager.
@@ -554,7 +459,7 @@
         }
     }
 
-    private boolean beginDraggingWidget(View v) {
+    private boolean beginDraggingWidget(PagedViewWidget v) {
         mDraggingWidget = true;
         // Get the widget preview as the drag representation
         ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
@@ -582,10 +487,7 @@
 
             PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
             createItemInfo = createWidgetInfo;
-            int spanX = createItemInfo.spanX;
-            int spanY = createItemInfo.spanY;
-            int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
-                    createWidgetInfo, true);
+            int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
 
             FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
             float minScale = 1.25f;
@@ -594,10 +496,9 @@
             int[] previewSizeBeforeScale = new int[1];
             preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
                     maxWidth, null, previewSizeBeforeScale);
-
             // Compare the size of the drag preview to the preview in the AppsCustomize tray
             int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
-                    getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX));
+                    v.getActualItemWidth());
             scale = previewWidthInAppsCustomize / (float) preview.getWidth();
 
             // The bitmap in the AppsCustomize tray is always the the same size, so there
@@ -637,7 +538,7 @@
         if (!super.beginDragging(v)) return false;
 
         if (v instanceof PagedViewWidget) {
-            if (!beginDraggingWidget(v)) {
+            if (!beginDraggingWidget((PagedViewWidget) v)) {
                 return false;
             }
         } else {
@@ -690,7 +591,7 @@
     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
         mInTransition = true;
         if (toWorkspace) {
-            cancelAllTasks();
+            cancelAllTasks(false);
         }
     }
 
@@ -705,15 +606,10 @@
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
         mInTransition = false;
-        for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
-            onSyncWidgetPageItems(d, false);
-        }
-        mDeferredSyncWidgetPageItems.clear();
-        for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
-            r.run();
-        }
-        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
         mForceDrawAllChildrenNextFrame = !toWorkspace;
+        if (!toWorkspace) {
+            loadPreviewsForPage(getNextPage());
+        }
     }
 
     @Override
@@ -782,45 +678,26 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        cancelAllTasks();
+        cancelAllTasks(true);
     }
 
     @Override
     public void trimMemory() {
         super.trimMemory();
-        clearAllWidgetPages();
+        cancelAllTasks(true);
     }
 
-    public void clearAllWidgetPages() {
-        cancelAllTasks();
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View v = getPageAt(i);
-            if (v instanceof PagedViewGridLayout) {
-                ((PagedViewGridLayout) v).removeAllViewsOnPage();
-                mDirtyPageContent.set(i, true);
+    private void cancelAllTasks(boolean clearCompletedTasks) {
+        for (int page = getPageCount() - 1; page >= 0; page--) {
+            final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+            if (layout != null) {
+                for (int i = 0; i < layout.getChildCount(); i++) {
+                    ((PagedViewWidget) layout.getChildAt(i)).deletePreview(clearCompletedTasks);
+                }
             }
         }
     }
 
-    private void cancelAllTasks() {
-        // Clean up all the async tasks
-        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
-        while (iter.hasNext()) {
-            AppsCustomizeAsyncTask task = iter.next();
-            task.cancel(false);
-            iter.remove();
-            mDirtyPageContent.set(task.page, true);
-
-            // We've already preallocated the views for the data to load into, so clear them as well
-            View v = getPageAt(task.page);
-            if (v instanceof PagedViewGridLayout) {
-                ((PagedViewGridLayout) v).removeAllViewsOnPage();
-            }
-        }
-        mDeferredSyncWidgetPageItems.clear();
-        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
-    }
 
     public void setContentType(ContentType type) {
         // Widgets appear to be cleared every time you leave, always force invalidate for them
@@ -835,23 +712,6 @@
         return mContentType;
     }
 
-    protected void snapToPage(int whichPage, int delta, int duration) {
-        super.snapToPage(whichPage, delta, duration);
-
-        // Update the thread priorities given the direction lookahead
-        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
-        while (iter.hasNext()) {
-            AppsCustomizeAsyncTask task = iter.next();
-            int pageIndex = task.page;
-            if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
-                (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
-                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
-            } else {
-                task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
-            }
-        }
-    }
-
     public void setPageBackgroundsVisible(boolean visible) {
         mPageBackgroundsVisible = visible;
         int childCount = getChildCount();
@@ -863,104 +723,6 @@
         }
     }
 
-    /**
-     * A helper to return the priority for loading of the specified widget page.
-     */
-    private int getWidgetPageLoadPriority(int page) {
-        // If we are snapping to another page, use that index as the target page index
-        int toPage = mCurrentPage;
-        if (mNextPage > -1) {
-            toPage = mNextPage;
-        }
-
-        // We use the distance from the target page as an initial guess of priority, but if there
-        // are no pages of higher priority than the page specified, then bump up the priority of
-        // the specified page.
-        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
-        int minPageDiff = Integer.MAX_VALUE;
-        while (iter.hasNext()) {
-            AppsCustomizeAsyncTask task = iter.next();
-            minPageDiff = Math.abs(task.page - toPage);
-        }
-
-        int rawPageDiff = Math.abs(page - toPage);
-        return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
-    }
-    /**
-     * Return the appropriate thread priority for loading for a given page (we give the current
-     * page much higher priority)
-     */
-    private int getThreadPriorityForPage(int page) {
-        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
-        int pageDiff = getWidgetPageLoadPriority(page);
-        if (pageDiff <= 0) {
-            return Process.THREAD_PRIORITY_LESS_FAVORABLE;
-        } else if (pageDiff <= 1) {
-            return Process.THREAD_PRIORITY_LOWEST;
-        } else {
-            return Process.THREAD_PRIORITY_LOWEST;
-        }
-    }
-    private int getSleepForPage(int page) {
-        int pageDiff = getWidgetPageLoadPriority(page);
-        return Math.max(0, pageDiff * sPageSleepDelay);
-    }
-    /**
-     * Creates and executes a new AsyncTask to load a page of widget previews.
-     */
-    @Thunk void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
-            int cellWidth, int cellHeight, int cellCountX) {
-
-        // Prune all tasks that are no longer needed
-        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
-        while (iter.hasNext()) {
-            AppsCustomizeAsyncTask task = iter.next();
-            int taskPage = task.page;
-            if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
-                    taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
-                task.cancel(false);
-                iter.remove();
-            } else {
-                task.setThreadPriority(getThreadPriorityForPage(taskPage));
-            }
-        }
-
-        // We introduce a slight delay to order the loading of side pages so that we don't thrash
-        final int sleepMs = getSleepForPage(page);
-        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
-            new AsyncTaskCallback() {
-                @Override
-                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
-                    try {
-                        try {
-                            Thread.sleep(sleepMs);
-                        } catch (Exception e) {}
-                        loadWidgetPreviewsInBackground(task, data);
-                    } finally {
-                        if (task.isCancelled()) {
-                            data.cleanup(true);
-                        }
-                    }
-                }
-            },
-            new AsyncTaskCallback() {
-                @Override
-                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
-                    mRunningTasks.remove(task);
-                    if (task.isCancelled()) return;
-                    // do cleanup inside onSyncWidgetPageItems
-                    onSyncWidgetPageItems(data, false);
-                }
-            }, getWidgetPreviewLoader());
-
-        // Ensure that the task is appropriately prioritized and runs in parallel
-        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
-                AsyncTaskPageData.Type.LoadWidgetPreviewData);
-        t.setThreadPriority(getThreadPriorityForPage(page));
-        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
-        mRunningTasks.add(t);
-    }
-
     /*
      * Widgets PagedView implementation
      */
@@ -1051,95 +813,18 @@
             layout.addView(widget, lp);
         }
 
-        // wait until a call on onLayout to start loading, because
-        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
-        // TODO: can we do a measure/layout immediately?
-        layout.setOnLayoutListener(new Runnable() {
-            public void run() {
-                // Load the widget previews
-                int maxPreviewWidth = cellWidth;
-                int maxPreviewHeight = cellHeight;
-                if (layout.getChildCount() > 0) {
-                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
-                    int[] maxSize = w.getPreviewSize();
-                    maxPreviewWidth = maxSize[0];
-                    maxPreviewHeight = maxSize[1];
-                }
-
-                getWidgetPreviewLoader().setPreviewSize(maxPreviewWidth, maxPreviewHeight);
-                if (immediate) {
-                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
-                            maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader());
-                    loadWidgetPreviewsInBackground(null, data);
-                    onSyncWidgetPageItems(data, immediate);
-                } else {
-                    if (mInTransition) {
-                        mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
-                    } else {
-                        prepareLoadWidgetPreviewsTask(page, items,
-                                maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
-                    }
-                }
-                layout.setOnLayoutListener(null);
-            }
-        });
-    }
-    @Thunk void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
-            AsyncTaskPageData data) {
-        // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
-        // previews synchronously
-        if (task != null) {
-            // Ensure that this task starts running at the correct priority
-            task.syncThreadPriority();
-        }
-
-        // Load each of the widget/shortcut previews
-        ArrayList<Object> items = data.items;
-        ArrayList<Bitmap> images = data.generatedImages;
-        int count = items.size();
-        for (int i = 0; i < count; ++i) {
-            if (task != null) {
-                // Ensure we haven't been cancelled yet
-                if (task.isCancelled()) break;
-                // Before work on each item, ensure that this task is running at the correct
-                // priority
-                task.syncThreadPriority();
-            }
-
-            images.add(getWidgetPreviewLoader().getPreview(items.get(i)));
+        if (immediate && !mInTransition) {
+            loadPreviewsForPage(page);
         }
     }
 
-    @Thunk void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
-        if (!immediatelySyncItems && mInTransition) {
-            mDeferredSyncWidgetPageItems.add(data);
-            return;
-        }
-        try {
-            int page = data.page;
-            PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+    private void loadPreviewsForPage(int page) {
+        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
 
-            ArrayList<Object> items = data.items;
-            int count = items.size();
-            for (int i = 0; i < count; ++i) {
-                PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
-                if (widget != null) {
-                    Bitmap preview = data.generatedImages.get(i);
-                    widget.applyPreview(new FastBitmapDrawable(preview), i);
-                }
+        if (layout != null) {
+            for (int i = 0; i < layout.getChildCount(); i++) {
+                ((PagedViewWidget) layout.getChildAt(i)).ensurePreview();
             }
-
-            enableHwLayersOnVisiblePages();
-
-            // Update all thread priorities
-            Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
-            while (iter.hasNext()) {
-                AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
-                int pageIndex = task.page;
-                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
-            }
-        } finally {
-            data.cleanup(false);
         }
     }
 
@@ -1148,7 +833,7 @@
         disablePagedViewAnimations();
 
         removeAllViews();
-        cancelAllTasks();
+        cancelAllTasks(true);
 
         Context context = getContext();
         if (mContentType == ContentType.Widgets) {
@@ -1254,6 +939,17 @@
         mSaveInstanceStateItemIndex = -1;
     }
 
+    @Override
+    protected void onPageBeginMoving() {
+        super.onPageBeginMoving();
+        if (!mInTransition) {
+            getVisiblePages(sTempPosArray);
+            for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
+                loadPreviewsForPage(i);
+            }
+        }
+    }
+
     /*
      * AllAppsView implementation
      */
@@ -1274,7 +970,7 @@
             // request a layout to trigger the page data when ready.
             requestLayout();
         } else {
-            cancelAllTasks();
+            cancelAllTasks(false);
             invalidatePageData();
         }
     }
@@ -1324,7 +1020,7 @@
         // should stop this now.
 
         // Stop all background tasks
-        cancelAllTasks();
+        cancelAllTasks(true);
     }
 
     /*
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index 9f3126c..6a13a13 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -511,13 +511,13 @@
 
     @Override
     public int getItemCount() {
-        int lastPage = getChildCount() - 1;
-        if (lastPage < 0) {
-            // If there are no pages, there must be only one icon in the folder.
-            return 1;
+        int lastPageIndex = getChildCount() - 1;
+        if (lastPageIndex < 0) {
+            // If there are no pages, nothing has yet been added to the folder.
+            return 0;
         }
-        return getPageAt(lastPage).getShortcutsAndWidgets().getChildCount()
-                + lastPage * mMaxItemsPerPage;
+        return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
+                + lastPageIndex * mMaxItemsPerPage;
     }
 
     @Override
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 3c7adbe..9b2119e 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -656,8 +656,8 @@
 
         public ContentValues newContentValues(Bitmap icon, String label) {
             ContentValues values = new ContentValues();
-            values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(icon));
-            values.put(IconDB.COLUMN_ICON_LOW_RES, ItemInfo.flattenBitmap(
+            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
+            values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(
                     Bitmap.createScaledBitmap(icon,
                             icon.getWidth() / LOW_RES_SCALE_FACTOR,
                             icon.getHeight() / LOW_RES_SCALE_FACTOR, true)));
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 4349e16..0db22a4 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -331,7 +331,7 @@
                     .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
                     .key(NAME_KEY).value(name);
                 if (icon != null) {
-                    byte[] iconByteArray = ItemInfo.flattenBitmap(icon);
+                    byte[] iconByteArray = Utilities.flattenBitmap(icon);
                     json = json.key(ICON_KEY).value(
                             Base64.encodeToString(
                                     iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index aff8323..f114de2 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -20,13 +20,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.util.Log;
 
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.util.Arrays;
 
 /**
@@ -177,25 +174,9 @@
         }
     }
 
-    static byte[] flattenBitmap(Bitmap bitmap) {
-        // Try go guesstimate how much space the icon will take when serialized
-        // to avoid unnecessary allocations/copies during the write.
-        int size = bitmap.getWidth() * bitmap.getHeight() * 4;
-        ByteArrayOutputStream out = new ByteArrayOutputStream(size);
-        try {
-            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
-            out.flush();
-            out.close();
-            return out.toByteArray();
-        } catch (IOException e) {
-            Log.w("Favorite", "Could not write icon");
-            return null;
-        }
-    }
-
     static void writeBitmap(ContentValues values, Bitmap bitmap) {
         if (bitmap != null) {
-            byte[] data = flattenBitmap(bitmap);
+            byte[] data = Utilities.flattenBitmap(bitmap);
             values.put(LauncherSettings.Favorites.ICON, data);
         }
     }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 9082276..555b1cc 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -32,7 +32,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
-import android.view.View.AccessibilityDelegate;
 import android.view.WindowManager;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -49,12 +48,12 @@
     private final BuildInfo mBuildInfo;
     @Thunk final LauncherModel mModel;
     private final IconCache mIconCache;
+    private final WidgetPreviewLoader mWidgetCache;
 
     private final boolean mIsScreenLarge;
     private final float mScreenDensity;
     private final int mLongPressTimeout = 300;
 
-    private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
     private boolean mWallpaperChangedSinceLastCheck;
 
     private static WeakReference<LauncherProvider> sLauncherProvider;
@@ -101,9 +100,8 @@
         // set sIsScreenXLarge and mScreenDensity *before* creating icon cache
         mIsScreenLarge = isScreenLarge(sContext.getResources());
         mScreenDensity = sContext.getResources().getDisplayMetrics().density;
-
-        recreateWidgetPreviewDb();
         mIconCache = new IconCache(sContext);
+        mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
 
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
         mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
@@ -125,13 +123,6 @@
                 mFavoritesObserver);
     }
 
-    public void recreateWidgetPreviewDb() {
-        if (mWidgetPreviewCacheDb != null) {
-            mWidgetPreviewCacheDb.close();
-        }
-        mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
-    }
-
     /**
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
@@ -181,10 +172,6 @@
         return mAppFilter == null || mAppFilter.shouldShowApp(componentName);
     }
 
-    WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() {
-        return mWidgetPreviewCacheDb;
-    }
-
     static void setLauncherProvider(LauncherProvider provider) {
         sLauncherProvider = new WeakReference<LauncherProvider>(provider);
     }
@@ -240,6 +227,10 @@
         return mDynamicGrid;
     }
 
+    public WidgetPreviewLoader getWidgetCache() {
+        return mWidgetCache;
+    }
+
     public boolean isScreenLarge() {
         return mIsScreenLarge;
     }
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 57f92bc..c034800 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -630,7 +630,7 @@
             return;
         }
         final ContentResolver cr = mContext.getContentResolver();
-        final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
+        final WidgetPreviewLoader previewLoader = appState.getWidgetCache();
         final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
         final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
         if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
@@ -646,7 +646,6 @@
                 final long id = cursor.getLong(ID_INDEX);
                 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
                 final int spanX = cursor.getInt(SPANX_INDEX);
-                final int spanY = cursor.getInt(SPANY_INDEX);
                 final ComponentName provider = ComponentName.unflattenFromString(providerName);
                 Key key = null;
                 String backupKey = null;
@@ -665,12 +664,10 @@
                     if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount);
                     if (backupWidgetCount < MAX_WIDGETS_PER_PASS) {
                         if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
-                        previewLoader.setPreviewSize(
-                                spanX * profile.cellWidthPx,
-                                spanY * profile.cellHeightPx);
                         UserHandleCompat user = UserHandleCompat.myUserHandle();
                         writeRowToBackup(key,
-                                packWidget(dpi, previewLoader, mIconCache, provider, user),
+                                packWidget(dpi, previewLoader,spanX * profile.cellWidthPx,
+                                        mIconCache, provider, user),
                                 data);
                         mKeys.add(key);
                         backupWidgetCount ++;
@@ -980,7 +977,8 @@
     }
 
     /** Serialize a widget for persistence, including a checksum wrapper. */
-    private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
+    private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader,
+            int previewWidth, IconCache iconCache,
             ComponentName provider, UserHandleCompat user) {
         final LauncherAppWidgetProviderInfo info =
                 LauncherModel.getProviderInfo(mContext, provider, user);
@@ -1000,7 +998,7 @@
         }
         if (info.previewImage != 0) {
             widget.preview = new Resource();
-            Bitmap preview = previewLoader.generateWidgetPreview(info, null);
+            Bitmap preview = previewLoader.generateWidgetPreview(info, previewWidth, null);
             ByteArrayOutputStream os = new ByteArrayOutputStream();
             if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
                 widget.preview.data = os.toByteArray();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 8cedcc5..dcb3759 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -1622,6 +1622,9 @@
                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                 loadAndBindAllApps();
 
+                // Remove entries for packages which changed while the launcher was dead.
+                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews();
+
                 // Restore the default thread priority after we are done loading items
                 synchronized (mLock) {
                     android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
@@ -3007,8 +3010,7 @@
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
                         mIconCache.updateIconsForPkg(packages[i], mUser);
                         mBgAllAppsList.updatePackage(context, packages[i], mUser);
-                        WidgetPreviewLoader.removePackageFromDb(
-                                mApp.getWidgetPreviewCacheDb(), packages[i]);
+                        mApp.getWidgetCache().removePackage(packages[i], mUser);
                     }
                     break;
                 case OP_REMOVE:
@@ -3034,8 +3036,7 @@
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
                         mBgAllAppsList.removePackage(packages[i], mUser);
-                        WidgetPreviewLoader.removePackageFromDb(
-                                mApp.getWidgetPreviewCacheDb(), packages[i]);
+                        mApp.getWidgetCache().removePackage(packages[i], mUser);
                     }
                     break;
             }
diff --git a/src/com/android/launcher3/PackageChangedReceiver.java b/src/com/android/launcher3/PackageChangedReceiver.java
index e59f6d8..b98f472 100644
--- a/src/com/android/launcher3/PackageChangedReceiver.java
+++ b/src/com/android/launcher3/PackageChangedReceiver.java
@@ -4,18 +4,10 @@
 import android.content.Context;
 import android.content.Intent;
 
+// TODO: Remove this
 public class PackageChangedReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(final Context context, Intent intent) {
-        final String packageName = intent.getData().getSchemeSpecificPart();
 
-        if (packageName == null || packageName.length() == 0) {
-            // they sent us a bad intent
-            return;
-        }
-        // in rare cases the receiver races with the application to set up LauncherAppState
-        LauncherAppState.setApplicationContext(context.getApplicationContext());
-        LauncherAppState app = LauncherAppState.getInstance();
-        WidgetPreviewLoader.removePackageFromDb(app.getWidgetPreviewCacheDb(), packageName);
     }
 }
diff --git a/src/com/android/launcher3/PagedViewCellLayout.java b/src/com/android/launcher3/PagedViewCellLayout.java
deleted file mode 100644
index e3ad9da..0000000
--- a/src/com/android/launcher3/PagedViewCellLayout.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-
-/**
- * An abstraction of the original CellLayout which supports laying out items
- * which span multiple cells into a grid-like layout.  Also supports dimming
- * to give a preview of its contents.
- */
-public class PagedViewCellLayout extends ViewGroup implements Page {
-    static final String TAG = "PagedViewCellLayout";
-
-    private int mCellCountX;
-    private int mCellCountY;
-    private int mOriginalCellWidth;
-    private int mOriginalCellHeight;
-    private int mCellWidth;
-    private int mCellHeight;
-    private int mOriginalWidthGap;
-    private int mOriginalHeightGap;
-    private int mWidthGap;
-    private int mHeightGap;
-    protected PagedViewCellLayoutChildren mChildren;
-
-    public PagedViewCellLayout(Context context) {
-        this(context, null);
-    }
-
-    public PagedViewCellLayout(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        setAlwaysDrawnWithCacheEnabled(false);
-
-        // setup default cell parameters
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-        mOriginalCellWidth = mCellWidth = grid.cellWidthPx;
-        mOriginalCellHeight = mCellHeight = grid.cellHeightPx;
-        mCellCountX = (int) grid.numColumns;
-        mCellCountY = (int) grid.numRows;
-        mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
-
-        mChildren = new PagedViewCellLayoutChildren(context);
-        mChildren.setCellDimensions(mCellWidth, mCellHeight);
-        mChildren.setGap(mWidthGap, mHeightGap);
-
-        addView(mChildren);
-    }
-
-    public int getCellWidth() {
-        return mCellWidth;
-    }
-
-    public int getCellHeight() {
-        return mCellHeight;
-    }
-
-    @Override
-    public void cancelLongPress() {
-        super.cancelLongPress();
-
-        // Cancel long press for all children
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            child.cancelLongPress();
-        }
-    }
-
-    public boolean addViewToCellLayout(View child, int index, int childId,
-            PagedViewCellLayout.LayoutParams params) {
-        final PagedViewCellLayout.LayoutParams lp = params;
-
-        // Generate an id for each view, this assumes we have at most 256x256 cells
-        // per workspace screen
-        if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
-                lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
-            // If the horizontal or vertical span is set to -1, it is taken to
-            // mean that it spans the extent of the CellLayout
-            if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
-            if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
-
-            child.setId(childId);
-            mChildren.addView(child, index, lp);
-
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void removeAllViewsOnPage() {
-        mChildren.removeAllViews();
-        setLayerType(LAYER_TYPE_NONE, null);
-    }
-
-    @Override
-    public void removeViewOnPageAt(int index) {
-        mChildren.removeViewAt(index);
-    }
-
-    /**
-     * Clears all the key listeners for the individual icons.
-     */
-    public void resetChildrenOnKeyListeners() {
-        int childCount = mChildren.getChildCount();
-        for (int j = 0; j < childCount; ++j) {
-            mChildren.getChildAt(j).setOnKeyListener(null);
-        }
-    }
-
-    @Override
-    public int getPageChildCount() {
-        return mChildren.getChildCount();
-    }
-
-    public PagedViewCellLayoutChildren getChildrenLayout() {
-        return mChildren;
-    }
-
-    @Override
-    public View getChildOnPageAt(int i) {
-        return mChildren.getChildAt(i);
-    }
-
-    @Override
-    public int indexOfChildOnPage(View v) {
-        return mChildren.indexOfChild(v);
-    }
-
-    public int getCellCountX() {
-        return mCellCountX;
-    }
-
-    public int getCellCountY() {
-        return mCellCountY;
-    }
-
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
-        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-
-        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
-        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
-
-        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
-            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
-        }
-
-        int numWidthGaps = mCellCountX - 1;
-        int numHeightGaps = mCellCountY - 1;
-
-        if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
-            int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
-            int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
-            int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth);
-            int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight);
-            mWidthGap = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0;
-            mHeightGap = numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0;
-
-            mChildren.setGap(mWidthGap, mHeightGap);
-        } else {
-            mWidthGap = mOriginalWidthGap;
-            mHeightGap = mOriginalHeightGap;
-        }
-
-        // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
-        int newWidth = widthSpecSize;
-        int newHeight = heightSpecSize;
-        if (widthSpecMode == MeasureSpec.AT_MOST) {
-            newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) +
-                ((mCellCountX - 1) * mWidthGap);
-            newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) +
-                ((mCellCountY - 1) * mHeightGap);
-            setMeasuredDimension(newWidth, newHeight);
-        }
-
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            int childWidthMeasureSpec =
-                MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
-                        getPaddingRight(), MeasureSpec.EXACTLY);
-            int childheightMeasureSpec =
-                MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
-                        getPaddingBottom(), MeasureSpec.EXACTLY);
-            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
-        }
-
-        setMeasuredDimension(newWidth, newHeight);
-    }
-
-    int getContentWidth() {
-        return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
-    }
-
-    int getContentHeight() {
-        if (mCellCountY > 0) {
-            return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
-        }
-        return 0;
-    }
-
-    int getWidthBeforeFirstLayout() {
-        if (mCellCountX > 0) {
-            return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap);
-        }
-        return 0;
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            child.layout(getPaddingLeft(), getPaddingTop(),
-                r - l - getPaddingRight(), b - t - getPaddingBottom());
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean result = super.onTouchEvent(event);
-        int count = getPageChildCount();
-        if (count > 0) {
-            // We only intercept the touch if we are tapping in empty space after the final row
-            View child = getChildOnPageAt(count - 1);
-            int bottom = child.getBottom();
-            int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX());
-            if (numRows < getCellCountY()) {
-                // Add a little bit of buffer if there is room for another row
-                bottom += mCellHeight / 2;
-            }
-            result = result || (event.getY() < bottom);
-        }
-        return result;
-    }
-
-    public void enableCenteredContent(boolean enabled) {
-        mChildren.enableCenteredContent(enabled);
-    }
-
-    @Override
-    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
-        mChildren.setChildrenDrawingCacheEnabled(enabled);
-    }
-
-    public void setCellCount(int xCount, int yCount) {
-        mCellCountX = xCount;
-        mCellCountY = yCount;
-        requestLayout();
-    }
-
-    public void setGap(int widthGap, int heightGap) {
-        mOriginalWidthGap = mWidthGap = widthGap;
-        mOriginalHeightGap = mHeightGap = heightGap;
-        mChildren.setGap(widthGap, heightGap);
-    }
-
-    public int[] getCellCountForDimensions(int width, int height) {
-        // Always assume we're working with the smallest span to make sure we
-        // reserve enough space in both orientations
-        int smallerSize = Math.min(mCellWidth, mCellHeight);
-
-        // Always round up to next largest cell
-        int spanX = (width + smallerSize) / smallerSize;
-        int spanY = (height + smallerSize) / smallerSize;
-
-        return new int[] { spanX, spanY };
-    }
-
-    /**
-     * Start dragging the specified child
-     *
-     * @param child The child that is being dragged
-     */
-    void onDragChild(View child) {
-        PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
-        lp.isDragging = true;
-    }
-
-    /**
-     * Estimates the number of cells that the specified width would take up.
-     */
-    public int estimateCellHSpan(int width) {
-        // We don't show the next/previous pages any more, so we use the full width, minus the
-        // padding
-        int availWidth = width - (getPaddingLeft() + getPaddingRight());
-
-        // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N
-        int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap));
-
-        // We don't do anything fancy to determine if we squeeze another row in.
-        return n;
-    }
-
-    /**
-     * Estimates the number of cells that the specified height would take up.
-     */
-    public int estimateCellVSpan(int height) {
-        // The space for a page is the height - top padding (current page) - bottom padding (current
-        // page)
-        int availHeight = height - (getPaddingTop() + getPaddingBottom());
-
-        // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N
-        int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap));
-
-        // We don't do anything fancy to determine if we squeeze another row in.
-        return n;
-    }
-
-    /** Returns an estimated center position of the cell at the specified index */
-    public int[] estimateCellPosition(int x, int y) {
-        return new int[] {
-                getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2),
-                getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2)
-        };
-    }
-
-    public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) {
-        mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width));
-        mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height));
-        requestLayout();
-    }
-
-    @Override
-    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof PagedViewCellLayout.LayoutParams;
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new PagedViewCellLayout.LayoutParams(p);
-    }
-
-    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        /**
-         * Horizontal location of the item in the grid.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellX;
-
-        /**
-         * Vertical location of the item in the grid.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellY;
-
-        /**
-         * Number of cells spanned horizontally by the item.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellHSpan;
-
-        /**
-         * Number of cells spanned vertically by the item.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellVSpan;
-
-        /**
-         * Is this item currently being dragged
-         */
-        public boolean isDragging;
-
-        // a data object that you can bind to this layout params
-        private Object mTag;
-
-        // X coordinate of the view in the layout.
-        @ViewDebug.ExportedProperty
-        int x;
-        // Y coordinate of the view in the layout.
-        @ViewDebug.ExportedProperty
-        int y;
-
-        public LayoutParams() {
-            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams source) {
-            super(source);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(LayoutParams source) {
-            super(source);
-            this.cellX = source.cellX;
-            this.cellY = source.cellY;
-            this.cellHSpan = source.cellHSpan;
-            this.cellVSpan = source.cellVSpan;
-        }
-
-        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
-            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-            this.cellX = cellX;
-            this.cellY = cellY;
-            this.cellHSpan = cellHSpan;
-            this.cellVSpan = cellVSpan;
-        }
-
-        public void setup(Context context,
-                          int cellWidth, int cellHeight, int widthGap, int heightGap,
-                          int hStartPadding, int vStartPadding) {
-
-            final int myCellHSpan = cellHSpan;
-            final int myCellVSpan = cellVSpan;
-            final int myCellX = cellX;
-            final int myCellY = cellY;
-
-            width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
-                    leftMargin - rightMargin;
-            height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
-                    topMargin - bottomMargin;
-
-            if (LauncherAppState.getInstance().isScreenLarge()) {
-                x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
-                y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
-            } else {
-                x = myCellX * (cellWidth + widthGap) + leftMargin;
-                y = myCellY * (cellHeight + heightGap) + topMargin;
-            }
-        }
-
-        public Object getTag() {
-            return mTag;
-        }
-
-        public void setTag(Object tag) {
-            mTag = tag;
-        }
-
-        public String toString() {
-            return "(" + this.cellX + ", " + this.cellY + ", " +
-                this.cellHSpan + ", " + this.cellVSpan + ")";
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedViewCellLayoutChildren.java b/src/com/android/launcher3/PagedViewCellLayoutChildren.java
deleted file mode 100644
index 84d2b1d..0000000
--- a/src/com/android/launcher3/PagedViewCellLayoutChildren.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * An abstraction of the original CellLayout which supports laying out items
- * which span multiple cells into a grid-like layout.  Also supports dimming
- * to give a preview of its contents.
- */
-public class PagedViewCellLayoutChildren extends ViewGroup {
-    static final String TAG = "PagedViewCellLayout";
-
-    private boolean mCenterContent;
-
-    private int mCellWidth;
-    private int mCellHeight;
-    private int mWidthGap;
-    private int mHeightGap;
-
-    public PagedViewCellLayoutChildren(Context context) {
-        super(context);
-    }
-
-    @Override
-    public void cancelLongPress() {
-        super.cancelLongPress();
-
-        // Cancel long press for all children
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            child.cancelLongPress();
-        }
-    }
-
-    public void setGap(int widthGap, int heightGap) {
-        mWidthGap = widthGap;
-        mHeightGap = heightGap;
-        requestLayout();
-    }
-
-    public void setCellDimensions(int width, int height) {
-        mCellWidth = width;
-        mCellHeight = height;
-        requestLayout();
-    }
-
-    @Override
-    public void requestChildFocus(View child, View focused) {
-        super.requestChildFocus(child, focused);
-        if (child != null) {
-            Rect r = new Rect();
-            child.getDrawingRect(r);
-            requestRectangleOnScreen(r);
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
-        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-
-        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
-        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
-
-        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
-            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
-        }
-
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            PagedViewCellLayout.LayoutParams lp =
-                (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
-            lp.setup(getContext(),
-                    mCellWidth, mCellHeight, mWidthGap, mHeightGap,
-                    getPaddingLeft(),
-                    getPaddingTop());
-
-            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
-                    MeasureSpec.EXACTLY);
-            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
-                    MeasureSpec.EXACTLY);
-
-            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
-        }
-
-        setMeasuredDimension(widthSpecSize, heightSpecSize);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int count = getChildCount();
-
-        int offsetX = 0;
-        if (mCenterContent && count > 0) {
-            // determine the max width of all the rows and center accordingly
-            int maxRowX = 0;
-            int minRowX = Integer.MAX_VALUE;
-            for (int i = 0; i < count; i++) {
-                View child = getChildAt(i);
-                if (child.getVisibility() != GONE) {
-                    PagedViewCellLayout.LayoutParams lp =
-                        (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
-                    minRowX = Math.min(minRowX, lp.x);
-                    maxRowX = Math.max(maxRowX, lp.x + lp.width);
-                }
-            }
-            int maxRowWidth = maxRowX - minRowX;
-            offsetX = (getMeasuredWidth() - maxRowWidth) / 2;
-        }
-
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                PagedViewCellLayout.LayoutParams lp =
-                    (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
-
-                int childLeft = offsetX + lp.x;
-                int childTop = lp.y;
-                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
-            }
-        }
-    }
-
-    public void enableCenteredContent(boolean enabled) {
-        mCenterContent = enabled;
-    }
-
-    @Override
-    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View view = getChildAt(i);
-            view.setDrawingCacheEnabled(enabled);
-            // Update the drawing caches
-            if (!view.isHardwareAccelerated()) {
-                view.buildDrawingCache(true);
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/PagedViewWidget.java
index 107069b..d9ca7be 100644
--- a/src/com/android/launcher3/PagedViewWidget.java
+++ b/src/com/android/launcher3/PagedViewWidget.java
@@ -20,35 +20,38 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnLayoutChangeListener;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 /**
  * The linear layout used strictly for the widget/wallpaper tab of the customization tray
  */
-public class PagedViewWidget extends LinearLayout {
-    static final String TAG = "PagedViewWidgetLayout";
+public class PagedViewWidget extends LinearLayout implements OnLayoutChangeListener {
 
-    private static boolean sDeletePreviewsWhenDetachedFromWindow = true;
-    private static boolean sRecyclePreviewsWhenDetachedFromWindow = true;
+    private static PagedViewWidget sShortpressTarget = null;
+
+    private final Rect mOriginalImagePadding = new Rect();
 
     private String mDimensionsFormatString;
-    CheckForShortPress mPendingCheckForShortPress = null;
-    ShortPressListener mShortPressListener = null;
-    boolean mShortPressTriggered = false;
-    static PagedViewWidget sShortpressTarget = null;
-    boolean mIsAppWidget;
-    private final Rect mOriginalImagePadding = new Rect();
+    private CheckForShortPress mPendingCheckForShortPress = null;
+    private ShortPressListener mShortPressListener = null;
+    private boolean mShortPressTriggered = false;
+    private boolean mIsAppWidget;
     private Object mInfo;
+
     private WidgetPreviewLoader mWidgetPreviewLoader;
+    private PreviewLoadRequest mActiveRequest;
 
     public PagedViewWidget(Context context) {
         this(context, null);
@@ -92,29 +95,24 @@
         }
     }
 
-    public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) {
-        sDeletePreviewsWhenDetachedFromWindow = value;
-    }
-
-    public static void setRecyclePreviewsWhenDetachedFromWindow(boolean value) {
-        sRecyclePreviewsWhenDetachedFromWindow = value;
-    }
-
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        deletePreview(true);
+    }
 
-        if (sDeletePreviewsWhenDetachedFromWindow) {
+    public void deletePreview(boolean recycleImage) {
+        if (recycleImage) {
             final ImageView image = (ImageView) findViewById(R.id.widget_preview);
             if (image != null) {
-                FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable();
-                if (sRecyclePreviewsWhenDetachedFromWindow &&
-                        mInfo != null && preview != null && preview.getBitmap() != null) {
-                    mWidgetPreviewLoader.recycleBitmap(mInfo, preview.getBitmap());
-                }
                 image.setImageDrawable(null);
             }
         }
+
+        if (mActiveRequest != null) {
+            mActiveRequest.cancel(recycleImage);
+            mActiveRequest = null;
+        }
     }
 
     public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
@@ -161,7 +159,8 @@
         return maxSize;
     }
 
-    void applyPreview(FastBitmapDrawable preview, int index) {
+    public void applyPreview(Bitmap bitmap) {
+        FastBitmapDrawable preview = new FastBitmapDrawable(bitmap);
         final PagedViewWidgetImageView image =
             (PagedViewWidgetImageView) findViewById(R.id.widget_preview);
         if (preview != null) {
@@ -259,4 +258,38 @@
         // we just always mark the touch event as handled.
         return true;
     }
+
+    public void ensurePreview() {
+        if (mActiveRequest != null) {
+            return;
+        }
+        int[] size = getPreviewSize();
+
+        if (size[0] <= 0 || size[1] <= 0) {
+            addOnLayoutChangeListener(this);
+            return;
+        }
+        Bitmap[] immediateResult = new Bitmap[1];
+        mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this,
+                immediateResult);
+        if (immediateResult[0] != null) {
+            applyPreview(immediateResult[0]);
+        }
+    }
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        removeOnLayoutChangeListener(this);
+        ensurePreview();
+    }
+
+    public int getActualItemWidth() {
+        ItemInfo info = (ItemInfo) getTag();
+        int[] size = getPreviewSize();
+        int cellWidth = LauncherAppState.getInstance()
+                .getDynamicGrid().getDeviceProfile().cellWidthPx;
+
+        return Math.min(size[0], info.spanX * cellWidth);
+    }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 497b438..22677c8 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -50,6 +50,8 @@
 import android.view.View;
 import android.widget.Toast;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Comparator;
 
@@ -555,6 +557,25 @@
         return defaultWidgetForSearchPackage;
     }
 
+    /**
+     * Compresses the bitmap to a byte array for serialization.
+     */
+    public static byte[] flattenBitmap(Bitmap bitmap) {
+        // Try go guesstimate how much space the icon will take when serialized
+        // to avoid unnecessary allocations/copies during the write.
+        int size = bitmap.getWidth() * bitmap.getHeight() * 4;
+        ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+        try {
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+            out.flush();
+            out.close();
+            return out.toByteArray();
+        } catch (IOException e) {
+            Log.w(TAG, "Could not write bitmap");
+            return null;
+        }
+    }
+
     public static final Comparator<ItemInfo> RANK_COMPARATOR = new Comparator<ItemInfo>() {
 
         @Override
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 0a5f0af..1043e2e 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -1,18 +1,16 @@
 package com.android.launcher3;
 
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.database.sqlite.SQLiteCantOpenDatabaseException;
+import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDiskIOException;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteReadOnlyDatabaseException;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
@@ -26,400 +24,332 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.util.Log;
+import android.util.LongSparseArray;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Thunk;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 
 public class WidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
-    private static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version";
 
     private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f;
-    @Thunk static final HashSet<String> sInvalidPackages = new HashSet<String>();
 
-    private final HashMap<String, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<>();
-    private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps = new ArrayList<>();
-
-    private final int mAppIconSize;
-    private final int mCellWidth;
+    private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
+    private final HashMap<WidgetCacheKey, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<>();
+    private Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
 
     private final Context mContext;
     private final IconCache mIconCache;
+    private final UserManagerCompat mUserManager;
     private final AppWidgetManagerCompat mManager;
-
-    private int mPreviewBitmapWidth;
-    private int mPreviewBitmapHeight;
-    private String mSize;
-
-    private String mCachedSelectQuery;
-    private CacheDb mDb;
+    private final CacheDb mDb;
 
     private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
 
-    public WidgetPreviewLoader(Context context) {
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
+    public WidgetPreviewLoader(Context context, IconCache iconCache) {
         mContext = context;
-        mAppIconSize = grid.iconSizePx;
-        mCellWidth = grid.cellWidthPx;
-
-        mIconCache = app.getIconCache();
+        mIconCache = iconCache;
         mManager = AppWidgetManagerCompat.getInstance(context);
-        mDb = app.getWidgetPreviewCacheDb();
-
-        SharedPreferences sp = context.getSharedPreferences(
-                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
-        final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null);
-        final String versionName = android.os.Build.VERSION.INCREMENTAL;
-        final boolean isLollipopOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-        if (!versionName.equals(lastVersionName)) {
-            try {
-                // clear all the previews whenever the system version changes, to ensure that
-                // previews are up-to-date for any apps that might have been updated with the system
-                clearDb();
-            } catch (SQLiteReadOnlyDatabaseException e) {
-                if (isLollipopOrGreater) {
-                    // Workaround for Bug. 18554839, if we fail to clear the db due to the read-only
-                    // issue, then ignore this error and leave the old previews
-                } else {
-                    throw e;
-                }
-            } finally {
-                SharedPreferences.Editor editor = sp.edit();
-                editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName);
-                editor.commit();
-            }
-        }
+        mUserManager = UserManagerCompat.getInstance(context);
+        mDb = new CacheDb(context);
     }
 
-    public void recreateDb() {
-        LauncherAppState app = LauncherAppState.getInstance();
-        app.recreateWidgetPreviewDb();
-        mDb = app.getWidgetPreviewCacheDb();
-    }
+    /**
+     * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
+     * called on UI thread
+     *
+     * @param o either {@link LauncherAppWidgetProviderInfo} or {@link ResolveInfo}
+     * @param immediateResult A bitmap array of size 1. If the result is already cached, it is
+     * set to the final result.
+     * @return a request id which can be used to cancel the request.
+     */
+    public PreviewLoadRequest getPreview(final Object o, int previewWidth, int previewHeight,
+            PagedViewWidget caller, Bitmap[] immediateResult) {
+        String size = previewWidth + "x" + previewHeight;
+        WidgetCacheKey key = getObjectKey(o, size);
 
-    public void setPreviewSize(int previewWidth, int previewHeight) {
-        mPreviewBitmapWidth = previewWidth;
-        mPreviewBitmapHeight = previewHeight;
-        mSize = previewWidth + "x" + previewHeight;
-    }
-
-    public Bitmap getPreview(final Object o) {
-        final String name = getObjectName(o);
-        final String packageName = getObjectPackage(o);
-        // check if the package is valid
-        synchronized(sInvalidPackages) {
-            boolean packageValid = !sInvalidPackages.contains(packageName);
-            if (!packageValid) {
-                return null;
-            }
-        }
-        synchronized(mLoadedPreviews) {
-            // check if it exists in our existing cache
-            if (mLoadedPreviews.containsKey(name)) {
-                WeakReference<Bitmap> bitmapReference = mLoadedPreviews.get(name);
-                Bitmap bitmap = bitmapReference.get();
-                if (bitmap != null) {
-                    return bitmap;
-                }
-            }
-        }
-
-        Bitmap unusedBitmap = null;
-        synchronized(mUnusedBitmaps) {
-            // not in cache; we need to load it from the db
-            while (unusedBitmap == null && mUnusedBitmaps.size() > 0) {
-                Bitmap candidate = mUnusedBitmaps.remove(0).get();
-                if (candidate != null && candidate.isMutable() &&
-                        candidate.getWidth() == mPreviewBitmapWidth &&
-                        candidate.getHeight() == mPreviewBitmapHeight) {
-                    unusedBitmap = candidate;
-                }
-            }
-        }
-
-        if (unusedBitmap == null) {
-            unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight,
-                    Bitmap.Config.ARGB_8888);
-        }
-        Bitmap preview = readFromDb(name, unusedBitmap);
-
-        if (preview != null) {
-            synchronized(mLoadedPreviews) {
-                mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
-            }
-            return preview;
-        } else {
-            // it's not in the db... we need to generate it
-            final Bitmap generatedPreview = generatePreview(o, unusedBitmap);
-            preview = generatedPreview;
-            if (preview != unusedBitmap) {
-                throw new RuntimeException("generatePreview is not recycling the bitmap " + o);
-            }
-
-            synchronized(mLoadedPreviews) {
-                mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
-            }
-
-            // write to db on a thread pool... this can be done lazily and improves the performance
-            // of the first time widget previews are loaded
-            new AsyncTask<Void, Void, Void>() {
-                public Void doInBackground(Void ... args) {
-                    writeToDb(o, generatedPreview);
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
-
-            return preview;
-        }
-    }
-
-    public void recycleBitmap(Object o, Bitmap bitmapToRecycle) {
-        String name = getObjectName(o);
+        // Check if we have the preview loaded or not.
         synchronized (mLoadedPreviews) {
-            if (mLoadedPreviews.containsKey(name)) {
-                Bitmap b = mLoadedPreviews.get(name).get();
-                if (b == bitmapToRecycle) {
-                    mLoadedPreviews.remove(name);
-                    if (bitmapToRecycle.isMutable()) {
-                        synchronized (mUnusedBitmaps) {
-                            mUnusedBitmaps.add(new SoftReference<Bitmap>(b));
-                        }
-                    }
-                } else {
-                    throw new RuntimeException("Bitmap passed in doesn't match up");
-                }
+            WeakReference<Bitmap> ref = mLoadedPreviews.get(key);
+            if (ref != null && ref.get() != null) {
+                immediateResult[0] = ref.get();
+                return new PreviewLoadRequest(null, key);
             }
         }
+
+        PreviewLoadTask task = new PreviewLoadTask(key, o, previewWidth, previewHeight, caller);
+        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        return new PreviewLoadRequest(task, key);
     }
 
-    static class CacheDb extends SQLiteOpenHelper {
-        final static int DB_VERSION = 2;
-        final static String TABLE_NAME = "shortcut_and_widget_previews";
-        final static String COLUMN_NAME = "name";
-        final static String COLUMN_SIZE = "size";
-        final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
-        Context mContext;
+    /**
+     * The DB holds the generated previews for various components. Previews can also have different
+     * sizes (landscape vs portrait).
+     */
+    private static class CacheDb extends SQLiteOpenHelper {
+        private static final int DB_VERSION = 3;
+
+        private static final String TABLE_NAME = "shortcut_and_widget_previews";
+        private static final String COLUMN_COMPONENT = "componentName";
+        private static final String COLUMN_USER = "profileId";
+        private static final String COLUMN_SIZE = "size";
+        private static final String COLUMN_PACKAGE = "packageName";
+        private static final String COLUMN_LAST_UPDATED = "lastUpdated";
+        private static final String COLUMN_VERSION = "version";
+        private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
 
         public CacheDb(Context context) {
-            super(context, new File(context.getCacheDir(),
-                    LauncherFiles.WIDGET_PREVIEWS_DB).getPath(), null, DB_VERSION);
-            // Store the context for later use
-            mContext = context;
+            super(context, LauncherFiles.WIDGET_PREVIEWS_DB, null, DB_VERSION);
         }
 
         @Override
         public void onCreate(SQLiteDatabase database) {
             database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
-                    COLUMN_NAME + " TEXT NOT NULL, " +
+                    COLUMN_COMPONENT + " TEXT NOT NULL, " +
+                    COLUMN_USER + " INTEGER NOT NULL, " +
                     COLUMN_SIZE + " TEXT NOT NULL, " +
-                    COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " +
-                    "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " +
+                    COLUMN_PACKAGE + " TEXT NOT NULL, " +
+                    COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
+                    COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
+                    COLUMN_PREVIEW_BITMAP + " BLOB, " +
+                    "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " +
                     ");");
         }
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             if (oldVersion != newVersion) {
-                // Delete all the records; they'll be repopulated as this is a cache
-                db.execSQL("DELETE FROM " + TABLE_NAME);
+                clearDB(db);
             }
         }
+
+        @Override
+        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion != newVersion) {
+                clearDB(db);
+            }
+        }
+
+        private void clearDB(SQLiteDatabase db) {
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+            onCreate(db);
+        }
     }
 
-    private static final String WIDGET_PREFIX = "Widget:";
-    private static final String SHORTCUT_PREFIX = "Shortcut:";
-
-    private static String getObjectName(Object o) {
+    private WidgetCacheKey getObjectKey(Object o, String size) {
         // should cache the string builder
-        StringBuilder sb = new StringBuilder();
-        String output;
-        if (o instanceof AppWidgetProviderInfo) {
-            sb.append(WIDGET_PREFIX);
-            sb.append(((AppWidgetProviderInfo) o).toString());
-            output = sb.toString();
-            sb.setLength(0);
-        } else {
-            sb.append(SHORTCUT_PREFIX);
-            ResolveInfo info = (ResolveInfo) o;
-            sb.append(new ComponentName(info.activityInfo.packageName,
-                    info.activityInfo.name).flattenToString());
-            output = sb.toString();
-            sb.setLength(0);
-        }
-        return output;
-    }
-
-    private String getObjectPackage(Object o) {
-        if (o instanceof AppWidgetProviderInfo) {
-            return ((AppWidgetProviderInfo) o).provider.getPackageName();
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) o;
+            return new WidgetCacheKey(info.provider, mManager.getUser(info), size);
         } else {
             ResolveInfo info = (ResolveInfo) o;
-            return info.activityInfo.packageName;
+            return new WidgetCacheKey(
+                    new ComponentName(info.activityInfo.packageName, info.activityInfo.name),
+                    UserHandleCompat.myUserHandle(), size);
         }
     }
 
-    @Thunk void writeToDb(Object o, Bitmap preview) {
-        String name = getObjectName(o);
-        SQLiteDatabase db = mDb.getWritableDatabase();
+    @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
         ContentValues values = new ContentValues();
+        values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
+        values.put(CacheDb.COLUMN_USER, mUserManager.getSerialNumberForUser(key.user));
+        values.put(CacheDb.COLUMN_SIZE, key.size);
+        values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
+        values.put(CacheDb.COLUMN_VERSION, versions[0]);
+        values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
+        values.put(CacheDb.COLUMN_PREVIEW_BITMAP, Utilities.flattenBitmap(preview));
 
-        values.put(CacheDb.COLUMN_NAME, name);
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
-        values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
-        values.put(CacheDb.COLUMN_SIZE, mSize);
         try {
-            db.insert(CacheDb.TABLE_NAME, null, values);
-        } catch (SQLiteDiskIOException e) {
-            recreateDb();
-        } catch (SQLiteCantOpenDatabaseException e) {
-            dumpOpenFiles();
-            throw e;
+            mDb.getWritableDatabase().insertWithOnConflict(CacheDb.TABLE_NAME, null, values,
+                    SQLiteDatabase.CONFLICT_REPLACE);
+        } catch (SQLException e) {
+            Log.e(TAG, "Error saving image to DB", e);
         }
     }
 
-    private void clearDb() {
-        SQLiteDatabase db = mDb.getWritableDatabase();
-        // Delete everything
+    public void removePackage(String packageName, UserHandleCompat user) {
+        removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
+    }
+
+    private void removePackage(String packageName, UserHandleCompat user, long userSerial) {
+        synchronized(mPackageVersions) {
+            mPackageVersions.remove(packageName);
+        }
+
+        synchronized (mLoadedPreviews) {
+            Set<WidgetCacheKey> keysToRemove = new HashSet<>();
+            for (WidgetCacheKey key : mLoadedPreviews.keySet()) {
+                if (key.componentName.getPackageName().equals(packageName) && key.user.equals(user)) {
+                    keysToRemove.add(key);
+                }
+            }
+
+            for (WidgetCacheKey key : keysToRemove) {
+                WeakReference<Bitmap> req = mLoadedPreviews.remove(key);
+                if (req != null && req.get() != null) {
+                    mUnusedBitmaps.add(req.get());
+                }
+            }
+        }
+
         try {
-            db.delete(CacheDb.TABLE_NAME, null, null);
-        } catch (SQLiteDiskIOException e) {
-        } catch (SQLiteCantOpenDatabaseException e) {
-            dumpOpenFiles();
-            throw e;
+            mDb.getWritableDatabase().delete(CacheDb.TABLE_NAME,
+                    CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
+                    new String[] {packageName, Long.toString(userSerial)});
+        } catch (SQLException e) {
+            Log.e(TAG, "Unable to delete items from DB", e);
         }
     }
 
-    public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
-        synchronized(sInvalidPackages) {
-            sInvalidPackages.add(packageName);
+    /**
+     * Updates the persistent DB:
+     *   1. Any preview generated for an old package version is removed
+     *   2. Any preview for an absent package is removed
+     * This ensures that we remove entries for packages which changed while the launcher was dead.
+     */
+    public void removeObsoletePreviews() {
+        LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
+        LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
+
+        for (Object obj : LauncherModel.getSortedWidgetsAndShortcuts(mContext, false)) {
+            final UserHandleCompat user;
+            final String pkg;
+            if (obj instanceof ResolveInfo) {
+                user = UserHandleCompat.myUserHandle();
+                pkg = ((ResolveInfo) obj).activityInfo.packageName;
+            } else {
+                LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) obj;
+                user = mManager.getUser(info);
+                pkg = info.provider.getPackageName();
+            }
+
+            int userIdIndex = userIdCache.indexOfValue(user);
+            final long userId;
+            if (userIdIndex < 0) {
+                userId = mUserManager.getSerialNumberForUser(user);
+                userIdCache.put(userId, user);
+            } else {
+                userId = userIdCache.keyAt(userIdIndex);
+            }
+
+            HashSet<String> packages = validPackages.get(userId);
+            if (packages == null) {
+                packages = new HashSet<>();
+                validPackages.put(userId, packages);
+            }
+            packages.add(pkg);
         }
-        new AsyncTask<Void, Void, Void>() {
-            public Void doInBackground(Void ... args) {
-                SQLiteDatabase db = cacheDb.getWritableDatabase();
+
+        LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
+        Cursor c = null;
+        try {
+            c = mDb.getReadableDatabase().query(CacheDb.TABLE_NAME,
+                    new String[] {CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
+                        CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
+                    null, null, null, null, null);
+            while (c.moveToNext()) {
+                long userId = c.getLong(0);
+                String pkg = c.getString(1);
+                long lastUpdated = c.getLong(2);
+                long version = c.getLong(3);
+
+                HashSet<String> packages = validPackages.get(userId);
+                if (packages != null && packages.contains(pkg)) {
+                    long[] versions = getPackageVersion(pkg);
+                    if (versions[0] == version && versions[1] == lastUpdated) {
+                        // Every thing checks out
+                        continue;
+                    }
+                }
+
+                // We need to delete this package.
+                packages = packagesToDelete.get(userId);
+                if (packages == null) {
+                    packages = new HashSet<>();
+                    packagesToDelete.put(userId, packages);
+                }
+                packages.add(pkg);
+            }
+
+            for (int i = 0; i < packagesToDelete.size(); i++) {
+                long userId = packagesToDelete.keyAt(i);
+                UserHandleCompat user = mUserManager.getUserForSerialNumber(userId);
+                for (String pkg : packagesToDelete.valueAt(i)) {
+                    removePackage(pkg, user, userId);
+                }
+            }
+        } catch (SQLException e) {
+            Log.e(TAG, "Error updatating widget previews", e);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle) {
+        Cursor cursor = null;
+        try {
+            cursor = mDb.getReadableDatabase().query(
+                    CacheDb.TABLE_NAME,
+                    new String[] { CacheDb.COLUMN_PREVIEW_BITMAP },
+                    CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND " + CacheDb.COLUMN_SIZE + " = ?",
+                    new String[] {
+                            key.componentName.flattenToString(),
+                            Long.toString(mUserManager.getSerialNumberForUser(key.user)),
+                            key.size
+                    },
+                    null, null, null);
+            if (cursor.moveToNext()) {
+                byte[] blob = cursor.getBlob(0);
+                BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inBitmap = recycle;
                 try {
-                    db.delete(CacheDb.TABLE_NAME,
-                            CacheDb.COLUMN_NAME + " LIKE ? OR " +
-                            CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
-                            new String[] {
-                                    WIDGET_PREFIX + packageName + "/%",
-                                    SHORTCUT_PREFIX + packageName + "/%"
-                            } // args to SELECT query
-                    );
-                } catch (SQLiteDiskIOException e) {
-                } catch (SQLiteCantOpenDatabaseException e) {
-                    dumpOpenFiles();
-                    throw e;
+                    return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
+                } catch (Exception e) {
+                    return null;
                 }
-                synchronized(sInvalidPackages) {
-                    sInvalidPackages.remove(packageName);
-                }
-                return null;
             }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+        } catch (SQLException e) {
+            Log.w(TAG, "Error loading preview from DB", e);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return null;
     }
 
-    private static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
-        new AsyncTask<Void, Void, Void>() {
-            public Void doInBackground(Void ... args) {
-                SQLiteDatabase db = cacheDb.getWritableDatabase();
-                try {
-                    db.delete(CacheDb.TABLE_NAME,
-                            CacheDb.COLUMN_NAME + " = ? ", // SELECT query
-                            new String[] { objectName }); // args to SELECT query
-                } catch (SQLiteDiskIOException e) {
-                } catch (SQLiteCantOpenDatabaseException e) {
-                    dumpOpenFiles();
-                    throw e;
-                }
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
-    }
-
-    private Bitmap readFromDb(String name, Bitmap b) {
-        if (mCachedSelectQuery == null) {
-            mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
-                    CacheDb.COLUMN_SIZE + " = ?";
-        }
-        SQLiteDatabase db = mDb.getReadableDatabase();
-        Cursor result;
-        try {
-            result = db.query(CacheDb.TABLE_NAME,
-                    new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
-                    mCachedSelectQuery, // select query
-                    new String[] { name, mSize }, // args to select query
-                    null,
-                    null,
-                    null,
-                    null);
-        } catch (SQLiteDiskIOException e) {
-            recreateDb();
-            return null;
-        } catch (SQLiteCantOpenDatabaseException e) {
-            dumpOpenFiles();
-            throw e;
-        }
-        if (result.getCount() > 0) {
-            result.moveToFirst();
-            byte[] blob = result.getBlob(0);
-            result.close();
-            final BitmapFactory.Options opts = new BitmapFactory.Options();
-            opts.inBitmap = b;
-            opts.inSampleSize = 1;
-            try {
-                return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
-            } catch (IllegalArgumentException e) {
-                removeItemFromDb(mDb, name);
-                return null;
-            }
-        } else {
-            result.close();
-            return null;
-        }
-    }
-
-    private Bitmap generatePreview(Object info, Bitmap preview) {
-        if (preview != null &&
-                (preview.getWidth() != mPreviewBitmapWidth ||
-                preview.getHeight() != mPreviewBitmapHeight)) {
-            throw new RuntimeException("Improperly sized bitmap passed as argument");
-        }
+    private Bitmap generatePreview(Object info, Bitmap recycle, int previewWidth, int previewHeight) {
         if (info instanceof LauncherAppWidgetProviderInfo) {
-            return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, preview);
+            return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, previewWidth, recycle);
         } else {
             return generateShortcutPreview(
-                    (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
+                    (ResolveInfo) info, previewWidth, previewHeight, recycle);
         }
     }
 
-    public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, Bitmap preview) {
-        int maxWidth = maxWidthForWidgetPreview(info.spanX);
+    public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
+            int previewWidth, Bitmap preview) {
+        int maxWidth = Math.min(previewWidth, info.spanX
+                * LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile().cellWidthPx);
         return generateWidgetPreview(info, maxWidth, preview, null);
     }
 
-    public int maxWidthForWidgetPreview(int spanX) {
-        return Math.min(mPreviewBitmapWidth, spanX * mCellWidth);
-    }
-
     public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
             int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
         // Load the preview image if possible
@@ -488,6 +418,8 @@
         } else {
             final Paint p = new Paint();
             p.setFilterBitmap(true);
+            int appIconSize = LauncherAppState.getInstance().getDynamicGrid()
+                    .getDeviceProfile().iconSizePx;
 
             // draw the spanX x spanY tiles
             final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight());
@@ -507,18 +439,18 @@
 
             // Draw the icon in the top left corner
             // TODO: use top right for RTL
-            int minOffset = (int) (mAppIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
+            int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
             int smallestSide = Math.min(previewWidth, previewHeight);
-            float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), scale);
+            float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale);
 
             try {
                 Drawable icon = mutateOnMainThread(mManager.loadIcon(info, mIconCache));
                 if (icon != null) {
-                    int hoffset = (int) ((tileW - mAppIconSize * iconScale) / 2) + x;
-                    int yoffset = (int) ((tileH - mAppIconSize * iconScale) / 2);
+                    int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x;
+                    int yoffset = (int) ((tileH - appIconSize * iconScale) / 2);
                     icon.setBounds(hoffset, yoffset,
-                            hoffset + (int) (mAppIconSize * iconScale),
-                            yoffset + (int) (mAppIconSize * iconScale));
+                            hoffset + (int) (appIconSize * iconScale),
+                            yoffset + (int) (appIconSize * iconScale));
                     icon.draw(c);
                 }
             } catch (Resources.NotFoundException e) { }
@@ -561,9 +493,11 @@
 
         // Draw the final icon at top left corner.
         // TODO: use top right for RTL
+        int appIconSize = LauncherAppState.getInstance().getDynamicGrid()
+                .getDeviceProfile().iconSizePx;
         icon.setAlpha(255);
         icon.setColorFilter(null);
-        icon.setBounds(0, 0, mAppIconSize, mAppIconSize);
+        icon.setBounds(0, 0, appIconSize, appIconSize);
         icon.draw(c);
 
         c.setBitmap(null);
@@ -586,82 +520,144 @@
         }
     }
 
-    private static final int MAX_OPEN_FILES = 1024;
-    private static final int SAMPLE_RATE = 23;
     /**
-     * Dumps all files that are open in this process without allocating a file descriptor.
+     * @return an array of containing versionCode and lastUpdatedTime for the package.
      */
-    @Thunk static void dumpOpenFiles() {
-        try {
-            Log.i(TAG, "DUMP OF OPEN FILES (sample rate: 1 every " + SAMPLE_RATE + "):");
-            final String TYPE_APK = "apk";
-            final String TYPE_JAR = "jar";
-            final String TYPE_PIPE = "pipe";
-            final String TYPE_SOCKET = "socket";
-            final String TYPE_DB = "db";
-            final String TYPE_ANON_INODE = "anon_inode";
-            final String TYPE_DEV = "dev";
-            final String TYPE_NON_FS = "non-fs";
-            final String TYPE_OTHER = "other";
-            List<String> types = Arrays.asList(TYPE_APK, TYPE_JAR, TYPE_PIPE, TYPE_SOCKET, TYPE_DB,
-                    TYPE_ANON_INODE, TYPE_DEV, TYPE_NON_FS, TYPE_OTHER);
-            int[] count = new int[types.size()];
-            int[] duplicates = new int[types.size()];
-            HashSet<String> files = new HashSet<String>();
-            int total = 0;
-            for (int i = 0; i < MAX_OPEN_FILES; i++) {
-                // This is a gigantic hack but unfortunately the only way to resolve an fd
-                // to a file name. Note that we have to loop over all possible fds because
-                // reading the directory would require allocating a new fd. The kernel is
-                // currently implemented such that no fd is larger then the current rlimit,
-                // which is why it's safe to loop over them in such a way.
-                String fd = "/proc/self/fd/" + i;
+    private long[] getPackageVersion(String packageName) {
+        synchronized (mPackageVersions) {
+            long[] versions = mPackageVersions.get(packageName);
+            if (versions == null) {
+                versions = new long[2];
                 try {
-                    // getCanonicalPath() uses readlink behind the scene which doesn't require
-                    // a file descriptor.
-                    String resolved = new File(fd).getCanonicalPath();
-                    int type = types.indexOf(TYPE_OTHER);
-                    if (resolved.startsWith("/dev/")) {
-                        type = types.indexOf(TYPE_DEV);
-                    } else if (resolved.endsWith(".apk")) {
-                        type = types.indexOf(TYPE_APK);
-                    } else if (resolved.endsWith(".jar")) {
-                        type = types.indexOf(TYPE_JAR);
-                    } else if (resolved.contains("/fd/pipe:")) {
-                        type = types.indexOf(TYPE_PIPE);
-                    } else if (resolved.contains("/fd/socket:")) {
-                        type = types.indexOf(TYPE_SOCKET);
-                    } else if (resolved.contains("/fd/anon_inode:")) {
-                        type = types.indexOf(TYPE_ANON_INODE);
-                    } else if (resolved.endsWith(".db") || resolved.contains("/databases/")) {
-                        type = types.indexOf(TYPE_DB);
-                    } else if (resolved.startsWith("/proc/") && resolved.contains("/fd/")) {
-                        // Those are the files that don't point anywhere on the file system.
-                        // getCanonicalPath() wrongly interprets these as relative symlinks and
-                        // resolves them within /proc/<pid>/fd/.
-                        type = types.indexOf(TYPE_NON_FS);
+                    PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
+                    versions[0] = info.versionCode;
+                    versions[1] = info.lastUpdateTime;
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "PackageInfo not found", e);
+                }
+                mPackageVersions.put(packageName, versions);
+            }
+            return versions;
+        }
+    }
+
+    /**
+     * A request Id which can be used by the client to cancel any request.
+     */
+    public class PreviewLoadRequest {
+
+        private final PreviewLoadTask mTask;
+        private final WidgetCacheKey mKey;
+
+        public PreviewLoadRequest(PreviewLoadTask task, WidgetCacheKey key) {
+            mTask = task;
+            mKey = key;
+        }
+
+        public void cancel(boolean recycleImage) {
+            if (mTask != null) {
+                mTask.cancel(true);
+            }
+
+            if (recycleImage) {
+                synchronized(mLoadedPreviews) {
+                    WeakReference<Bitmap> result = mLoadedPreviews.remove(mKey);
+                    if (result != null && result.get() != null) {
+                        mUnusedBitmaps.add(result.get());
                     }
-                    count[type]++;
-                    total++;
-                    if (files.contains(resolved)) {
-                        duplicates[type]++;
-                    }
-                    files.add(resolved);
-                    if (total % SAMPLE_RATE == 0) {
-                        Log.i(TAG, " fd " + i + ": " + resolved
-                                + " (" + types.get(type) + ")");
-                    }
-                } catch (IOException e) {
-                    // Ignoring exceptions for non-existing file descriptors.
                 }
             }
-            for (int i = 0; i < types.size(); i++) {
-                Log.i(TAG, String.format("Open %10s files: %4d total, %4d duplicates",
-                        types.get(i), count[i], duplicates[i]));
+        }
+    }
+
+    public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> {
+
+        private final WidgetCacheKey mKey;
+        private final Object mInfo;
+        private final int mPreviewHeight;
+        private final int mPreviewWidth;
+        private final PagedViewWidget mCaller;
+
+        PreviewLoadTask(WidgetCacheKey key, Object info, int previewWidth,
+                int previewHeight, PagedViewWidget caller) {
+            mKey = key;
+            mInfo = info;
+            mPreviewHeight = previewHeight;
+            mPreviewWidth = previewWidth;
+            mCaller = caller;
+        }
+
+
+        @Override
+        protected Bitmap doInBackground(Void... params) {
+            Bitmap unusedBitmap = null;
+            synchronized (mUnusedBitmaps) {
+                // Check if we can use a bitmap
+                for (Bitmap candidate : mUnusedBitmaps) {
+                    if (candidate != null && candidate.isMutable() &&
+                            candidate.getWidth() == mPreviewWidth &&
+                            candidate.getHeight() == mPreviewHeight) {
+                        unusedBitmap = candidate;
+                        break;
+                    }
+                }
+
+                if (unusedBitmap == null) {
+                    unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
+                } else {
+                    mUnusedBitmaps.remove(unusedBitmap);
+                }
             }
-        } catch (Throwable t) {
-            // Catch everything. This is called from an exception handler that we shouldn't upset.
-            Log.e(TAG, "Unable to log open files.", t);
+
+            if (isCancelled()) {
+                return null;
+            }
+            Bitmap preview = readFromDb(mKey, unusedBitmap);
+            if (!isCancelled() && preview == null) {
+                // Fetch the version info before we generate the preview, so that, in-case the
+                // app was updated while we are generating the preview, we use the old version info,
+                // which would gets re-written next time.
+                long[] versions = getPackageVersion(mKey.componentName.getPackageName());
+
+                // it's not in the db... we need to generate it
+                preview = generatePreview(mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
+
+                if (!isCancelled()) {
+                    writeToDb(mKey, versions, preview);
+                }
+            }
+
+            return preview;
+        }
+
+        @Override
+        protected void onPostExecute(Bitmap result) {
+            synchronized(mLoadedPreviews) {
+                mLoadedPreviews.put(mKey, new WeakReference<Bitmap>(result));
+            }
+
+            mCaller.applyPreview(result);
+        }
+    }
+
+    private static final class WidgetCacheKey extends ComponentKey {
+
+        // TODO: remove dependency on size
+        private final String size;
+
+        public WidgetCacheKey(ComponentName componentName, UserHandleCompat user, String size) {
+            super(componentName, user);
+            this.size = size;
+        }
+
+        @Override
+        public int hashCode() {
+            return super.hashCode() ^ size.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return super.equals(o) && ((WidgetCacheKey) o).size.equals(size);
         }
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7c12d4c..7d6f59b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -367,13 +367,12 @@
 
     // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
     // dimension if unsuccessful
-    public int[] estimateItemSize(int hSpan, int vSpan,
-            ItemInfo itemInfo, boolean springLoaded) {
+    public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
         int[] size = new int[2];
         if (getChildCount() > 0) {
             // Use the first non-custom page to estimate the child position
             CellLayout cl = (CellLayout) getChildAt(numCustomPages());
-            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
+            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, itemInfo.spanX, itemInfo.spanY);
             size[0] = r.width();
             size[1] = r.height();
             if (springLoaded) {
@@ -2066,7 +2065,7 @@
     }
 
     public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
-        int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
+        int[] size = estimateItemSize(info, false);
 
         // The outline is used to visualize where the item will land if dropped
         mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
@@ -4033,8 +4032,7 @@
     }
 
     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
-        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
-                widgetInfo.spanY, widgetInfo, false);
+        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
         int visibility = layout.getVisibility();
         layout.setVisibility(VISIBLE);