michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.fxa.sync; michael@0: michael@0: import org.mozilla.gecko.background.common.log.Logger; michael@0: import org.mozilla.gecko.db.BrowserContract; michael@0: import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; michael@0: import org.mozilla.gecko.fxa.login.State.Action; michael@0: import org.mozilla.gecko.sync.BackoffHandler; michael@0: michael@0: import android.accounts.Account; michael@0: import android.content.ContentResolver; michael@0: import android.content.Context; michael@0: import android.os.Bundle; michael@0: michael@0: public class FxAccountSchedulePolicy implements SchedulePolicy { michael@0: private static final String LOG_TAG = "FxAccountSchedulePolicy"; michael@0: michael@0: // Our poll intervals are used to trigger automatic background syncs michael@0: // in the absence of user activity. michael@0: // michael@0: // We also receive sync requests as a result of network tickles, so michael@0: // these intervals are long, with the exception of the rapid polling michael@0: // while we wait for verification: if we're waiting for the user to michael@0: // click on a verification link, we sync very often in order to detect michael@0: // a change in state. michael@0: // michael@0: // In the case of unverified -> unverified (no transition), this should be michael@0: // very close to a single HTTP request (with the SyncAdapter overhead, of michael@0: // course, but that's not wildly different from alarm manager overhead). michael@0: // michael@0: // The /account/status endpoint is HAWK authed by sessionToken, so we still michael@0: // have to do some crypto no matter what. michael@0: michael@0: // TODO: only do this for a while... michael@0: public static final long POLL_INTERVAL_PENDING_VERIFICATION = 60; // 1 minute. michael@0: michael@0: // If we're in some kind of error state, there's no point trying often. michael@0: // This is not the same as a server-imposed backoff, which will be michael@0: // reflected dynamically. michael@0: public static final long POLL_INTERVAL_ERROR_STATE_SEC = 24 * 60 * 60; // 24 hours. michael@0: michael@0: // If we're the only device, just sync once or twice a day in case that michael@0: // changes. michael@0: public static final long POLL_INTERVAL_SINGLE_DEVICE_SEC = 18 * 60 * 60; // 18 hours. michael@0: michael@0: // And if we know there are other devices, let's sync often enough that michael@0: // we'll be more likely to be caught up (even if not completely) by the michael@0: // time you next use this device. This is also achieved via Android's michael@0: // network tickles. michael@0: public static final long POLL_INTERVAL_MULTI_DEVICE_SEC = 12 * 60 * 60; // 12 hours. michael@0: michael@0: // This is used solely as an optimization for backoff handling, so it's not michael@0: // persisted. michael@0: private static volatile long POLL_INTERVAL_CURRENT_SEC = POLL_INTERVAL_SINGLE_DEVICE_SEC; michael@0: michael@0: // Never sync more frequently than this, unless forced. michael@0: // This is to avoid overly-frequent syncs during active browsing. michael@0: public static final long RATE_LIMIT_FUNDAMENTAL_SEC = 90; // 90 seconds. michael@0: michael@0: /** michael@0: * We are prompted to sync by several inputs: michael@0: * * Periodic syncs that we schedule at long intervals. See the POLL constants. michael@0: * * Network-tickle-based syncs that Android starts. michael@0: * * Upload-only syncs that are caused by local database writes. michael@0: * michael@0: * We rate-limit periodic and network-sourced events with this constant. michael@0: * We rate limit both with {@link FxAccountSchedulePolicy#RATE_LIMIT_FUNDAMENTAL_SEC}. michael@0: */ michael@0: public static final long RATE_LIMIT_BACKGROUND_SEC = 60 * 60; // 1 hour. michael@0: michael@0: private final AndroidFxAccount account; michael@0: private final Context context; michael@0: michael@0: public FxAccountSchedulePolicy(Context context, AndroidFxAccount account) { michael@0: this.account = account; michael@0: this.context = context; michael@0: } michael@0: michael@0: /** michael@0: * Return a millisecond timestamp in the future, offset from the current michael@0: * time by the provided amount. michael@0: * @param millis the duration by which to delay michael@0: * @return a timestamp. michael@0: */ michael@0: private static long delay(long millis) { michael@0: return System.currentTimeMillis() + millis; michael@0: } michael@0: michael@0: /** michael@0: * Updates the existing system periodic sync interval to the specified duration. michael@0: * michael@0: * @param intervalSeconds the requested period, which Android will vary by up to 4%. michael@0: */ michael@0: protected void requestPeriodicSync(final long intervalSeconds) { michael@0: final String authority = BrowserContract.AUTHORITY; michael@0: final Account account = this.account.getAndroidAccount(); michael@0: this.context.getContentResolver(); michael@0: Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + "."); michael@0: ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds); michael@0: POLL_INTERVAL_CURRENT_SEC = intervalSeconds; michael@0: } michael@0: michael@0: @Override michael@0: public void onSuccessfulSync(int otherClientsCount) { michael@0: // This undoes the change made in observeBackoffMillis -- once we hit backoff we'll michael@0: // periodically sync at the backoff duration, but as soon as we succeed we'll switch michael@0: // into the client-count-dependent interval. michael@0: long interval = (otherClientsCount > 0) ? POLL_INTERVAL_MULTI_DEVICE_SEC : POLL_INTERVAL_SINGLE_DEVICE_SEC; michael@0: requestPeriodicSync(interval); michael@0: } michael@0: michael@0: @Override michael@0: public void onHandleFinal(Action needed) { michael@0: switch (needed) { michael@0: case NeedsPassword: michael@0: case NeedsUpgrade: michael@0: requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); michael@0: break; michael@0: case NeedsVerification: michael@0: requestPeriodicSync(POLL_INTERVAL_PENDING_VERIFICATION); michael@0: break; michael@0: case None: michael@0: // No action needed: we'll set the periodic sync interval michael@0: // when the sync finishes, via the SessionCallback. michael@0: break; michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onUpgradeRequired() { michael@0: // TODO: this shouldn't occur in FxA, but when we upgrade we michael@0: // need to reduce the interval again. michael@0: requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); michael@0: } michael@0: michael@0: @Override michael@0: public void onUnauthorized() { michael@0: // TODO: this shouldn't occur in FxA, but when we fix our credentials michael@0: // we need to reduce the interval again. michael@0: requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); michael@0: } michael@0: michael@0: @Override michael@0: public void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend) { michael@0: if (onlyExtend) { michael@0: backoffHandler.extendEarliestNextRequest(delay(backoffMillis)); michael@0: } else { michael@0: backoffHandler.setEarliestNextRequest(delay(backoffMillis)); michael@0: } michael@0: michael@0: // Yes, we might be part-way through the interval, in which case the backoff michael@0: // code will do its job. But we certainly don't want to reduce the interval michael@0: // if we're given a small backoff instruction. michael@0: // We'll reset the poll interval next time we sync without a backoff instruction. michael@0: if (backoffMillis > (POLL_INTERVAL_CURRENT_SEC * 1000)) { michael@0: // Slightly inflate the backoff duration to ensure that a fuzzed michael@0: // periodic sync doesn't occur before our backoff has passed. Android michael@0: // 19+ default to a 4% fuzz factor. michael@0: requestPeriodicSync((long) Math.ceil((1.05 * backoffMillis) / 1000)); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Accepts two {@link BackoffHandler} instances as input. These are used michael@0: * respectively to track fundamental rate limiting, and to separately michael@0: * rate-limit periodic and network-tickled syncs. michael@0: */ michael@0: @Override michael@0: public void configureBackoffMillisBeforeSyncing(BackoffHandler fundamentalRateHandler, BackoffHandler backgroundRateHandler) { michael@0: fundamentalRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_FUNDAMENTAL_SEC * 1000)); michael@0: backgroundRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_BACKGROUND_SEC * 1000)); michael@0: } michael@0: }