diff -r 000000000000 -r 6474c204b198 mobile/android/base/sync/config/ConfigurationMigrator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mobile/android/base/sync/config/ConfigurationMigrator.java Wed Dec 31 06:09:35 2014 +0100
@@ -0,0 +1,382 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.sync.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConfiguration;
+import org.mozilla.gecko.sync.Utils;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+/**
+ * Migrate Sync preferences between versions.
+ *
+ * The original preferences were un-versioned; we refer to that as "version 0".
+ * The original preferences were stored in three places:
+ *
+ * - most prefs were kept in per-Sync account Android shared prefs;
+ * - some prefs were kept in per-App Android shared prefs;
+ * - some client prefs were kept in the (assumed unique) Android Account.
+ *
+ *
+ * Post version 0, all preferences are stored in per-Sync account Android shared prefs.
+ */
+public class ConfigurationMigrator {
+ public static final String LOG_TAG = "ConfigMigrator";
+
+ /**
+ * Copy and rename preferences.
+ *
+ * @param from source.
+ * @param to sink.
+ * @param map map from old preference names to new preference names.
+ * @return the number of preferences migrated.
+ */
+ protected static int copyPreferences(final SharedPreferences from, final Map map, final Editor to) {
+ int count = 0;
+
+ // SharedPreferences has no way to get a key/value pair without specifying the value type, so we do this instead.
+ for (Entry entry : from.getAll().entrySet()) {
+ String fromKey = entry.getKey();
+ String toKey = map.get(fromKey);
+ if (toKey == null) {
+ continue;
+ }
+
+ Object value = entry.getValue();
+ if (value instanceof Boolean) {
+ to.putBoolean(toKey, ((Boolean) value).booleanValue());
+ } else if (value instanceof Float) {
+ to.putFloat(toKey, ((Float) value).floatValue());
+ } else if (value instanceof Integer) {
+ to.putInt(toKey, ((Integer) value).intValue());
+ } else if (value instanceof Long) {
+ to.putLong(toKey, ((Long) value).longValue());
+ } else if (value instanceof String) {
+ to.putString(toKey, (String) value);
+ } else {
+ // Do nothing -- perhaps SharedPreferences accepts types we don't know about.
+ }
+
+ if (Logger.LOG_PERSONAL_INFORMATION) {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + value + ").");
+ } else {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
+ }
+ count += 1;
+ }
+
+ return count;
+ }
+
+ protected final static String V0_PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
+ protected final static String V1_PREF_CLUSTER_URL_IS_STALE = V0_PREF_CLUSTER_URL_IS_STALE;
+ protected final static String V0_PREF_EARLIEST_NEXT_SYNC = "earliestnextsync";
+ protected final static String V1_PREF_EARLIEST_NEXT_SYNC = V0_PREF_EARLIEST_NEXT_SYNC;
+
+ /**
+ * Extract version 0 preferences from per-App Android shared prefs and write to version 1 per-Sync account shared prefs.
+ *
+ * @param from per-App version 0 Android shared prefs.
+ * @param to per-Sync account version 1 shared prefs.
+ * @return the number of preferences migrated.
+ * @throws Exception
+ */
+ protected static int upgradeGlobals0to1(final SharedPreferences from, final SharedPreferences to) throws Exception {
+ Map map = new HashMap();
+ map.put(V0_PREF_CLUSTER_URL_IS_STALE, V1_PREF_CLUSTER_URL_IS_STALE);
+ map.put(V0_PREF_EARLIEST_NEXT_SYNC, V1_PREF_EARLIEST_NEXT_SYNC);
+
+ Editor editor = to.edit();
+ int count = copyPreferences(from, map, editor);
+ if (count > 0) {
+ editor.commit();
+ }
+ return count;
+ }
+
+ /**
+ * Extract version 1 per-Sync account shared prefs and write to version 0 preferences from per-App Android shared prefs.
+ *
+ * @param from per-Sync account version 1 shared prefs.
+ * @param to per-App version 0 Android shared prefs.
+ * @return the number of preferences migrated.
+ * @throws Exception
+ */
+ protected static int downgradeGlobals1to0(final SharedPreferences from, final SharedPreferences to) throws Exception {
+ Map map = new HashMap();
+ map.put(V1_PREF_CLUSTER_URL_IS_STALE, V0_PREF_CLUSTER_URL_IS_STALE);
+ map.put(V1_PREF_EARLIEST_NEXT_SYNC, V0_PREF_EARLIEST_NEXT_SYNC);
+
+ Editor editor = to.edit();
+ int count = copyPreferences(from, map, editor);
+ if (count > 0) {
+ editor.commit();
+ }
+ return count;
+ }
+
+ protected static final String V0_PREF_ACCOUNT_GUID = "account.guid";
+ protected static final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID;
+ protected static final String V0_PREF_CLIENT_NAME = "account.clientName";
+ protected static final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME;
+ protected static final String V0_PREF_NUM_CLIENTS = "account.numClients";
+ protected static final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS;
+
+ /**
+ * Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs.
+ *
+ * @param accountManager Android account manager.
+ * @param account Android account.
+ * @param to per-Sync account version 1 shared prefs.
+ * @return the number of preferences migrated.
+ * @throws Exception
+ */
+ protected static int upgradeAndroidAccount0to1(final AccountManager accountManager, final Account account, final SharedPreferences to) throws Exception {
+ final String V0_PREF_ACCOUNT_GUID = "account.guid";
+ final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID;
+ final String V0_PREF_CLIENT_NAME = "account.clientName";
+ final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME;
+ final String V0_PREF_NUM_CLIENTS = "account.numClients";
+ final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS;
+
+ String accountGUID = null;
+ String clientName = null;
+ long numClients = -1;
+ try {
+ accountGUID = accountManager.getUserData(account, V0_PREF_ACCOUNT_GUID);
+ } catch (Exception e) {
+ // Do nothing.
+ }
+ try {
+ clientName = accountManager.getUserData(account, V0_PREF_CLIENT_NAME);
+ } catch (Exception e) {
+ // Do nothing.
+ }
+ try {
+ numClients = Long.parseLong(accountManager.getUserData(account, V0_PREF_NUM_CLIENTS));
+ } catch (Exception e) {
+ // Do nothing.
+ }
+
+ final Editor editor = to.edit();
+
+ int count = 0;
+ if (accountGUID != null) {
+ final String fromKey = V0_PREF_ACCOUNT_GUID;
+ final String toKey = V1_PREF_ACCOUNT_GUID;
+ if (Logger.LOG_PERSONAL_INFORMATION) {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + accountGUID + ").");
+ } else {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
+ }
+ editor.putString(toKey, accountGUID);
+ count += 1;
+ }
+ if (clientName != null) {
+ final String fromKey = V0_PREF_CLIENT_NAME;
+ final String toKey = V1_PREF_CLIENT_NAME;
+ if (Logger.LOG_PERSONAL_INFORMATION) {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + clientName + ").");
+ } else {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
+ }
+ editor.putString(toKey, clientName);
+ count += 1;
+ }
+ if (numClients > -1) {
+ final String fromKey = V0_PREF_NUM_CLIENTS;
+ final String toKey = V1_PREF_NUM_CLIENTS;
+ if (Logger.LOG_PERSONAL_INFORMATION) {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + numClients + ").");
+ } else {
+ Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
+ }
+ editor.putLong(toKey, numClients);
+ count += 1;
+ }
+
+ if (count > 0) {
+ editor.commit();
+ }
+ return count;
+ }
+
+ /**
+ * Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data.
+ *
+ * @param from per-Sync account version 1 shared prefs.
+ * @param accountManager Android account manager.
+ * @param account Android account.
+ * @return the number of preferences migrated.
+ * @throws Exception
+ */
+ protected static int downgradeAndroidAccount1to0(final SharedPreferences from, final AccountManager accountManager, final Account account) throws Exception {
+ final String accountGUID = from.getString(V1_PREF_ACCOUNT_GUID, null);
+ final String clientName = from.getString(V1_PREF_CLIENT_NAME, null);
+ final long numClients = from.getLong(V1_PREF_NUM_CLIENTS, -1L);
+
+ int count = 0;
+ if (accountGUID != null) {
+ Logger.debug(LOG_TAG, "Migrated account GUID.");
+ accountManager.setUserData(account, V0_PREF_ACCOUNT_GUID, accountGUID);
+ count += 1;
+ }
+ if (clientName != null) {
+ Logger.debug(LOG_TAG, "Migrated client name.");
+ accountManager.setUserData(account, V1_PREF_CLIENT_NAME, clientName);
+ count += 1;
+ }
+ if (numClients > -1) {
+ Logger.debug(LOG_TAG, "Migrated clients count.");
+ accountManager.setUserData(account, V1_PREF_NUM_CLIENTS, new Long(numClients).toString());
+ count += 1;
+ }
+ return count;
+ }
+
+ /**
+ * Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs.
+ *
+ * @param from per-Sync account version 0 shared prefs.
+ * @param to per-Sync account version 1 shared prefs.
+ * @return the number of preferences migrated.
+ * @throws Exception
+ */
+ protected static int upgradeShared0to1(final SharedPreferences from, final SharedPreferences to) {
+ final Map map = new HashMap();
+ final String[] prefs = new String [] {
+ "syncID",
+ "clusterURL",
+ "enabledEngineNames",
+
+ "metaGlobalLastModified", "metaGlobalServerResponseBody",
+
+ "crypto5KeysLastModified", "crypto5KeysServerResponseBody",
+
+ "serverClientsTimestamp", "serverClientRecordTimestamp",
+
+ "forms.remote", "forms.local", "forms.syncID",
+ "tabs.remote", "tabs.local", "tabs.syncID",
+ "passwords.remote", "passwords.local", "passwords.syncID",
+ "history.remote", "history.local", "history.syncID",
+ "bookmarks.remote", "bookmarks.local", "bookmarks.syncID",
+ };
+ for (String pref : prefs) {
+ map.put(pref, pref);
+ }
+
+ Editor editor = to.edit();
+ int count = copyPreferences(from, map, editor);
+ if (count > 0) {
+ editor.commit();
+ }
+ return count;
+ }
+
+ /**
+ * Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data.
+ *
+ * @param from per-Sync account version 1 shared prefs.
+ * @param to per-Sync account version 0 shared prefs.
+ * @return the number of preferences migrated.
+ * @throws Exception
+ */
+ protected static int downgradeShared1to0(final SharedPreferences from, final SharedPreferences to) {
+ // Strictly a copy, no re-naming, no deletions -- so just invert.
+ return upgradeShared0to1(from, to);
+ }
+
+ public static void upgrade0to1(final Context context, final AccountManager accountManager, final Account account,
+ final String product, final String username, final String serverURL, final String profile) throws Exception {
+
+ final String GLOBAL_SHARED_PREFS = "sync.prefs.global";
+
+ final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE);
+ final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0);
+ final SharedPreferences newPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1);
+
+ upgradeGlobals0to1(globalPrefs, newPrefs);
+ upgradeAndroidAccount0to1(accountManager, account, newPrefs);
+ upgradeShared0to1(accountPrefs, newPrefs);
+ }
+
+ public static void downgrade1to0(final Context context, final AccountManager accountManager, final Account account,
+ final String product, final String username, final String serverURL, final String profile) throws Exception {
+
+ final String GLOBAL_SHARED_PREFS = "sync.prefs.global";
+
+ final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE);
+ final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0);
+ final SharedPreferences oldPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1);
+
+ downgradeGlobals1to0(oldPrefs, globalPrefs);
+ downgradeAndroidAccount1to0(oldPrefs, accountManager, account);
+ downgradeShared1to0(oldPrefs, accountPrefs);
+ }
+
+ /**
+ * Migrate, if necessary, existing prefs to a certain version.
+ *
+ * Stores current prefs version in Android shared prefs with root
+ * "sync.prefs.version", which corresponds to the file
+ * "sync.prefs.version.xml".
+ *
+ * @param desiredVersion
+ * version to finish it.
+ * @param context
+ * @param accountManager
+ * @param account
+ * @param product
+ * @param username
+ * @param serverURL
+ * @param profile
+ * @throws Exception
+ */
+ public static void ensurePrefsAreVersion(final long desiredVersion,
+ final Context context, final AccountManager accountManager, final Account account,
+ final String product, final String username, final String serverURL, final String profile) throws Exception {
+ if (desiredVersion < 0 || desiredVersion > SyncConfiguration.CURRENT_PREFS_VERSION) {
+ throw new IllegalArgumentException("Cannot migrate to unknown version " + desiredVersion + ".");
+ }
+
+ SharedPreferences versionPrefs = context.getSharedPreferences("sync.prefs.version", Utils.SHARED_PREFERENCES_MODE);
+
+ // We default to 0 since clients getting this code for the first time will
+ // not have "sync.prefs.version.xml" *at all*, and upgrading when all old
+ // data is missing is expected to be safe.
+ long currentVersion = versionPrefs.getLong(SyncConfiguration.PREF_PREFS_VERSION, 0);
+ if (currentVersion == desiredVersion) {
+ Logger.info(LOG_TAG, "Current version (" + currentVersion + ") is desired version; no need to migrate.");
+ return;
+ }
+
+ if (currentVersion < 0 || currentVersion > SyncConfiguration.CURRENT_PREFS_VERSION) {
+ throw new IllegalStateException("Cannot migrate from unknown version " + currentVersion + ".");
+ }
+
+ // Now we're down to either version 0 or version 1.
+ if (currentVersion == 0 && desiredVersion == 1) {
+ Logger.info(LOG_TAG, "Upgrading from version 0 to version 1.");
+ upgrade0to1(context, accountManager, account, product, username, serverURL, profile);
+ } else if (currentVersion == 1 && desiredVersion == 0) {
+ Logger.info(LOG_TAG, "Upgrading from version 0 to version 1.");
+ upgrade0to1(context, accountManager, account, product, username, serverURL, profile);
+ } else {
+ Logger.warn(LOG_TAG, "Don't know how to migrate from version " + currentVersion + " to " + desiredVersion + ".");
+ }
+
+ Logger.info(LOG_TAG, "Migrated from version " + currentVersion + " to version " + desiredVersion + ".");
+ versionPrefs.edit().putLong(SyncConfiguration.PREF_PREFS_VERSION, desiredVersion).commit();
+ }
+}