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