Bug 1333586 - Convert AccountsHelper events to bundle events; r=sebastian
authorJim Chen <nchen@mozilla.com>
Wed, 25 Jan 2017 18:57:31 -0500
changeset 466665 639e6a9174f466b5f3e0ed894d15dfb8cc3081af
parent 466664 174bd8a438db50a4274fbb94ea0d35c34fad793b
child 466666 45316394ec1d7a06fa15404ba21c67f1ca4027d6
push id42948
push userbmo:gasolin@mozilla.com
push dateThu, 26 Jan 2017 07:49:21 +0000
reviewerssebastian
bugs1333586
milestone54.0a1
Bug 1333586 - Convert AccountsHelper events to bundle events; r=sebastian Convert events used in AccountsHelper to bundle events. Most events are kept as Gecko thread events, but a couple events that start activities are converted to UI thread events.
mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
mobile/android/modules/Accounts.jsm
--- a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
@@ -11,288 +11,297 @@ import android.accounts.AccountManagerCa
 import android.accounts.AccountManagerFuture;
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
 
 import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
 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.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
+import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * Helper class to manage Android Accounts corresponding to Firefox Accounts.
  */
-public class AccountsHelper implements NativeEventListener {
-    public static final String LOGTAG = "GeckoAccounts";
+public class AccountsHelper implements BundleEventListener {
+    private static final String LOGTAG = "GeckoAccounts";
 
     protected final Context mContext;
     protected final GeckoProfile mProfile;
 
     public AccountsHelper(Context context, GeckoProfile profile) {
         mContext = context;
         mProfile = profile;
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
                 "Accounts:UpdateFirefoxAccountFromJSON",
-                "Accounts:Create",
                 "Accounts:DeleteFirefoxAccount",
                 "Accounts:Exist",
-                "Accounts:ProfileUpdated",
+                "Accounts:ProfileUpdated");
+        EventDispatcher.getInstance().registerUiThreadListener(this,
+                "Accounts:Create",
                 "Accounts:ShowSyncPreferences");
     }
 
     public synchronized void uninit() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
                 "Accounts:UpdateFirefoxAccountFromJSON",
-                "Accounts:Create",
                 "Accounts:DeleteFirefoxAccount",
                 "Accounts:Exist",
-                "Accounts:ProfileUpdated",
+                "Accounts:ProfileUpdated");
+        EventDispatcher.getInstance().unregisterUiThreadListener(this,
+                "Accounts:Create",
                 "Accounts:ShowSyncPreferences");
     }
 
