Bug 850600 - Refactor FirefoxAccounts. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Fri, 18 Apr 2014 11:32:32 -0700
changeset 179728 f2e5bc417f969d31a5c1eba73cb5fef0391e36d9
parent 179727 f7320bbfb006e75eacb5d92d59673bbd1855dc60
child 179729 5c8763cd059df9396e586d378679bc624ab73a15
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersrnewman
bugs850600
milestone31.0a1
Bug 850600 - Refactor FirefoxAccounts. r=rnewman ======== https://github.com/mozilla-services/android-sync/commit/9acbc565be9e1065e3e4f1d62f482a5de02a6323 Author: Nick Alexander <nalexander@mozilla.com> Date: Fri Apr 18 11:30:26 2014 -0700 Bug 850600 - Part 0: Refactor FirefoxAccounts.
mobile/android/base/fxa/FirefoxAccounts.java
mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
mobile/android/base/fxa/activities/FxAccountStatusFragment.java
mobile/android/base/fxa/sync/FxAccountSyncStatusHelper.java
--- a/mobile/android/base/fxa/FirefoxAccounts.java
+++ b/mobile/android/base/fxa/FirefoxAccounts.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.fxa;
 import java.io.File;
 import java.util.EnumSet;
 import java.util.concurrent.CountDownLatch;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Bundle;
@@ -56,16 +57,23 @@ public class FirefoxAccounts {
   public static final EnumSet<SyncHint> NOW = EnumSet.of(
       SyncHint.SCHEDULE_NOW);
 
   public static final EnumSet<SyncHint> FORCE = EnumSet.of(
       SyncHint.SCHEDULE_NOW,
       SyncHint.IGNORE_LOCAL_RATE_LIMIT,
       SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
 
+  public interface SyncStatusListener {
+    public Context getContext();
+    public Account getAccount();
+    public void onSyncStarted();
+    public void onSyncFinished();
+  }
+
   /**
    * Returns true if a FirefoxAccount exists, false otherwise.
    *
    * @param context Android context.
    * @return true if at least one Firefox account exists.
    */
   public static boolean firefoxAccountsExist(final Context context) {
     return getFirefoxAccounts(context).length > 0;
@@ -195,34 +203,65 @@ public class FirefoxAccounts {
   }
 
   /**
    * Request a sync for the given Android Account.
    * <p>
    * Any hints are strictly optional: the actual requested sync is scheduled by
    * the Android sync scheduler, and the sync mechanism may ignore hints as it
    * sees fit.
+   * <p>
+   * It is safe to call this method from any thread.
    *
    * @param account to sync.
    * @param syncHints to pass to sync.
    * @param stagesToSync stage names to sync.
    * @param stagesToSkip stage names to skip.
    */
-  public static void requestSync(Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
+  public static void requestSync(final Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
     if (account == null) {
       throw new IllegalArgumentException("account must not be null");
     }
     if (syncHints == null) {
       throw new IllegalArgumentException("syncHints must not be null");
     }
 
     final Bundle extras = new Bundle();
     putHintsToSync(extras, syncHints);
     Utils.putStageNamesToSync(extras, stagesToSync, stagesToSkip);
 
     Logger.info(LOG_TAG, "Requesting sync.");
     logSyncHints(syncHints);
 
-    for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
-      ContentResolver.requestSync(account, authority, extras);
-    }
+    // We get strict mode warnings on some devices, so make the request on a
+    // background thread.
+    ThreadPool.run(new Runnable() {
+      @Override
+      public void run() {
+        for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
+          ContentResolver.requestSync(account, authority, extras);
+        }
+      }
+    });
+  }
+
+  /**
+   * Start notifying <code>syncStatusListener</code> of sync status changes.
+   * <p>
+   * Only a weak reference to <code>syncStatusListener</code> is held.
+   *
+   * @param syncStatusListener to start notifying.
+   */
+  public static void addSyncStatusListener(SyncStatusListener syncStatusListener) {
+    // startObserving null-checks its argument.
+    FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusListener);
+  }
+
+  /**
+   * Stop notifying <code>syncStatusListener</code> of sync status changes.
+   *
+   * @param syncStatusListener to stop notifying.
+   */
+  public static void removeSyncStatusListener(SyncStatusListener syncStatusListener) {
+    // stopObserving null-checks its argument.
+    FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener);
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
@@ -16,16 +16,17 @@ import org.mozilla.gecko.background.fxa.
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.Action;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
+import android.accounts.Account;
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -38,17 +39,17 @@ public class FxAccountConfirmAccountActi
 
   // Set in onCreate.
   protected TextView verificationLinkTextView;
   protected View resendLink;
 
   // Set in onResume.
   protected AndroidFxAccount fxAccount;
 
-  protected final SyncStatusDelegate syncStatusDelegate = new SyncStatusDelegate();
+  protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate();
 
   public FxAccountConfirmAccountActivity() {
     super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
   }
 
   /**
    * {@inheritDoc}
    */
@@ -97,36 +98,41 @@ public class FxAccountConfirmAccountActi
     super.onPause();
     FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
 
     if (fxAccount != null) {
       fxAccount.requestSync(FirefoxAccounts.SOON);
     }
   }
 
