Updating backup restore logic

> Adding DeviceProfile information in the backup
> Removing SharedPreference backup
> Adding helper methods to abort backup in the middle
> Comparing keys against the backup journal during restore
to avoid restoring corrupt/lost entries
> Old backups are still compatible, but lost keys verification
will be ignored in that case.

Bug: 17937935
Bug: 17951775
Bug: 17260941
Change-Id: Iad48646cfdd69abaff5c163b2055f3b8a9b39b19
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index b2ac577..8b9c5d9 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -40,6 +40,7 @@
 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.DeviceProfieData;
 import com.android.launcher3.backup.BackupProtos.Favorite;
 import com.android.launcher3.backup.BackupProtos.Journal;
 import com.android.launcher3.backup.BackupProtos.Key;
@@ -66,24 +67,24 @@
  * Persist the launcher home state across calamities.
  */
 public class LauncherBackupHelper implements BackupHelper {
-
     private static final String TAG = "LauncherBackupHelper";
     private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
     private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
 
+    private static final int BACKUP_VERSION = 2;
     private static final int MAX_JOURNAL_SIZE = 1000000;
 
+    // Journal key is such that it is always smaller than any dynamically generated
+    // key (any Base64 encoded string).
+    private static final String JOURNAL_KEY = "#";
+
     /** icons are large, dribble them out */
     private static final int MAX_ICONS_PER_PASS = 10;
 
     /** widgets contain previews, which are very large, dribble them out */
     private static final int MAX_WIDGETS_PER_PASS = 5;
 
-    public static final int IMAGE_COMPRESSION_QUALITY = 75;
-
-    public static final String LAUNCHER_PREFIX = "L";
-
-    public static final String LAUNCHER_PREFS_PREFIX = "LP";
+    private static final int IMAGE_COMPRESSION_QUALITY = 75;
 
     private static final Bitmap.CompressFormat IMAGE_FORMAT =
             android.graphics.Bitmap.CompressFormat.PNG;
@@ -145,10 +146,13 @@
     private byte[] mBuffer = new byte[512];
     private long mLastBackupRestoreTime;
 
-    public LauncherBackupHelper(Context context, boolean restoreEnabled) {
+    boolean restoreSuccessful;
+
+    public LauncherBackupHelper(Context context) {
         mContext = context;
         mExistingKeys = new HashSet<String>();
         mKeys = new ArrayList<Key>();
+        restoreSuccessful = true;
     }
 
     private void dataChanged() {
@@ -178,7 +182,6 @@
      * @param oldState notes from the last backup
      * @param data incremental key/value pairs to persist off-device
      * @param newState notes for the next backup
-     * @throws IOException
      */
     @Override
     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
@@ -220,6 +223,12 @@
 
             mExistingKeys.clear();
             mLastBackupRestoreTime = newBackupTime;
+
+            // We store the journal at two places.
+            //   1) Storing it in newState allows us to do partial backups by comparing old state
+            //   2) Storing it in backup data allows us to validate keys during restore
+            Journal state = getCurrentStateJournal();
+            writeRowToBackup(JOURNAL_KEY, state, data);
         } catch (IOException e) {
             Log.e(TAG, "launcher backup has failed", e);
         }
@@ -228,14 +237,26 @@
     }
 
     /**
+     * @return true if the backup corresponding to oldstate can be successfully applied
+     * to this device.
+     */
+    private boolean isBackupCompatible(Journal oldState) {
+        return true;
+    }
+
+    /**
      * Restore launcher configuration from the restored data stream.
-     *
-     * <P>Keys may arrive in any order.
+     * It assumes that the keys will arrive in lexical order. So if the journal was present in the
+     * backup, it should arrive first.
      *
      * @param data the key/value pair from the server
      */
     @Override
     public void restoreEntity(BackupDataInputStream data) {
+        if (!restoreSuccessful) {
+            return;
+        }
+
         int dataSize = data.size();
         if (mBuffer.length < dataSize) {
             mBuffer = new byte[dataSize];
@@ -244,6 +265,27 @@
             int bytesRead = data.read(mBuffer, 0, dataSize);
             if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
             String backupKey = data.getKey();
+
+            if (JOURNAL_KEY.equals(backupKey)) {
+                if (VERBOSE) Log.v(TAG, "Journal entry restored");
+                if (!mKeys.isEmpty()) {
+                    // We received the journal key after a restore key.
+                    Log.wtf(TAG, keyToBackupKey(mKeys.get(0)) + " received after " + JOURNAL_KEY);
+                    restoreSuccessful = false;
+                    return;
+                }
+
+                Journal journal = new Journal();
+                MessageNano.mergeFrom(journal, readCheckedBytes(mBuffer, dataSize));
+                applyJournal(journal);
+                restoreSuccessful = isBackupCompatible(journal);
+                return;
+            }
+
+            if (!mExistingKeys.isEmpty() && !mExistingKeys.contains(backupKey)) {
+                if (DEBUG) Log.e(TAG, "Ignoring key not present in the backup state " + backupKey);
+                return;
+            }
             Key key = backupKeyToKey(backupKey);
             mKeys.add(key);
             switch (key.type) {
@@ -288,6 +330,8 @@
         journal.t = mLastBackupRestoreTime;
         journal.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
         journal.appVersion = getAppVersion();
+        journal.backupVersion = BACKUP_VERSION;
+        journal.profile = getDeviceProfieData();
         return journal;
     }
 
@@ -301,6 +345,29 @@
     }
 
     /**
+     * @return the current device profile information.
+     */
+    private DeviceProfieData getDeviceProfieData() {
+        LauncherAppState.setApplicationContext(mContext.getApplicationContext());
+        LauncherAppState app = LauncherAppState.getInstance();
+
+        DeviceProfile profile;
+        if (app.getDynamicGrid() == null) {
+            // Initialize the grid
+            profile = app.initDynamicGrid(mContext);
+        } else {
+            profile = app.getDynamicGrid().getDeviceProfile();
+        }
+
+        DeviceProfieData data = new DeviceProfieData();
+        data.desktopRows = profile.numRows;
+        data.desktopCols = profile.numColumns;
+        data.hotseatCount = profile.numHotseatIcons;
+        data.allappsRank = profile.hotseatAllAppsRank;
+        return data;
+    }
+
+    /**
      * Write all modified favorites to the data stream.
      *
      * @param data output stream for key/value pairs
@@ -902,7 +969,6 @@
         return journal;
     }
 
-
     private void writeRowToBackup(Key key, MessageNano proto, BackupDataOutput data)
             throws IOException {
         writeRowToBackup(keyToBackupKey(key), proto, data);