|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.sync.config; |
|
6 |
|
7 import java.util.HashMap; |
|
8 import java.util.Map; |
|
9 import java.util.Map.Entry; |
|
10 |
|
11 import org.mozilla.gecko.background.common.log.Logger; |
|
12 import org.mozilla.gecko.sync.SyncConfiguration; |
|
13 import org.mozilla.gecko.sync.Utils; |
|
14 |
|
15 import android.accounts.Account; |
|
16 import android.accounts.AccountManager; |
|
17 import android.content.Context; |
|
18 import android.content.SharedPreferences; |
|
19 import android.content.SharedPreferences.Editor; |
|
20 |
|
21 /** |
|
22 * Migrate Sync preferences between versions. |
|
23 * <p> |
|
24 * The original preferences were un-versioned; we refer to that as "version 0". |
|
25 * The original preferences were stored in three places: |
|
26 * <ul> |
|
27 * <li>most prefs were kept in per-Sync account Android shared prefs;</li> |
|
28 * <li>some prefs were kept in per-App Android shared prefs;</li> |
|
29 * <li>some client prefs were kept in the (assumed unique) Android Account.</li> |
|
30 * </ul> |
|
31 * <p> |
|
32 * Post version 0, all preferences are stored in per-Sync account Android shared prefs. |
|
33 */ |
|
34 public class ConfigurationMigrator { |
|
35 public static final String LOG_TAG = "ConfigMigrator"; |
|
36 |
|
37 /** |
|
38 * Copy and rename preferences. |
|
39 * |
|
40 * @param from source. |
|
41 * @param to sink. |
|
42 * @param map map from old preference names to new preference names. |
|
43 * @return the number of preferences migrated. |
|
44 */ |
|
45 protected static int copyPreferences(final SharedPreferences from, final Map<String, String> map, final Editor to) { |
|
46 int count = 0; |
|
47 |
|
48 // SharedPreferences has no way to get a key/value pair without specifying the value type, so we do this instead. |
|
49 for (Entry<String, ?> entry : from.getAll().entrySet()) { |
|
50 String fromKey = entry.getKey(); |
|
51 String toKey = map.get(fromKey); |
|
52 if (toKey == null) { |
|
53 continue; |
|
54 } |
|
55 |
|
56 Object value = entry.getValue(); |
|
57 if (value instanceof Boolean) { |
|
58 to.putBoolean(toKey, ((Boolean) value).booleanValue()); |
|
59 } else if (value instanceof Float) { |
|
60 to.putFloat(toKey, ((Float) value).floatValue()); |
|
61 } else if (value instanceof Integer) { |
|
62 to.putInt(toKey, ((Integer) value).intValue()); |
|
63 } else if (value instanceof Long) { |
|
64 to.putLong(toKey, ((Long) value).longValue()); |
|
65 } else if (value instanceof String) { |
|
66 to.putString(toKey, (String) value); |
|
67 } else { |
|
68 // Do nothing -- perhaps SharedPreferences accepts types we don't know about. |
|
69 } |
|
70 |
|
71 if (Logger.LOG_PERSONAL_INFORMATION) { |
|
72 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + value + ")."); |
|
73 } else { |
|
74 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); |
|
75 } |
|
76 count += 1; |
|
77 } |
|
78 |
|
79 return count; |
|
80 } |
|
81 |
|
82 protected final static String V0_PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale"; |
|
83 protected final static String V1_PREF_CLUSTER_URL_IS_STALE = V0_PREF_CLUSTER_URL_IS_STALE; |
|
84 protected final static String V0_PREF_EARLIEST_NEXT_SYNC = "earliestnextsync"; |
|
85 protected final static String V1_PREF_EARLIEST_NEXT_SYNC = V0_PREF_EARLIEST_NEXT_SYNC; |
|
86 |
|
87 /** |
|
88 * Extract version 0 preferences from per-App Android shared prefs and write to version 1 per-Sync account shared prefs. |
|
89 * |
|
90 * @param from per-App version 0 Android shared prefs. |
|
91 * @param to per-Sync account version 1 shared prefs. |
|
92 * @return the number of preferences migrated. |
|
93 * @throws Exception |
|
94 */ |
|
95 protected static int upgradeGlobals0to1(final SharedPreferences from, final SharedPreferences to) throws Exception { |
|
96 Map<String, String> map = new HashMap<String, String>(); |
|
97 map.put(V0_PREF_CLUSTER_URL_IS_STALE, V1_PREF_CLUSTER_URL_IS_STALE); |
|
98 map.put(V0_PREF_EARLIEST_NEXT_SYNC, V1_PREF_EARLIEST_NEXT_SYNC); |
|
99 |
|
100 Editor editor = to.edit(); |
|
101 int count = copyPreferences(from, map, editor); |
|
102 if (count > 0) { |
|
103 editor.commit(); |
|
104 } |
|
105 return count; |
|
106 } |
|
107 |
|
108 /** |
|
109 * Extract version 1 per-Sync account shared prefs and write to version 0 preferences from per-App Android shared prefs. |
|
110 * |
|
111 * @param from per-Sync account version 1 shared prefs. |
|
112 * @param to per-App version 0 Android shared prefs. |
|
113 * @return the number of preferences migrated. |
|
114 * @throws Exception |
|
115 */ |
|
116 protected static int downgradeGlobals1to0(final SharedPreferences from, final SharedPreferences to) throws Exception { |
|
117 Map<String, String> map = new HashMap<String, String>(); |
|
118 map.put(V1_PREF_CLUSTER_URL_IS_STALE, V0_PREF_CLUSTER_URL_IS_STALE); |
|
119 map.put(V1_PREF_EARLIEST_NEXT_SYNC, V0_PREF_EARLIEST_NEXT_SYNC); |
|
120 |
|
121 Editor editor = to.edit(); |
|
122 int count = copyPreferences(from, map, editor); |
|
123 if (count > 0) { |
|
124 editor.commit(); |
|
125 } |
|
126 return count; |
|
127 } |
|
128 |
|
129 protected static final String V0_PREF_ACCOUNT_GUID = "account.guid"; |
|
130 protected static final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID; |
|
131 protected static final String V0_PREF_CLIENT_NAME = "account.clientName"; |
|
132 protected static final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME; |
|
133 protected static final String V0_PREF_NUM_CLIENTS = "account.numClients"; |
|
134 protected static final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS; |
|
135 |
|
136 /** |
|
137 * Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs. |
|
138 * |
|
139 * @param accountManager Android account manager. |
|
140 * @param account Android account. |
|
141 * @param to per-Sync account version 1 shared prefs. |
|
142 * @return the number of preferences migrated. |
|
143 * @throws Exception |
|
144 */ |
|
145 protected static int upgradeAndroidAccount0to1(final AccountManager accountManager, final Account account, final SharedPreferences to) throws Exception { |
|
146 final String V0_PREF_ACCOUNT_GUID = "account.guid"; |
|
147 final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID; |
|
148 final String V0_PREF_CLIENT_NAME = "account.clientName"; |
|
149 final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME; |
|
150 final String V0_PREF_NUM_CLIENTS = "account.numClients"; |
|
151 final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS; |
|
152 |
|
153 String accountGUID = null; |
|
154 String clientName = null; |
|
155 long numClients = -1; |
|
156 try { |
|
157 accountGUID = accountManager.getUserData(account, V0_PREF_ACCOUNT_GUID); |
|
158 } catch (Exception e) { |
|
159 // Do nothing. |
|
160 } |
|
161 try { |
|
162 clientName = accountManager.getUserData(account, V0_PREF_CLIENT_NAME); |
|
163 } catch (Exception e) { |
|
164 // Do nothing. |
|
165 } |
|
166 try { |
|
167 numClients = Long.parseLong(accountManager.getUserData(account, V0_PREF_NUM_CLIENTS)); |
|
168 } catch (Exception e) { |
|
169 // Do nothing. |
|
170 } |
|
171 |
|
172 final Editor editor = to.edit(); |
|
173 |
|
174 int count = 0; |
|
175 if (accountGUID != null) { |
|
176 final String fromKey = V0_PREF_ACCOUNT_GUID; |
|
177 final String toKey = V1_PREF_ACCOUNT_GUID; |
|
178 if (Logger.LOG_PERSONAL_INFORMATION) { |
|
179 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + accountGUID + ")."); |
|
180 } else { |
|
181 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); |
|
182 } |
|
183 editor.putString(toKey, accountGUID); |
|
184 count += 1; |
|
185 } |
|
186 if (clientName != null) { |
|
187 final String fromKey = V0_PREF_CLIENT_NAME; |
|
188 final String toKey = V1_PREF_CLIENT_NAME; |
|
189 if (Logger.LOG_PERSONAL_INFORMATION) { |
|
190 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + clientName + ")."); |
|
191 } else { |
|
192 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); |
|
193 } |
|
194 editor.putString(toKey, clientName); |
|
195 count += 1; |
|
196 } |
|
197 if (numClients > -1) { |
|
198 final String fromKey = V0_PREF_NUM_CLIENTS; |
|
199 final String toKey = V1_PREF_NUM_CLIENTS; |
|
200 if (Logger.LOG_PERSONAL_INFORMATION) { |
|
201 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + numClients + ")."); |
|
202 } else { |
|
203 Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'."); |
|
204 } |
|
205 editor.putLong(toKey, numClients); |
|
206 count += 1; |
|
207 } |
|
208 |
|
209 if (count > 0) { |
|
210 editor.commit(); |
|
211 } |
|
212 return count; |
|
213 } |
|
214 |
|
215 /** |
|
216 * Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data. |
|
217 * |
|
218 * @param from per-Sync account version 1 shared prefs. |
|
219 * @param accountManager Android account manager. |
|
220 * @param account Android account. |
|
221 * @return the number of preferences migrated. |
|
222 * @throws Exception |
|
223 */ |
|
224 protected static int downgradeAndroidAccount1to0(final SharedPreferences from, final AccountManager accountManager, final Account account) throws Exception { |
|
225 final String accountGUID = from.getString(V1_PREF_ACCOUNT_GUID, null); |
|
226 final String clientName = from.getString(V1_PREF_CLIENT_NAME, null); |
|
227 final long numClients = from.getLong(V1_PREF_NUM_CLIENTS, -1L); |
|
228 |
|
229 int count = 0; |
|
230 if (accountGUID != null) { |
|
231 Logger.debug(LOG_TAG, "Migrated account GUID."); |
|
232 accountManager.setUserData(account, V0_PREF_ACCOUNT_GUID, accountGUID); |
|
233 count += 1; |
|
234 } |
|
235 if (clientName != null) { |
|
236 Logger.debug(LOG_TAG, "Migrated client name."); |
|
237 accountManager.setUserData(account, V1_PREF_CLIENT_NAME, clientName); |
|
238 count += 1; |
|
239 } |
|
240 if (numClients > -1) { |
|
241 Logger.debug(LOG_TAG, "Migrated clients count."); |
|
242 accountManager.setUserData(account, V1_PREF_NUM_CLIENTS, new Long(numClients).toString()); |
|
243 count += 1; |
|
244 } |
|
245 return count; |
|
246 } |
|
247 |
|
248 /** |
|
249 * Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs. |
|
250 * |
|
251 * @param from per-Sync account version 0 shared prefs. |
|
252 * @param to per-Sync account version 1 shared prefs. |
|
253 * @return the number of preferences migrated. |
|
254 * @throws Exception |
|
255 */ |
|
256 protected static int upgradeShared0to1(final SharedPreferences from, final SharedPreferences to) { |
|
257 final Map<String, String> map = new HashMap<String, String>(); |
|
258 final String[] prefs = new String [] { |
|
259 "syncID", |
|
260 "clusterURL", |
|
261 "enabledEngineNames", |
|
262 |
|
263 "metaGlobalLastModified", "metaGlobalServerResponseBody", |
|
264 |
|
265 "crypto5KeysLastModified", "crypto5KeysServerResponseBody", |
|
266 |
|
267 "serverClientsTimestamp", "serverClientRecordTimestamp", |
|
268 |
|
269 "forms.remote", "forms.local", "forms.syncID", |
|
270 "tabs.remote", "tabs.local", "tabs.syncID", |
|
271 "passwords.remote", "passwords.local", "passwords.syncID", |
|
272 "history.remote", "history.local", "history.syncID", |
|
273 "bookmarks.remote", "bookmarks.local", "bookmarks.syncID", |
|
274 }; |
|
275 for (String pref : prefs) { |
|
276 map.put(pref, pref); |
|
277 } |
|
278 |
|
279 Editor editor = to.edit(); |
|
280 int count = copyPreferences(from, map, editor); |
|
281 if (count > 0) { |
|
282 editor.commit(); |
|
283 } |
|
284 return count; |
|
285 } |
|
286 |
|
287 /** |
|
288 * Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data. |
|
289 * |
|
290 * @param from per-Sync account version 1 shared prefs. |
|
291 * @param to per-Sync account version 0 shared prefs. |
|
292 * @return the number of preferences migrated. |
|
293 * @throws Exception |
|
294 */ |
|
295 protected static int downgradeShared1to0(final SharedPreferences from, final SharedPreferences to) { |
|
296 // Strictly a copy, no re-naming, no deletions -- so just invert. |
|
297 return upgradeShared0to1(from, to); |
|
298 } |
|
299 |
|
300 public static void upgrade0to1(final Context context, final AccountManager accountManager, final Account account, |
|
301 final String product, final String username, final String serverURL, final String profile) throws Exception { |
|
302 |
|
303 final String GLOBAL_SHARED_PREFS = "sync.prefs.global"; |
|
304 |
|
305 final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE); |
|
306 final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0); |
|
307 final SharedPreferences newPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1); |
|
308 |
|
309 upgradeGlobals0to1(globalPrefs, newPrefs); |
|
310 upgradeAndroidAccount0to1(accountManager, account, newPrefs); |
|
311 upgradeShared0to1(accountPrefs, newPrefs); |
|
312 } |
|
313 |
|
314 public static void downgrade1to0(final Context context, final AccountManager accountManager, final Account account, |
|
315 final String product, final String username, final String serverURL, final String profile) throws Exception { |
|
316 |
|
317 final String GLOBAL_SHARED_PREFS = "sync.prefs.global"; |
|
318 |
|
319 final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE); |
|
320 final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0); |
|
321 final SharedPreferences oldPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1); |
|
322 |
|
323 downgradeGlobals1to0(oldPrefs, globalPrefs); |
|
324 downgradeAndroidAccount1to0(oldPrefs, accountManager, account); |
|
325 downgradeShared1to0(oldPrefs, accountPrefs); |
|
326 } |
|
327 |
|
328 /** |
|
329 * Migrate, if necessary, existing prefs to a certain version. |
|
330 * <p> |
|
331 * Stores current prefs version in Android shared prefs with root |
|
332 * "sync.prefs.version", which corresponds to the file |
|
333 * "sync.prefs.version.xml". |
|
334 * |
|
335 * @param desiredVersion |
|
336 * version to finish it. |
|
337 * @param context |
|
338 * @param accountManager |
|
339 * @param account |
|
340 * @param product |
|
341 * @param username |
|
342 * @param serverURL |
|
343 * @param profile |
|
344 * @throws Exception |
|
345 */ |
|
346 public static void ensurePrefsAreVersion(final long desiredVersion, |
|
347 final Context context, final AccountManager accountManager, final Account account, |
|
348 final String product, final String username, final String serverURL, final String profile) throws Exception { |
|
349 if (desiredVersion < 0 || desiredVersion > SyncConfiguration.CURRENT_PREFS_VERSION) { |
|
350 throw new IllegalArgumentException("Cannot migrate to unknown version " + desiredVersion + "."); |
|
351 } |
|
352 |
|
353 SharedPreferences versionPrefs = context.getSharedPreferences("sync.prefs.version", Utils.SHARED_PREFERENCES_MODE); |
|
354 |
|
355 // We default to 0 since clients getting this code for the first time will |
|
356 // not have "sync.prefs.version.xml" *at all*, and upgrading when all old |
|
357 // data is missing is expected to be safe. |
|
358 long currentVersion = versionPrefs.getLong(SyncConfiguration.PREF_PREFS_VERSION, 0); |
|
359 if (currentVersion == desiredVersion) { |
|
360 Logger.info(LOG_TAG, "Current version (" + currentVersion + ") is desired version; no need to migrate."); |
|
361 return; |
|
362 } |
|
363 |
|
364 if (currentVersion < 0 || currentVersion > SyncConfiguration.CURRENT_PREFS_VERSION) { |
|
365 throw new IllegalStateException("Cannot migrate from unknown version " + currentVersion + "."); |
|
366 } |
|
367 |
|
368 // Now we're down to either version 0 or version 1. |
|
369 if (currentVersion == 0 && desiredVersion == 1) { |
|
370 Logger.info(LOG_TAG, "Upgrading from version 0 to version 1."); |
|
371 upgrade0to1(context, accountManager, account, product, username, serverURL, profile); |
|
372 } else if (currentVersion == 1 && desiredVersion == 0) { |
|
373 Logger.info(LOG_TAG, "Upgrading from version 0 to version 1."); |
|
374 upgrade0to1(context, accountManager, account, product, username, serverURL, profile); |
|
375 } else { |
|
376 Logger.warn(LOG_TAG, "Don't know how to migrate from version " + currentVersion + " to " + desiredVersion + "."); |
|
377 } |
|
378 |
|
379 Logger.info(LOG_TAG, "Migrated from version " + currentVersion + " to version " + desiredVersion + "."); |
|
380 versionPrefs.edit().putLong(SyncConfiguration.PREF_PREFS_VERSION, desiredVersion).commit(); |
|
381 } |
|
382 } |