Refactoring LauncherBackupHelper.java

> Moving a bunch of variables to Global level to make it easier
to maintain state.

Change-Id: I9ded313bd4f673f45c61556b8c66607dc78a5ae9
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 201f3e9..b2ac577 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -15,22 +15,6 @@
  */
 package com.android.launcher3;
 
-import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
-import com.google.protobuf.nano.MessageNano;
-
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.WorkspaceScreens;
-import com.android.launcher3.backup.BackupProtos;
-import com.android.launcher3.backup.BackupProtos.CheckedMessage;
-import com.android.launcher3.backup.BackupProtos.Favorite;
-import com.android.launcher3.backup.BackupProtos.Journal;
-import com.android.launcher3.backup.BackupProtos.Key;
-import com.android.launcher3.backup.BackupProtos.Resource;
-import com.android.launcher3.backup.BackupProtos.Screen;
-import com.android.launcher3.backup.BackupProtos.Widget;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
-
 import android.app.backup.BackupDataInputStream;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupHelper;
@@ -42,6 +26,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -51,6 +36,21 @@
 import android.util.Base64;
 import android.util.Log;
 
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.WorkspaceScreens;
+import com.android.launcher3.backup.BackupProtos;
+import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Resource;
+import com.android.launcher3.backup.BackupProtos.Screen;
+import com.android.launcher3.backup.BackupProtos.Widget;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -60,7 +60,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.zip.CRC32;
 
 /**
@@ -71,7 +70,6 @@
     private static final String TAG = "LauncherBackupHelper";
     private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
     private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
-    private static final boolean DEBUG_PAYLOAD = false;
 
     private static final int MAX_JOURNAL_SIZE = 1000000;
 
@@ -90,27 +88,25 @@
     private static final Bitmap.CompressFormat IMAGE_FORMAT =
             android.graphics.Bitmap.CompressFormat.PNG;
 
-    private static BackupManager sBackupManager;
-
     private static final String[] FAVORITE_PROJECTION = {
-            Favorites._ID,                     // 0
-            Favorites.MODIFIED,                // 1
-            Favorites.INTENT,                  // 2
-            Favorites.APPWIDGET_PROVIDER,      // 3
-            Favorites.APPWIDGET_ID,            // 4
-            Favorites.CELLX,                   // 5
-            Favorites.CELLY,                   // 6
-            Favorites.CONTAINER,               // 7
-            Favorites.ICON,                    // 8
-            Favorites.ICON_PACKAGE,            // 9
-            Favorites.ICON_RESOURCE,           // 10
-            Favorites.ICON_TYPE,               // 11
-            Favorites.ITEM_TYPE,               // 12
-            Favorites.SCREEN,                  // 13
-            Favorites.SPANX,                   // 14
-            Favorites.SPANY,                   // 15
-            Favorites.TITLE,                   // 16
-            Favorites.PROFILE_ID,              // 17
+        Favorites._ID,                     // 0
+        Favorites.MODIFIED,                // 1
+        Favorites.INTENT,                  // 2
+        Favorites.APPWIDGET_PROVIDER,      // 3
+        Favorites.APPWIDGET_ID,            // 4
+        Favorites.CELLX,                   // 5
+        Favorites.CELLY,                   // 6
+        Favorites.CONTAINER,               // 7
+        Favorites.ICON,                    // 8
+        Favorites.ICON_PACKAGE,            // 9
+        Favorites.ICON_RESOURCE,           // 10
+        Favorites.ICON_TYPE,               // 11
+        Favorites.ITEM_TYPE,               // 12
+        Favorites.SCREEN,                  // 13
+        Favorites.SPANX,                   // 14
+        Favorites.SPANY,                   // 15
+        Favorites.TITLE,                   // 16
+        Favorites.PROFILE_ID,              // 17
     };
 
     private static final int ID_INDEX = 0;
@@ -130,37 +126,46 @@
     private static final int SPANX_INDEX = 14;
     private static final int SPANY_INDEX = 15;
     private static final int TITLE_INDEX = 16;
-    private static final int PROFILE_ID_INDEX = 17;
 
     private static final String[] SCREEN_PROJECTION = {
-            WorkspaceScreens._ID,              // 0
-            WorkspaceScreens.MODIFIED,         // 1
-            WorkspaceScreens.SCREEN_RANK       // 2
+        WorkspaceScreens._ID,              // 0
+        WorkspaceScreens.MODIFIED,         // 1
+        WorkspaceScreens.SCREEN_RANK       // 2
     };
 
     private static final int SCREEN_RANK_INDEX = 2;
 
-    private static IconCache mIconCache;
-
     private final Context mContext;
-
-    private final boolean mRestoreEnabled;
-
-    private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
-
+    private final HashSet<String> mExistingKeys;
     private final ArrayList<Key> mKeys;
 
+    private IconCache mIconCache;
+    private BackupManager mBackupManager;
+    private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
+    private byte[] mBuffer = new byte[512];
+    private long mLastBackupRestoreTime;
+
     public LauncherBackupHelper(Context context, boolean restoreEnabled) {
         mContext = context;
-        mRestoreEnabled = restoreEnabled;
+        mExistingKeys = new HashSet<String>();
         mKeys = new ArrayList<Key>();
     }
 
     private void dataChanged() {
-        if (sBackupManager == null) {
-            sBackupManager = new BackupManager(mContext);
+        if (mBackupManager == null) {
+            mBackupManager = new BackupManager(mContext);
         }
-        sBackupManager.dataChanged();
+        mBackupManager.dataChanged();
+    }
+
+    private void applyJournal(Journal journal) {
+        mLastBackupRestoreTime = journal.t;
+        mExistingKeys.clear();
+        if (journal.key != null) {
+            for (Key key : journal.key) {
+                mExistingKeys.add(keyToBackupKey(key));
+            }
+        }
     }
 
     /**
@@ -181,32 +186,45 @@
         if (VERBOSE) Log.v(TAG, "onBackup");
 
         Journal in = readJournal(oldState);
-        Journal out = new Journal();
+        if (!launcherIsReady()) {
+            // Perform backup later.
+            writeJournal(newState, in);
+            return;
+        }
+        Log.v(TAG, "lastBackupTime = " + in.t);
+        mKeys.clear();
+        applyJournal(in);
 
-        long lastBackupTime = in.t;
-        out.t = System.currentTimeMillis();
-        out.rows = 0;
-        out.bytes = 0;
+        // Record the time before performing backup so that entries edited while the backup
+        // was going on, do not get missed in next backup.
+        long newBackupTime = System.currentTimeMillis();
 
-        Log.v(TAG, "lastBackupTime = " + lastBackupTime);
+        try {
+            backupFavorites(data);
+            backupScreens(data);
+            backupIcons(data);
+            backupWidgets(data);
 
-        ArrayList<Key> keys = new ArrayList<Key>();
-        if (launcherIsReady()) {
-            try {
-                backupFavorites(in, data, out, keys);
-                backupScreens(in, data, out, keys);
-                backupIcons(in, data, out, keys);
-                backupWidgets(in, data, out, keys);
-            } catch (IOException e) {
-                Log.e(TAG, "launcher backup has failed", e);
+            // Delete any key which still exist in the old backup, but is not valid anymore.
+            HashSet<String> validKeys = new HashSet<String>();
+            for (Key key : mKeys) {
+                validKeys.add(keyToBackupKey(key));
             }
-            out.key = keys.toArray(new BackupProtos.Key[keys.size()]);
-        } else {
-            out = in;
+            mExistingKeys.removeAll(validKeys);
+
+            // Delete anything left in the existing keys.
+            for (String deleted: mExistingKeys) {
+                if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
+                data.writeEntityHeader(deleted, -1);
+            }
+
+            mExistingKeys.clear();
+            mLastBackupRestoreTime = newBackupTime;
+        } catch (IOException e) {
+            Log.e(TAG, "launcher backup has failed", e);
         }
 
-        writeJournal(newState, out);
-        Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
+        writeNewStateDescription(newState);
     }
 
     /**
@@ -218,49 +236,41 @@
      */
     @Override
     public void restoreEntity(BackupDataInputStream data) {
-        if (VERBOSE) Log.v(TAG, "restoreEntity");
-        byte[] buffer = new byte[512];
-            String backupKey = data.getKey();
-            int dataSize = data.size();
-            if (buffer.length < dataSize) {
-                buffer = new byte[dataSize];
-            }
-            Key key = null;
-        int bytesRead = 0;
-        try {
-            bytesRead = data.read(buffer, 0, dataSize);
-            if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
-        } catch (IOException e) {
-            Log.e(TAG, "failed to read entity from restore data", e);
+        int dataSize = data.size();
+        if (mBuffer.length < dataSize) {
+            mBuffer = new byte[dataSize];
         }
         try {
-            key = backupKeyToKey(backupKey);
+            int bytesRead = data.read(mBuffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
+            String backupKey = data.getKey();
+            Key key = backupKeyToKey(backupKey);
             mKeys.add(key);
             switch (key.type) {
                 case Key.FAVORITE:
-                    restoreFavorite(key, buffer, dataSize, mKeys);
+                    restoreFavorite(key, mBuffer, dataSize);
                     break;
 
                 case Key.SCREEN:
-                    restoreScreen(key, buffer, dataSize, mKeys);
+                    restoreScreen(key, mBuffer, dataSize);
                     break;
 
                 case Key.ICON:
-                    restoreIcon(key, buffer, dataSize, mKeys);
+                    restoreIcon(key, mBuffer, dataSize);
                     break;
 
                 case Key.WIDGET:
-                    restoreWidget(key, buffer, dataSize, mKeys);
+                    restoreWidget(key, mBuffer, dataSize);
                     break;
 
                 default:
                     Log.w(TAG, "unknown restore entity type: " + key.type);
+                    mKeys.remove(key);
                     break;
             }
-        } catch (KeyParsingException e) {
-            Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
+        } catch (IOException e) {
+            Log.w(TAG, "ignoring unparsable backup entry", e);
         }
-
     }
 
     /**
@@ -270,63 +280,55 @@
      */
     @Override
     public void writeNewStateDescription(ParcelFileDescriptor newState) {
-        // clear the output journal time, to force a full backup to
-        // will catch any changes the restore process might have made
-        Journal out = new Journal();
-        out.t = 0;
-        out.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
-        writeJournal(newState, out);
-        Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
-        mKeys.clear();
+        writeJournal(newState, getCurrentStateJournal());
+    }
+
+    private Journal getCurrentStateJournal() {
+        Journal journal = new Journal();
+        journal.t = mLastBackupRestoreTime;
+        journal.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
+        journal.appVersion = getAppVersion();
+        return journal;
+    }
+
+    private int getAppVersion() {
+        try {
+            return mContext.getPackageManager()
+                    .getPackageInfo(mContext.getPackageName(), 0).versionCode;
+        } catch (NameNotFoundException e) {
+            return 0;
+        }
     }
 
     /**
      * Write all modified favorites to the data stream.
      *
-     *
-     * @param in notes from last backup
      * @param data output stream for key/value pairs
-     * @param out notes about this backup
-     * @param keys keys to mark as clean in the notes for next backup
      * @throws IOException
      */
-    private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
-            ArrayList<Key> keys)
-            throws IOException {
-        // read the old ID set
-        Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
-        if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
-
+    private void backupFavorites(BackupDataOutput data) throws IOException {
         // persist things that have changed since the last backup
         ContentResolver cr = mContext.getContentResolver();
         // Don't backup apps in other profiles for now.
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
                 getUserSelectionArg(), null, null);
-        Set<String> currentIds = new HashSet<String>(cursor.getCount());
         try {
             cursor.moveToPosition(-1);
             while(cursor.moveToNext()) {
                 final long id = cursor.getLong(ID_INDEX);
                 final long updateTime = cursor.getLong(ID_MODIFIED);
                 Key key = getKey(Key.FAVORITE, id);
-                keys.add(key);
+                mKeys.add(key);
                 final String backupKey = keyToBackupKey(key);
-                currentIds.add(backupKey);
-                if (!savedIds.contains(backupKey) || updateTime >= in.t) {
-                    byte[] blob = packFavorite(cursor);
-                    writeRowToBackup(key, blob, out, data);
+                if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
+                    writeRowToBackup(key, packFavorite(cursor), data);
                 } else {
-                    if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
+                    if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
                 }
             }
         } finally {
             cursor.close();
         }
-        if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
-
-        // these IDs must have been deleted
-        savedIds.removeAll(currentIds);
-        out.rows += removeDeletedKeysFromBackup(savedIds, data);
     }
 
     /**
@@ -337,74 +339,46 @@
      * @param key identifier for the row
      * @param buffer the serialized proto from the stream, may be larger than dataSize
      * @param dataSize the size of the proto from the stream
-     * @param keys keys to mark as clean in the notes for next backup
      */
-    private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+    private void restoreFavorite(Key key, byte[] buffer, int dataSize) throws IOException {
         if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
 
-        if (!mRestoreEnabled) {
-            if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
-            return;
-        }
-
-        try {
-            ContentResolver cr = mContext.getContentResolver();
-            ContentValues values = unpackFavorite(buffer, 0, dataSize);
-            cr.insert(Favorites.CONTENT_URI_NO_NOTIFICATION, values);
-        } catch (InvalidProtocolBufferNanoException e) {
-            Log.e(TAG, "failed to decode favorite", e);
-        }
+        ContentResolver cr = mContext.getContentResolver();
+        ContentValues values = unpackFavorite(buffer, dataSize);
+        cr.insert(Favorites.CONTENT_URI_NO_NOTIFICATION, values);
     }
 
     /**
      * Write all modified screens to the data stream.
      *
-     *
-     * @param in notes from last backup
      * @param data output stream for key/value pairs
-     * @param out notes about this backup
-     * @param keys keys to mark as clean in the notes for next backup
      * @throws IOException
      */
-    private void backupScreens(Journal in, BackupDataOutput data, Journal out,
-            ArrayList<Key> keys)
-            throws IOException {
-        // read the old ID set
-        Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
-        if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
-
+    private void backupScreens(BackupDataOutput data) throws IOException {
         // persist things that have changed since the last backup
         ContentResolver cr = mContext.getContentResolver();
         Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
                 null, null, null);
-        Set<String> currentIds = new HashSet<String>(cursor.getCount());
         try {
             cursor.moveToPosition(-1);
-            if (DEBUG) Log.d(TAG, "dumping screens after: " + in.t);
+            if (DEBUG) Log.d(TAG, "dumping screens after: " + mLastBackupRestoreTime);
             while(cursor.moveToNext()) {
                 final long id = cursor.getLong(ID_INDEX);
                 final long updateTime = cursor.getLong(ID_MODIFIED);
                 Key key = getKey(Key.SCREEN, id);
-                keys.add(key);
+                mKeys.add(key);
                 final String backupKey = keyToBackupKey(key);
-                currentIds.add(backupKey);
-                if (!savedIds.contains(backupKey) || updateTime >= in.t) {
-                    byte[] blob = packScreen(cursor);
-                    writeRowToBackup(key, blob, out, data);
+                if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
+                    writeRowToBackup(key, packScreen(cursor), data);
                 } else {
-                    if (VERBOSE) Log.v(TAG, "screen " + id + " was too old: " + updateTime);
+                    if (VERBOSE) Log.v(TAG, "screen already backup up " + id);
                 }
             }
         } finally {
             cursor.close();
         }
-        if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
-
-        // these IDs must have been deleted
-        savedIds.removeAll(currentIds);
-        out.rows += removeDeletedKeysFromBackup(savedIds, data);
     }
 
     /**
@@ -415,40 +389,24 @@
      * @param key identifier for the row
      * @param buffer the serialized proto from the stream, may be larger than dataSize
      * @param dataSize the size of the proto from the stream
-     * @param keys keys to mark as clean in the notes for next backup
      */
-    private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+    private void restoreScreen(Key key, byte[] buffer, int dataSize) throws IOException {
         if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
 
-        if (!mRestoreEnabled) {
-            if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
-            return;
-        }
-
-        try {
-            ContentResolver cr = mContext.getContentResolver();
-            ContentValues values = unpackScreen(buffer, 0, dataSize);
-            cr.insert(WorkspaceScreens.CONTENT_URI, values);
-
-        } catch (InvalidProtocolBufferNanoException e) {
-            Log.e(TAG, "failed to decode screen", e);
-        }
+        ContentResolver cr = mContext.getContentResolver();
+        ContentValues values = unpackScreen(buffer, dataSize);
+        cr.insert(WorkspaceScreens.CONTENT_URI, values);
     }
 
     /**
      * Write all the static icon resources we need to render placeholders
      * for a package that is not installed.
      *
-     * @param in notes from last backup
      * @param data output stream for key/value pairs
-     * @param out notes about this backup
-     * @param keys keys to mark as clean in the notes for next backup
-     * @throws IOException
      */
-    private void backupIcons(Journal in, BackupDataOutput data, Journal out,
-            ArrayList<Key> keys) throws IOException {
+    private void backupIcons(BackupDataOutput data) throws IOException {
         // persist icons that haven't been persisted yet
         if (!initializeIconCache()) {
             dataChanged(); // try again later
@@ -458,21 +416,14 @@
         final ContentResolver cr = mContext.getContentResolver();
         final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
         final UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
-
-        // read the old ID set
-        Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
-        if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
+        int backupUpIconCount = 0;
 
         // Don't backup apps in other profiles for now.
-        int startRows = out.rows;
-        if (DEBUG) Log.d(TAG, "starting here: " + startRows);
-
         String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
                 Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
                 getUserSelectionArg();
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
                 where, null, null);
-        Set<String> currentIds = new HashSet<String>(cursor.getCount());
         try {
             cursor.moveToPosition(-1);
             while(cursor.moveToNext()) {
@@ -486,27 +437,26 @@
                     if (cn != null) {
                         key = getKey(Key.ICON, cn.flattenToShortString());
                         backupKey = keyToBackupKey(key);
-                        currentIds.add(backupKey);
                     } else {
                         Log.w(TAG, "empty intent on application favorite: " + id);
                     }
-                    if (savedIds.contains(backupKey)) {
-                        if (VERBOSE) Log.v(TAG, "already saved icon " + backupKey);
+                    if (mExistingKeys.contains(backupKey)) {
+                        if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
 
                         // remember that we already backed this up previously
-                        keys.add(key);
+                        mKeys.add(key);
                     } else if (backupKey != null) {
-                        if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
-                        if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
-                            if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
+                        if (DEBUG) Log.d(TAG, "I can count this high: " + backupUpIconCount);
+                        if (backupUpIconCount < MAX_ICONS_PER_PASS) {
+                            if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
                             Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
-                            keys.add(key);
                             if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
-                                byte[] blob = packIcon(dpi, icon);
-                                writeRowToBackup(key, blob, out, data);
+                                writeRowToBackup(key, packIcon(dpi, icon), data);
+                                mKeys.add(key);
+                                backupUpIconCount ++;
                             }
                         } else {
-                            if (VERBOSE) Log.d(TAG, "deferring icon backup " + backupKey);
+                            if (VERBOSE) Log.v(TAG, "deferring icon backup " + backupKey);
                             // too many icons for this pass, request another.
                             dataChanged();
                         }
@@ -521,11 +471,6 @@
         } finally {
             cursor.close();
         }
-        if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
-
-        // these IDs must have been deleted
-        savedIds.removeAll(currentIds);
-        out.rows += removeDeletedKeysFromBackup(savedIds, data);
     }
 
     /**
@@ -536,55 +481,32 @@
      * @param key identifier for the row
      * @param buffer the serialized proto from the stream, may be larger than dataSize
      * @param dataSize the size of the proto from the stream
-     * @param keys keys to mark as clean in the notes for next backup
      */
-    private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+    private void restoreIcon(Key key, byte[] buffer, int dataSize) throws IOException {
         if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
 
-        try {
-            Resource res = unpackIcon(buffer, 0, dataSize);
-            if (DEBUG) {
-                Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
-            }
-            if (DEBUG_PAYLOAD) {
-                Log.d(TAG, "read " +
-                        Base64.encodeToString(res.data, 0, res.data.length,
-                                Base64.NO_WRAP));
-            }
-            Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
-            if (icon == null) {
-                Log.w(TAG, "failed to unpack icon for " + key.name);
-            }
-
-            if (!mRestoreEnabled) {
-                if (VERBOSE) {
-                    Log.v(TAG, "restore not enabled: skipping database mutation");
-                }
-                return;
-            } else {
-                if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
-                IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
-                        icon, res.dpi);
-            }
-        } catch (IOException e) {
-            Log.d(TAG, "failed to save restored icon for: " + key.name, e);
+        Resource res = unpackProto(new Resource(), buffer, dataSize);
+        if (DEBUG) {
+            Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
         }
+        Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
+        if (icon == null) {
+            Log.w(TAG, "failed to unpack icon for " + key.name);
+        }
+        if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
+        IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name), icon, res.dpi);
     }
 
     /**
      * Write all the static widget resources we need to render placeholders
      * for a package that is not installed.
      *
-     * @param in notes from last backup
      * @param data output stream for key/value pairs
-     * @param out notes about this backup
-     * @param keys keys to mark as clean in the notes for next backup
      * @throws IOException
      */
-    private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
-            ArrayList<Key> keys) throws IOException {
+    private void backupWidgets(BackupDataOutput data) throws IOException {
         // persist static widget info that hasn't been persisted yet
         final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
         if (appState == null || !initializeIconCache()) {
@@ -597,18 +519,12 @@
         final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
         final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
         if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
+        int backupWidgetCount = 0;
 
-        // read the old ID set
-        Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
-        if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
-
-        int startRows = out.rows;
-        if (DEBUG) Log.d(TAG, "starting here: " + startRows);
         String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET + " AND "
                 + getUserSelectionArg();
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
                 where, null, null);
-        Set<String> currentIds = new HashSet<String>(cursor.getCount());
         try {
             cursor.moveToPosition(-1);
             while(cursor.moveToNext()) {
@@ -622,27 +538,25 @@
                 if (provider != null) {
                     key = getKey(Key.WIDGET, providerName);
                     backupKey = keyToBackupKey(key);
-                    currentIds.add(backupKey);
                 } else {
                     Log.w(TAG, "empty intent on appwidget: " + id);
                 }
-                if (savedIds.contains(backupKey)) {
-                    if (VERBOSE) Log.v(TAG, "already saved widget " + backupKey);
+                if (mExistingKeys.contains(backupKey)) {
+                    if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
 
                     // remember that we already backed this up previously
-                    keys.add(key);
+                    mKeys.add(key);
                 } else if (backupKey != null) {
-                    if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
-                    if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
-                        if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
+                    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, widgetSpacingLayout);
-                        byte[] blob = packWidget(dpi, previewLoader, mIconCache, provider);
-                        keys.add(key);
-                        writeRowToBackup(key, blob, out, data);
-
+                        writeRowToBackup(key, packWidget(dpi, previewLoader, mIconCache, provider), data);
+                        mKeys.add(key);
+                        backupWidgetCount ++;
                     } else {
-                        if (VERBOSE) Log.d(TAG, "deferring widget backup " + backupKey);
+                        if (VERBOSE) Log.v(TAG, "deferring widget backup " + backupKey);
                         // too many widgets for this pass, request another.
                         dataChanged();
                     }
@@ -651,11 +565,6 @@
         } finally {
             cursor.close();
         }
-        if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
-
-        // these IDs must have been deleted
-        savedIds.removeAll(currentIds);
-        out.rows += removeDeletedKeysFromBackup(savedIds, data);
     }
 
     /**
@@ -666,35 +575,25 @@
      * @param key identifier for the row
      * @param buffer the serialized proto from the stream, may be larger than dataSize
      * @param dataSize the size of the proto from the stream
-     * @param keys keys to mark as clean in the notes for next backup
      */
-    private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+    private void restoreWidget(Key key, byte[] buffer, int dataSize) throws IOException {
         if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
-        try {
-            Widget widget = unpackWidget(buffer, 0, dataSize);
-            if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
-            if (widget.icon.data != null)  {
-                Bitmap icon = BitmapFactory
-                        .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
-                if (icon == null) {
-                    Log.w(TAG, "failed to unpack widget icon for " + key.name);
-                } else {
-                    IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
-                            icon, widget.icon.dpi);
-                }
-            }
-
-            if (!mRestoreEnabled) {
-                if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
-                return;
+        Widget widget = unpackProto(new Widget(), buffer, dataSize);
+        if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
+        if (widget.icon.data != null)  {
+            Bitmap icon = BitmapFactory
+                    .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
+            if (icon == null) {
+                Log.w(TAG, "failed to unpack widget icon for " + key.name);
             } else {
-                // future site of widget table mutation
+                IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
+                        icon, widget.icon.dpi);
             }
-        } catch (InvalidProtocolBufferNanoException e) {
-            Log.e(TAG, "failed to decode widget", e);
         }
+
+        // future site of widget table mutation
     }
 
     /** create a new key, with an integer ID.
@@ -744,30 +643,6 @@
         }
     }
 
-    private String getKeyName(Key key) {
-        if (TextUtils.isEmpty(key.name)) {
-            return Long.toString(key.id);
-        } else {
-            return key.name;
-        }
-
-    }
-
-    private String geKeyType(Key key) {
-        switch (key.type) {
-            case Key.FAVORITE:
-                return "favorite";
-            case Key.SCREEN:
-                return "screen";
-            case Key.ICON:
-                return "icon";
-            case Key.WIDGET:
-                return "widget";
-            default:
-                return "anonymous";
-        }
-    }
-
     /** Compute the checksum over the important bits of a key. */
     private long checkKey(Key key) {
         CRC32 checksum = new CRC32();
@@ -781,7 +656,7 @@
     }
 
     /** Serialize a Favorite for persistence, including a checksum wrapper. */
-    private byte[] packFavorite(Cursor c) {
+    private Favorite packFavorite(Cursor c) {
         Favorite favorite = new Favorite();
         favorite.id = c.getLong(ID_INDEX);
         favorite.screen = c.getInt(SCREEN_INDEX);
@@ -819,7 +694,7 @@
                 favorite.intent = intent.toUri(0);
             } catch (URISyntaxException e) {
                 Log.e(TAG, "Invalid intent", e);
-           }
+            }
         }
         favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
@@ -830,16 +705,13 @@
             }
         }
 
