mobile/android/base/fxa/FirefoxAccounts.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     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 }

mercurial