-  protected class SyncStatusDelegate implements FxAccountSyncStatusHelper.Delegate {
+  protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener {
     protected final Runnable refreshRunnable = new Runnable() {
       @Override
       public void run() {
         refresh();
       }
     };
 
     @Override
-    public AndroidFxAccount getAccount() {
-      return fxAccount;
+    public Context getContext() {
+      return FxAccountConfirmAccountActivity.this;
     }
 
     @Override
-    public void handleSyncStarted() {
+    public Account getAccount() {
+      return fxAccount.getAndroidAccount();
+    }
+
+    @Override
+    public void onSyncStarted() {
       Logger.info(LOG_TAG, "Got sync started message; ignoring.");
     }
 
     @Override
-    public void handleSyncFinished() {
+    public void onSyncFinished() {
       if (fxAccount == null) {
         return;
       }
       Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
       runOnUiThread(refreshRunnable);
     }
   }
 
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -14,17 +14,19 @@ import org.mozilla.gecko.background.pref
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.sync.SyncConfiguration;
 
+import android.accounts.Account;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.Handler;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
@@ -67,17 +69,17 @@ public class FxAccountStatusFragment ext
   // Used to post delayed sync requests.
   protected Handler handler;
 
   // Member variable so that re-posting pushes back the already posted instance.
   // This Runnable references the fxAccount above, but it is not specific to a
   // single account. (That is, it does not capture a single account instance.)
   protected Runnable requestSyncRunnable;
 
-  protected final SyncStatusDelegate syncStatusDelegate = new SyncStatusDelegate();
+  protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate();
 
   protected Preference ensureFindPreference(String key) {
     Preference preference = findPreference(key);
     if (preference == null) {
       throw new IllegalStateException("Could not find preference with key: " + key);
     }
     return preference;
   }
@@ -235,40 +237,45 @@ public class FxAccountStatusFragment ext
   }
 
   protected void showConnected() {
     syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
     showOnlyOneErrorPreference(null);
     setCheckboxesEnabled(true);
   }
 
-  protected class SyncStatusDelegate implements FxAccountSyncStatusHelper.Delegate {
+  protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener {
     protected final Runnable refreshRunnable = new Runnable() {
       @Override
       public void run() {
         refresh();
       }
     };
 
     @Override
-    public AndroidFxAccount getAccount() {
-      return fxAccount;
+    public Context getContext() {
+      return FxAccountStatusFragment.this.getActivity();
     }
 
     @Override
-    public void handleSyncStarted() {
+    public Account getAccount() {
+      return fxAccount.getAndroidAccount();
+    }
+
+    @Override
+    public void onSyncStarted() {
       if (fxAccount == null) {
         return;
       }
       Logger.info(LOG_TAG, "Got sync started message; refreshing.");
       getActivity().runOnUiThread(refreshRunnable);
     }
 
     @Override
-    public void handleSyncFinished() {
+    public void onSyncFinished() {
       if (fxAccount == null) {
         return;
       }
       Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
       getActivity().runOnUiThread(refreshRunnable);
     }
   }
 
--- a/mobile/android/base/fxa/sync/FxAccountSyncStatusHelper.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncStatusHelper.java
@@ -3,77 +3,65 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.fxa.sync;
 
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.WeakHashMap;
 
+import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 
 import android.content.ContentResolver;
 import android.content.SyncStatusObserver;
 
 /**
  * Abstract away some details of Android's SyncStatusObserver.
  * <p>
  * Provides a simplified sync started/sync finished delegate.
- * <p>
- * We would prefer to register multiple observers, but it's of limited value
- * right now, so we support only a single observer, and we are as tolerant as
- * possible of non-paired add/remove calls.
  */
 public class FxAccountSyncStatusHelper implements SyncStatusObserver {
   @SuppressWarnings("unused")
   private static final String LOG_TAG = FxAccountSyncStatusHelper.class.getSimpleName();
 
   protected static FxAccountSyncStatusHelper sInstance = null;
 
   public synchronized static FxAccountSyncStatusHelper getInstance() {
     if (sInstance == null) {
       sInstance = new FxAccountSyncStatusHelper();
     }
     return sInstance;
   }
 
-  public interface Delegate {
-    public AndroidFxAccount getAccount();
-    public void handleSyncStarted();
-    public void handleSyncFinished();
-  }
-
   // Used to unregister this as a listener.
   protected Object handle = null;
 
   // Maps delegates to whether their underlying Android account was syncing the
   // last time we observed a status change.
-  protected Map<Delegate, Boolean> delegates = new WeakHashMap<Delegate, Boolean>();
+  protected Map<FirefoxAccounts.SyncStatusListener, Boolean> delegates = new WeakHashMap<FirefoxAccounts.SyncStatusListener, Boolean>();
 
   @Override
   public synchronized void onStatusChanged(int which) {
-    for (Entry<Delegate, Boolean> entry : delegates.entrySet()) {
-      final Delegate delegate = entry.getKey();
-      final AndroidFxAccount fxAccount = delegate.getAccount();
-      if (fxAccount == null) {
-        continue;
-      }
+    for (Entry<FirefoxAccounts.SyncStatusListener, Boolean> entry : delegates.entrySet()) {
+      final FirefoxAccounts.SyncStatusListener delegate = entry.getKey();
+      final AndroidFxAccount fxAccount = new AndroidFxAccount(delegate.getContext(), delegate.getAccount());
       final boolean active = fxAccount.isCurrentlySyncing();
       // Remember for later.
       boolean wasActiveLastTime = entry.getValue();
       // It's okay to update the value of an entry while iterating the entrySet.
       entry.setValue(active);
 
       if (active && !wasActiveLastTime) {
         // We've started a sync.
-        delegate.handleSyncStarted();
+        delegate.onSyncStarted();
       }
       if (!active && wasActiveLastTime) {
         // We've finished a sync.
-        delegate.handleSyncFinished();
+        delegate.onSyncFinished();
       }
     }
   }
 
   protected void addListener() {
     final int mask = ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
     if (this.handle != null) {
       throw new IllegalStateException("Already registered this as an observer?");
@@ -84,30 +72,30 @@ public class FxAccountSyncStatusHelper i
   protected void removeListener() {
     Object handle = this.handle;
     this.handle = null;
     if (handle != null) {
       ContentResolver.removeStatusChangeListener(handle);
     }
   }
 
-  public synchronized void startObserving(Delegate delegate) {
+  public synchronized void startObserving(FirefoxAccounts.SyncStatusListener delegate) {
     if (delegate == null) {
       throw new IllegalArgumentException("delegate must not be null");
     }
     if (delegates.containsKey(delegate)) {
       return;
     }
     // If we are the first delegate to the party, start listening.
     if (delegates.isEmpty()) {
       addListener();
     }
     delegates.put(delegate, Boolean.FALSE);
   }
 
-  public synchronized void stopObserving(Delegate delegate) {
+  public synchronized void stopObserving(FirefoxAccounts.SyncStatusListener delegate) {
     delegates.remove(delegate);
     // If we are the last delegate leaving the party, stop listening.
     if (delegates.isEmpty()) {
       removeListener();
     }
   }
 }