-        return writeCheckedBytes(favorite);
+        return favorite;
     }
 
     /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
-    private ContentValues unpackFavorite(byte[] buffer, int offset, int dataSize)
+    private ContentValues unpackFavorite(byte[] buffer, int dataSize)
             throws InvalidProtocolBufferNanoException {
-        Favorite favorite = new Favorite();
-        MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
-        if (VERBOSE) Log.v(TAG, "unpacked favorite " + favorite.itemType + ", " +
-                (TextUtils.isEmpty(favorite.title) ? favorite.id : favorite.title));
+        Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
         ContentValues values = new ContentValues();
         values.put(Favorites._ID, favorite.id);
         values.put(Favorites.SCREEN, favorite.screen);
@@ -889,20 +761,17 @@
     }
 
     /** Serialize a Screen for persistence, including a checksum wrapper. */
-    private byte[] packScreen(Cursor c) {
+    private Screen packScreen(Cursor c) {
         Screen screen = new Screen();
         screen.id = c.getLong(ID_INDEX);
         screen.rank = c.getInt(SCREEN_RANK_INDEX);
-
-        return writeCheckedBytes(screen);
+        return screen;
     }
 
     /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
-    private ContentValues unpackScreen(byte[] buffer, int offset, int dataSize)
+    private ContentValues unpackScreen(byte[] buffer, int dataSize)
             throws InvalidProtocolBufferNanoException {
-        Screen screen = new Screen();
-        MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
-        if (VERBOSE) Log.v(TAG, "unpacked screen " + screen.id + "/" + screen.rank);
+        Screen screen = unpackProto(new Screen(), buffer, dataSize);
         ContentValues values = new ContentValues();
         values.put(WorkspaceScreens._ID, screen.id);
         values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
@@ -910,27 +779,18 @@
     }
 
     /** Serialize an icon Resource for persistence, including a checksum wrapper. */
-    private byte[] packIcon(int dpi, Bitmap icon) {
+    private Resource packIcon(int dpi, Bitmap icon) {
         Resource res = new Resource();
         res.dpi = dpi;
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
             res.data = os.toByteArray();
         }
-        return writeCheckedBytes(res);
-    }
-
-    /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
-    private static Resource unpackIcon(byte[] buffer, int offset, int dataSize)
-            throws InvalidProtocolBufferNanoException {
-        Resource res = new Resource();
-        MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
-        if (VERBOSE) Log.v(TAG, "unpacked icon " + res.dpi + "/" + res.data.length);
         return res;
     }
 
     /** Serialize a widget for persistence, including a checksum wrapper. */
-    private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
+    private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
             ComponentName provider) {
         final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
         Widget widget = new Widget();
@@ -956,16 +816,17 @@
                 widget.preview.dpi = dpi;
             }
         }
