Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | package org.mozilla.gecko.fxa.sync; |
michael@0 | 6 | |
michael@0 | 7 | import org.mozilla.gecko.background.common.log.Logger; |
michael@0 | 8 | import org.mozilla.gecko.db.BrowserContract; |
michael@0 | 9 | import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; |
michael@0 | 10 | import org.mozilla.gecko.fxa.login.State.Action; |
michael@0 | 11 | import org.mozilla.gecko.sync.BackoffHandler; |
michael@0 | 12 | |
michael@0 | 13 | import android.accounts.Account; |
michael@0 | 14 | import android.content.ContentResolver; |
michael@0 | 15 | import android.content.Context; |
michael@0 | 16 | import android.os.Bundle; |
michael@0 | 17 | |
michael@0 | 18 | public class FxAccountSchedulePolicy implements SchedulePolicy { |
michael@0 | 19 | private static final String LOG_TAG = "FxAccountSchedulePolicy"; |
michael@0 | 20 | |
michael@0 | 21 | // Our poll intervals are used to trigger automatic background syncs |
michael@0 | 22 | // in the absence of user activity. |
michael@0 | 23 | // |
michael@0 | 24 | // We also receive sync requests as a result of network tickles, so |
michael@0 | 25 | // these intervals are long, with the exception of the rapid polling |
michael@0 | 26 | // while we wait for verification: if we're waiting for the user to |
michael@0 | 27 | // click on a verification link, we sync very often in order to detect |
michael@0 | 28 | // a change in state. |
michael@0 | 29 | // |
michael@0 | 30 | // In the case of unverified -> unverified (no transition), this should be |
michael@0 | 31 | // very close to a single HTTP request (with the SyncAdapter overhead, of |
michael@0 | 32 | // course, but that's not wildly different from alarm manager overhead). |
michael@0 | 33 | // |
michael@0 | 34 | // The /account/status endpoint is HAWK authed by sessionToken, so we still |
michael@0 | 35 | // have to do some crypto no matter what. |
michael@0 | 36 | |
michael@0 | 37 | // TODO: only do this for a while... |
michael@0 | 38 | public static final long POLL_INTERVAL_PENDING_VERIFICATION = 60; // 1 minute. |
michael@0 | 39 | |
michael@0 | 40 | // If we're in some kind of error state, there's no point trying often. |
michael@0 | 41 | // This is not the same as a server-imposed backoff, which will be |
michael@0 | 42 | // reflected dynamically. |
michael@0 | 43 | public static final long POLL_INTERVAL_ERROR_STATE_SEC = 24 * 60 * 60; // 24 hours. |
michael@0 | 44 | |
michael@0 | 45 | // If we're the only device, just sync once or twice a day in case that |
michael@0 | 46 | // changes. |
michael@0 | 47 | public static final long POLL_INTERVAL_SINGLE_DEVICE_SEC = 18 * 60 * 60; // 18 hours. |
michael@0 | 48 | |
michael@0 | 49 | // And if we know there are other devices, let's sync often enough that |
michael@0 | 50 | // we'll be more likely to be caught up (even if not completely) by the |
michael@0 | 51 | // time you next use this device. This is also achieved via Android's |
michael@0 | 52 | // network tickles. |
michael@0 | 53 | public static final long POLL_INTERVAL_MULTI_DEVICE_SEC = 12 * 60 * 60; // 12 hours. |
michael@0 | 54 | |
michael@0 | 55 | // This is used solely as an optimization for backoff handling, so it's not |
michael@0 | 56 | // persisted. |
michael@0 | 57 | private static volatile long POLL_INTERVAL_CURRENT_SEC = POLL_INTERVAL_SINGLE_DEVICE_SEC; |
michael@0 | 58 | |
michael@0 | 59 | // Never sync more frequently than this, unless forced. |
michael@0 | 60 | // This is to avoid overly-frequent syncs during active browsing. |
michael@0 | 61 | public static final long RATE_LIMIT_FUNDAMENTAL_SEC = 90; // 90 seconds. |
michael@0 | 62 | |
michael@0 | 63 | /** |
michael@0 | 64 | * We are prompted to sync by several inputs: |
michael@0 | 65 | * * Periodic syncs that we schedule at long intervals. See the POLL constants. |
michael@0 | 66 | * * Network-tickle-based syncs that Android starts. |
michael@0 | 67 | * * Upload-only syncs that are caused by local database writes. |
michael@0 | 68 | * |
michael@0 | 69 | * We rate-limit periodic and network-sourced events with this constant. |
michael@0 | 70 | * We rate limit <b>both</b> with {@link FxAccountSchedulePolicy#RATE_LIMIT_FUNDAMENTAL_SEC}. |
michael@0 | 71 | */ |
michael@0 | 72 | public static final long RATE_LIMIT_BACKGROUND_SEC = 60 * 60; // 1 hour. |
michael@0 | 73 | |
michael@0 | 74 | private final AndroidFxAccount account; |
michael@0 | 75 | private final Context context; |
michael@0 | 76 | |
michael@0 | 77 | public FxAccountSchedulePolicy(Context context, AndroidFxAccount account) { |
michael@0 | 78 | this.account = account; |
michael@0 | 79 | this.context = context; |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | /** |
michael@0 | 83 | * Return a millisecond timestamp in the future, offset from the current |
michael@0 | 84 | * time by the provided amount. |
michael@0 | 85 | * @param millis the duration by which to delay |
michael@0 | 86 | * @return a timestamp. |
michael@0 | 87 | */ |
michael@0 | 88 | private static long delay(long millis) { |
michael@0 | 89 | return System.currentTimeMillis() + millis; |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | /** |
michael@0 | 93 | * Updates the existing system periodic sync interval to the specified duration. |
michael@0 | 94 | * |
michael@0 | 95 | * @param intervalSeconds the requested period, which Android will vary by up to 4%. |
michael@0 | 96 | */ |
michael@0 | 97 | protected void requestPeriodicSync(final long intervalSeconds) { |
michael@0 | 98 | final String authority = BrowserContract.AUTHORITY; |
michael@0 | 99 | final Account account = this.account.getAndroidAccount(); |
michael@0 | 100 | this.context.getContentResolver(); |
michael@0 | 101 | Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + "."); |
michael@0 | 102 | ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds); |
michael@0 | 103 | POLL_INTERVAL_CURRENT_SEC = intervalSeconds; |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | @Override |
michael@0 | 107 | public void onSuccessfulSync(int otherClientsCount) { |
michael@0 | 108 | // This undoes the change made in observeBackoffMillis -- once we hit backoff we'll |
michael@0 | 109 | // periodically sync at the backoff duration, but as soon as we succeed we'll switch |
michael@0 | 110 | // into the client-count-dependent interval. |
michael@0 | 111 | long interval = (otherClientsCount > 0) ? POLL_INTERVAL_MULTI_DEVICE_SEC : POLL_INTERVAL_SINGLE_DEVICE_SEC; |
michael@0 | 112 | requestPeriodicSync(interval); |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | @Override |
michael@0 | 116 | public void onHandleFinal(Action needed) { |
michael@0 | 117 | switch (needed) { |
michael@0 | 118 | case NeedsPassword: |
michael@0 | 119 | case NeedsUpgrade: |
michael@0 | 120 | requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); |
michael@0 | 121 | break; |
michael@0 | 122 | case NeedsVerification: |
michael@0 | 123 | requestPeriodicSync(POLL_INTERVAL_PENDING_VERIFICATION); |
michael@0 | 124 | break; |
michael@0 | 125 | case None: |
michael@0 | 126 | // No action needed: we'll set the periodic sync interval |
michael@0 | 127 | // when the sync finishes, via the SessionCallback. |
michael@0 | 128 | break; |
michael@0 | 129 | } |
michael@0 | 130 | } |
michael@0 | 131 | |
michael@0 | 132 | @Override |
michael@0 | 133 | public void onUpgradeRequired() { |
michael@0 | 134 | // TODO: this shouldn't occur in FxA, but when we upgrade we |
michael@0 | 135 | // need to reduce the interval again. |
michael@0 | 136 | requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | @Override |
michael@0 | 140 | public void onUnauthorized() { |
michael@0 | 141 | // TODO: this shouldn't occur in FxA, but when we fix our credentials |
michael@0 | 142 | // we need to reduce the interval again. |
michael@0 | 143 | requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC); |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | @Override |
michael@0 | 147 | public void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend) { |
michael@0 | 148 | if (onlyExtend) { |
michael@0 | 149 | backoffHandler.extendEarliestNextRequest(delay(backoffMillis)); |
michael@0 | 150 | } else { |
michael@0 | 151 | backoffHandler.setEarliestNextRequest(delay(backoffMillis)); |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | // Yes, we might be part-way through the interval, in which case the backoff |
michael@0 | 155 | // code will do its job. But we certainly don't want to reduce the interval |
michael@0 | 156 | // if we're given a small backoff instruction. |
michael@0 | 157 | // We'll reset the poll interval next time we sync without a backoff instruction. |
michael@0 | 158 | if (backoffMillis > (POLL_INTERVAL_CURRENT_SEC * 1000)) { |
michael@0 | 159 | // Slightly inflate the backoff duration to ensure that a fuzzed |
michael@0 | 160 | // periodic sync doesn't occur before our backoff has passed. Android |
michael@0 | 161 | // 19+ default to a 4% fuzz factor. |
michael@0 | 162 | requestPeriodicSync((long) Math.ceil((1.05 * backoffMillis) / 1000)); |
michael@0 | 163 | } |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | /** |
michael@0 | 167 | * Accepts two {@link BackoffHandler} instances as input. These are used |
michael@0 | 168 | * respectively to track fundamental rate limiting, and to separately |
michael@0 | 169 | * rate-limit periodic and network-tickled syncs. |
michael@0 | 170 | */ |
michael@0 | 171 | @Override |
michael@0 | 172 | public void configureBackoffMillisBeforeSyncing(BackoffHandler fundamentalRateHandler, BackoffHandler backgroundRateHandler) { |
michael@0 | 173 | fundamentalRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_FUNDAMENTAL_SEC * 1000)); |
michael@0 | 174 | backgroundRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_BACKGROUND_SEC * 1000)); |
michael@0 | 175 | } |
michael@0 | 176 | } |