1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/sync/syncadapter/SyncAdapter.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,569 @@ 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.syncadapter; 1.9 + 1.10 +import java.io.IOException; 1.11 +import java.net.URI; 1.12 +import java.security.NoSuchAlgorithmException; 1.13 +import java.util.Collection; 1.14 +import java.util.concurrent.atomic.AtomicBoolean; 1.15 + 1.16 +import org.json.simple.parser.ParseException; 1.17 +import org.mozilla.gecko.background.common.GlobalConstants; 1.18 +import org.mozilla.gecko.background.common.log.Logger; 1.19 +import org.mozilla.gecko.db.BrowserContract; 1.20 +import org.mozilla.gecko.sync.AlreadySyncingException; 1.21 +import org.mozilla.gecko.sync.BackoffHandler; 1.22 +import org.mozilla.gecko.sync.CredentialException; 1.23 +import org.mozilla.gecko.sync.GlobalSession; 1.24 +import org.mozilla.gecko.sync.NonObjectJSONException; 1.25 +import org.mozilla.gecko.sync.PrefsBackoffHandler; 1.26 +import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; 1.27 +import org.mozilla.gecko.sync.SharedPreferencesNodeAssignmentCallback; 1.28 +import org.mozilla.gecko.sync.Sync11Configuration; 1.29 +import org.mozilla.gecko.sync.SyncConfiguration; 1.30 +import org.mozilla.gecko.sync.SyncConfigurationException; 1.31 +import org.mozilla.gecko.sync.SyncConstants; 1.32 +import org.mozilla.gecko.sync.SyncException; 1.33 +import org.mozilla.gecko.sync.ThreadPool; 1.34 +import org.mozilla.gecko.sync.Utils; 1.35 +import org.mozilla.gecko.sync.config.AccountPickler; 1.36 +import org.mozilla.gecko.sync.crypto.CryptoException; 1.37 +import org.mozilla.gecko.sync.crypto.KeyBundle; 1.38 +import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback; 1.39 +import org.mozilla.gecko.sync.delegates.ClientsDataDelegate; 1.40 +import org.mozilla.gecko.sync.net.AuthHeaderProvider; 1.41 +import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider; 1.42 +import org.mozilla.gecko.sync.net.ConnectionMonitorThread; 1.43 +import org.mozilla.gecko.sync.setup.Constants; 1.44 +import org.mozilla.gecko.sync.setup.SyncAccounts; 1.45 +import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; 1.46 +import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; 1.47 + 1.48 +import android.accounts.Account; 1.49 +import android.accounts.AccountManager; 1.50 +import android.accounts.AuthenticatorException; 1.51 +import android.accounts.OperationCanceledException; 1.52 +import android.content.AbstractThreadedSyncAdapter; 1.53 +import android.content.ContentProviderClient; 1.54 +import android.content.ContentResolver; 1.55 +import android.content.Context; 1.56 +import android.content.SharedPreferences; 1.57 +import android.content.SyncResult; 1.58 +import android.database.sqlite.SQLiteConstraintException; 1.59 +import android.database.sqlite.SQLiteException; 1.60 +import android.os.Bundle; 1.61 + 1.62 +public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlobalSessionCallback { 1.63 + private static final String LOG_TAG = "SyncAdapter"; 1.64 + 1.65 + private static final int BACKOFF_PAD_SECONDS = 5; 1.66 + public static final int MULTI_DEVICE_INTERVAL_MILLISECONDS = 5 * 60 * 1000; // 5 minutes. 1.67 + public static final int SINGLE_DEVICE_INTERVAL_MILLISECONDS = 24 * 60 * 60 * 1000; // 24 hours. 1.68 + 1.69 + private final Context mContext; 1.70 + 1.71 + protected long syncStartTimestamp; 1.72 + 1.73 + protected volatile BackoffHandler backoffHandler; 1.74 + 1.75 + public SyncAdapter(Context context, boolean autoInitialize) { 1.76 + super(context, autoInitialize); 1.77 + mContext = context; 1.78 + } 1.79 + 1.80 + /** 1.81 + * Handle an exception: update stats, log errors, etc. 1.82 + * Wakes up sleeping threads by calling notifyMonitor(). 1.83 + * 1.84 + * @param globalSession 1.85 + * current global session, or null. 1.86 + * @param e 1.87 + * Exception to handle. 1.88 + */ 1.89 + protected void processException(final GlobalSession globalSession, final Exception e) { 1.90 + try { 1.91 + if (e instanceof SQLiteConstraintException) { 1.92 + Logger.error(LOG_TAG, "Constraint exception. Aborting sync.", e); 1.93 + syncResult.stats.numParseExceptions++; // This is as good as we can do. 1.94 + return; 1.95 + } 1.96 + if (e instanceof SQLiteException) { 1.97 + Logger.error(LOG_TAG, "Couldn't open database (locked?). Aborting sync.", e); 1.98 + syncResult.stats.numIoExceptions++; 1.99 + return; 1.100 + } 1.101 + if (e instanceof OperationCanceledException) { 1.102 + Logger.error(LOG_TAG, "Operation canceled. Aborting sync.", e); 1.103 + return; 1.104 + } 1.105 + if (e instanceof AuthenticatorException) { 1.106 + syncResult.stats.numParseExceptions++; 1.107 + Logger.error(LOG_TAG, "AuthenticatorException. Aborting sync.", e); 1.108 + return; 1.109 + } 1.110 + if (e instanceof IOException) { 1.111 + syncResult.stats.numIoExceptions++; 1.112 + Logger.error(LOG_TAG, "IOException. Aborting sync.", e); 1.113 + e.printStackTrace(); 1.114 + return; 1.115 + } 1.116 + 1.117 + // Blanket stats updating for SyncException subclasses. 1.118 + if (e instanceof SyncException) { 1.119 + ((SyncException) e).updateStats(globalSession, syncResult); 1.120 + } else { 1.121 + // Generic exception. 1.122 + syncResult.stats.numIoExceptions++; 1.123 + } 1.124 + 1.125 + if (e instanceof CredentialException.MissingAllCredentialsException) { 1.126 + // This is bad: either we couldn't fetch credentials, or the credentials 1.127 + // were totally blank. Most likely the user has two copies of Firefox 1.128 + // installed, and something is misbehaving. 1.129 + // Either way, disable this account. 1.130 + if (localAccount == null) { 1.131 + // Should not happen, but be safe. 1.132 + Logger.error(LOG_TAG, "No credentials attached to account. Aborting sync."); 1.133 + return; 1.134 + } 1.135 + 1.136 + Logger.error(LOG_TAG, "No credentials attached to account " + localAccount.name + ". Aborting sync."); 1.137 + try { 1.138 + SyncAccounts.setSyncAutomatically(localAccount, false); 1.139 + } catch (Exception ex) { 1.140 + Logger.error(LOG_TAG, "Unable to disable account " + localAccount.name + ".", ex); 1.141 + } 1.142 + return; 1.143 + } 1.144 + 1.145 + if (e instanceof CredentialException.MissingCredentialException) { 1.146 + Logger.error(LOG_TAG, "Credentials attached to account, but missing " + 1.147 + ((CredentialException.MissingCredentialException) e).missingCredential + ". Aborting sync."); 1.148 + return; 1.149 + } 1.150 + 1.151 + if (e instanceof CredentialException) { 1.152 + Logger.error(LOG_TAG, "Credentials attached to account were bad."); 1.153 + return; 1.154 + } 1.155 + 1.156 + // Bug 755638 - Uncaught SecurityException when attempting to sync multiple Fennecs 1.157 + // to the same Sync account. 1.158 + // Uncheck Sync checkbox because we cannot sync this instance. 1.159 + if (e instanceof SecurityException) { 1.160 + Logger.error(LOG_TAG, "SecurityException, multiple Fennecs. Disabling this instance.", e); 1.161 + SyncAccounts.backgroundSetSyncAutomatically(localAccount, false); 1.162 + return; 1.163 + } 1.164 + // Generic exception. 1.165 + Logger.error(LOG_TAG, "Unknown exception. Aborting sync.", e); 1.166 + } finally { 1.167 + notifyMonitor(); 1.168 + } 1.169 + } 1.170 + 1.171 + @Override 1.172 + public void onSyncCanceled() { 1.173 + super.onSyncCanceled(); 1.174 + // TODO: cancel the sync! 1.175 + // From the docs: "This will be invoked on a separate thread than the sync 1.176 + // thread and so you must consider the multi-threaded implications of the 1.177 + // work that you do in this method." 1.178 + } 1.179 + 1.180 + public Object syncMonitor = new Object(); 1.181 + private SyncResult syncResult; 1.182 + 1.183 + protected Account localAccount; 1.184 + protected boolean thisSyncIsForced = false; 1.185 + protected SharedPreferences accountSharedPreferences; 1.186 + protected SharedPreferencesClientsDataDelegate clientsDataDelegate; 1.187 + protected SharedPreferencesNodeAssignmentCallback nodeAssignmentDelegate; 1.188 + 1.189 + /** 1.190 + * Request that no sync start right away. A new sync won't start until 1.191 + * at least <code>backoff</code> milliseconds from now. 1.192 + * 1.193 + * Don't call this unless you are inside `run`. 1.194 + * 1.195 + * @param backoff time to wait in milliseconds. 1.196 + */ 1.197 + @Override 1.198 + public void requestBackoff(final long backoff) { 1.199 + if (this.backoffHandler == null) { 1.200 + throw new IllegalStateException("No backoff handler: requesting backoff outside run()?"); 1.201 + } 1.202 + if (backoff > 0) { 1.203 + // Fuzz the backoff time (up to 25% more) to prevent client lock-stepping; agrees with desktop. 1.204 + final long fuzzedBackoff = backoff + Math.round((double) backoff * 0.25d * Math.random()); 1.205 + this.backoffHandler.extendEarliestNextRequest(System.currentTimeMillis() + fuzzedBackoff); 1.206 + } 1.207 + } 1.208 + 1.209 + @Override 1.210 + public boolean shouldBackOffStorage() { 1.211 + if (thisSyncIsForced) { 1.212 + /* 1.213 + * If the user asks us to sync, we should sync regardless. This path is 1.214 + * hit if the user force syncs and we restart a session after a 1.215 + * freshStart. 1.216 + */ 1.217 + return false; 1.218 + } 1.219 + 1.220 + if (nodeAssignmentDelegate.wantNodeAssignment()) { 1.221 + /* 1.222 + * We recently had a 401 and we aborted the last sync. We should kick off 1.223 + * another sync to fetch a new node/weave cluster URL, since ours is 1.224 + * stale. If we have a user authentication error, the next sync will 1.225 + * determine that and will stop requesting node assignment, so this will 1.226 + * only force one abnormally scheduled sync. 1.227 + */ 1.228 + return false; 1.229 + } 1.230 + 1.231 + if (this.backoffHandler == null) { 1.232 + throw new IllegalStateException("No backoff handler: checking backoff outside run()?"); 1.233 + } 1.234 + return this.backoffHandler.delayMilliseconds() > 0; 1.235 + } 1.236 + 1.237 + /** 1.238 + * Asynchronously request an immediate sync, optionally syncing only the given 1.239 + * named stages. 1.240 + * <p> 1.241 + * Returns immediately. 1.242 + * 1.243 + * @param account 1.244 + * the Android <code>Account</code> instance to sync. 1.245 + * @param stageNames 1.246 + * stage names to sync, or <code>null</code> to sync all known stages. 1.247 + */ 1.248 + public static void requestImmediateSync(final Account account, final String[] stageNames) { 1.249 + requestImmediateSync(account, stageNames, null); 1.250 + } 1.251 + 1.252 + /** 1.253 + * Asynchronously request an immediate sync, optionally syncing only the given 1.254 + * named stages. 1.255 + * <p> 1.256 + * Returns immediately. 1.257 + * 1.258 + * @param account 1.259 + * the Android <code>Account</code> instance to sync. 1.260 + * @param stageNames 1.261 + * stage names to sync, or <code>null</code> to sync all known stages. 1.262 + * @param moreExtras 1.263 + * bundle of extras to give to the sync, or <code>null</code> 1.264 + */ 1.265 + public static void requestImmediateSync(final Account account, final String[] stageNames, Bundle moreExtras) { 1.266 + if (account == null) { 1.267 + Logger.warn(LOG_TAG, "Not requesting immediate sync because Android Account is null."); 1.268 + return; 1.269 + } 1.270 + 1.271 + final Bundle extras = new Bundle(); 1.272 + Utils.putStageNamesToSync(extras, stageNames, null); 1.273 + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 1.274 + 1.275 + if (moreExtras != null) { 1.276 + extras.putAll(moreExtras); 1.277 + } 1.278 + 1.279 + ContentResolver.requestSync(account, BrowserContract.AUTHORITY, extras); 1.280 + } 1.281 + 1.282 + @Override 1.283 + public void onPerformSync(final Account account, 1.284 + final Bundle extras, 1.285 + final String authority, 1.286 + final ContentProviderClient provider, 1.287 + final SyncResult syncResult) { 1.288 + syncStartTimestamp = System.currentTimeMillis(); 1.289 + 1.290 + Logger.setThreadLogTag(SyncConstants.GLOBAL_LOG_TAG); 1.291 + Logger.resetLogging(); 1.292 + Utils.reseedSharedRandom(); // Make sure we don't work with the same random seed for too long. 1.293 + 1.294 + // Set these so that we don't need to thread them through assorted calls and callbacks. 1.295 + this.syncResult = syncResult; 1.296 + this.localAccount = account; 1.297 + 1.298 + SyncAccountParameters params; 1.299 + try { 1.300 + params = SyncAccounts.blockingFromAndroidAccountV0(mContext, AccountManager.get(mContext), this.localAccount); 1.301 + } catch (Exception e) { 1.302 + // Updates syncResult and (harmlessly) calls notifyMonitor(). 1.303 + processException(null, e); 1.304 + return; 1.305 + } 1.306 + 1.307 + // params and the following fields are non-null at this point. 1.308 + final String username = params.username; // Encoded with Utils.usernameFromAccount. 1.309 + final String password = params.password; 1.310 + final String serverURL = params.serverURL; 1.311 + final String syncKey = params.syncKey; 1.312 + 1.313 + final AtomicBoolean setNextSync = new AtomicBoolean(true); 1.314 + final SyncAdapter self = this; 1.315 + final Runnable runnable = new Runnable() { 1.316 + @Override 1.317 + public void run() { 1.318 + Logger.trace(LOG_TAG, "AccountManagerCallback invoked."); 1.319 + // TODO: N.B.: Future must not be used on the main thread. 1.320 + try { 1.321 + if (Logger.LOG_PERSONAL_INFORMATION) { 1.322 + Logger.pii(LOG_TAG, "Syncing account named " + account.name + 1.323 + " for authority " + authority + "."); 1.324 + } else { 1.325 + // Replace "foo@bar.com" with "XXX@XXX.XXX". 1.326 + Logger.info(LOG_TAG, "Syncing account named like " + Utils.obfuscateEmail(account.name) + 1.327 + " for authority " + authority + "."); 1.328 + } 1.329 + 1.330 + // We dump this information right away to help with debugging. 1.331 + Logger.debug(LOG_TAG, "Username: " + username); 1.332 + Logger.debug(LOG_TAG, "Server: " + serverURL); 1.333 + if (Logger.LOG_PERSONAL_INFORMATION) { 1.334 + Logger.debug(LOG_TAG, "Password: " + password); 1.335 + Logger.debug(LOG_TAG, "Sync key: " + syncKey); 1.336 + } else { 1.337 + Logger.debug(LOG_TAG, "Password? " + (password != null)); 1.338 + Logger.debug(LOG_TAG, "Sync key? " + (syncKey != null)); 1.339 + } 1.340 + 1.341 + // Support multiple accounts by mapping each server/account pair to a branch of the 1.342 + // shared preferences space. 1.343 + final String product = GlobalConstants.BROWSER_INTENT_PACKAGE; 1.344 + final String profile = Constants.DEFAULT_PROFILE; 1.345 + final long version = SyncConfiguration.CURRENT_PREFS_VERSION; 1.346 + self.accountSharedPreferences = Utils.getSharedPreferences(mContext, product, username, serverURL, profile, version); 1.347 + self.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(accountSharedPreferences); 1.348 + self.backoffHandler = new PrefsBackoffHandler(accountSharedPreferences, SyncConstants.BACKOFF_PREF_SUFFIX_11); 1.349 + final String nodeWeaveURL = Utils.nodeWeaveURL(serverURL, username); 1.350 + self.nodeAssignmentDelegate = new SharedPreferencesNodeAssignmentCallback(accountSharedPreferences, nodeWeaveURL); 1.351 + 1.352 + Logger.info(LOG_TAG, 1.353 + "Client is named '" + clientsDataDelegate.getClientName() + "'" + 1.354 + ", has client guid " + clientsDataDelegate.getAccountGUID() + 1.355 + ", and has " + clientsDataDelegate.getClientsCount() + " clients."); 1.356 + 1.357 + final boolean thisSyncIsForced = (extras != null) && (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)); 1.358 + final long delayMillis = backoffHandler.delayMilliseconds(); 1.359 + boolean shouldSync = thisSyncIsForced || (delayMillis <= 0L); 1.360 + if (!shouldSync) { 1.361 + long remainingSeconds = delayMillis / 1000; 1.362 + syncResult.delayUntil = remainingSeconds + BACKOFF_PAD_SECONDS; 1.363 + setNextSync.set(false); 1.364 + self.notifyMonitor(); 1.365 + return; 1.366 + } 1.367 + 1.368 + final String prefsPath = Utils.getPrefsPath(product, username, serverURL, profile, version); 1.369 + self.performSync(account, extras, authority, provider, syncResult, 1.370 + username, password, prefsPath, serverURL, syncKey); 1.371 + } catch (Exception e) { 1.372 + self.processException(null, e); 1.373 + return; 1.374 + } 1.375 + } 1.376 + }; 1.377 + 1.378 + synchronized (syncMonitor) { 1.379 + // Perform the work in a new thread from within this synchronized block, 1.380 + // which allows us to be waiting on the monitor before the callback can 1.381 + // notify us in a failure case. Oh, concurrent programming. 1.382 + new Thread(runnable).start(); 1.383 + 1.384 + // Start our stale connection monitor thread. 1.385 + ConnectionMonitorThread stale = new ConnectionMonitorThread(); 1.386 + stale.start(); 1.387 + 1.388 + Logger.trace(LOG_TAG, "Waiting on sync monitor."); 1.389 + try { 1.390 + syncMonitor.wait(); 1.391 + 1.392 + if (setNextSync.get()) { 1.393 + long interval = getSyncInterval(clientsDataDelegate); 1.394 + long next = System.currentTimeMillis() + interval; 1.395 + 1.396 + if (thisSyncIsForced) { 1.397 + Logger.info(LOG_TAG, "Setting minimum next sync time to " + next + " (" + interval + "ms from now)."); 1.398 + self.backoffHandler.setEarliestNextRequest(next); 1.399 + } else { 1.400 + Logger.info(LOG_TAG, "Extending minimum next sync time to " + next + " (" + interval + "ms from now)."); 1.401 + self.backoffHandler.extendEarliestNextRequest(next); 1.402 + } 1.403 + } 1.404 + Logger.info(LOG_TAG, "Sync took " + Utils.formatDuration(syncStartTimestamp, System.currentTimeMillis()) + "."); 1.405 + } catch (InterruptedException e) { 1.406 + Logger.warn(LOG_TAG, "Waiting on sync monitor interrupted.", e); 1.407 + } finally { 1.408 + // And we're done with HTTP stuff. 1.409 + stale.shutdown(); 1.410 + } 1.411 + } 1.412 + } 1.413 + 1.414 + public int getSyncInterval(ClientsDataDelegate clientsDataDelegate) { 1.415 + // Must have been a problem that means we can't access the Account. 1.416 + if (this.localAccount == null) { 1.417 + return SINGLE_DEVICE_INTERVAL_MILLISECONDS; 1.418 + } 1.419 + 1.420 + int clientsCount = clientsDataDelegate.getClientsCount(); 1.421 + if (clientsCount <= 1) { 1.422 + return SINGLE_DEVICE_INTERVAL_MILLISECONDS; 1.423 + } 1.424 + 1.425 + return MULTI_DEVICE_INTERVAL_MILLISECONDS; 1.426 + } 1.427 + 1.428 + /** 1.429 + * Now that we have a sync key and password, go ahead and do the work. 1.430 + * @throws NoSuchAlgorithmException 1.431 + * @throws IllegalArgumentException 1.432 + * @throws SyncConfigurationException 1.433 + * @throws AlreadySyncingException 1.434 + * @throws NonObjectJSONException 1.435 + * @throws ParseException 1.436 + * @throws IOException 1.437 + * @throws CryptoException 1.438 + */ 1.439 + protected void performSync(final Account account, 1.440 + final Bundle extras, 1.441 + final String authority, 1.442 + final ContentProviderClient provider, 1.443 + final SyncResult syncResult, 1.444 + final String username, 1.445 + final String password, 1.446 + final String prefsPath, 1.447 + final String serverURL, 1.448 + final String syncKey) 1.449 + throws NoSuchAlgorithmException, 1.450 + SyncConfigurationException, 1.451 + IllegalArgumentException, 1.452 + AlreadySyncingException, 1.453 + IOException, ParseException, 1.454 + NonObjectJSONException, CryptoException { 1.455 + Logger.trace(LOG_TAG, "Performing sync."); 1.456 + 1.457 + /** 1.458 + * Bug 769745: pickle Sync account parameters to JSON file. Un-pickle in 1.459 + * <code>SyncAccounts.syncAccountsExist</code>. 1.460 + */ 1.461 + try { 1.462 + // Constructor can throw on nulls, which should not happen -- but let's be safe. 1.463 + final SyncAccountParameters params = new SyncAccountParameters(mContext, null, 1.464 + account.name, // Un-encoded, like "test@mozilla.com". 1.465 + syncKey, 1.466 + password, 1.467 + serverURL, 1.468 + null, // We'll re-fetch cluster URL; not great, but not harmful. 1.469 + clientsDataDelegate.getClientName(), 1.470 + clientsDataDelegate.getAccountGUID()); 1.471 + 1.472 + // Bug 772971: pickle Sync account parameters on background thread to 1.473 + // avoid strict mode warnings. 1.474 + ThreadPool.run(new Runnable() { 1.475 + @Override 1.476 + public void run() { 1.477 + final boolean syncAutomatically = ContentResolver.getSyncAutomatically(account, authority); 1.478 + try { 1.479 + AccountPickler.pickle(mContext, Constants.ACCOUNT_PICKLE_FILENAME, params, syncAutomatically); 1.480 + } catch (Exception e) { 1.481 + // Should never happen, but we really don't want to die in a background thread. 1.482 + Logger.warn(LOG_TAG, "Got exception pickling current account details; ignoring.", e); 1.483 + } 1.484 + } 1.485 + }); 1.486 + } catch (IllegalArgumentException e) { 1.487 + // Do nothing. 1.488 + } 1.489 + 1.490 + if (username == null) { 1.491 + throw new IllegalArgumentException("username must not be null."); 1.492 + } 1.493 + 1.494 + if (syncKey == null) { 1.495 + throw new SyncConfigurationException(); 1.496 + } 1.497 + 1.498 + final KeyBundle keyBundle = new KeyBundle(username, syncKey); 1.499 + 1.500 + if (keyBundle == null || 1.501 + keyBundle.getEncryptionKey() == null || 1.502 + keyBundle.getHMACKey() == null) { 1.503 + throw new SyncConfigurationException(); 1.504 + } 1.505 + 1.506 + final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(username, password); 1.507 + final SharedPreferences prefs = getContext().getSharedPreferences(prefsPath, Utils.SHARED_PREFERENCES_MODE); 1.508 + final SyncConfiguration config = new Sync11Configuration(username, authHeaderProvider, prefs, keyBundle); 1.509 + 1.510 + Collection<String> knownStageNames = SyncConfiguration.validEngineNames(); 1.511 + config.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras); 1.512 + 1.513 + GlobalSession globalSession = new GlobalSession(config, this, this.mContext, clientsDataDelegate, nodeAssignmentDelegate); 1.514 + globalSession.start(); 1.515 + } 1.516 + 1.517 + private void notifyMonitor() { 1.518 + synchronized (syncMonitor) { 1.519 + Logger.trace(LOG_TAG, "Notifying sync monitor."); 1.520 + syncMonitor.notifyAll(); 1.521 + } 1.522 + } 1.523 + 1.524 + // Implementing GlobalSession callbacks. 1.525 + @Override 1.526 + public void handleError(GlobalSession globalSession, Exception ex) { 1.527 + Logger.info(LOG_TAG, "GlobalSession indicated error."); 1.528 + this.processException(globalSession, ex); 1.529 + } 1.530 + 1.531 + @Override 1.532 + public void handleAborted(GlobalSession globalSession, String reason) { 1.533 + Logger.warn(LOG_TAG, "Sync aborted: " + reason); 1.534 + notifyMonitor(); 1.535 + } 1.536 + 1.537 + @Override 1.538 + public void handleSuccess(GlobalSession globalSession) { 1.539 + Logger.info(LOG_TAG, "GlobalSession indicated success."); 1.540 + globalSession.config.persistToPrefs(); 1.541 + notifyMonitor(); 1.542 + } 1.543 + 1.544 + @Override 1.545 + public void handleStageCompleted(Stage currentState, 1.546 + GlobalSession globalSession) { 1.547 + Logger.trace(LOG_TAG, "Stage completed: " + currentState); 1.548 + } 1.549 + 1.550 + @Override 1.551 + public void informUnauthorizedResponse(GlobalSession session, URI oldClusterURL) { 1.552 + nodeAssignmentDelegate.setClusterURLIsStale(true); 1.553 + } 1.554 + 1.555 + @Override 1.556 + public void informUpgradeRequiredResponse(final GlobalSession session) { 1.557 + final AccountManager manager = AccountManager.get(mContext); 1.558 + final Account toDisable = localAccount; 1.559 + if (toDisable == null || manager == null) { 1.560 + Logger.warn(LOG_TAG, "Attempting to disable account, but null found."); 1.561 + return; 1.562 + } 1.563 + // Sync needs to be upgraded. Don't automatically sync anymore. 1.564 + ThreadPool.run(new Runnable() { 1.565 + @Override 1.566 + public void run() { 1.567 + manager.setUserData(toDisable, Constants.DATA_ENABLE_ON_UPGRADE, "1"); 1.568 + SyncAccounts.setSyncAutomatically(toDisable, false); 1.569 + } 1.570 + }); 1.571 + } 1.572 +}