mobile/android/base/fxa/sync/FxAccountSyncAdapter.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     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/. */
     5 package org.mozilla.gecko.fxa.sync;
     7 import java.net.URI;
     8 import java.net.URISyntaxException;
     9 import java.security.NoSuchAlgorithmException;
    10 import java.util.Collection;
    11 import java.util.Collections;
    12 import java.util.EnumSet;
    13 import java.util.concurrent.CountDownLatch;
    14 import java.util.concurrent.ExecutorService;
    15 import java.util.concurrent.Executors;
    17 import org.mozilla.gecko.background.common.log.Logger;
    18 import org.mozilla.gecko.background.fxa.FxAccountClient;
    19 import org.mozilla.gecko.background.fxa.FxAccountClient20;
    20 import org.mozilla.gecko.background.fxa.SkewHandler;
    21 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
    22 import org.mozilla.gecko.browserid.JSONWebTokenUtils;
    23 import org.mozilla.gecko.fxa.FirefoxAccounts;
    24 import org.mozilla.gecko.fxa.FxAccountConstants;
    25 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
    26 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
    27 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
    28 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
    29 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
    30 import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
    31 import org.mozilla.gecko.fxa.login.Married;
    32 import org.mozilla.gecko.fxa.login.State;
    33 import org.mozilla.gecko.fxa.login.State.StateLabel;
    34 import org.mozilla.gecko.fxa.login.StateFactory;
    35 import org.mozilla.gecko.sync.BackoffHandler;
    36 import org.mozilla.gecko.sync.GlobalSession;
    37 import org.mozilla.gecko.sync.PrefsBackoffHandler;
    38 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
    39 import org.mozilla.gecko.sync.SyncConfiguration;
    40 import org.mozilla.gecko.sync.ThreadPool;
    41 import org.mozilla.gecko.sync.Utils;
    42 import org.mozilla.gecko.sync.crypto.KeyBundle;
    43 import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback;
    44 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
    45 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
    46 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
    47 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
    48 import org.mozilla.gecko.tokenserver.TokenServerClient;
    49 import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
    50 import org.mozilla.gecko.tokenserver.TokenServerException;
    51 import org.mozilla.gecko.tokenserver.TokenServerToken;
    53 import android.accounts.Account;
    54 import android.content.AbstractThreadedSyncAdapter;
    55 import android.content.ContentProviderClient;
    56 import android.content.ContentResolver;
    57 import android.content.Context;
    58 import android.content.SharedPreferences;
    59 import android.content.SyncResult;
    60 import android.os.Bundle;
    61 import android.os.SystemClock;
    63 public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
    64   private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
    66   public static final String SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT = "respect_local_rate_limit";
    67   public static final String SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF = "respect_remote_server_backoff";
    69   protected static final int NOTIFICATION_ID = LOG_TAG.hashCode();
    71   // Tracks the last seen storage hostname for backoff purposes.
    72   private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
    74   // Used to do cheap in-memory rate limiting. Don't sync again if we
    75   // successfully synced within this duration.
    76   private static final int MINIMUM_SYNC_DELAY_MILLIS = 15 * 1000;        // 15 seconds.
    77   private volatile long lastSyncRealtimeMillis = 0L;
    79   protected final ExecutorService executor;
    80   protected final FxAccountNotificationManager notificationManager;
    82   public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
    83     super(context, autoInitialize);
    84     this.executor = Executors.newSingleThreadExecutor();
    85     this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
    86   }
    88   protected static class SyncDelegate {
    89     protected final CountDownLatch latch;
    90     protected final SyncResult syncResult;
    91     protected final AndroidFxAccount fxAccount;
    92     protected final Collection<String> stageNamesToSync;
    94     public SyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) {
    95       if (latch == null) {
    96         throw new IllegalArgumentException("latch must not be null");
    97       }
    98       if (syncResult == null) {
    99         throw new IllegalArgumentException("syncResult must not be null");
   100       }
   101       if (fxAccount == null) {
   102         throw new IllegalArgumentException("fxAccount must not be null");
   103       }
   104       this.latch = latch;
   105       this.syncResult = syncResult;
   106       this.fxAccount = fxAccount;
   107       this.stageNamesToSync = Collections.unmodifiableCollection(stageNamesToSync);
   108     }
   110     /**
   111      * No error!  Say that we made progress.
   112      */
   113     protected void setSyncResultSuccess() {
   114       syncResult.stats.numUpdates += 1;
   115     }
   117     /**
   118      * Soft error. Say that we made progress, so that Android will sync us again
   119      * after exponential backoff.
   120      */
   121     protected void setSyncResultSoftError() {
   122       syncResult.stats.numUpdates += 1;
   123       syncResult.stats.numIoExceptions += 1;
   124     }
   126     /**
   127      * Hard error. We don't want Android to sync us again, even if we make
   128      * progress, until the user intervenes.
   129      */
   130     protected void setSyncResultHardError() {
   131       syncResult.stats.numAuthExceptions += 1;
   132     }
   134     public void handleSuccess() {
   135       Logger.info(LOG_TAG, "Sync succeeded.");
   136       setSyncResultSuccess();
   137       latch.countDown();
   138     }
   140     public void handleError(Exception e) {
   141       Logger.error(LOG_TAG, "Got exception syncing.", e);
   142       setSyncResultSoftError();
   143       // This is awful, but we need to propagate bad assertions back up the
   144       // chain somehow, and this will do for now.
   145       if (e instanceof TokenServerException) {
   146         // We should only get here *after* we're locked into the married state.
   147         State state = fxAccount.getState();
   148         if (state.getStateLabel() == StateLabel.Married) {
   149           Married married = (Married) state;
   150           fxAccount.setState(married.makeCohabitingState());
   151         }
   152       }
   153       latch.countDown();
   154     }
   156     /**
   157      * When the login machine terminates, we might not be in the
   158      * <code>Married</code> state, and therefore we can't sync. This method
   159      * messages as much to the user.
   160      * <p>
   161      * To avoid stopping us syncing altogether, we set a soft error rather than
   162      * a hard error. In future, we would like to set a hard error if we are in,
   163      * for example, the <code>Separated</code> state, and then have some user
   164      * initiated activity mark the Android account as ready to sync again. This
   165      * is tricky, though, so we play it safe for now.
   166      *
   167      * @param finalState
   168      *          that login machine ended in.
   169      */
   170     public void handleCannotSync(State finalState) {
   171       Logger.warn(LOG_TAG, "Cannot sync from state: " + finalState.getStateLabel());
   172       setSyncResultSoftError();
   173       latch.countDown();
   174     }
   176     public void postponeSync(long millis) {
   177       if (millis <= 0) {
   178         Logger.debug(LOG_TAG, "Asked to postpone sync, but zero delay. Short-circuiting.");
   179       } else {
   180         // delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
   181         // So we don't bother doing this. Instead, we rely on the periodic sync
   182         // we schedule, and the backoff handler for the rest.
   183         /*
   184         Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
   185         syncResult.delayUntil = millis / 1000;
   186          */
   187       }
   188       setSyncResultSoftError();
   189       latch.countDown();
   190     }
   192     /**
   193      * Simply don't sync, without setting any error flags.
   194      * This is the appropriate behavior when a routine backoff has not yet
   195      * been met.
   196      */
   197     public void rejectSync() {
   198       latch.countDown();
   199     }
   201     public Collection<String> getStageNamesToSync() {
   202       return this.stageNamesToSync;
   203     }
   204   }
   206   protected static class SessionCallback implements BaseGlobalSessionCallback {
   207     protected final SyncDelegate syncDelegate;
   208     protected final SchedulePolicy schedulePolicy;
   209     protected volatile BackoffHandler storageBackoffHandler;
   211     public SessionCallback(SyncDelegate syncDelegate, SchedulePolicy schedulePolicy) {
   212       this.syncDelegate = syncDelegate;
   213       this.schedulePolicy = schedulePolicy;
   214     }
   216     public void setBackoffHandler(BackoffHandler backoffHandler) {
   217       this.storageBackoffHandler = backoffHandler;
   218     }
   220     @Override
   221     public boolean shouldBackOffStorage() {
   222       return storageBackoffHandler.delayMilliseconds() > 0;
   223     }
   225     @Override
   226     public void requestBackoff(long backoffMillis) {
   227       final boolean onlyExtend = true;      // Because we trust what the storage server says.
   228       schedulePolicy.configureBackoffMillisOnBackoff(storageBackoffHandler, backoffMillis, onlyExtend);
   229     }
   231     @Override
   232     public void informUpgradeRequiredResponse(GlobalSession session) {
   233       schedulePolicy.onUpgradeRequired();
   234     }
   236     @Override
   237     public void informUnauthorizedResponse(GlobalSession globalSession, URI oldClusterURL) {
   238       schedulePolicy.onUnauthorized();
   239     }
   241     @Override
   242     public void handleStageCompleted(Stage currentState, GlobalSession globalSession) {
   243     }
   245     @Override
   246     public void handleSuccess(GlobalSession globalSession) {
   247       Logger.info(LOG_TAG, "Global session succeeded.");
   249       // Get the number of clients, so we can schedule the sync interval accordingly.
   250       try {
   251         int otherClientsCount = globalSession.getClientsDelegate().getClientsCount();
   252         Logger.debug(LOG_TAG, "" + otherClientsCount + " other client(s).");
   253         this.schedulePolicy.onSuccessfulSync(otherClientsCount);
   254       } finally {
   255         // Continue with the usual success flow.
   256         syncDelegate.handleSuccess();
   257       }
   258     }
   260     @Override
   261     public void handleError(GlobalSession globalSession, Exception e) {
   262       Logger.warn(LOG_TAG, "Global session failed."); // Exception will be dumped by delegate below.
   263       syncDelegate.handleError(e);
   264       // TODO: should we reduce the periodic sync interval?
   265     }
   267     @Override
   268     public void handleAborted(GlobalSession globalSession, String reason) {
   269       Logger.warn(LOG_TAG, "Global session aborted: " + reason);
   270       syncDelegate.handleError(null);
   271       // TODO: should we reduce the periodic sync interval?
   272     }
   273   };
   275   /**
   276    * Return true if the provided {@link BackoffHandler} isn't reporting that we're in
   277    * a backoff state, or the provided {@link Bundle} contains flags that indicate
   278    * we should force a sync.
   279    */
   280   private boolean shouldPerformSync(final BackoffHandler backoffHandler, final String kind, final Bundle extras) {
   281     final long delay = backoffHandler.delayMilliseconds();
   282     if (delay <= 0) {
   283       return true;
   284     }
   286     if (extras == null) {
   287       return false;
   288     }
   290     final boolean forced = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
   291     if (forced) {
   292       Logger.info(LOG_TAG, "Forced sync (" + kind + "): overruling remaining backoff of " + delay + "ms.");
   293     } else {
   294       Logger.info(LOG_TAG, "Not syncing (" + kind + "): must wait another " + delay + "ms.");
   295     }
   296     return forced;
   297   }
   299   protected void syncWithAssertion(final String audience,
   300                                    final String assertion,
   301                                    final URI tokenServerEndpointURI,
   302                                    final BackoffHandler tokenBackoffHandler,
   303                                    final SharedPreferences sharedPrefs,
   304                                    final KeyBundle syncKeyBundle,
   305                                    final String clientState,
   306                                    final SessionCallback callback,
   307                                    final Bundle extras) {
   308     final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
   309       private boolean didReceiveBackoff = false;
   311       @Override
   312       public String getUserAgent() {
   313         return FxAccountConstants.USER_AGENT;
   314       }
   316       @Override
   317       public void handleSuccess(final TokenServerToken token) {
   318         FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
   320         if (!didReceiveBackoff) {
   321           // We must be OK to touch this token server.
   322           tokenBackoffHandler.setEarliestNextRequest(0L);
   323         }
   325         final URI storageServerURI;
   326         try {
   327           storageServerURI = new URI(token.endpoint);
   328         } catch (URISyntaxException e) {
   329           handleError(e);
   330           return;
   331         }
   332         final String storageHostname = storageServerURI.getHost();
   334         // We back off on a per-host basis. When we have an endpoint URI from a token, we
   335         // can check on the backoff status for that host.
   336         // If we're supposed to be backing off, we abort the not-yet-started session.
   337         final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "sync.storage");
   338         callback.setBackoffHandler(storageBackoffHandler);
   340         String lastStorageHost = sharedPrefs.getString(PREF_BACKOFF_STORAGE_HOST, null);
   341         final boolean storageHostIsUnchanged = lastStorageHost != null &&
   342                                                lastStorageHost.equalsIgnoreCase(storageHostname);
   343         if (storageHostIsUnchanged) {
   344           Logger.debug(LOG_TAG, "Storage host is unchanged.");
   345           if (!shouldPerformSync(storageBackoffHandler, "storage", extras)) {
   346             Logger.info(LOG_TAG, "Not syncing: storage server requested backoff.");
   347             callback.handleAborted(null, "Storage backoff");
   348             return;
   349           }
   350         } else {
   351           Logger.debug(LOG_TAG, "Received new storage host.");
   352         }
   354         // Invalidate the previous backoff, because our storage host has changed,
   355         // or we never had one at all, or we're OK to sync.
   356         storageBackoffHandler.setEarliestNextRequest(0L);
   358         FxAccountGlobalSession globalSession = null;
   359         try {
   360           ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
   362           // We compute skew over time using SkewHandler. This yields an unchanging
   363           // skew adjustment that the HawkAuthHeaderProvider uses to adjust its
   364           // timestamps. Eventually we might want this to adapt within the scope of a
   365           // global session.
   366           final SkewHandler storageServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
   367           final long storageServerSkew = storageServerSkewHandler.getSkewInSeconds();
   368           // We expect Sync to upload large sets of records. Calculating the
   369           // payload verification hash for these record sets could be expensive,
   370           // so we explicitly do not send payload verification hashes to the
   371           // Sync storage endpoint.
   372           final boolean includePayloadVerificationHash = false;
   373           final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), includePayloadVerificationHash, storageServerSkew);
   375           final Context context = getContext();
   376           final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
   378           Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
   379           syncConfig.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
   380           syncConfig.setClusterURL(storageServerURI);
   382           globalSession = new FxAccountGlobalSession(syncConfig, callback, context, clientsDataDelegate);
   383           globalSession.start();
   384         } catch (Exception e) {
   385           callback.handleError(globalSession, e);
   386           return;
   387         }
   388       }
   390       @Override
   391       public void handleFailure(TokenServerException e) {
   392         handleError(e);
   393       }
   395       @Override
   396       public void handleError(Exception e) {
   397         Logger.error(LOG_TAG, "Failed to get token.", e);
   398         callback.handleError(null, e);
   399       }
   401       @Override
   402       public void handleBackoff(int backoffSeconds) {
   403         // This is the token server telling us to back off.
   404         Logger.info(LOG_TAG, "Token server requesting backoff of " + backoffSeconds + "s. Backoff handler: " + tokenBackoffHandler);
   405         didReceiveBackoff = true;
   407         // If we've already stored a backoff, overrule it: we only use the server
   408         // value for token server scheduling.
   409         tokenBackoffHandler.setEarliestNextRequest(delay(backoffSeconds * 1000));
   410       }
   412       private long delay(long delay) {
   413         return System.currentTimeMillis() + delay;
   414       }
   415     };
   417     TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
   418     tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, delegate);
   419   }
   421   /**
   422    * A trivial Sync implementation that does not cache client keys,
   423    * certificates, or tokens.
   424    *
   425    * This should be replaced with a full {@link FxAccountAuthenticator}-based
   426    * token implementation.
   427    */
   428   @Override
   429   public void onPerformSync(final Account account, final Bundle extras, final String authority, ContentProviderClient provider, final SyncResult syncResult) {
   430     Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
   431     Logger.resetLogging();
   433     Logger.info(LOG_TAG, "Syncing FxAccount" +
   434         " account named like " + Utils.obfuscateEmail(account.name) +
   435         " for authority " + authority +
   436         " with instance " + this + ".");
   438     final EnumSet<FirefoxAccounts.SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
   439     FirefoxAccounts.logSyncHints(syncHints);
   441     // This applies even to forced syncs, but only on success.
   442     if (this.lastSyncRealtimeMillis > 0L &&
   443         (this.lastSyncRealtimeMillis + MINIMUM_SYNC_DELAY_MILLIS) > SystemClock.elapsedRealtime()) {
   444       Logger.info(LOG_TAG, "Not syncing FxAccount " + Utils.obfuscateEmail(account.name) +
   445                            ": minimum interval not met.");
   446       return;
   447     }
   449     final Context context = getContext();
   450     final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
   451     if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
   452       fxAccount.dump();
   453     }
   455     // Pickle in a background thread to avoid strict mode warnings.
   456     ThreadPool.run(new Runnable() {
   457       @Override
   458       public void run() {
   459         try {
   460           AccountPickler.pickle(fxAccount, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
   461         } catch (Exception e) {
   462           // Should never happen, but we really don't want to die in a background thread.
   463           Logger.warn(LOG_TAG, "Got exception pickling current account details; ignoring.", e);
   464         }
   465       }
   466     });
   468     final CountDownLatch latch = new CountDownLatch(1);
   470     Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
   471     Collection<String> stageNamesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
   473     final SyncDelegate syncDelegate = new SyncDelegate(latch, syncResult, fxAccount, stageNamesToSync);
   475     try {
   476       final State state;
   477       try {
   478         state = fxAccount.getState();
   479       } catch (Exception e) {
   480         syncDelegate.handleError(e);
   481         return;
   482       }
   484       // This will be the same chunk of SharedPreferences that we pass through to GlobalSession/SyncConfiguration.
   485       final SharedPreferences sharedPrefs = fxAccount.getSyncPrefs();
   487       final BackoffHandler backgroundBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "background");
   488       final BackoffHandler rateLimitBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "rate");
   490       // If this sync was triggered by user action, this will be true.
   491       final boolean isImmediate = (extras != null) &&
   492                                   (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) ||
   493                                    extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
   495       // If it's not an immediate sync, it must be either periodic or tickled.
   496       // Check our background rate limiter.
   497       if (!isImmediate) {
   498         if (!shouldPerformSync(backgroundBackoffHandler, "background", extras)) {
   499           syncDelegate.rejectSync();
   500           return;
   501         }
   502       }
   504       // Regardless, let's make sure we're not syncing too often.
   505       if (!shouldPerformSync(rateLimitBackoffHandler, "rate", extras)) {
   506         syncDelegate.postponeSync(rateLimitBackoffHandler.delayMilliseconds());
   507         return;
   508       }
   510       final SchedulePolicy schedulePolicy = new FxAccountSchedulePolicy(context, fxAccount);
   512       // Set a small scheduled 'backoff' to rate-limit the next sync,
   513       // and extend the background delay even further into the future.
   514       schedulePolicy.configureBackoffMillisBeforeSyncing(rateLimitBackoffHandler, backgroundBackoffHandler);
   516       final String audience = fxAccount.getAudience();
   517       final String authServerEndpoint = fxAccount.getAccountServerURI();
   518       final String tokenServerEndpoint = fxAccount.getTokenServerURI();
   519       final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
   521       // TODO: why doesn't the loginPolicy extract the audience from the account?
   522       final FxAccountClient client = new FxAccountClient20(authServerEndpoint, executor);
   523       final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
   524       stateMachine.advance(state, StateLabel.Married, new LoginStateMachineDelegate() {
   525         @Override
   526         public FxAccountClient getClient() {
   527           return client;
   528         }
   530         @Override
   531         public long getCertificateDurationInMilliseconds() {
   532           return 12 * 60 * 60 * 1000;
   533         }
   535         @Override
   536         public long getAssertionDurationInMilliseconds() {
   537           return 15 * 60 * 1000;
   538         }
   540         @Override
   541         public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
   542           return StateFactory.generateKeyPair();
   543         }
   545         @Override
   546         public void handleTransition(Transition transition, State state) {
   547           Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
   548         }
   550         private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
   551           return shouldPerformSync(tokenBackoffHandler, "token", extras);
   552         }
   554         @Override
   555         public void handleFinal(State state) {
   556           Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
   557           fxAccount.setState(state);
   558           schedulePolicy.onHandleFinal(state.getNeededAction());
   559           notificationManager.update(context, fxAccount);
   560           try {
   561             if (state.getStateLabel() != StateLabel.Married) {
   562               syncDelegate.handleCannotSync(state);
   563               return;
   564             }
   566             final Married married = (Married) state;
   567             final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
   569             /*
   570              * At this point we're in the correct state to sync, and we're ready to fetch
   571              * a token and do some work.
   572              *
   573              * But first we need to do two things:
   574              * 1. Check to see whether we're in a backoff situation for the token server.
   575              *    If we are, but we're not forcing a sync, then we go no further.
   576              * 2. Clear an existing backoff (if we're syncing it doesn't matter, and if
   577              *    we're forcing we'll get a new backoff if things are still bad).
   578              *
   579              * Note that we don't check the storage backoff before the token dance: the token
   580              * server tells us which server we're syncing to!
   581              *
   582              * That logic lives in the TokenServerClientDelegate elsewhere in this file.
   583              */
   585             // Strictly speaking this backoff check could be done prior to walking through
   586             // the login state machine, allowing us to short-circuit sooner.
   587             // We don't expect many token server backoffs, and most users will be sitting
   588             // in the Married state, so instead we simply do this here, once.
   589             final BackoffHandler tokenBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "token");
   590             if (!shouldRequestToken(tokenBackoffHandler, extras)) {
   591               Logger.info(LOG_TAG, "Not syncing (token server).");
   592               syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
   593               return;
   594             }
   596             final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
   597             final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
   598             final String clientState = married.getClientState();
   599             syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras);
   600           } catch (Exception e) {
   601             syncDelegate.handleError(e);
   602             return;
   603           }
   604         }
   605       });
   607       latch.await();
   608     } catch (Exception e) {
   609       Logger.error(LOG_TAG, "Got error syncing.", e);
   610       syncDelegate.handleError(e);
   611     }
   613     Logger.info(LOG_TAG, "Syncing done.");
   614     lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
   615   }
   616 }

mercurial