Adding a utility class to enforce that icons are always flattened on
the background thread.

Bug: 21325319
Change-Id: I4d0f02557cdbf70cfa86750ac047faf2917e39bf
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 8b5a8a8..2a4212a 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -437,7 +436,8 @@
                 return -1;
             }
 
-            ItemInfo.writeBitmap(mValues, LauncherIcons.createIconBitmap(icon, mContext));
+            mValues.put(LauncherSettings.Favorites.ICON,
+                    Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext)));
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 8b70d1c..c244235 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,10 +16,10 @@
 
 package com.android.launcher3;
 
-import android.content.ContentValues;
 import android.content.Context;
 
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
 
@@ -93,10 +93,10 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-        values.put(LauncherSettings.Favorites.TITLE, title.toString());
-        values.put(LauncherSettings.Favorites.OPTIONS, options);
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.TITLE, title)
+                .put(LauncherSettings.Favorites.OPTIONS, options);
 
     }
 
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 04d0c8c..db72b2f 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -494,7 +494,6 @@
             shortcutInfo.setIcon(getDefaultIcon(user));
             shortcutInfo.title = "";
             shortcutInfo.contentDescription = "";
-            shortcutInfo.usingFallbackIcon = true;
             shortcutInfo.usingLowResIcon = false;
         } else {
             LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
@@ -512,7 +511,6 @@
         shortcutInfo.setIcon(getNonNullIcon(entry, user));
         shortcutInfo.title = Utilities.trim(entry.title);
         shortcutInfo.contentDescription = entry.contentDescription;
-        shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
         shortcutInfo.usingLowResIcon = entry.isLowResIcon;
     }
 
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 2043772..3e0ae4f 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -18,12 +18,10 @@
 
 import android.content.ComponentName;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 
 import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents an item in the launcher.
@@ -142,15 +140,15 @@
         return getIntent() == null ? null : getIntent().getComponent();
     }
 
