1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/sync/SyncConfiguration.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,615 @@ 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; 1.9 + 1.10 +import java.net.URI; 1.11 +import java.net.URISyntaxException; 1.12 +import java.util.Collection; 1.13 +import java.util.HashMap; 1.14 +import java.util.HashSet; 1.15 +import java.util.Map; 1.16 +import java.util.Map.Entry; 1.17 +import java.util.Set; 1.18 + 1.19 +import org.mozilla.gecko.background.common.log.Logger; 1.20 +import org.mozilla.gecko.sync.crypto.KeyBundle; 1.21 +import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys; 1.22 +import org.mozilla.gecko.sync.net.AuthHeaderProvider; 1.23 +import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; 1.24 + 1.25 +import android.content.SharedPreferences; 1.26 +import android.content.SharedPreferences.Editor; 1.27 + 1.28 +public class SyncConfiguration { 1.29 + 1.30 + public class EditorBranch implements Editor { 1.31 + 1.32 + private String prefix; 1.33 + private Editor editor; 1.34 + 1.35 + public EditorBranch(SyncConfiguration config, String prefix) { 1.36 + if (!prefix.endsWith(".")) { 1.37 + throw new IllegalArgumentException("No trailing period in prefix."); 1.38 + } 1.39 + this.prefix = prefix; 1.40 + this.editor = config.getEditor(); 1.41 + } 1.42 + 1.43 + public void apply() { 1.44 + // Android <=r8 SharedPreferences.Editor does not contain apply() for overriding. 1.45 + this.editor.commit(); 1.46 + } 1.47 + 1.48 + @Override 1.49 + public Editor clear() { 1.50 + this.editor = this.editor.clear(); 1.51 + return this; 1.52 + } 1.53 + 1.54 + @Override 1.55 + public boolean commit() { 1.56 + return this.editor.commit(); 1.57 + } 1.58 + 1.59 + @Override 1.60 + public Editor putBoolean(String key, boolean value) { 1.61 + this.editor = this.editor.putBoolean(prefix + key, value); 1.62 + return this; 1.63 + } 1.64 + 1.65 + @Override 1.66 + public Editor putFloat(String key, float value) { 1.67 + this.editor = this.editor.putFloat(prefix + key, value); 1.68 + return this; 1.69 + } 1.70 + 1.71 + @Override 1.72 + public Editor putInt(String key, int value) { 1.73 + this.editor = this.editor.putInt(prefix + key, value); 1.74 + return this; 1.75 + } 1.76 + 1.77 + @Override 1.78 + public Editor putLong(String key, long value) { 1.79 + this.editor = this.editor.putLong(prefix + key, value); 1.80 + return this; 1.81 + } 1.82 + 1.83 + @Override 1.84 + public Editor putString(String key, String value) { 1.85 + this.editor = this.editor.putString(prefix + key, value); 1.86 + return this; 1.87 + } 1.88 + 1.89 + // Not marking as Override, because Android <= 10 doesn't have 1.90 + // putStringSet. Neither can we implement it. 1.91 + public Editor putStringSet(String key, Set<String> value) { 1.92 + throw new RuntimeException("putStringSet not available."); 1.93 + } 1.94 + 1.95 + @Override 1.96 + public Editor remove(String key) { 1.97 + this.editor = this.editor.remove(prefix + key); 1.98 + return this; 1.99 + } 1.100 + 1.101 + } 1.102 + 1.103 + /** 1.104 + * A wrapper around a portion of the SharedPreferences space. 1.105 + * 1.106 + * @author rnewman 1.107 + * 1.108 + */ 1.109 + public class ConfigurationBranch implements SharedPreferences { 1.110 + 1.111 + private SyncConfiguration config; 1.112 + private String prefix; // Including trailing period. 1.113 + 1.114 + public ConfigurationBranch(SyncConfiguration syncConfiguration, 1.115 + String prefix) { 1.116 + if (!prefix.endsWith(".")) { 1.117 + throw new IllegalArgumentException("No trailing period in prefix."); 1.118 + } 1.119 + this.config = syncConfiguration; 1.120 + this.prefix = prefix; 1.121 + } 1.122 + 1.123 + @Override 1.124 + public boolean contains(String key) { 1.125 + return config.getPrefs().contains(prefix + key); 1.126 + } 1.127 + 1.128 + @Override 1.129 + public Editor edit() { 1.130 + return new EditorBranch(config, prefix); 1.131 + } 1.132 + 1.133 + @Override 1.134 + public Map<String, ?> getAll() { 1.135 + // Not implemented. TODO 1.136 + return null; 1.137 + } 1.138 + 1.139 + @Override 1.140 + public boolean getBoolean(String key, boolean defValue) { 1.141 + return config.getPrefs().getBoolean(prefix + key, defValue); 1.142 + } 1.143 + 1.144 + @Override 1.145 + public float getFloat(String key, float defValue) { 1.146 + return config.getPrefs().getFloat(prefix + key, defValue); 1.147 + } 1.148 + 1.149 + @Override 1.150 + public int getInt(String key, int defValue) { 1.151 + return config.getPrefs().getInt(prefix + key, defValue); 1.152 + } 1.153 + 1.154 + @Override 1.155 + public long getLong(String key, long defValue) { 1.156 + return config.getPrefs().getLong(prefix + key, defValue); 1.157 + } 1.158 + 1.159 + @Override 1.160 + public String getString(String key, String defValue) { 1.161 + return config.getPrefs().getString(prefix + key, defValue); 1.162 + } 1.163 + 1.164 + // Not marking as Override, because Android <= 10 doesn't have 1.165 + // getStringSet. Neither can we implement it. 1.166 + public Set<String> getStringSet(String key, Set<String> defValue) { 1.167 + throw new RuntimeException("getStringSet not available."); 1.168 + } 1.169 + 1.170 + @Override 1.171 + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 1.172 + config.getPrefs().registerOnSharedPreferenceChangeListener(listener); 1.173 + } 1.174 + 1.175 + @Override 1.176 + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 1.177 + config.getPrefs().unregisterOnSharedPreferenceChangeListener(listener); 1.178 + } 1.179 + } 1.180 + 1.181 + private static final String LOG_TAG = "SyncConfiguration"; 1.182 + 1.183 + // These must be set in GlobalSession's constructor. 1.184 + public URI clusterURL; 1.185 + public KeyBundle syncKeyBundle; 1.186 + 1.187 + public CollectionKeys collectionKeys; 1.188 + public InfoCollections infoCollections; 1.189 + public MetaGlobal metaGlobal; 1.190 + public String syncID; 1.191 + 1.192 + protected final String username; 1.193 + 1.194 + /** 1.195 + * Persisted collection of enabledEngineNames. 1.196 + * <p> 1.197 + * Can contain engines Android Sync is not currently aware of, such as "prefs" 1.198 + * or "addons". 1.199 + * <p> 1.200 + * Copied from latest downloaded meta/global record and used to generate a 1.201 + * fresh meta/global record for upload. 1.202 + */ 1.203 + public Set<String> enabledEngineNames; 1.204 + public Set<String> declinedEngineNames = new HashSet<String>(); 1.205 + 1.206 + /** 1.207 + * Names of stages to sync <it>this sync</it>, or <code>null</code> to sync 1.208 + * all known stages. 1.209 + * <p> 1.210 + * Generated <it>each sync</it> from extras bundle passed to 1.211 + * <code>SyncAdapter.onPerformSync</code> and not persisted. 1.212 + * <p> 1.213 + * Not synchronized! Set this exactly once per global session and don't modify 1.214 + * it -- especially not from multiple threads. 1.215 + */ 1.216 + public Collection<String> stagesToSync; 1.217 + 1.218 + /** 1.219 + * Engines whose sync state has been modified by the user through 1.220 + * SelectEnginesActivity, where each key-value pair is an engine name and 1.221 + * its sync state. 1.222 + * 1.223 + * This differs from <code>enabledEngineNames</code> in that 1.224 + * <code>enabledEngineNames</code> reflects the downloaded meta/global, 1.225 + * whereas <code>userSelectedEngines</code> stores the differences in engines to 1.226 + * sync that the user has selected. 1.227 + * 1.228 + * Each engine stage will check for engine changes at the beginning of the 1.229 + * stage. 1.230 + * 1.231 + * If no engine sync state changes have been made by the user, userSelectedEngines 1.232 + * will be null, and Sync will proceed normally. 1.233 + * 1.234 + * If the user has made changes to engine syncing state, each engine will sync 1.235 + * according to the sync state specified in userSelectedEngines and propagate that 1.236 + * state to meta/global, to be uploaded. 1.237 + */ 1.238 + public Map<String, Boolean> userSelectedEngines; 1.239 + public long userSelectedEnginesTimestamp; 1.240 + 1.241 + public SharedPreferences prefs; 1.242 + 1.243 + protected final AuthHeaderProvider authHeaderProvider; 1.244 + 1.245 + public static final String PREF_PREFS_VERSION = "prefs.version"; 1.246 + public static final long CURRENT_PREFS_VERSION = 1; 1.247 + 1.248 + public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched. 1.249 + public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched. 1.250 + 1.251 + public static final String PREF_CLUSTER_URL = "clusterURL"; 1.252 + public static final String PREF_SYNC_ID = "syncID"; 1.253 + 1.254 + public static final String PREF_ENABLED_ENGINE_NAMES = "enabledEngineNames"; 1.255 + public static final String PREF_DECLINED_ENGINE_NAMES = "declinedEngineNames"; 1.256 + public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines"; 1.257 + public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp"; 1.258 + 1.259 + public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale"; 1.260 + 1.261 + public static final String PREF_ACCOUNT_GUID = "account.guid"; 1.262 + public static final String PREF_CLIENT_NAME = "account.clientName"; 1.263 + public static final String PREF_NUM_CLIENTS = "account.numClients"; 1.264 + 1.265 + private static final String API_VERSION = "1.5"; 1.266 + 1.267 + public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) { 1.268 + this.username = username; 1.269 + this.authHeaderProvider = authHeaderProvider; 1.270 + this.prefs = prefs; 1.271 + this.loadFromPrefs(prefs); 1.272 + } 1.273 + 1.274 + public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs, KeyBundle syncKeyBundle) { 1.275 + this(username, authHeaderProvider, prefs); 1.276 + this.syncKeyBundle = syncKeyBundle; 1.277 + } 1.278 + 1.279 + public String getAPIVersion() { 1.280 + return API_VERSION; 1.281 + } 1.282 + 1.283 + public SharedPreferences getPrefs() { 1.284 + return this.prefs; 1.285 + } 1.286 + 1.287 + /** 1.288 + * Valid engines supported by Android Sync. 1.289 + * 1.290 + * @return Set<String> of valid engine names that Android Sync implements. 1.291 + */ 1.292 + public static Set<String> validEngineNames() { 1.293 + Set<String> engineNames = new HashSet<String>(); 1.294 + for (Stage stage : Stage.getNamedStages()) { 1.295 + engineNames.add(stage.getRepositoryName()); 1.296 + } 1.297 + return engineNames; 1.298 + } 1.299 + 1.300 + /** 1.301 + * Return a convenient accessor for part of prefs. 1.302 + * @return 1.303 + * A ConfigurationBranch object representing this 1.304 + * section of the preferences space. 1.305 + */ 1.306 + public ConfigurationBranch getBranch(String prefix) { 1.307 + return new ConfigurationBranch(this, prefix); 1.308 + } 1.309 + 1.310 + /** 1.311 + * Gets the engine names that are enabled, declined, or other (depending on pref) in meta/global. 1.312 + * 1.313 + * @param prefs 1.314 + * SharedPreferences that the engines are associated with. 1.315 + * @param pref 1.316 + * The preference name to use. E.g, PREF_ENABLED_ENGINE_NAMES. 1.317 + * @return Set<String> of the enabled engine names if they have been stored, 1.318 + * or null otherwise. 1.319 + */ 1.320 + protected static Set<String> getEngineNamesFromPref(SharedPreferences prefs, String pref) { 1.321 + final String json = prefs.getString(pref, null); 1.322 + if (json == null) { 1.323 + return null; 1.324 + } 1.325 + try { 1.326 + final ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json); 1.327 + return new HashSet<String>(o.keySet()); 1.328 + } catch (Exception e) { 1.329 + return null; 1.330 + } 1.331 + } 1.332 + 1.333 + /** 1.334 + * Returns the set of engine names that the user has enabled. If none 1.335 + * have been stored in prefs, <code>null</code> is returned. 1.336 + */ 1.337 + public static Set<String> getEnabledEngineNames(SharedPreferences prefs) { 1.338 + return getEngineNamesFromPref(prefs, PREF_ENABLED_ENGINE_NAMES); 1.339 + } 1.340 + 1.341 + /** 1.342 + * Returns the set of engine names that the user has declined. 1.343 + */ 1.344 + public static Set<String> getDeclinedEngineNames(SharedPreferences prefs) { 1.345 + final Set<String> names = getEngineNamesFromPref(prefs, PREF_DECLINED_ENGINE_NAMES); 1.346 + if (names == null) { 1.347 + return new HashSet<String>(); 1.348 + } 1.349 + return names; 1.350 + } 1.351 + 1.352 + /** 1.353 + * Gets the engines whose sync states have been changed by the user through the 1.354 + * SelectEnginesActivity. 1.355 + * 1.356 + * @param prefs 1.357 + * SharedPreferences of account that the engines are associated with. 1.358 + * @return Map<String, Boolean> of changed engines. Key is the lower-cased 1.359 + * engine name, Value is the new sync state. 1.360 + */ 1.361 + public static Map<String, Boolean> getUserSelectedEngines(SharedPreferences prefs) { 1.362 + String json = prefs.getString(PREF_USER_SELECTED_ENGINES_TO_SYNC, null); 1.363 + if (json == null) { 1.364 + return null; 1.365 + } 1.366 + try { 1.367 + ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json); 1.368 + Map<String, Boolean> map = new HashMap<String, Boolean>(); 1.369 + for (Entry<String, Object> e : o.entrySet()) { 1.370 + String key = e.getKey(); 1.371 + Boolean value = (Boolean) e.getValue(); 1.372 + map.put(key, value); 1.373 + // Forms depends on history. Add forms if history is selected. 1.374 + if ("history".equals(key)) { 1.375 + map.put("forms", value); 1.376 + } 1.377 + } 1.378 + // Sanity check: remove forms if history does not exist. 1.379 + if (!map.containsKey("history")) { 1.380 + map.remove("forms"); 1.381 + } 1.382 + return map; 1.383 + } catch (Exception e) { 1.384 + return null; 1.385 + } 1.386 + } 1.387 + 1.388 + /** 1.389 + * Store a Map of engines and their sync states to prefs. 1.390 + * 1.391 + * Any engine that's disabled in the input is also recorded 1.392 + * as a declined engine, overwriting the stored values. 1.393 + * 1.394 + * @param prefs 1.395 + * SharedPreferences that the engines are associated with. 1.396 + * @param selectedEngines 1.397 + * Map<String, Boolean> of engine name to sync state 1.398 + */ 1.399 + public static void storeSelectedEnginesToPrefs(SharedPreferences prefs, Map<String, Boolean> selectedEngines) { 1.400 + ExtendedJSONObject jObj = new ExtendedJSONObject(); 1.401 + HashSet<String> declined = new HashSet<String>(); 1.402 + for (Entry<String, Boolean> e : selectedEngines.entrySet()) { 1.403 + final Boolean enabled = e.getValue(); 1.404 + final String engine = e.getKey(); 1.405 + jObj.put(engine, enabled); 1.406 + if (!enabled) { 1.407 + declined.add(engine); 1.408 + } 1.409 + } 1.410 + 1.411 + // Our history checkbox drives form history, too. 1.412 + // We don't need to do this for enablement: that's done at retrieval time. 1.413 + if (selectedEngines.containsKey("history") && !selectedEngines.get("history").booleanValue()) { 1.414 + declined.add("forms"); 1.415 + } 1.416 + 1.417 + String json = jObj.toJSONString(); 1.418 + long currentTime = System.currentTimeMillis(); 1.419 + Editor edit = prefs.edit(); 1.420 + edit.putString(PREF_USER_SELECTED_ENGINES_TO_SYNC, json); 1.421 + edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declined)); 1.422 + edit.putLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, currentTime); 1.423 + Logger.error(LOG_TAG, "Storing user-selected engines at [" + currentTime + "]."); 1.424 + edit.commit(); 1.425 + } 1.426 + 1.427 + public void loadFromPrefs(SharedPreferences prefs) { 1.428 + if (prefs.contains(PREF_CLUSTER_URL)) { 1.429 + String u = prefs.getString(PREF_CLUSTER_URL, null); 1.430 + try { 1.431 + clusterURL = new URI(u); 1.432 + Logger.trace(LOG_TAG, "Set clusterURL from bundle: " + u); 1.433 + } catch (URISyntaxException e) { 1.434 + Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e); 1.435 + } 1.436 + } 1.437 + if (prefs.contains(PREF_SYNC_ID)) { 1.438 + syncID = prefs.getString(PREF_SYNC_ID, null); 1.439 + Logger.trace(LOG_TAG, "Set syncID from bundle: " + syncID); 1.440 + } 1.441 + enabledEngineNames = getEnabledEngineNames(prefs); 1.442 + declinedEngineNames = getDeclinedEngineNames(prefs); 1.443 + userSelectedEngines = getUserSelectedEngines(prefs); 1.444 + userSelectedEnginesTimestamp = prefs.getLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, 0); 1.445 + // We don't set crypto/keys here because we need the syncKeyBundle to decrypt the JSON 1.446 + // and we won't have it on construction. 1.447 + // TODO: MetaGlobal, password, infoCollections. 1.448 + } 1.449 + 1.450 + public void persistToPrefs() { 1.451 + this.persistToPrefs(this.getPrefs()); 1.452 + } 1.453 + 1.454 + private static String setToJSONObjectString(Set<String> set) { 1.455 + ExtendedJSONObject o = new ExtendedJSONObject(); 1.456 + for (String name : set) { 1.457 + o.put(name, 0); 1.458 + } 1.459 + return o.toJSONString(); 1.460 + } 1.461 + 1.462 + public void persistToPrefs(SharedPreferences prefs) { 1.463 + Editor edit = prefs.edit(); 1.464 + if (clusterURL == null) { 1.465 + edit.remove(PREF_CLUSTER_URL); 1.466 + } else { 1.467 + edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString()); 1.468 + } 1.469 + if (syncID != null) { 1.470 + edit.putString(PREF_SYNC_ID, syncID); 1.471 + } 1.472 + if (enabledEngineNames == null) { 1.473 + edit.remove(PREF_ENABLED_ENGINE_NAMES); 1.474 + } else { 1.475 + edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames)); 1.476 + } 1.477 + if (declinedEngineNames.isEmpty()) { 1.478 + edit.remove(PREF_DECLINED_ENGINE_NAMES); 1.479 + } else { 1.480 + edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames)); 1.481 + } 1.482 + if (userSelectedEngines == null) { 1.483 + edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC); 1.484 + edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP); 1.485 + } 1.486 + // Don't bother saving userSelectedEngines - these should only be changed by 1.487 + // SelectEnginesActivity. 1.488 + edit.commit(); 1.489 + // TODO: keys. 1.490 + } 1.491 + 1.492 + public AuthHeaderProvider getAuthHeaderProvider() { 1.493 + return authHeaderProvider; 1.494 + } 1.495 + 1.496 + public CollectionKeys getCollectionKeys() { 1.497 + return collectionKeys; 1.498 + } 1.499 + 1.500 + public void setCollectionKeys(CollectionKeys k) { 1.501 + collectionKeys = k; 1.502 + } 1.503 + 1.504 + /** 1.505 + * Return path to storage endpoint without trailing slash. 1.506 + * 1.507 + * @return storage endpoint without trailing slash. 1.508 + */ 1.509 + public String storageURL() { 1.510 + return clusterURL + "/storage"; 1.511 + } 1.512 + 1.513 + protected String infoBaseURL() { 1.514 + return clusterURL + "/info/"; 1.515 + } 1.516 + 1.517 + public String infoCollectionsURL() { 1.518 + return infoBaseURL() + "collections"; 1.519 + } 1.520 + 1.521 + public String infoCollectionCountsURL() { 1.522 + return infoBaseURL() + "collection_counts"; 1.523 + } 1.524 + 1.525 + public String metaURL() { 1.526 + return storageURL() + "/meta/global"; 1.527 + } 1.528 + 1.529 + public URI collectionURI(String collection) throws URISyntaxException { 1.530 + return new URI(storageURL() + "/" + collection); 1.531 + } 1.532 + 1.533 + public URI collectionURI(String collection, boolean full) throws URISyntaxException { 1.534 + // Do it this way to make it easier to add more params later. 1.535 + // It's pretty ugly, I'll grant. 1.536 + boolean anyParams = full; 1.537 + String uriParams = ""; 1.538 + if (anyParams) { 1.539 + StringBuilder params = new StringBuilder("?"); 1.540 + if (full) { 1.541 + params.append("full=1"); 1.542 + } 1.543 + uriParams = params.toString(); 1.544 + } 1.545 + String uri = storageURL() + "/" + collection + uriParams; 1.546 + return new URI(uri); 1.547 + } 1.548 + 1.549 + public URI wboURI(String collection, String id) throws URISyntaxException { 1.550 + return new URI(storageURL() + "/" + collection + "/" + id); 1.551 + } 1.552 + 1.553 + public URI keysURI() throws URISyntaxException { 1.554 + return wboURI("crypto", "keys"); 1.555 + } 1.556 + 1.557 + public URI getClusterURL() { 1.558 + return clusterURL; 1.559 + } 1.560 + 1.561 + public String getClusterURLString() { 1.562 + if (clusterURL == null) { 1.563 + return null; 1.564 + } 1.565 + return clusterURL.toASCIIString(); 1.566 + } 1.567 + 1.568 + public void setClusterURL(URI u) { 1.569 + this.clusterURL = u; 1.570 + } 1.571 + 1.572 + /** 1.573 + * Used for direct management of related prefs. 1.574 + */ 1.575 + public Editor getEditor() { 1.576 + return this.getPrefs().edit(); 1.577 + } 1.578 + 1.579 + /** 1.580 + * We persist two different clients timestamps: our own record's, 1.581 + * and the timestamp for the collection. 1.582 + */ 1.583 + public void persistServerClientRecordTimestamp(long timestamp) { 1.584 + getEditor().putLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, timestamp).commit(); 1.585 + } 1.586 + 1.587 + public long getPersistedServerClientRecordTimestamp() { 1.588 + return getPrefs().getLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, 0); 1.589 + } 1.590 + 1.591 + public void persistServerClientsTimestamp(long timestamp) { 1.592 + getEditor().putLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, timestamp).commit(); 1.593 + } 1.594 + 1.595 + public long getPersistedServerClientsTimestamp() { 1.596 + return getPrefs().getLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, 0); 1.597 + } 1.598 + 1.599 + public void purgeCryptoKeys() { 1.600 + if (collectionKeys != null) { 1.601 + collectionKeys.clear(); 1.602 + } 1.603 + persistedCryptoKeys().purge(); 1.604 + } 1.605 + 1.606 + public void purgeMetaGlobal() { 1.607 + metaGlobal = null; 1.608 + persistedMetaGlobal().purge(); 1.609 + } 1.610 + 1.611 + public PersistedCrypto5Keys persistedCryptoKeys() { 1.612 + return new PersistedCrypto5Keys(getPrefs(), syncKeyBundle); 1.613 + } 1.614 + 1.615 + public PersistedMetaGlobal persistedMetaGlobal() { 1.616 + return new PersistedMetaGlobal(getPrefs()); 1.617 + } 1.618 +}