1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/sync/config/ConfigurationMigrator.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,382 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.sync.config; 1.9 + 1.10 +import java.util.HashMap; 1.11 +import java.util.Map; 1.12 +import java.util.Map.Entry; 1.13 + 1.14 +import org.mozilla.gecko.background.common.log.Logger; 1.15 +import org.mozilla.gecko.sync.SyncConfiguration; 1.16 +import org.mozilla.gecko.sync.Utils; 1.17 + 1.18 +import android.accounts.Account; 1.19 +import android.accounts.AccountManager; 1.20 +import android.content.Context; 1.21 +import android.content.SharedPreferences; 1.22 +import android.content.SharedPreferences.Editor; 1.23 + 1.24 +/** 1.25 + * Migrate Sync preferences between versions. 1.26 + * <p> 1.27 + * The original preferences were un-versioned; we refer to that as "version 0". 1.28 + * The original preferences were stored in three places: 1.29 + * <ul> 1.30 + * <li>most prefs were kept in per-Sync account Android shared prefs;</li> 1.31 + * <li>some prefs were kept in per-App Android shared prefs;</li> 1.32 + * <li>some client prefs were kept in the (assumed unique) Android Account.</li> 1.33 + * </ul> 1.34 + * <p> 1.35 + * Post version 0, all preferences are stored in per-Sync account Android shared prefs. 1.36 + */ 1.37 +public class ConfigurationMigrator { 1.38 + public static final String LOG_TAG = "ConfigMigrator"; 1.39 + 1.40 + /** 1.41 + * Copy and rename preferences. 1.42 + * 1.43 + * @param from source. 1.44 + * @param to sink. 1.45 + * @param map map from old preference names to new preference names. 1.46 + * @return the number of preferences migrated. 1.47 + */ 1.48 + protected static int copyPreferences(final SharedPreferences from, final Map<String, String> map, final Editor to) { 1.49 + int count = 0; 1.50 + 1.51 + // SharedPreferences has no way to get a key/value pair without specifying the value type, so we do this instead. 1.52 + for (Entry<String, ?> entry : from.getAll().entrySet()) { 1.53 + String fromKey = entry.getKey(); 1.54 + String toKey = map.get(fromKey); 1.55 + if (toKey == null) { 1.56 + continue; 1.57 + } 1.58 + 1.59 + Object value = entry.getValue(); 1.60 + if (value instanceof Boolean) { 1.61 + to.putBoolean(toKey, ((Boolean) value).booleanValue()); 1.62 + } else if (value instanceof Float) { 1.63 + to.putFloat(toKey, ((Float) value).floatValue()); 1.64 + } else if (value instanceof Integer) { 1.65 + to.putInt(toKey, ((Integer) value).intValue()); 1.66 + } else if (value instanceof Long) { 1.67 + to.putLong(toKey, ((Long) value).longValue()); 1.68 + } else if (value instanceof String) { 1.69 + to.putString(toKey, (String) value); 1.70 + } else { 1.71 + // Do nothing -- perhaps SharedPreferences accepts types we don't know about. 1.72 + } 1.73 + 1.74 + if (Logger.LOG_PERSONAL_INFORMATION) { 1.75 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + value + ")."); 1.76 + } else { 1.77 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); 1.78 + } 1.79 + count += 1; 1.80 + } 1.81 + 1.82 + return count; 1.83 + } 1.84 + 1.85 + protected final static String V0_PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale"; 1.86 + protected final static String V1_PREF_CLUSTER_URL_IS_STALE = V0_PREF_CLUSTER_URL_IS_STALE; 1.87 + protected final static String V0_PREF_EARLIEST_NEXT_SYNC = "earliestnextsync"; 1.88 + protected final static String V1_PREF_EARLIEST_NEXT_SYNC = V0_PREF_EARLIEST_NEXT_SYNC; 1.89 + 1.90 + /** 1.91 + * Extract version 0 preferences from per-App Android shared prefs and write to version 1 per-Sync account shared prefs. 1.92 + * 1.93 + * @param from per-App version 0 Android shared prefs. 1.94 + * @param to per-Sync account version 1 shared prefs. 1.95 + * @return the number of preferences migrated. 1.96 + * @throws Exception 1.97 + */ 1.98 + protected static int upgradeGlobals0to1(final SharedPreferences from, final SharedPreferences to) throws Exception { 1.99 + Map<String, String> map = new HashMap<String, String>(); 1.100 + map.put(V0_PREF_CLUSTER_URL_IS_STALE, V1_PREF_CLUSTER_URL_IS_STALE); 1.101 + map.put(V0_PREF_EARLIEST_NEXT_SYNC, V1_PREF_EARLIEST_NEXT_SYNC); 1.102 + 1.103 + Editor editor = to.edit(); 1.104 + int count = copyPreferences(from, map, editor); 1.105 + if (count > 0) { 1.106 + editor.commit(); 1.107 + } 1.108 + return count; 1.109 + } 1.110 + 1.111 + /** 1.112 + * Extract version 1 per-Sync account shared prefs and write to version 0 preferences from per-App Android shared prefs. 1.113 + * 1.114 + * @param from per-Sync account version 1 shared prefs. 1.115 + * @param to per-App version 0 Android shared prefs. 1.116 + * @return the number of preferences migrated. 1.117 + * @throws Exception 1.118 + */ 1.119 + protected static int downgradeGlobals1to0(final SharedPreferences from, final SharedPreferences to) throws Exception { 1.120 + Map<String, String> map = new HashMap<String, String>(); 1.121 + map.put(V1_PREF_CLUSTER_URL_IS_STALE, V0_PREF_CLUSTER_URL_IS_STALE); 1.122 + map.put(V1_PREF_EARLIEST_NEXT_SYNC, V0_PREF_EARLIEST_NEXT_SYNC); 1.123 + 1.124 + Editor editor = to.edit(); 1.125 + int count = copyPreferences(from, map, editor); 1.126 + if (count > 0) { 1.127 + editor.commit(); 1.128 + } 1.129 + return count; 1.130 + } 1.131 + 1.132 + protected static final String V0_PREF_ACCOUNT_GUID = "account.guid"; 1.133 + protected static final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID; 1.134 + protected static final String V0_PREF_CLIENT_NAME = "account.clientName"; 1.135 + protected static final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME; 1.136 + protected static final String V0_PREF_NUM_CLIENTS = "account.numClients"; 1.137 + protected static final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS; 1.138 + 1.139 + /** 1.140 + * Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs. 1.141 + * 1.142 + * @param accountManager Android account manager. 1.143 + * @param account Android account. 1.144 + * @param to per-Sync account version 1 shared prefs. 1.145 + * @return the number of preferences migrated. 1.146 + * @throws Exception 1.147 + */ 1.148 + protected static int upgradeAndroidAccount0to1(final AccountManager accountManager, final Account account, final SharedPreferences to) throws Exception { 1.149 + final String V0_PREF_ACCOUNT_GUID = "account.guid"; 1.150 + final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID; 1.151 + final String V0_PREF_CLIENT_NAME = "account.clientName"; 1.152 + final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME; 1.153 + final String V0_PREF_NUM_CLIENTS = "account.numClients"; 1.154 + final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS; 1.155 + 1.156 + String accountGUID = null; 1.157 + String clientName = null; 1.158 + long numClients = -1; 1.159 + try { 1.160 + accountGUID = accountManager.getUserData(account, V0_PREF_ACCOUNT_GUID); 1.161 + } catch (Exception e) { 1.162 + // Do nothing. 1.163 + } 1.164 + try { 1.165 + clientName = accountManager.getUserData(account, V0_PREF_CLIENT_NAME); 1.166 + } catch (Exception e) { 1.167 + // Do nothing. 1.168 + } 1.169 + try { 1.170 + numClients = Long.parseLong(accountManager.getUserData(account, V0_PREF_NUM_CLIENTS)); 1.171 + } catch (Exception e) { 1.172 + // Do nothing. 1.173 + } 1.174 + 1.175 + final Editor editor = to.edit(); 1.176 + 1.177 + int count = 0; 1.178 + if (accountGUID != null) { 1.179 + final String fromKey = V0_PREF_ACCOUNT_GUID; 1.180 + final String toKey = V1_PREF_ACCOUNT_GUID; 1.181 + if (Logger.LOG_PERSONAL_INFORMATION) { 1.182 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + accountGUID + ")."); 1.183 + } else { 1.184 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); 1.185 + } 1.186 + editor.putString(toKey, accountGUID); 1.187 + count += 1; 1.188 + } 1.189 + if (clientName != null) { 1.190 + final String fromKey = V0_PREF_CLIENT_NAME; 1.191 + final String toKey = V1_PREF_CLIENT_NAME; 1.192 + if (Logger.LOG_PERSONAL_INFORMATION) { 1.193 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + clientName + ")."); 1.194 + } else { 1.195 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); 1.196 + } 1.197 + editor.putString(toKey, clientName); 1.198 + count += 1; 1.199 + } 1.200 + if (numClients > -1) { 1.201 + final String fromKey = V0_PREF_NUM_CLIENTS; 1.202 + final String toKey = V1_PREF_NUM_CLIENTS; 1.203 + if (Logger.LOG_PERSONAL_INFORMATION) { 1.204 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + numClients + ")."); 1.205 + } else { 1.206 + Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); 1.207 + } 1.208 + editor.putLong(toKey, numClients); 1.209 + count += 1; 1.210 + } 1.211 + 1.212 + if (count > 0) { 1.213 + editor.commit(); 1.214 + } 1.215 + return count; 1.216 + } 1.217 + 1.218 + /** 1.219 + * Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data. 1.220 + * 1.221 + * @param from per-Sync account version 1 shared prefs. 1.222 + * @param accountManager Android account manager. 1.223 + * @param account Android account. 1.224 + * @return the number of preferences migrated. 1.225 + * @throws Exception 1.226 + */ 1.227 + protected static int downgradeAndroidAccount1to0(final SharedPreferences from, final AccountManager accountManager, final Account account) throws Exception { 1.228 + final String accountGUID = from.getString(V1_PREF_ACCOUNT_GUID, null); 1.229 + final String clientName = from.getString(V1_PREF_CLIENT_NAME, null); 1.230 + final long numClients = from.getLong(V1_PREF_NUM_CLIENTS, -1L); 1.231 + 1.232 + int count = 0; 1.233 + if (accountGUID != null) { 1.234 + Logger.debug(LOG_TAG, "Migrated account GUID."); 1.235 + accountManager.setUserData(account, V0_PREF_ACCOUNT_GUID, accountGUID); 1.236 + count += 1; 1.237 + } 1.238 + if (clientName != null) { 1.239 + Logger.debug(LOG_TAG, "Migrated client name."); 1.240 + accountManager.setUserData(account, V1_PREF_CLIENT_NAME, clientName); 1.241 + count += 1; 1.242 + } 1.243 + if (numClients > -1) { 1.244 + Logger.debug(LOG_TAG, "Migrated clients count."); 1.245 + accountManager.setUserData(account, V1_PREF_NUM_CLIENTS, new Long(numClients).toString()); 1.246 + count += 1; 1.247 + } 1.248 + return count; 1.249 + } 1.250 + 1.251 + /** 1.252 + * Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs. 1.253 + * 1.254 + * @param from per-Sync account version 0 shared prefs. 1.255 + * @param to per-Sync account version 1 shared prefs. 1.256 + * @return the number of preferences migrated. 1.257 + * @throws Exception 1.258 + */ 1.259 + protected static int upgradeShared0to1(final SharedPreferences from, final SharedPreferences to) { 1.260 + final Map<String, String> map = new HashMap<String, String>(); 1.261 + final String[] prefs = new String [] { 1.262 + "syncID", 1.263 + "clusterURL", 1.264 + "enabledEngineNames", 1.265 + 1.266 + "metaGlobalLastModified", "metaGlobalServerResponseBody", 1.267 + 1.268 + "crypto5KeysLastModified", "crypto5KeysServerResponseBody", 1.269 + 1.270 + "serverClientsTimestamp", "serverClientRecordTimestamp", 1.271 + 1.272 + "forms.remote", "forms.local", "forms.syncID", 1.273 + "tabs.remote", "tabs.local", "tabs.syncID", 1.274 + "passwords.remote", "passwords.local", "passwords.syncID", 1.275 + "history.remote", "history.local", "history.syncID", 1.276 + "bookmarks.remote", "bookmarks.local", "bookmarks.syncID", 1.277 + }; 1.278 + for (String pref : prefs) { 1.279 + map.put(pref, pref); 1.280 + } 1.281 + 1.282 + Editor editor = to.edit(); 1.283 + int count = copyPreferences(from, map, editor); 1.284 + if (count > 0) { 1.285 + editor.commit(); 1.286 + } 1.287 + return count; 1.288 + } 1.289 + 1.290 + /** 1.291 + * Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data. 1.292 + * 1.293 + * @param from per-Sync account version 1 shared prefs. 1.294 + * @param to per-Sync account version 0 shared prefs. 1.295 + * @return the number of preferences migrated. 1.296 + * @throws Exception 1.297 + */ 1.298 + protected static int downgradeShared1to0(final SharedPreferences from, final SharedPreferences to) { 1.299 + // Strictly a copy, no re-naming, no deletions -- so just invert. 1.300 + return upgradeShared0to1(from, to); 1.301 + } 1.302 + 1.303 + public static void upgrade0to1(final Context context, final AccountManager accountManager, final Account account, 1.304 + final String product, final String username, final String serverURL, final String profile) throws Exception { 1.305 + 1.306 + final String GLOBAL_SHARED_PREFS = "sync.prefs.global"; 1.307 + 1.308 + final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE); 1.309 + final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0); 1.310 + final SharedPreferences newPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1); 1.311 + 1.312 + upgradeGlobals0to1(globalPrefs, newPrefs); 1.313 + upgradeAndroidAccount0to1(accountManager, account, newPrefs); 1.314 + upgradeShared0to1(accountPrefs, newPrefs); 1.315 + } 1.316 + 1.317 + public static void downgrade1to0(final Context context, final AccountManager accountManager, final Account account, 1.318 + final String product, final String username, final String serverURL, final String profile) throws Exception { 1.319 + 1.320 + final String GLOBAL_SHARED_PREFS = "sync.prefs.global"; 1.321 + 1.322 + final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE); 1.323 + final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0); 1.324 + final SharedPreferences oldPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1); 1.325 + 1.326 + downgradeGlobals1to0(oldPrefs, globalPrefs); 1.327 + downgradeAndroidAccount1to0(oldPrefs, accountManager, account); 1.328 + downgradeShared1to0(oldPrefs, accountPrefs); 1.329 + } 1.330 + 1.331 + /** 1.332 + * Migrate, if necessary, existing prefs to a certain version. 1.333 + * <p> 1.334 + * Stores current prefs version in Android shared prefs with root 1.335 + * "sync.prefs.version", which corresponds to the file 1.336 + * "sync.prefs.version.xml". 1.337 + * 1.338 + * @param desiredVersion 1.339 + * version to finish it. 1.340 + * @param context 1.341 + * @param accountManager 1.342 + * @param account 1.343 + * @param product 1.344 + * @param username 1.345 + * @param serverURL 1.346 + * @param profile 1.347 + * @throws Exception 1.348 + */ 1.349 + public static void ensurePrefsAreVersion(final long desiredVersion, 1.350 + final Context context, final AccountManager accountManager, final Account account, 1.351 + final String product, final String username, final String serverURL, final String profile) throws Exception { 1.352 + if (desiredVersion < 0 || desiredVersion > SyncConfiguration.CURRENT_PREFS_VERSION) { 1.353 + throw new IllegalArgumentException("Cannot migrate to unknown version " + desiredVersion + "."); 1.354 + } 1.355 + 1.356 + SharedPreferences versionPrefs = context.getSharedPreferences("sync.prefs.version", Utils.SHARED_PREFERENCES_MODE); 1.357 + 1.358 + // We default to 0 since clients getting this code for the first time will 1.359 + // not have "sync.prefs.version.xml" *at all*, and upgrading when all old 1.360 + // data is missing is expected to be safe. 1.361 + long currentVersion = versionPrefs.getLong(SyncConfiguration.PREF_PREFS_VERSION, 0); 1.362 + if (currentVersion == desiredVersion) { 1.363 + Logger.info(LOG_TAG, "Current version (" + currentVersion + ") is desired version; no need to migrate."); 1.364 + return; 1.365 + } 1.366 + 1.367 + if (currentVersion < 0 || currentVersion > SyncConfiguration.CURRENT_PREFS_VERSION) { 1.368 + throw new IllegalStateException("Cannot migrate from unknown version " + currentVersion + "."); 1.369 + } 1.370 + 1.371 + // Now we're down to either version 0 or version 1. 1.372 + if (currentVersion == 0 && desiredVersion == 1) { 1.373 + Logger.info(LOG_TAG, "Upgrading from version 0 to version 1."); 1.374 + upgrade0to1(context, accountManager, account, product, username, serverURL, profile); 1.375 + } else if (currentVersion == 1 && desiredVersion == 0) { 1.376 + Logger.info(LOG_TAG, "Upgrading from version 0 to version 1."); 1.377 + upgrade0to1(context, accountManager, account, product, username, serverURL, profile); 1.378 + } else { 1.379 + Logger.warn(LOG_TAG, "Don't know how to migrate from version " + currentVersion + " to " + desiredVersion + "."); 1.380 + } 1.381 + 1.382 + Logger.info(LOG_TAG, "Migrated from version " + currentVersion + " to version " + desiredVersion + "."); 1.383 + versionPrefs.edit().putLong(SyncConfiguration.PREF_PREFS_VERSION, desiredVersion).commit(); 1.384 + } 1.385 +}