mobile/android/base/fxa/FirefoxAccounts.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/fxa/FirefoxAccounts.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,267 @@
     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;
     1.9 +
    1.10 +import java.io.File;
    1.11 +import java.util.EnumSet;
    1.12 +import java.util.concurrent.CountDownLatch;
    1.13 +
    1.14 +import org.mozilla.gecko.background.common.log.Logger;
    1.15 +import org.mozilla.gecko.fxa.authenticator.AccountPickler;
    1.16 +import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
    1.17 +import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
    1.18 +import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
    1.19 +import org.mozilla.gecko.sync.ThreadPool;
    1.20 +import org.mozilla.gecko.sync.Utils;
    1.21 +
    1.22 +import android.accounts.Account;
    1.23 +import android.accounts.AccountManager;
    1.24 +import android.content.ContentResolver;
    1.25 +import android.content.Context;
    1.26 +import android.os.Bundle;
    1.27 +
    1.28 +/**
    1.29 + * Simple public accessors for Firefox account objects.
    1.30 + */
    1.31 +public class FirefoxAccounts {
    1.32 +  private static final String LOG_TAG = FirefoxAccounts.class.getSimpleName();
    1.33 +
    1.34 +  public enum SyncHint {
    1.35 +    /**
    1.36 +     * Hint that a requested sync is preferred immediately.
    1.37 +     * <p>
    1.38 +     * On many devices, not including <code>SCHEDULE_NOW</code> means a delay of
    1.39 +     * at least 30 seconds.
    1.40 +     */
    1.41 +    SCHEDULE_NOW,
    1.42 +
    1.43 +    /**
    1.44 +     * Hint that a requested sync may ignore local rate limiting.
    1.45 +     * <p>
    1.46 +     * This is just a hint; the actual requested sync may not obey the hint.
    1.47 +     */
    1.48 +    IGNORE_LOCAL_RATE_LIMIT,
    1.49 +
    1.50 +    /**
    1.51 +     * Hint that a requested sync may ignore remote server backoffs.
    1.52 +     * <p>
    1.53 +     * This is just a hint; the actual requested sync may not obey the hint.
    1.54 +     */
    1.55 +    IGNORE_REMOTE_SERVER_BACKOFF,
    1.56 +  }
    1.57 +
    1.58 +  public static final EnumSet<SyncHint> SOON = EnumSet.noneOf(SyncHint.class);
    1.59 +
    1.60 +  public static final EnumSet<SyncHint> NOW = EnumSet.of(
    1.61 +      SyncHint.SCHEDULE_NOW);
    1.62 +
    1.63 +  public static final EnumSet<SyncHint> FORCE = EnumSet.of(
    1.64 +      SyncHint.SCHEDULE_NOW,
    1.65 +      SyncHint.IGNORE_LOCAL_RATE_LIMIT,
    1.66 +      SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
    1.67 +
    1.68 +  public interface SyncStatusListener {
    1.69 +    public Context getContext();
    1.70 +    public Account getAccount();
    1.71 +    public void onSyncStarted();
    1.72 +    public void onSyncFinished();
    1.73 +  }
    1.74 +
    1.75 +  /**
    1.76 +   * Returns true if a FirefoxAccount exists, false otherwise.
    1.77 +   *
    1.78 +   * @param context Android context.
    1.79 +   * @return true if at least one Firefox account exists.
    1.80 +   */
    1.81 +  public static boolean firefoxAccountsExist(final Context context) {
    1.82 +    return getFirefoxAccounts(context).length > 0;
    1.83 +  }
    1.84 +
    1.85 +  /**
    1.86 +   * Return Firefox accounts.
    1.87 +   * <p>
    1.88 +   * If no accounts exist in the AccountManager, one may be created
    1.89 +   * via a pickled FirefoxAccount, if available, and that account
    1.90 +   * will be added to the AccountManager and returned.
    1.91 +   * <p>
    1.92 +   * Note that this can be called from any thread.
    1.93 +   *
    1.94 +   * @param context Android context.
    1.95 +   * @return Firefox account objects.
    1.96 +   */
    1.97 +  public static Account[] getFirefoxAccounts(final Context context) {
    1.98 +    final Account[] accounts =
    1.99 +        AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
   1.100 +    if (accounts.length > 0) {
   1.101 +      return accounts;
   1.102 +    }
   1.103 +
   1.104 +    final Account pickledAccount = getPickledAccount(context);
   1.105 +    return (pickledAccount != null) ? new Account[] {pickledAccount} : new Account[0];
   1.106 +  }
   1.107 +
   1.108 +  private static Account getPickledAccount(final Context context) {
   1.109 +    // To avoid a StrictMode violation for disk access, we call this from a background thread.
   1.110 +    // We do this every time, so the caller doesn't have to care.
   1.111 +    final CountDownLatch latch = new CountDownLatch(1);
   1.112 +    final Account[] accounts = new Account[1];
   1.113 +    ThreadPool.run(new Runnable() {
   1.114 +      @Override
   1.115 +      public void run() {
   1.116 +        try {
   1.117 +          final File file = context.getFileStreamPath(FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
   1.118 +          if (!file.exists()) {
   1.119 +            accounts[0] = null;
   1.120 +            return;
   1.121 +          }
   1.122 +
   1.123 +          // There is a small race window here: if the user creates a new Firefox account
   1.124 +          // between our checks, this could erroneously report that no Firefox accounts
   1.125 +          // exist.
   1.126 +          final AndroidFxAccount fxAccount =
   1.127 +              AccountPickler.unpickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
   1.128 +          accounts[0] = fxAccount.getAndroidAccount();
   1.129 +        } finally {
   1.130 +          latch.countDown();
   1.131 +        }
   1.132 +      }
   1.133 +    });
   1.134 +
   1.135 +    try {
   1.136 +      latch.await(); // Wait for the background thread to return.
   1.137 +    } catch (InterruptedException e) {
   1.138 +      Logger.warn(LOG_TAG,
   1.139 +          "Foreground thread unexpectedly interrupted while getting pickled account", e);
   1.140 +      return null;
   1.141 +    }
   1.142 +
   1.143 +    return accounts[0];
   1.144 +  }
   1.145 +
   1.146 +  /**
   1.147 +   * @param context Android context.
   1.148 +   * @return the configured Firefox account if one exists, or null otherwise.
   1.149 +   */
   1.150 +  public static Account getFirefoxAccount(final Context context) {
   1.151 +    Account[] accounts = getFirefoxAccounts(context);
   1.152 +    if (accounts.length > 0) {
   1.153 +      return accounts[0];
   1.154 +    }
   1.155 +    return null;
   1.156 +  }
   1.157 +
   1.158 +  protected static void putHintsToSync(final Bundle extras, EnumSet<SyncHint> syncHints) {
   1.159 +    // stagesToSync and stagesToSkip are allowed to be null.
   1.160 +    if (syncHints == null) {
   1.161 +      throw new IllegalArgumentException("syncHints must not be null");
   1.162 +    }
   1.163 +
   1.164 +    final boolean scheduleNow = syncHints.contains(SyncHint.SCHEDULE_NOW);
   1.165 +    final boolean ignoreLocalRateLimit = syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
   1.166 +    final boolean ignoreRemoteServerBackoff = syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
   1.167 +
   1.168 +    extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, scheduleNow);
   1.169 +    // The default when manually syncing is to ignore the local rate limit and
   1.170 +    // any remote server backoff requests. Since we can't add flags to a manual
   1.171 +    // sync instigated by the user, we have to reverse the natural conditionals.
   1.172 +    // See also the FORCE EnumSet.
   1.173 +    extras.putBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT, !ignoreLocalRateLimit);
   1.174 +    extras.putBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF, !ignoreRemoteServerBackoff);
   1.175 +  }
   1.176 +
   1.177 +  public static EnumSet<SyncHint> getHintsToSyncFromBundle(final Bundle extras) {
   1.178 +    final EnumSet<SyncHint> syncHints = EnumSet.noneOf(SyncHint.class);
   1.179 +
   1.180 +    final boolean scheduleNow = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
   1.181 +    final boolean ignoreLocalRateLimit = !extras.getBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT, false);
   1.182 +    final boolean ignoreRemoteServerBackoff = !extras.getBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF, false);
   1.183 +
   1.184 +    if (scheduleNow) {
   1.185 +      syncHints.add(SyncHint.SCHEDULE_NOW);
   1.186 +    }
   1.187 +    if (ignoreLocalRateLimit) {
   1.188 +      syncHints.add(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
   1.189 +    }
   1.190 +    if (ignoreRemoteServerBackoff) {
   1.191 +      syncHints.add(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
   1.192 +    }
   1.193 +
   1.194 +    return syncHints;
   1.195 +  }
   1.196 +
   1.197 +  public static void logSyncHints(EnumSet<SyncHint> syncHints) {
   1.198 +    final boolean scheduleNow = syncHints.contains(SyncHint.SCHEDULE_NOW);
   1.199 +    final boolean ignoreLocalRateLimit = syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
   1.200 +    final boolean ignoreRemoteServerBackoff = syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
   1.201 +
   1.202 +    Logger.info(LOG_TAG, "Sync hints" +
   1.203 +        "; scheduling now: " + scheduleNow +
   1.204 +        "; ignoring local rate limit: " + ignoreLocalRateLimit +
   1.205 +        "; ignoring remote server backoff: " + ignoreRemoteServerBackoff + ".");
   1.206 +  }
   1.207 +
   1.208 +  /**
   1.209 +   * Request a sync for the given Android Account.
   1.210 +   * <p>
   1.211 +   * Any hints are strictly optional: the actual requested sync is scheduled by
   1.212 +   * the Android sync scheduler, and the sync mechanism may ignore hints as it
   1.213 +   * sees fit.
   1.214 +   * <p>
   1.215 +   * It is safe to call this method from any thread.
   1.216 +   *
   1.217 +   * @param account to sync.
   1.218 +   * @param syncHints to pass to sync.
   1.219 +   * @param stagesToSync stage names to sync.
   1.220 +   * @param stagesToSkip stage names to skip.
   1.221 +   */
   1.222 +  public static void requestSync(final Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
   1.223 +    if (account == null) {
   1.224 +      throw new IllegalArgumentException("account must not be null");
   1.225 +    }
   1.226 +    if (syncHints == null) {
   1.227 +      throw new IllegalArgumentException("syncHints must not be null");
   1.228 +    }
   1.229 +
   1.230 +    final Bundle extras = new Bundle();
   1.231 +    putHintsToSync(extras, syncHints);
   1.232 +    Utils.putStageNamesToSync(extras, stagesToSync, stagesToSkip);
   1.233 +
   1.234 +    Logger.info(LOG_TAG, "Requesting sync.");
   1.235 +    logSyncHints(syncHints);
   1.236 +
   1.237 +    // We get strict mode warnings on some devices, so make the request on a
   1.238 +    // background thread.
   1.239 +    ThreadPool.run(new Runnable() {
   1.240 +      @Override
   1.241 +      public void run() {
   1.242 +        for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
   1.243 +          ContentResolver.requestSync(account, authority, extras);
   1.244 +        }
   1.245 +      }
   1.246 +    });
   1.247 +  }
   1.248 +
   1.249 +  /**
   1.250 +   * Start notifying <code>syncStatusListener</code> of sync status changes.
   1.251 +   * <p>
   1.252 +   * Only a weak reference to <code>syncStatusListener</code> is held.
   1.253 +   *
   1.254 +   * @param syncStatusListener to start notifying.
   1.255 +   */
   1.256 +  public static void addSyncStatusListener(SyncStatusListener syncStatusListener) {
   1.257 +    // startObserving null-checks its argument.
   1.258 +    FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusListener);
   1.259 +  }
   1.260 +
   1.261 +  /**
   1.262 +   * Stop notifying <code>syncStatusListener</code> of sync status changes.
   1.263 +   *
   1.264 +   * @param syncStatusListener to stop notifying.
   1.265 +   */
   1.266 +  public static void removeSyncStatusListener(SyncStatusListener syncStatusListener) {
   1.267 +    // stopObserving null-checks its argument.
   1.268 +    FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener);
   1.269 +  }
   1.270 +}

mercurial