Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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;
7 import java.io.File;
8 import java.util.EnumSet;
9 import java.util.concurrent.CountDownLatch;
11 import org.mozilla.gecko.background.common.log.Logger;
12 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
13 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
14 import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
15 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
16 import org.mozilla.gecko.sync.ThreadPool;
17 import org.mozilla.gecko.sync.Utils;
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.os.Bundle;
25 /**
26 * Simple public accessors for Firefox account objects.
27 */
28 public class FirefoxAccounts {
29 private static final String LOG_TAG = FirefoxAccounts.class.getSimpleName();
31 public enum SyncHint {
32 /**
33 * Hint that a requested sync is preferred immediately.
34 * <p>
35 * On many devices, not including <code>SCHEDULE_NOW</code> means a delay of
36 * at least 30 seconds.
37 */
38 SCHEDULE_NOW,
40 /**
41 * Hint that a requested sync may ignore local rate limiting.
42 * <p>
43 * This is just a hint; the actual requested sync may not obey the hint.
44 */
45 IGNORE_LOCAL_RATE_LIMIT,
47 /**
48 * Hint that a requested sync may ignore remote server backoffs.
49 * <p>
50 * This is just a hint; the actual requested sync may not obey the hint.
51 */
52 IGNORE_REMOTE_SERVER_BACKOFF,
53 }
55 public static final EnumSet<SyncHint> SOON = EnumSet.noneOf(SyncHint.class);
57 public static final EnumSet<SyncHint> NOW = EnumSet.of(
58 SyncHint.SCHEDULE_NOW);
60 public static final EnumSet<SyncHint> FORCE = EnumSet.of(
61 SyncHint.SCHEDULE_NOW,
62 SyncHint.IGNORE_LOCAL_RATE_LIMIT,
63 SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
65 public interface SyncStatusListener {
66 public Context getContext();
67 public Account getAccount();
68 public void onSyncStarted();
69 public void onSyncFinished();
70 }
72 /**
73 * Returns true if a FirefoxAccount exists, false otherwise.
74 *
75 * @param context Android context.
76 * @return true if at least one Firefox account exists.
77 */
78 public static boolean firefoxAccountsExist(final Context context) {
79 return getFirefoxAccounts(context).length > 0;
80 }
82 /**
83 * Return Firefox accounts.
84 * <p>
85 * If no accounts exist in the AccountManager, one may be created
86 * via a pickled FirefoxAccount, if available, and that account
87 * will be added to the AccountManager and returned.
88 * <p>
89 * Note that this can be called from any thread.
90 *
91 * @param context Android context.
92 * @return Firefox account objects.
93 */
94 public static Account[] getFirefoxAccounts(final Context context) {
95 final Account[] accounts =
96 AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
97 if (accounts.length > 0) {
98 return accounts;
99 }
101 final Account pickledAccount = getPickledAccount(context);
102 return (pickledAccount != null) ? new Account[] {pickledAccount} : new Account[0];
103 }
105 private static Account getPickledAccount(final Context context) {
106 // To avoid a StrictMode violation for disk access, we call this from a background thread.
107 // We do this every time, so the caller doesn't have to care.
108 final CountDownLatch latch = new CountDownLatch(1);
109 final Account[] accounts = new Account[1];
110 ThreadPool.run(new Runnable() {
111 @Override
112 public void run() {
113 try {
114 final File file = context.getFileStreamPath(FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
115 if (!file.exists()) {
116 accounts[0] = null;
117 return;
118 }
120 // There is a small race window here: if the user creates a new Firefox account
121 // between our checks, this could erroneously report that no Firefox accounts
122 // exist.
123 final AndroidFxAccount fxAccount =
124 AccountPickler.unpickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
125 accounts[0] = fxAccount.getAndroidAccount();
126 } finally {
127 latch.countDown();
128 }
129 }
130 });
132 try {
133 latch.await(); // Wait for the background thread to return.
134 } catch (InterruptedException e) {
135 Logger.warn(LOG_TAG,
136 "Foreground thread unexpectedly interrupted while getting pickled account", e);
137 return null;
138 }
140 return accounts[0];
141 }
143 /**
144 * @param context Android context.
145 * @return the configured Firefox account if one exists, or null otherwise.
146 */
147 public static Account getFirefoxAccount(final Context context) {
148 Account[] accounts = getFirefoxAccounts(context);
149 if (accounts.length > 0) {
150 return accounts[0];
151 }
152 return null;
153 }
155 protected static void putHintsToSync(final Bundle extras, EnumSet<SyncHint> syncHints) {
156 // stagesToSync and stagesToSkip are allowed to be null.
157 if (syncHints == null) {
158 throw new IllegalArgumentException("syncHints must not be null");
159 }
161 final boolean scheduleNow = syncHints.contains(SyncHint.SCHEDULE_NOW);
162 final boolean ignoreLocalRateLimit = syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
163 final boolean ignoreRemoteServerBackoff = syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
165 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, scheduleNow);
166 // The default when manually syncing is to ignore the local rate limit and
167 // any remote server backoff requests. Since we can't add flags to a manual
168 // sync instigated by the user, we have to reverse the natural conditionals.
169 // See also the FORCE EnumSet.
170 extras.putBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT, !ignoreLocalRateLimit);
171 extras.putBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF, !ignoreRemoteServerBackoff);
172 }
174 public static EnumSet<SyncHint> getHintsToSyncFromBundle(final Bundle extras) {
175 final EnumSet<SyncHint> syncHints = EnumSet.noneOf(SyncHint.class);
177 final boolean scheduleNow = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
178 final boolean ignoreLocalRateLimit = !extras.getBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT, false);
179 final boolean ignoreRemoteServerBackoff = !extras.getBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF, false);
181 if (scheduleNow) {
182 syncHints.add(SyncHint.SCHEDULE_NOW);
183 }
184 if (ignoreLocalRateLimit) {
185 syncHints.add(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
186 }
187 if (ignoreRemoteServerBackoff) {
188 syncHints.add(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
189 }
191 return syncHints;
192 }
194 public static void logSyncHints(EnumSet<SyncHint> syncHints) {
195 final boolean scheduleNow = syncHints.contains(SyncHint.SCHEDULE_NOW);
196 final boolean ignoreLocalRateLimit = syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
197 final boolean ignoreRemoteServerBackoff = syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
199 Logger.info(LOG_TAG, "Sync hints" +
200 "; scheduling now: " + scheduleNow +
201 "; ignoring local rate limit: " + ignoreLocalRateLimit +
202 "; ignoring remote server backoff: " + ignoreRemoteServerBackoff + ".");
203 }
205 /**
206 * Request a sync for the given Android Account.
207 * <p>
208 * Any hints are strictly optional: the actual requested sync is scheduled by
209 * the Android sync scheduler, and the sync mechanism may ignore hints as it
210 * sees fit.
211 * <p>
212 * It is safe to call this method from any thread.
213 *
214 * @param account to sync.
215 * @param syncHints to pass to sync.
216 * @param stagesToSync stage names to sync.
217 * @param stagesToSkip stage names to skip.
218 */
219 public static void requestSync(final Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
220 if (account == null) {
221 throw new IllegalArgumentException("account must not be null");
222 }
223 if (syncHints == null) {
224 throw new IllegalArgumentException("syncHints must not be null");
225 }
227 final Bundle extras = new Bundle();
228 putHintsToSync(extras, syncHints);
229 Utils.putStageNamesToSync(extras, stagesToSync, stagesToSkip);
231 Logger.info(LOG_TAG, "Requesting sync.");
232 logSyncHints(syncHints);
234 // We get strict mode warnings on some devices, so make the request on a
235 // background thread.
236 ThreadPool.run(new Runnable() {
237 @Override
238 public void run() {
239 for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
240 ContentResolver.requestSync(account, authority, extras);
241 }
242 }
243 });
244 }
246 /**
247 * Start notifying <code>syncStatusListener</code> of sync status changes.
248 * <p>
249 * Only a weak reference to <code>syncStatusListener</code> is held.
250 *
251 * @param syncStatusListener to start notifying.
252 */
253 public static void addSyncStatusListener(SyncStatusListener syncStatusListener) {
254 // startObserving null-checks its argument.
255 FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusListener);
256 }
258 /**
259 * Stop notifying <code>syncStatusListener</code> of sync status changes.
260 *
261 * @param syncStatusListener to stop notifying.
262 */
263 public static void removeSyncStatusListener(SyncStatusListener syncStatusListener) {
264 // stopObserving null-checks its argument.
265 FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener);
266 }
267 }