|
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; |
|
6 |
|
7 import java.net.URI; |
|
8 import java.net.URISyntaxException; |
|
9 import java.util.Collection; |
|
10 import java.util.HashMap; |
|
11 import java.util.HashSet; |
|
12 import java.util.Map; |
|
13 import java.util.Map.Entry; |
|
14 import java.util.Set; |
|
15 |
|
16 import org.mozilla.gecko.background.common.log.Logger; |
|
17 import org.mozilla.gecko.sync.crypto.KeyBundle; |
|
18 import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys; |
|
19 import org.mozilla.gecko.sync.net.AuthHeaderProvider; |
|
20 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; |
|
21 |
|
22 import android.content.SharedPreferences; |
|
23 import android.content.SharedPreferences.Editor; |
|
24 |
|
25 public class SyncConfiguration { |
|
26 |
|
27 public class EditorBranch implements Editor { |
|
28 |
|
29 private String prefix; |
|
30 private Editor editor; |
|
31 |
|
32 public EditorBranch(SyncConfiguration config, String prefix) { |
|
33 if (!prefix.endsWith(".")) { |
|
34 throw new IllegalArgumentException("No trailing period in prefix."); |
|
35 } |
|
36 this.prefix = prefix; |
|
37 this.editor = config.getEditor(); |
|
38 } |
|
39 |
|
40 public void apply() { |
|
41 // Android <=r8 SharedPreferences.Editor does not contain apply() for overriding. |
|
42 this.editor.commit(); |
|
43 } |
|
44 |
|
45 @Override |
|
46 public Editor clear() { |
|
47 this.editor = this.editor.clear(); |
|
48 return this; |
|
49 } |
|
50 |
|
51 @Override |
|
52 public boolean commit() { |
|
53 return this.editor.commit(); |
|
54 } |
|
55 |
|
56 @Override |
|
57 public Editor putBoolean(String key, boolean value) { |
|
58 this.editor = this.editor.putBoolean(prefix + key, value); |
|
59 return this; |
|
60 } |
|
61 |
|
62 @Override |
|
63 public Editor putFloat(String key, float value) { |
|
64 this.editor = this.editor.putFloat(prefix + key, value); |
|
65 return this; |
|
66 } |
|
67 |
|
68 @Override |
|
69 public Editor putInt(String key, int value) { |
|
70 this.editor = this.editor.putInt(prefix + key, value); |
|
71 return this; |
|
72 } |
|
73 |
|
74 @Override |
|
75 public Editor putLong(String key, long value) { |
|
76 this.editor = this.editor.putLong(prefix + key, value); |
|
77 return this; |
|
78 } |
|
79 |
|
80 @Override |
|
81 public Editor putString(String key, String value) { |
|
82 this.editor = this.editor.putString(prefix + key, value); |
|
83 return this; |
|
84 } |
|
85 |
|
86 // Not marking as Override, because Android <= 10 doesn't have |
|
87 // putStringSet. Neither can we implement it. |
|
88 public Editor putStringSet(String key, Set<String> value) { |
|
89 throw new RuntimeException("putStringSet not available."); |
|
90 } |
|
91 |
|
92 @Override |
|
93 public Editor remove(String key) { |
|
94 this.editor = this.editor.remove(prefix + key); |
|
95 return this; |
|
96 } |
|
97 |
|
98 } |
|
99 |
|
100 /** |
|
101 * A wrapper around a portion of the SharedPreferences space. |
|
102 * |
|
103 * @author rnewman |
|
104 * |
|
105 */ |
|
106 public class ConfigurationBranch implements SharedPreferences { |
|
107 |
|
108 private SyncConfiguration config; |
|
109 private String prefix; // Including trailing period. |
|
110 |
|
111 public ConfigurationBranch(SyncConfiguration syncConfiguration, |
|
112 String prefix) { |
|
113 if (!prefix.endsWith(".")) { |
|
114 throw new IllegalArgumentException("No trailing period in prefix."); |
|
115 } |
|
116 this.config = syncConfiguration; |
|
117 this.prefix = prefix; |
|
118 } |
|
119 |
|
120 @Override |
|
121 public boolean contains(String key) { |
|
122 return config.getPrefs().contains(prefix + key); |
|
123 } |
|
124 |
|
125 @Override |
|
126 public Editor edit() { |
|
127 return new EditorBranch(config, prefix); |
|
128 } |
|
129 |
|
130 @Override |
|
131 public Map<String, ?> getAll() { |
|
132 // Not implemented. TODO |
|
133 return null; |
|
134 } |
|
135 |
|
136 @Override |
|
137 public boolean getBoolean(String key, boolean defValue) { |
|
138 return config.getPrefs().getBoolean(prefix + key, defValue); |
|
139 } |
|
140 |
|
141 @Override |
|
142 public float getFloat(String key, float defValue) { |
|
143 return config.getPrefs().getFloat(prefix + key, defValue); |
|
144 } |
|
145 |
|
146 @Override |
|
147 public int getInt(String key, int defValue) { |
|
148 return config.getPrefs().getInt(prefix + key, defValue); |
|
149 } |
|
150 |
|
151 @Override |
|
152 public long getLong(String key, long defValue) { |
|
153 return config.getPrefs().getLong(prefix + key, defValue); |
|
154 } |
|
155 |
|
156 @Override |
|
157 public String getString(String key, String defValue) { |
|
158 return config.getPrefs().getString(prefix + key, defValue); |
|
159 } |
|
160 |
|
161 // Not marking as Override, because Android <= 10 doesn't have |
|
162 // getStringSet. Neither can we implement it. |
|
163 public Set<String> getStringSet(String key, Set<String> defValue) { |
|
164 throw new RuntimeException("getStringSet not available."); |
|
165 } |
|
166 |
|
167 @Override |
|
168 public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { |
|
169 config.getPrefs().registerOnSharedPreferenceChangeListener(listener); |
|
170 } |
|
171 |
|
172 @Override |
|
173 public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { |
|
174 config.getPrefs().unregisterOnSharedPreferenceChangeListener(listener); |
|
175 } |
|
176 } |
|
177 |
|
178 private static final String LOG_TAG = "SyncConfiguration"; |
|
179 |
|
180 // These must be set in GlobalSession's constructor. |
|
181 public URI clusterURL; |
|
182 public KeyBundle syncKeyBundle; |
|
183 |
|
184 public CollectionKeys collectionKeys; |
|
185 public InfoCollections infoCollections; |
|
186 public MetaGlobal metaGlobal; |
|
187 public String syncID; |
|
188 |
|
189 protected final String username; |
|
190 |
|
191 /** |
|
192 * Persisted collection of enabledEngineNames. |
|
193 * <p> |
|
194 * Can contain engines Android Sync is not currently aware of, such as "prefs" |
|
195 * or "addons". |
|
196 * <p> |
|
197 * Copied from latest downloaded meta/global record and used to generate a |
|
198 * fresh meta/global record for upload. |
|
199 */ |
|
200 public Set<String> enabledEngineNames; |
|
201 public Set<String> declinedEngineNames = new HashSet<String>(); |
|
202 |
|
203 /** |
|
204 * Names of stages to sync <it>this sync</it>, or <code>null</code> to sync |
|
205 * all known stages. |
|
206 * <p> |
|
207 * Generated <it>each sync</it> from extras bundle passed to |
|
208 * <code>SyncAdapter.onPerformSync</code> and not persisted. |
|
209 * <p> |
|
210 * Not synchronized! Set this exactly once per global session and don't modify |
|
211 * it -- especially not from multiple threads. |
|
212 */ |
|
213 public Collection<String> stagesToSync; |
|
214 |
|
215 /** |
|
216 * Engines whose sync state has been modified by the user through |
|
217 * SelectEnginesActivity, where each key-value pair is an engine name and |
|
218 * its sync state. |
|
219 * |
|
220 * This differs from <code>enabledEngineNames</code> in that |
|
221 * <code>enabledEngineNames</code> reflects the downloaded meta/global, |
|
222 * whereas <code>userSelectedEngines</code> stores the differences in engines to |
|
223 * sync that the user has selected. |
|
224 * |
|
225 * Each engine stage will check for engine changes at the beginning of the |
|
226 * stage. |
|
227 * |
|
228 * If no engine sync state changes have been made by the user, userSelectedEngines |
|
229 * will be null, and Sync will proceed normally. |
|
230 * |
|
231 * If the user has made changes to engine syncing state, each engine will sync |
|
232 * according to the sync state specified in userSelectedEngines and propagate that |
|
233 * state to meta/global, to be uploaded. |
|
234 */ |
|
235 public Map<String, Boolean> userSelectedEngines; |
|
236 public long userSelectedEnginesTimestamp; |
|
237 |
|
238 public SharedPreferences prefs; |
|
239 |
|
240 protected final AuthHeaderProvider authHeaderProvider; |
|
241 |
|
242 public static final String PREF_PREFS_VERSION = "prefs.version"; |
|
243 public static final long CURRENT_PREFS_VERSION = 1; |
|
244 |
|
245 public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched. |
|
246 public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched. |
|
247 |
|
248 public static final String PREF_CLUSTER_URL = "clusterURL"; |
|
249 public static final String PREF_SYNC_ID = "syncID"; |
|
250 |
|
251 public static final String PREF_ENABLED_ENGINE_NAMES = "enabledEngineNames"; |
|
252 public static final String PREF_DECLINED_ENGINE_NAMES = "declinedEngineNames"; |
|
253 public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines"; |
|
254 public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp"; |
|
255 |
|
256 public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale"; |
|
257 |
|
258 public static final String PREF_ACCOUNT_GUID = "account.guid"; |
|
259 public static final String PREF_CLIENT_NAME = "account.clientName"; |
|
260 public static final String PREF_NUM_CLIENTS = "account.numClients"; |
|
261 |
|
262 private static final String API_VERSION = "1.5"; |
|
263 |
|
264 public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) { |
|
265 this.username = username; |
|
266 this.authHeaderProvider = authHeaderProvider; |
|
267 this.prefs = prefs; |
|
268 this.loadFromPrefs(prefs); |
|
269 } |
|
270 |
|
271 public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs, KeyBundle syncKeyBundle) { |
|
272 this(username, authHeaderProvider, prefs); |
|
273 this.syncKeyBundle = syncKeyBundle; |
|
274 } |
|
275 |
|
276 public String getAPIVersion() { |
|
277 return API_VERSION; |
|
278 } |
|
279 |
|
280 public SharedPreferences getPrefs() { |
|
281 return this.prefs; |
|
282 } |
|
283 |
|
284 /** |
|
285 * Valid engines supported by Android Sync. |
|
286 * |
|
287 * @return Set<String> of valid engine names that Android Sync implements. |
|
288 */ |
|
289 public static Set<String> validEngineNames() { |
|
290 Set<String> engineNames = new HashSet<String>(); |
|
291 for (Stage stage : Stage.getNamedStages()) { |
|
292 engineNames.add(stage.getRepositoryName()); |
|
293 } |
|
294 return engineNames; |
|
295 } |
|
296 |
|
297 /** |
|
298 * Return a convenient accessor for part of prefs. |
|
299 * @return |
|
300 * A ConfigurationBranch object representing this |
|
301 * section of the preferences space. |
|
302 */ |
|
303 public ConfigurationBranch getBranch(String prefix) { |
|
304 return new ConfigurationBranch(this, prefix); |
|
305 } |
|
306 |
|
307 /** |
|
308 * Gets the engine names that are enabled, declined, or other (depending on pref) in meta/global. |
|
309 * |
|
310 * @param prefs |
|
311 * SharedPreferences that the engines are associated with. |
|
312 * @param pref |
|
313 * The preference name to use. E.g, PREF_ENABLED_ENGINE_NAMES. |
|
314 * @return Set<String> of the enabled engine names if they have been stored, |
|
315 * or null otherwise. |
|
316 */ |
|
317 protected static Set<String> getEngineNamesFromPref(SharedPreferences prefs, String pref) { |
|
318 final String json = prefs.getString(pref, null); |
|
319 if (json == null) { |
|
320 return null; |
|
321 } |
|
322 try { |
|
323 final ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json); |
|
324 return new HashSet<String>(o.keySet()); |
|
325 } catch (Exception e) { |
|
326 return null; |
|
327 } |
|
328 } |
|
329 |
|
330 /** |
|
331 * Returns the set of engine names that the user has enabled. If none |
|
332 * have been stored in prefs, <code>null</code> is returned. |
|
333 */ |
|
334 public static Set<String> getEnabledEngineNames(SharedPreferences prefs) { |
|
335 return getEngineNamesFromPref(prefs, PREF_ENABLED_ENGINE_NAMES); |
|
336 } |
|
337 |
|
338 /** |
|
339 * Returns the set of engine names that the user has declined. |
|
340 */ |
|
341 public static Set<String> getDeclinedEngineNames(SharedPreferences prefs) { |
|
342 final Set<String> names = getEngineNamesFromPref(prefs, PREF_DECLINED_ENGINE_NAMES); |
|
343 if (names == null) { |
|
344 return new HashSet<String>(); |
|
345 } |
|
346 return names; |
|
347 } |
|
348 |
|
349 /** |
|
350 * Gets the engines whose sync states have been changed by the user through the |
|
351 * SelectEnginesActivity. |
|
352 * |
|
353 * @param prefs |
|
354 * SharedPreferences of account that the engines are associated with. |
|
355 * @return Map<String, Boolean> of changed engines. Key is the lower-cased |
|
356 * engine name, Value is the new sync state. |
|
357 */ |
|
358 public static Map<String, Boolean> getUserSelectedEngines(SharedPreferences prefs) { |
|
359 String json = prefs.getString(PREF_USER_SELECTED_ENGINES_TO_SYNC, null); |
|
360 if (json == null) { |
|
361 return null; |
|
362 } |
|
363 try { |
|
364 ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json); |
|
365 Map<String, Boolean> map = new HashMap<String, Boolean>(); |
|
366 for (Entry<String, Object> e : o.entrySet()) { |
|
367 String key = e.getKey(); |
|
368 Boolean value = (Boolean) e.getValue(); |
|
369 map.put(key, value); |
|
370 // Forms depends on history. Add forms if history is selected. |
|
371 if ("history".equals(key)) { |
|
372 map.put("forms", value); |
|
373 } |
|
374 } |
|
375 // Sanity check: remove forms if history does not exist. |
|
376 if (!map.containsKey("history")) { |
|
377 map.remove("forms"); |
|
378 } |
|
379 return map; |
|
380 } catch (Exception e) { |
|
381 return null; |
|
382 } |
|
383 } |
|
384 |
|
385 /** |
|
386 * Store a Map of engines and their sync states to prefs. |
|
387 * |
|
388 * Any engine that's disabled in the input is also recorded |
|
389 * as a declined engine, overwriting the stored values. |
|
390 * |
|
391 * @param prefs |
|
392 * SharedPreferences that the engines are associated with. |
|
393 * @param selectedEngines |
|
394 * Map<String, Boolean> of engine name to sync state |
|
395 */ |
|
396 public static void storeSelectedEnginesToPrefs(SharedPreferences prefs, Map<String, Boolean> selectedEngines) { |
|
397 ExtendedJSONObject jObj = new ExtendedJSONObject(); |
|
398 HashSet<String> declined = new HashSet<String>(); |
|
399 for (Entry<String, Boolean> e : selectedEngines.entrySet()) { |
|
400 final Boolean enabled = e.getValue(); |
|
401 final String engine = e.getKey(); |
|
402 jObj.put(engine, enabled); |
|
403 if (!enabled) { |
|
404 declined.add(engine); |
|
405 } |
|
406 } |
|
407 |
|
408 // Our history checkbox drives form history, too. |
|
409 // We don't need to do this for enablement: that's done at retrieval time. |
|
410 if (selectedEngines.containsKey("history") && !selectedEngines.get("history").booleanValue()) { |
|
411 declined.add("forms"); |
|
412 } |
|
413 |
|
414 String json = jObj.toJSONString(); |
|
415 long currentTime = System.currentTimeMillis(); |
|
416 Editor edit = prefs.edit(); |
|
417 edit.putString(PREF_USER_SELECTED_ENGINES_TO_SYNC, json); |
|
418 edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declined)); |
|
419 edit.putLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, currentTime); |
|
420 Logger.error(LOG_TAG, "Storing user-selected engines at [" + currentTime + "]."); |
|
421 edit.commit(); |
|
422 } |
|
423 |
|
424 public void loadFromPrefs(SharedPreferences prefs) { |
|
425 if (prefs.contains(PREF_CLUSTER_URL)) { |
|
426 String u = prefs.getString(PREF_CLUSTER_URL, null); |
|
427 try { |
|
428 clusterURL = new URI(u); |
|
429 Logger.trace(LOG_TAG, "Set clusterURL from bundle: " + u); |
|
430 } catch (URISyntaxException e) { |
|
431 Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e); |
|
432 } |
|
433 } |
|
434 if (prefs.contains(PREF_SYNC_ID)) { |
|
435 syncID = prefs.getString(PREF_SYNC_ID, null); |
|
436 Logger.trace(LOG_TAG, "Set syncID from bundle: " + syncID); |
|
437 } |
|
438 enabledEngineNames = getEnabledEngineNames(prefs); |
|
439 declinedEngineNames = getDeclinedEngineNames(prefs); |
|
440 userSelectedEngines = getUserSelectedEngines(prefs); |
|
441 userSelectedEnginesTimestamp = prefs.getLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, 0); |
|
442 // We don't set crypto/keys here because we need the syncKeyBundle to decrypt the JSON |
|
443 // and we won't have it on construction. |
|
444 // TODO: MetaGlobal, password, infoCollections. |
|
445 } |
|
446 |
|
447 public void persistToPrefs() { |
|
448 this.persistToPrefs(this.getPrefs()); |
|
449 } |
|
450 |
|
451 private static String setToJSONObjectString(Set<String> set) { |
|
452 ExtendedJSONObject o = new ExtendedJSONObject(); |
|
453 for (String name : set) { |
|
454 o.put(name, 0); |
|
455 } |
|
456 return o.toJSONString(); |
|
457 } |
|
458 |
|
459 public void persistToPrefs(SharedPreferences prefs) { |
|
460 Editor edit = prefs.edit(); |
|
461 if (clusterURL == null) { |
|
462 edit.remove(PREF_CLUSTER_URL); |
|
463 } else { |
|
464 edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString()); |
|
465 } |
|
466 if (syncID != null) { |
|
467 edit.putString(PREF_SYNC_ID, syncID); |
|
468 } |
|
469 if (enabledEngineNames == null) { |
|
470 edit.remove(PREF_ENABLED_ENGINE_NAMES); |
|
471 } else { |
|
472 edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames)); |
|
473 } |
|
474 if (declinedEngineNames.isEmpty()) { |
|
475 edit.remove(PREF_DECLINED_ENGINE_NAMES); |
|
476 } else { |
|
477 edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames)); |
|
478 } |
|
479 if (userSelectedEngines == null) { |
|
480 edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC); |
|
481 edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP); |
|
482 } |
|
483 // Don't bother saving userSelectedEngines - these should only be changed by |
|
484 // SelectEnginesActivity. |
|
485 edit.commit(); |
|
486 // TODO: keys. |
|
487 } |
|
488 |
|
489 public AuthHeaderProvider getAuthHeaderProvider() { |
|
490 return authHeaderProvider; |
|
491 } |
|
492 |
|
493 public CollectionKeys getCollectionKeys() { |
|
494 return collectionKeys; |
|
495 } |
|
496 |
|
497 public void setCollectionKeys(CollectionKeys k) { |
|
498 collectionKeys = k; |
|
499 } |
|
500 |
|
501 /** |
|
502 * Return path to storage endpoint without trailing slash. |
|
503 * |
|
504 * @return storage endpoint without trailing slash. |
|
505 */ |
|
506 public String storageURL() { |
|
507 return clusterURL + "/storage"; |
|
508 } |
|
509 |
|
510 protected String infoBaseURL() { |
|
511 return clusterURL + "/info/"; |
|
512 } |
|
513 |
|
514 public String infoCollectionsURL() { |
|
515 return infoBaseURL() + "collections"; |
|
516 } |
|
517 |
|
518 public String infoCollectionCountsURL() { |
|
519 return infoBaseURL() + "collection_counts"; |
|
520 } |
|
521 |
|
522 public String metaURL() { |
|
523 return storageURL() + "/meta/global"; |
|
524 } |
|
525 |
|
526 public URI collectionURI(String collection) throws URISyntaxException { |
|
527 return new URI(storageURL() + "/" + collection); |
|
528 } |
|
529 |
|
530 public URI collectionURI(String collection, boolean full) throws URISyntaxException { |
|
531 // Do it this way to make it easier to add more params later. |
|
532 // It's pretty ugly, I'll grant. |
|
533 boolean anyParams = full; |
|
534 String uriParams = ""; |
|
535 if (anyParams) { |
|
536 StringBuilder params = new StringBuilder("?"); |
|
537 if (full) { |
|
538 params.append("full=1"); |
|
539 } |
|
540 uriParams = params.toString(); |
|
541 } |
|
542 String uri = storageURL() + "/" + collection + uriParams; |
|
543 return new URI(uri); |
|
544 } |
|
545 |
|
546 public URI wboURI(String collection, String id) throws URISyntaxException { |
|
547 return new URI(storageURL() + "/" + collection + "/" + id); |
|
548 } |
|
549 |
|
550 public URI keysURI() throws URISyntaxException { |
|
551 return wboURI("crypto", "keys"); |
|
552 } |
|
553 |
|
554 public URI getClusterURL() { |
|
555 return clusterURL; |
|
556 } |
|
557 |
|
558 public String getClusterURLString() { |
|
559 if (clusterURL == null) { |
|
560 return null; |
|
561 } |
|
562 return clusterURL.toASCIIString(); |
|
563 } |
|
564 |
|
565 public void setClusterURL(URI u) { |
|
566 this.clusterURL = u; |
|
567 } |
|
568 |
|
569 /** |
|
570 * Used for direct management of related prefs. |
|
571 */ |
|
572 public Editor getEditor() { |
|
573 return this.getPrefs().edit(); |
|
574 } |
|
575 |
|
576 /** |
|
577 * We persist two different clients timestamps: our own record's, |
|
578 * and the timestamp for the collection. |
|
579 */ |
|
580 public void persistServerClientRecordTimestamp(long timestamp) { |
|
581 getEditor().putLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, timestamp).commit(); |
|
582 } |
|
583 |
|
584 public long getPersistedServerClientRecordTimestamp() { |
|
585 return getPrefs().getLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, 0); |
|
586 } |
|
587 |
|
588 public void persistServerClientsTimestamp(long timestamp) { |
|
589 getEditor().putLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, timestamp).commit(); |
|
590 } |
|
591 |
|
592 public long getPersistedServerClientsTimestamp() { |
|
593 return getPrefs().getLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, 0); |
|
594 } |
|
595 |
|
596 public void purgeCryptoKeys() { |
|
597 if (collectionKeys != null) { |
|
598 collectionKeys.clear(); |
|
599 } |
|
600 persistedCryptoKeys().purge(); |
|
601 } |
|
602 |
|
603 public void purgeMetaGlobal() { |
|
604 metaGlobal = null; |
|
605 persistedMetaGlobal().purge(); |
|
606 } |
|
607 |
|
608 public PersistedCrypto5Keys persistedCryptoKeys() { |
|
609 return new PersistedCrypto5Keys(getPrefs(), syncKeyBundle); |
|
610 } |
|
611 |
|
612 public PersistedMetaGlobal persistedMetaGlobal() { |
|
613 return new PersistedMetaGlobal(getPrefs()); |
|
614 } |
|
615 } |