mobile/android/base/sync/syncadapter/SyncAdapter.java

changeset 0
6474c204b198
     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 +}

mercurial