1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,176 @@ 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.fxa.sync; 1.9 + 1.10 +import org.mozilla.gecko.background.common.log.Logger; 1.11 +import org.mozilla.gecko.db.BrowserContract; 1.12 +import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; 1.13 +import org.mozilla.gecko.fxa.login.State.Action; 1.14 +import org.mozilla.gecko.sync.BackoffHandler; 1.15 + 1.16 +import android.accounts.Account; 1.17 +import android.content.ContentResolver; 1.18 +import android.content.Context; 1.19 +import android.os.Bundle; 1.20 + 1.21 +public class FxAccountSchedulePolicy implements SchedulePolicy { 1.22 + private static final String LOG_TAG = "FxAccountSchedulePolicy"; 1.23 + 1.24 + // Our poll intervals are used to trigger automatic background syncs 1.25 + // in the absence of user activity. 1.26 + // 1.27 + // We also receive sync requests as a result of network tickles, so 1.28 + // these intervals are long, with the exception of the rapid polling 1.29 + // while we wait for verification: if we're waiting for the user to 1.30 + // click on a verification link, we sync very often in order to detect 1.31 + // a change in state. 1.32 + // 1.33 + // In the case of unverified -> unverified (no transition), this should be 1.34 + // very close to a single HTTP request (with the SyncAdapter overhead, of 1.35 + // course, but that's not wildly different from alarm manager overhead). 1.36 + // 1.37 + // The /account/status endpoint is HAWK authed by sessionToken, so we still 1.38 + // have to do some crypto no matter what. 1.39 + 1.40 + // TODO: only do this for a while... 1.41 + public static final long POLL_INTERVAL_PENDING_VERIFICATION = 60; // 1 minute. 1.42 + 1.43 + // If we're in some kind of error state, there's no point trying often. 1.44 + // This is not the same as a server-imposed backoff, which will be 1.45 + // reflected dynamically. 1.46 + public static final long POLL_INTERVAL_ERROR_STATE_SEC = 24 * 60 * 60; // 24 hours. 1.47 + 1.48 + // If we're the only device, just sync once or twice a day in case that 1.49 + // changes. 1.50 + public static final long POLL_INTERVAL_SINGLE_DEVICE_SEC = 18 * 60 * 60; // 18 hours. 1.51 + 1.52 + // And if we know there are other devices, let's sync often enough that 1.53 + // we'll be more likely to be caught up (even if not completely) by the 1.54 + // time you next use this device. This is also achieved via Android's 1.55 + // network tickles. 1.56 + public static final long POLL_INTERVAL_MULTI_DEVICE_SEC = 12 * 60 * 60; // 12 hours. 1.57 + 1.58 + // This is used solely as an optimization for backoff handling, so it's not 1.59 + // persisted. 1.60 + private static volatile long POLL_INTERVAL_CURRENT_SEC = POLL_INTERVAL_SINGLE_DEVICE_SEC; 1.61 + 1.62 + // Never sync more frequently than this, unless forced. 1.63 + // This is to avoid overly-frequent syncs during active browsing. 1.64 + public static final long RATE_LIMIT_FUNDAMENTAL_SEC = 90; // 90 seconds. 1.65 + 1.66 + /** 1.67 + * We are prompted to sync by several inputs: 1.68 + * * Periodic syncs that we schedule at long intervals. See the POLL constants. 1.69 + * * Network-tickle-based syncs that Android starts. 1.70 + * * Upload-only syncs that are caused by local database writes. 1.71 + * 1.72 + * We rate-limit periodic and network-sourced events with this constant. 1.73 + * We rate limit <b>both</b> with {@link FxAccountSchedulePolicy#RATE_LIMIT_FUNDAMENTAL_SEC}. 1.74 + */ 1.75 + public static final long RATE_LIMIT_BACKGROUND_SEC = 60 * 60; // 1 hour. 1.76 + 1.77 + private final AndroidFxAccount account; 1.78 + private final Context context; 1.79 + 1.80 + public FxAccountSchedulePolicy(Context context, AndroidFxAccount account) { 1.81 + this.account = account; 1.82 + this.context = context; 1.83 + } 1.84 + 1.85 + /** 1.86 + * Return a millisecond timestamp in the future, offset from the current 1.87 + * time by the provided amount. 1.88 + * @param millis the duration by which to delay 1.89 + * @return a timestamp. 1.90 + */ 1.91 + private static long delay(long millis) { 1.92 + return System.currentTimeMillis() + millis; 1.93 + } 1.94 + 1.95 + /** 1.96 + * Updates the existing system periodic sync interval to the specified duration. 1.97 + * 1.98 + * @param intervalSeconds the requested period, which Android will vary by up to 4%. 1.99 + */ 1.100 + protected void requestPeriodicSync(final long intervalSeconds) { 1.101 + final String authority = BrowserContract.AUTHORITY; 1.102 + final Account account = this.account.getAndroidAccount(); 1.103 + this.context.getContentResolver(); 1.104 + Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + "."); 1.105 + ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds); 1.106 + POLL_INTERVAL_CURRENT_SEC = intervalSeconds; 1.107 + } 1.108 + 1.109 + @Override 1.110 + public void onSuccessfulSync(int otherClientsCount) { 1.111 + // This undoes the change made in observeBackoffMillis -- once we hit backoff we'll 1.112 + // periodically sync at the backoff duration, but as soon as we succeed we'll switch 1.113 + // into the client-count-dependent interval. 1.114 + long interval = (otherClientsCount > 0) ? POLL_INTERVAL_MULTI_DEVICE_SEC : POLL_INTERVAL_SINGLE_DEVICE_SEC; 1.115 + requestPeriodicSync(interval); 1.116 + } 1.117 + 1.118 + @Override 1.119 + public void onHandleFinal(Action needed) { 1.120 + switch (needed) { 1.121 + case NeedsPassword: 1.122 + case NeedsUpgrade: 1.123 + requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); 1.124 + break; 1.125 + case NeedsVerification: 1.126 + requestPeriodicSync(POLL_INTERVAL_PENDING_VERIFICATION); 1.127 + break; 1.128 + case None: 1.129 + // No action needed: we'll set the periodic sync interval 1.130 + // when the sync finishes, via the SessionCallback. 1.131 + break; 1.132 + } 1.133 + } 1.134 + 1.135 + @Override 1.136 + public void onUpgradeRequired() { 1.137 + // TODO: this shouldn't occur in FxA, but when we upgrade we 1.138 + // need to reduce the interval again. 1.139 + requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); 1.140 + } 1.141 + 1.142 + @Override 1.143 + public void onUnauthorized() { 1.144 + // TODO: this shouldn't occur in FxA, but when we fix our credentials 1.145 + // we need to reduce the interval again. 1.146 + requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); 1.147 + } 1.148 + 1.149 + @Override 1.150 + public void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend) { 1.151 + if (onlyExtend) { 1.152 + backoffHandler.extendEarliestNextRequest(delay(backoffMillis)); 1.153 + } else { 1.154 + backoffHandler.setEarliestNextRequest(delay(backoffMillis)); 1.155 + } 1.156 + 1.157 + // Yes, we might be part-way through the interval, in which case the backoff 1.158 + // code will do its job. But we certainly don't want to reduce the interval 1.159 + // if we're given a small backoff instruction. 1.160 + // We'll reset the poll interval next time we sync without a backoff instruction. 1.161 + if (backoffMillis > (POLL_INTERVAL_CURRENT_SEC * 1000)) { 1.162 + // Slightly inflate the backoff duration to ensure that a fuzzed 1.163 + // periodic sync doesn't occur before our backoff has passed. Android 1.164 + // 19+ default to a 4% fuzz factor. 1.165 + requestPeriodicSync((long) Math.ceil((1.05 * backoffMillis) / 1000)); 1.166 + } 1.167 + } 1.168 + 1.169 + /** 1.170 + * Accepts two {@link BackoffHandler} instances as input. These are used 1.171 + * respectively to track fundamental rate limiting, and to separately 1.172 + * rate-limit periodic and network-tickled syncs. 1.173 + */ 1.174 + @Override 1.175 + public void configureBackoffMillisBeforeSyncing(BackoffHandler fundamentalRateHandler, BackoffHandler backgroundRateHandler) { 1.176 + fundamentalRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_FUNDAMENTAL_SEC * 1000)); 1.177 + backgroundRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_BACKGROUND_SEC * 1000)); 1.178 + } 1.179 +} 1.180 \ No newline at end of file