-    public void writeToValues(ContentValues values) {
-        values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
-        values.put(LauncherSettings.Favorites.CONTAINER, container);
-        values.put(LauncherSettings.Favorites.SCREEN, screenId);
-        values.put(LauncherSettings.Favorites.CELLX, cellX);
-        values.put(LauncherSettings.Favorites.CELLY, cellY);
-        values.put(LauncherSettings.Favorites.SPANX, spanX);
-        values.put(LauncherSettings.Favorites.SPANY, spanY);
-        values.put(LauncherSettings.Favorites.RANK, rank);
+    public void writeToValues(ContentWriter writer) {
+        writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
+                .put(LauncherSettings.Favorites.CONTAINER, container)
+                .put(LauncherSettings.Favorites.SCREEN, screenId)
+                .put(LauncherSettings.Favorites.CELLX, cellX)
+                .put(LauncherSettings.Favorites.CELLY, cellY)
+                .put(LauncherSettings.Favorites.SPANX, spanX)
+                .put(LauncherSettings.Favorites.SPANY, spanY)
+                .put(LauncherSettings.Favorites.RANK, rank);
     }
 
     public void readFromValues(ContentValues values) {
@@ -166,26 +164,15 @@
 
     /**
      * Write the fields of this item to the DB
-     *
-     * @param context A context object to use for getting UserManagerCompat
-     * @param values
      */
-    void onAddToDatabase(Context context, ContentValues values) {
-        writeToValues(values);
-        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
-        values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
-
+    void onAddToDatabase(ContentWriter writer) {
         if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
             // We should never persist an item on the extra empty screen.
             throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
         }
-    }
 
-    static void writeBitmap(ContentValues values, Bitmap bitmap) {
-        if (bitmap != null) {
-            byte[] data = Utilities.flattenBitmap(bitmap);
-            values.put(LauncherSettings.Favorites.ICON, data);
-        }
+        writeToValues(writer);
+        writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 78f5b8e..2218767 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -18,11 +18,10 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a widget (either instantiated or about to be) in the Launcher.
@@ -127,13 +126,12 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-        values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
-        values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
-        values.put(LauncherSettings.Favorites.RESTORED, restoreStatus);
-        values.put(LauncherSettings.Favorites.INTENT,
-                bindOptions == null ? null : bindOptions.toUri(0));
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
+                .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
+                .put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+                .put(LauncherSettings.Favorites.INTENT, bindOptions);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index c70a475..42c7983 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -76,6 +76,7 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -332,7 +333,7 @@
         runOnWorkerThread(r);
     }
 
-    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
+    static void updateItemInDatabaseHelper(Context context, final ContentWriter writer,
             final ItemInfo item, final String callingFunction) {
         final long itemId = item.id;
         final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
@@ -341,7 +342,7 @@
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         Runnable r = new Runnable() {
             public void run() {
-                cr.update(uri, values, null, null);
+                cr.update(uri, writer.getValues(), null, null);
                 updateItemArrays(item, itemId, stackTrace);
             }
         };
@@ -439,14 +440,14 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
-        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
-        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
-        values.put(LauncherSettings.Favorites.RANK, item.rank);
-        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+        final ContentWriter writer = new ContentWriter(context)
+                .put(LauncherSettings.Favorites.CONTAINER, item.container)
+                .put(LauncherSettings.Favorites.CELLX, item.cellX)
+                .put(LauncherSettings.Favorites.CELLY, item.cellY)
+                .put(LauncherSettings.Favorites.RANK, item.rank)
+                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
 
-        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
+        updateItemInDatabaseHelper(context, writer, item, "moveItemInDatabase");
     }
 
     /**
@@ -506,25 +507,25 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
-        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
-        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
-        values.put(LauncherSettings.Favorites.RANK, item.rank);
-        values.put(LauncherSettings.Favorites.SPANX, item.spanX);
-        values.put(LauncherSettings.Favorites.SPANY, item.spanY);
-        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+        final ContentWriter writer = new ContentWriter(context)
+                .put(LauncherSettings.Favorites.CONTAINER, item.container)
+                .put(LauncherSettings.Favorites.CELLX, item.cellX)
+                .put(LauncherSettings.Favorites.CELLY, item.cellY)
+                .put(LauncherSettings.Favorites.RANK, item.rank)
+                .put(LauncherSettings.Favorites.SPANX, item.spanX)
+                .put(LauncherSettings.Favorites.SPANY, item.spanY)
+                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
 
-        updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
+        updateItemInDatabaseHelper(context, writer, item, "modifyItemInDatabase");
     }
 
     /**
      * Update an item to the database in a specified container.
      */
     public static void updateItemInDatabase(Context context, final ItemInfo item) {
-        final ContentValues values = new ContentValues();
-        item.onAddToDatabase(context, values);
-        updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
+        ContentWriter writer = new ContentWriter(context);
+        item.onAddToDatabase(writer);
+        updateItemInDatabaseHelper(context, writer, item, "updateItemInDatabase");
     }
 
     /**
@@ -546,19 +547,19 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
+        final ContentWriter writer = new ContentWriter(context);
         final ContentResolver cr = context.getContentResolver();
-        item.onAddToDatabase(context, values);
+        item.onAddToDatabase(writer);
 
         item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
 
-        values.put(LauncherSettings.Favorites._ID, item.id);
+        writer.put(LauncherSettings.Favorites._ID, item.id);
 
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         Runnable r = new Runnable() {
             public void run() {
-                cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
+                cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
 
                 synchronized (sBgDataModel) {
                     checkItemInfoLocked(item.id, item, stackTrace);
@@ -2632,7 +2633,6 @@
         // the fallback icon
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
     }
@@ -2669,7 +2669,6 @@
         info.user = UserHandleCompat.myUserHandle();
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
 
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fc08736..f894aae 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -18,7 +18,6 @@
 
 import android.annotation.TargetApi;
 import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -33,6 +32,7 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
@@ -77,12 +77,6 @@
     public Intent intent;
 
     /**
-     * Indicates whether we're using the default fallback icon instead of something from the
-     * app.
-     */
-    public boolean usingFallbackIcon;
-
-    /**
      * Indicates whether we're using a low res icon
      */
     boolean usingLowResIcon;
@@ -187,7 +181,6 @@
         status = info.status;
         mInstallProgress = info.mInstallProgress;
         isDisabled = info.isDisabled;
-        usingFallbackIcon = info.usingFallbackIcon;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -240,25 +233,19 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
+                .put(LauncherSettings.BaseLauncherColumns.INTENT, getPromisedIntent())
+                .put(LauncherSettings.Favorites.RESTORED, status);
 
-        String titleStr = title != null ? title.toString() : null;
-        values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
-
-        String uri = promisedIntent != null ? promisedIntent.toUri(0)
-                : (intent != null ? intent.toUri(0) : null);
-        values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
-        values.put(LauncherSettings.Favorites.RESTORED, status);
-
-        if (!usingFallbackIcon && !usingLowResIcon) {
-            writeBitmap(values, mIcon);
+        if (!usingLowResIcon) {
+            writer.putIcon(mIcon, user);
         }
         if (iconResource != null) {
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
-                    iconResource.packageName);
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
-                    iconResource.resourceName);
+            writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
+                    .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+                            iconResource.resourceName);
         }
     }
 
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 7286bf5..668b21b 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -213,7 +213,6 @@
                                     si.iconResource.resourceName, context);
                             if (icon != null) {
                                 si.setIcon(icon);
-                                si.usingFallbackIcon = false;
                                 infoUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
new file mode 100644
index 0000000..33d979c
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.util;
+
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * A wrapper around {@link ContentValues} with some utility methods.
+ */
+public class ContentWriter {
+
+    private final ContentValues mValues;
+    private final Context mContext;
+
+    private Bitmap mIcon;
+    private UserHandleCompat mUser;
+
+    public ContentWriter(Context context) {
+        this(new ContentValues(), context);
+    }
+
+    public ContentWriter(ContentValues values, Context context) {
+        mValues = values;
+        mContext = context;
+    }
+
+    public ContentWriter put(String key, Integer value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, Long value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, String value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, CharSequence value) {
+        mValues.put(key, value == null ? null : value.toString());
+        return this;
+    }
+
+    public ContentWriter put(String key, Intent value) {
+        mValues.put(key, value == null ? null : value.toUri(0));
+        return this;
+    }
+
+    public ContentWriter putIcon(Bitmap value, UserHandleCompat user) {
+        mIcon = value;
+        mUser = user;
+        return this;
+    }
+
+    public ContentWriter put(String key, UserHandleCompat user) {
+        return put(key, UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user));
+    }
+
+    /**
+     * Commits any pending validation and returns the final values.
+     * Must not be called on UI thread.
+     */
+    public ContentValues getValues() {
+        Preconditions.assertNonUiThread();
+        if (mIcon != null && !LauncherAppState.getInstance().getIconCache()
+                .isDefaultIcon(mIcon, mUser)) {
+            mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(mIcon));
+            mIcon = null;
+        }
+        return mValues;
+    }
+}
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index bade967..8eea28b 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -73,7 +73,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         ContentValues itemValues = new ContentValues();
-        writeToValues(itemValues);
+        writeToValues(new ContentWriter(itemValues, null));
         itemValues.writeToParcel(dest, flags);
 
         dest.writeInt(mArg1);