-    @Override
-    public void handleMessage(String event, NativeJSObject message, final EventCallback callback) {
+    @Override // BundleEventListener
+    public void handleMessage(final String event, final GeckoBundle message,
+                              final EventCallback callback) {
         if (!Restrictions.isAllowed(mContext, Restrictable.MODIFY_ACCOUNTS)) {
             // We register for messages in all contexts; we drop, with a log and an error to JavaScript,
             // when the profile is restricted.  It's better to return errors than silently ignore messages.
             Log.e(LOGTAG, "Profile is not allowed to modify accounts!  Ignoring event: " + event);
             if (callback != null) {
                 callback.sendError("Profile is not allowed to modify accounts!");
             }
             return;
         }
 
         if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
             AndroidFxAccount fxAccount = null;
             try {
-                final NativeJSObject json = message.getObject("json");
+                final GeckoBundle json = message.getBundle("json");
                 final String email = json.getString("email");
                 final String uid = json.getString("uid");
-                final boolean verified = json.optBoolean("verified", false);
+                final boolean verified = json.getBoolean("verified", false);
                 final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
                 final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
                 final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
-                final String authServerEndpoint =
-                        json.optString("authServerEndpoint", FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT);
-                final String tokenServerEndpoint =
-                        json.optString("tokenServerEndpoint", FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
-                final String profileServerEndpoint =
-                        json.optString("profileServerEndpoint", FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT);
+                final String authServerEndpoint = json.getString("authServerEndpoint",
+                        FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT);
+                final String tokenServerEndpoint = json.getString("tokenServerEndpoint",
+                        FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
+                final String profileServerEndpoint = json.getString("profileServerEndpoint",
+                        FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT);
                 // TODO: handle choose what to Sync.
                 State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
                 fxAccount = AndroidFxAccount.addAndroidAccount(mContext,
                         email,
                         mProfile.getName(),
                         authServerEndpoint,
                         tokenServerEndpoint,
                         profileServerEndpoint,
                         state,
                         AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP);
 
-                final String[] declinedSyncEngines = json.optStringArray("declinedSyncEngines", null);
+                final String[] declinedSyncEngines = json.getStringArray("declinedSyncEngines");
                 if (declinedSyncEngines != null) {
                     Log.i(LOGTAG, "User has selected engines; storing to prefs.");
                     final Map<String, Boolean> selectedEngines = new HashMap<String, Boolean>();
                     for (String enabledSyncEngine : SyncConfiguration.validEngineNames()) {
                         selectedEngines.put(enabledSyncEngine, true);
                     }
                     for (String declinedSyncEngine : declinedSyncEngines) {
                         selectedEngines.put(declinedSyncEngine, false);
                     }
                     // The "forms" engine has the same state as the "history" engine.
                     selectedEngines.put("forms", selectedEngines.get("history"));
-                    FxAccountUtils.pii(LOGTAG, "User selected engines: " + selectedEngines.toString());
+                    FxAccountUtils.pii(LOGTAG, "User selected engines: " +
+                                               selectedEngines.toString());
                     try {
-                        SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), selectedEngines);
+                        SyncConfiguration.storeSelectedEnginesToPrefs(
+                                fxAccount.getSyncPrefs(), selectedEngines);
                     } catch (UnsupportedEncodingException | GeneralSecurityException e) {
                         Log.e(LOGTAG, "Got exception storing selected engines; ignoring.", e);
                     }
                 }
-            } catch (URISyntaxException | GeneralSecurityException | UnsupportedEncodingException e) {
+            } catch (URISyntaxException | GeneralSecurityException |
+                     UnsupportedEncodingException e) {
                 Log.w(LOGTAG, "Got exception creating Firefox Account from JSON; ignoring.", e);
                 if (callback != null) {
-                    callback.sendError("Could not create Firefox Account from JSON: " + e.toString());
+                    callback.sendError("Could not create Firefox Account from JSON: " +
+                                       e.toString());
                     return;
                 }
             }
             if (callback != null) {
                 callback.sendSuccess(fxAccount != null);
             }
 
         } else if ("Accounts:UpdateFirefoxAccountFromJSON".equals(event)) {
-            try {
-                final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
-                if (account == null) {
-                    if (callback != null) {
-                        callback.sendError("Could not update Firefox Account since none exists");
-                    }
-                    return;
+            final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
+            if (account == null) {
+                if (callback != null) {
+                    callback.sendError("Could not update Firefox Account since none exists");
                 }
+                return;
+            }
 
-                final NativeJSObject json = message.getObject("json");
-                final String email = json.getString("email");
-                final String uid = json.getString("uid");
+            final GeckoBundle json = message.getBundle("json");
+            final String email = json.getString("email");
+            final String uid = json.getString("uid");
 
-                // Protect against cross-connecting accounts.
-                if (account.name == null || !account.name.equals(email)) {
-                    final String errorMessage = "Cannot update Firefox Account from JSON: datum has different email address!";
-                    Log.e(LOGTAG, errorMessage);
-                    if (callback != null) {
-                        callback.sendError(errorMessage);
-                    }
-                    return;
+            // Protect against cross-connecting accounts.
+            if (account.name == null || !account.name.equals(email)) {
+                final String errorMessage = "Cannot update Firefox Account from JSON: " +
+                        "datum has different email address!";
+                Log.e(LOGTAG, errorMessage);
+                if (callback != null) {
+                    callback.sendError(errorMessage);
                 }
+                return;
+            }
 
-                final boolean verified = json.optBoolean("verified", false);
-                final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
-                final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
-                final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
-                final State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
-
-                final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
-                fxAccount.setState(state);
+            final boolean verified = json.getBoolean("verified", false);
+            final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
+            final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
+            final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
+            final State state = new Engaged(email, uid, verified, unwrapkB,
+                                            sessionToken, keyFetchToken);
 
-                if (callback != null) {
-                    callback.sendSuccess(true);
-                }
-            } catch (NativeJSObject.InvalidPropertyException e) {
-                Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
-                if (callback != null) {
-                    callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
-                    return;
-                }
+            final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
+            fxAccount.setState(state);
+
+            if (callback != null) {
+                callback.sendSuccess(true);
             }
 
         } else if ("Accounts:Create".equals(event)) {
             // Do exactly the same thing as if you tapped 'Sync' in Settings.
             final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            final NativeJSObject extras = message.optObject("extras", null);
+            final GeckoBundle extras = message.getBundle("extras");
             if (extras != null) {
-                intent.putExtra("extras", extras.toString());
+                try {
+                    intent.putExtra("extras", extras.toJSONObject().toString());
+                } catch (final JSONException e) {
+                    Log.e(LOGTAG, "Cannot convert extras", e);
+                }
             }
             mContext.startActivity(intent);
 
         } else if ("Accounts:DeleteFirefoxAccount".equals(event)) {
             try {
                 final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
                 if (account == null) {
                     Log.w(LOGTAG, "Could not delete Firefox Account since none exists!");
                     if (callback != null) {
                         callback.sendError("Could not delete Firefox Account since none exists");
                     }
                     return;
                 }
 
-                final AccountManagerCallback<Boolean> accountManagerCallback = new AccountManagerCallback<Boolean>() {
+                final AccountManagerCallback<Boolean> accountManagerCallback =
+                        new AccountManagerCallback<Boolean>() {
                     @Override
                     public void run(AccountManagerFuture<Boolean> future) {
                         try {
                             final boolean result = future.getResult();
-                            Log.i(LOGTAG, "Account named like " + Utils.obfuscateEmail(account.name) + " removed: " + result);
+                            Log.i(LOGTAG, "Account named like " +
+                                          Utils.obfuscateEmail(account.name) + " removed: " +
+                                          result);
                             if (callback != null) {
                                 callback.sendSuccess(result);
                             }
-                        } catch (OperationCanceledException | IOException | AuthenticatorException e) {
+                        } catch (OperationCanceledException | IOException |
+                                 AuthenticatorException e) {
                             if (callback != null) {
-                                callback.sendError("Could not delete Firefox Account: " + e.toString());
+                                callback.sendError("Could not delete Firefox Account: " +
+                                                   e.toString());
                             }
                         }
                     }
                 };
 
-                AccountManager.get(mContext).removeAccount(account, accountManagerCallback, null);
+                AccountManager.get(mContext).removeAccount(
+                        account, accountManagerCallback, ThreadUtils.getBackgroundHandler());
             } catch (Exception e) {
                 Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
                 if (callback != null) {
-                    callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
+                    callback.sendError("Could not update Firefox Account from JSON: " +
+                                       e.toString());
                     return;
                 }
             }
 
         } else if ("Accounts:Exist".equals(event)) {
             if (callback == null) {
                 Log.w(LOGTAG, "Accounts:Exist requires a callback");
                 return;
             }
 
-            final String kind = message.optString("kind", null);
-            final JSONObject response = new JSONObject();
+            final String kind = message.getString("kind", null);
+            final GeckoBundle response = new GeckoBundle();
 
-            try {
-                if ("any".equals(kind)) {
-                    response.put("exists", FirefoxAccounts.firefoxAccountsExist(mContext));
-                    callback.sendSuccess(response);
-                } else if ("fxa".equals(kind)) {
-                    final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
-                    response.put("exists", account != null);
-                    if (account != null) {
-                        response.put("email", account.name);
-                        // We should always be able to extract the server endpoints.
-                        final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
-                        response.put("authServerEndpoint", fxAccount.getAccountServerURI());
-                        response.put("profileServerEndpoint", fxAccount.getProfileServerURI());
-                        response.put("tokenServerEndpoint", fxAccount.getTokenServerURI());
-                        try {
-                            // It is possible for the state fetch to fail and us to not be able to provide a UID.
-                            // Long term, the UID (and verification flag) will be attached to the Android account
-                            // user data and not the internal state representation.
-                            final State state = fxAccount.getState();
-                            response.put("uid", state.uid);
-                        } catch (Exception e) {
-                            Log.w(LOGTAG, "Got exception extracting account UID; ignoring.", e);
-                        }
+            if ("any".equals(kind)) {
+                response.putBoolean("exists", FirefoxAccounts.firefoxAccountsExist(mContext));
+                callback.sendSuccess(response);
+            } else if ("fxa".equals(kind)) {
+                final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
+                response.putBoolean("exists", account != null);
+                if (account != null) {
+                    response.putString("email", account.name);
+                    // We should always be able to extract the server endpoints.
+                    final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
+                    response.putString("authServerEndpoint", fxAccount.getAccountServerURI());
+                    response.putString("profileServerEndpoint", fxAccount.getProfileServerURI());
+                    response.putString("tokenServerEndpoint", fxAccount.getTokenServerURI());
+                    try {
+                        // It is possible for the state fetch to fail and us to not be
+                        // able to provide a UID.  Long term, the UID (and verification
+                        // flag) will be attached to the Android account user data and not
+                        // the internal state representation.
+                        final State state = fxAccount.getState();
+                        response.putString("uid", state.uid);
+                    } catch (Exception e) {
+                        Log.w(LOGTAG, "Got exception extracting account UID; ignoring.", e);
                     }
+                }
+                callback.sendSuccess(response);
+            } else {
+                callback.sendError("Could not query account existence: unknown kind.");
+            }
 
-                    callback.sendSuccess(response);
-                } else {
-                    callback.sendError("Could not query account existence: unknown kind.");
-                }
-            } catch (JSONException e) {
-                Log.w(LOGTAG, "Got exception querying account existence; ignoring.", e);
-                callback.sendError("Could not query account existence: " + e.toString());
-                return;
-            }
         } else if ("Accounts:ProfileUpdated".equals(event)) {
             final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
             if (account == null) {
                 Log.w(LOGTAG, "Can't change profile of non-existent Firefox Account!; ignored");
                 return;
             }
             final AndroidFxAccount androidFxAccount = new AndroidFxAccount(mContext, account);
             androidFxAccount.fetchProfileJSON();
+
         } else if ("Accounts:ShowSyncPreferences".equals(event)) {
             final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
             if (account == null) {
-                Log.w(LOGTAG, "Can't change show Sync preferences of non-existent Firefox Account!; ignored");
+                Log.w(LOGTAG, "Can't change show Sync preferences of " +
+                              "non-existent Firefox Account!; ignored");
                 return;
             }
             // We don't necessarily have an Activity context here, so we always start in a new task.
             final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_STATUS);
             intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mContext.startActivity(intent);
         }
--- a/mobile/android/modules/Accounts.jsm
+++ b/mobile/android/modules/Accounts.jsm
@@ -29,17 +29,17 @@ Cu.import("resource://gre/modules/Servic
  *     },
  *     (err) => {
  *       console.log("We failed so hard.");
  *     }
  *   );
  */
 var Accounts = Object.freeze({
   _accountsExist: function (kind) {
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "Accounts:Exist",
       kind: kind
     }).then(data => data.exists);
   },
 
   firefoxAccountsExist: function () {
     return this._accountsExist("fxa");
   },
@@ -61,17 +61,17 @@ var Accounts = Object.freeze({
    *
    * Optional extras are passed, as a JSON string, to the Firefox
    * Account Getting Started activity in the extras bundle of the
    * activity launch intent, under the key "extras".
    *
    * There is no return value from this method.
    */
   launchSetup: function (extras) {
-    Messaging.sendRequest({
+    EventDispatcher.instance.sendRequest({
       type: "Accounts:Create",
       extras: extras
     });
   },
 
   _addDefaultEndpoints: function (json) {
     let newData = Cu.cloneInto(json, {}, { cloneFunctions: false });
     let associations = {
@@ -90,61 +90,61 @@ var Accounts = Object.freeze({
    * fxa-content-server "login" JSON datum.  The new account will be
    * in the "Engaged" state, and will start syncing immediately.
    *
    * It is an error if an Android Account already exists.
    *
    * Returns a Promise that resolves to a boolean indicating success.
    */
   createFirefoxAccountFromJSON: function (json) {
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "Accounts:CreateFirefoxAccountFromJSON",
       json: this._addDefaultEndpoints(json)
     });
   },
 
   /**
    * Move an existing Android Account to the "Engaged" state with the given
    * fxa-content-server "login" JSON datum.  The account will (re)start
    * syncing immediately, unless the user has manually configured the account
    * to not Sync.
    *
    * It is an error if no Android Account exists.
    *
    * Returns a Promise that resolves to a boolean indicating success.
    */
   updateFirefoxAccountFromJSON: function (json) {
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "Accounts:UpdateFirefoxAccountFromJSON",
       json: this._addDefaultEndpoints(json)
     });
   },
 
   /**
    * Notify that profile for Android Account has updated.
    * The account will re-fetch the profile image.
    *
    * It is an error if no Android Account exists.
    *
    * There is no return value from this method.
    */
   notifyFirefoxAccountProfileChanged: function () {
-    Messaging.sendRequest({
+    EventDispatcher.instance.sendRequest({
       type: "Accounts:ProfileUpdated",
     });
   },
 
   /**
    * Fetch information about an existing Android Firefox Account.
    *
    * Returns a Promise that resolves to null if no Android Firefox Account
    * exists, or an object including at least a string-valued 'email' key.
    */
   getFirefoxAccount: function () {
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "Accounts:Exist",
       kind: "fxa",
     }).then(data => {
       if (!data || !data.exists) {
         return null;
       }
       delete data.exists;
       return data;
@@ -154,25 +154,25 @@ var Accounts = Object.freeze({
   /**
    * Delete an existing Android Firefox Account.
    *
    * It is an error if no Android Account exists.
    *
    * Returns a Promise that resolves to a boolean indicating success.
    */
   deleteFirefoxAccount: function () {
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "Accounts:DeleteFirefoxAccount",
     });
   },
 
   showSyncPreferences: function () {
     // Only show Sync preferences of an existing Android Account.
     return Accounts.getFirefoxAccount().then(account => {
       if (!account) {
         throw new Error("Can't show Sync preferences of non-existent Firefox Account!");
       }
-      return Messaging.sendRequestForResult({
+      return EventDispatcher.instance.sendRequestForResult({
         type: "Accounts:ShowSyncPreferences"
       });
     });
   }
 });