-        return writeCheckedBytes(widget);
+        return widget;
     }
 
-    /** Deserialize a widget from persistence, after verifying checksum wrapper. */
-    private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
+    /**
+     * Deserialize a proto after verifying checksum wrapper.
+     */
+    private <T extends MessageNano> T unpackProto(T proto, byte[] buffer, int dataSize)
             throws InvalidProtocolBufferNanoException {
-        Widget widget = new Widget();
-        MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
-        if (VERBOSE) Log.v(TAG, "unpacked widget " + widget.provider);
-        return widget;
+        MessageNano.mergeFrom(proto, readCheckedBytes(buffer, dataSize));
+        if (DEBUG) Log.d(TAG, "unpacked proto " + proto);
+        return proto;
     }
 
     /**
@@ -1001,9 +862,6 @@
                         if (result > 0) {
                             availableBytes -= result;
                             bytesRead += result;
-                            if (DEBUG && (bytesRead % 100 == 0)) {
-                                Log.d(TAG, "read some bytes: " + bytesRead);
-                            }
                         } else {
                             Log.w(TAG, "unexpected end of file while reading journal.");
                             // stop reading and see what there is to parse
@@ -1016,7 +874,7 @@
 
                     // check the buffer to see if we have a valid journal
                     try {
-                        MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
+                        MessageNano.mergeFrom(journal, readCheckedBytes(buffer, bytesRead));
                         // if we are here, then we have read a valid, checksum-verified journal
                         valid = true;
                         availableBytes = 0;
@@ -1044,46 +902,18 @@
         return journal;
     }
 
-    private void writeRowToBackup(Key key, byte[] blob, Journal out,
+
+    private void writeRowToBackup(Key key, MessageNano proto, BackupDataOutput data)
+            throws IOException {
+        writeRowToBackup(keyToBackupKey(key), proto, data);
+    }
+
+    private void writeRowToBackup(String backupKey, MessageNano proto,
             BackupDataOutput data) throws IOException {
-        String backupKey = keyToBackupKey(key);
+        byte[] blob = writeCheckedBytes(proto);
         data.writeEntityHeader(backupKey, blob.length);
         data.writeEntityData(blob, blob.length);
-        out.rows++;
-        out.bytes += blob.length;
-        if (VERBOSE) Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
-                getKeyName(key) + "/" + blob.length);
-        if(DEBUG_PAYLOAD) {
-            String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
-            final int chunkSize = 1024;
-            for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
-                int end = offset + chunkSize;
-                end = Math.min(end, encoded.length());
-                Log.w(TAG, "wrote " + encoded.substring(offset, end));
-            }
-        }
-    }
-
-    private Set<String> getSavedIdsByType(int type, Journal in) {
-        Set<String> savedIds = new HashSet<String>();
-        for(int i = 0; i < in.key.length; i++) {
-            Key key = in.key[i];
-            if (key.type == type) {
-                savedIds.add(keyToBackupKey(key));
-            }
-        }
-        return savedIds;
-    }
-
-    private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
-            throws IOException {
-        int rows = 0;
-        for(String deleted: deletedIds) {
-            if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
-            data.writeEntityHeader(deleted, -1);
-            rows++;
-        }
-        return rows;
+        if (VERBOSE) Log.v(TAG, "Writing New entry " + backupKey);
     }
 
     /**
@@ -1119,10 +949,10 @@
     }
 
     /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
-    private static byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+    private static byte[] readCheckedBytes(byte[] buffer, int dataSize)
             throws InvalidProtocolBufferNanoException {
         CheckedMessage wrapper = new CheckedMessage();
-        MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
+        MessageNano.mergeFrom(wrapper, buffer, 0, dataSize);
         CRC32 checksum = new CRC32();
         checksum.update(wrapper.payload);
         if (wrapper.checksum != checksum.getValue()) {
@@ -1161,7 +991,9 @@
     }
 
 
-   // check if the launcher is in a state to support backup
+    /**
+     * @return true if the launcher is in a state to support backup
+     */
     private boolean launcherIsReady() {
         ContentResolver cr = mContext.getContentResolver();
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
@@ -1185,7 +1017,7 @@
                 .getSerialNumberForUser(UserHandleCompat.myUserHandle());
     }
 
-    private class KeyParsingException extends Throwable {
+    private class KeyParsingException extends IOException {
         private KeyParsingException(Throwable cause) {
             super(cause);
         }