Bug 899217 - Part 2: Connect about:accounts to FirefoxAccountsHelper. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Tue, 17 Dec 2013 15:14:57 -0800
changeset 161504 5663a2789f97da77b3f51774fc8d737e7257c0c5
parent 161503 136b89107d14d69df8a94fce6d95c1340ef0b987
child 161505 755f7983b4e27cd63717a4d50299762ab7242d73
push id25884
push userttaubert@mozilla.com
push dateSat, 21 Dec 2013 00:37:32 +0000
treeherdermozilla-central@b3d4af4ec2df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs899217
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 899217 - Part 2: Connect about:accounts to FirefoxAccountsHelper. r=rnewman
mobile/android/base/BrowserApp.java
mobile/android/base/FirefoxAccountsHelper.java
mobile/android/base/moz.build
mobile/android/chrome/content/aboutAccounts.js
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -171,16 +171,18 @@ abstract public class BrowserApp extends
     private boolean mDynamicToolbarCanScroll = false;
 
     private Integer mPrefObserverId;
 
     private SharedPreferencesHelper mSharedPreferencesHelper;
 
     private OrderedBroadcastHelper mOrderedBroadcastHelper;
 
+    private FirefoxAccountsHelper mFirefoxAccountsHelper;
+
     private BrowserHealthReporter mBrowserHealthReporter;
 
     // The tab to be selected on editing mode exit.
     private Integer mTargetTabForEditingMode = null;
 
     // The animator used to toggle HomePager visibility has a race where if the HomePager is shown
     // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
     // both the web content and the HomePager will be hidden. This flag is used to prevent the
@@ -536,16 +538,17 @@ abstract public class BrowserApp extends
         registerEventListener("Settings:Show");
         registerEventListener("Updater:Launch");
         registerEventListener("Reader:GoToReadingList");
 
         Distribution.init(this);
         JavaAddonManager.getInstance().init(getApplicationContext());
         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
+        mFirefoxAccountsHelper = new FirefoxAccountsHelper(getApplicationContext());
         mBrowserHealthReporter = new BrowserHealthReporter();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
                     @Override
                     public NdefMessage createNdefMessage(NfcEvent event) {
@@ -828,16 +831,21 @@ abstract public class BrowserApp extends
             mSharedPreferencesHelper = null;
         }
 
         if (mOrderedBroadcastHelper != null) {
             mOrderedBroadcastHelper.uninit();
             mOrderedBroadcastHelper = null;
         }
 
+        if (mFirefoxAccountsHelper != null) {
+            mFirefoxAccountsHelper.uninit();
+            mFirefoxAccountsHelper = null;
+        }
+
         if (mBrowserHealthReporter != null) {
             mBrowserHealthReporter.uninit();
             mBrowserHealthReporter = null;
         }
 
         unregisterEventListener("CharEncoding:Data");
         unregisterEventListener("CharEncoding:State");
         unregisterEventListener("Feedback:LastUrl");
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/FirefoxAccountsHelper.java
@@ -0,0 +1,126 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import org.mozilla.gecko.util.EventDispatcher;
+import org.mozilla.gecko.util.GeckoEventListener;
+
+import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.accounts.Account;
+import android.content.Context;
+
+import android.util.Log;
+
+/**
+ * Helper class to manage Firefox Accounts.
+ *
+ * Listens for messages starting "FxAccount:" from Javascript, and creates a new
+ * Android account object in response to "FxAccount:{Verified,Login}".
+ *
+ * "FxAccount:Create" is ignored: it corresponds to a user creating a Firefox
+ * Account on the server; but they have not yet completed an email verification
+ * loop.  The provided data will not include the users keys.
+ *
+ * "FxAccount:Verified" corresponds to a user signing up: creating a Firefox
+ * Account on the server, and then completing an email verification loop while
+ * still in the about:accounts window.
+ *
+ * "FxAccount:Login" corresponds to a user signing in to an existing verified
+ * Firefox Account.
+ */
+public final class FirefoxAccountsHelper
+             implements GeckoEventListener
+{
+    public static final String LOGTAG = "FxAcctsHelper";
+
+    // For extra debugging.  Not final so it can be changed by an add-on.
+    public static boolean LOG_PERSONAL_INFORMATION = false;
+
+    public static final String EVENT_CREATE   = "FxAccount:Create";
+    public static final String EVENT_LOGIN    = "FxAccount:Login";
+    public static final String EVENT_VERIFIED = "FxAccount:Verified";
+
+    protected final Context mContext;
+
+    public FirefoxAccountsHelper(Context context) {
+        mContext = context;
+
+        EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+        if (dispatcher == null) {
+            Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
+            return;
+        }
+        dispatcher.registerEventListener(EVENT_CREATE, this);
+        dispatcher.registerEventListener(EVENT_LOGIN, this);
+        dispatcher.registerEventListener(EVENT_VERIFIED, this);
+    }
+
+    public synchronized void uninit() {
+        EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+        if (dispatcher == null) {
+            Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
+            return;
+        }
+        dispatcher.unregisterEventListener(EVENT_CREATE, this);
+        dispatcher.unregisterEventListener(EVENT_LOGIN, this);
+        dispatcher.unregisterEventListener(EVENT_VERIFIED, this);
+    }
+
+    @Override
+    public void handleMessage(String event, JSONObject message) {
+        Log.i(LOGTAG, "FirefoxAccountsHelper got event " + event);
+        if (!(EVENT_CREATE.equals(event) ||
+              EVENT_LOGIN.equals(event) ||
+              EVENT_VERIFIED.equals(event))) {
+            Log.e(LOGTAG, "FirefoxAccountsHelper got unexpected event " + event);
+            return;
+        }
+
+        if (EVENT_CREATE.equals(event)) {
+            Log.i(LOGTAG, "FirefoxAccountsHelper ignoring event " + event);
+            return;
+        }
+
+        try {
+            final JSONObject data = message.getJSONObject("data");
+            if (data == null) {
+                Log.e(LOGTAG, "data must not be null");
+                return;
+            }
+
+            if (LOG_PERSONAL_INFORMATION) {
+                Log.w(LOGTAG, "data: " + data.toString());
+            }
+
+            String email = data.optString("email");
+            String uid = data.optString("uid");
+            String sessionToken = data.optString("sessionToken");
+            String kA = data.optString("kA");
+            String kB = data.optString("kB");
+
+            if (LOG_PERSONAL_INFORMATION) {
+                Log.w(LOGTAG, "email: " + email);
+                Log.w(LOGTAG, "uid: " + uid);
+                Log.w(LOGTAG, "sessionToken: " + sessionToken);
+                Log.w(LOGTAG, "kA: " + kA);
+                Log.w(LOGTAG, "kB: " + kB);
+            }
+
+            Account account = FxAccountAuthenticator.addAccount(mContext, email, uid, sessionToken, kA, kB);
+            if (account == null) {
+                Log.e(LOGTAG, "Got null adding FxAccount.");
+                return;
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e);
+            return;
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -126,16 +126,17 @@ gbjar.sources += [
     'favicons/cache/FaviconCacheElement.java',
     'favicons/cache/FaviconsForURL.java',
     'favicons/Favicons.java',
     'favicons/LoadFaviconTask.java',
     'favicons/OnFaviconLoadedListener.java',
     'FilePickerResultHandler.java',
     'FilePickerResultHandlerSync.java',
     'FindInPageBar.java',
+    'FirefoxAccountsHelper.java',
     'FormAssistPopup.java',
     'GeckoAccessibility.java',
     'GeckoActivity.java',
     'GeckoActivityStatus.java',
     'GeckoApp.java',
     'GeckoApplication.java',
     'GeckoAppShell.java',
     'GeckoBatteryManager.java',
--- a/mobile/android/chrome/content/aboutAccounts.js
+++ b/mobile/android/chrome/content/aboutAccounts.js
@@ -31,16 +31,20 @@ try {
   let level =
     Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
     && Services.prefs.getCharPref(PREF_LOG_LEVEL);
   log.level = Log.Level[level] || Log.Level.Error;
 } catch (e) {
   log.error(e);
 }
 
+function sendMessageToJava(message) {
+  return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
+}
+
 let wrapper = {
   iframe: null,
 
   init: function () {
     log.info("about:accounts init");
     let iframe = document.getElementById("remote");
     this.iframe = iframe;
     iframe.addEventListener("load", this);
@@ -58,26 +62,38 @@ let wrapper = {
         this.handleRemoteCommand(evt);
         break;
     }
   },
 
   onLogin: function (data) {
     log.debug("Received: 'login'. Data:" + JSON.stringify(data));
     this.injectData("message", { status: "login" });
+    sendMessageToJava({
+      type: "FxAccount:Login",
+      data: data,
+    });
   },
 
   onCreate: function (data) {
     log.debug("Received: 'create'. Data:" + JSON.stringify(data));
     this.injectData("message", { status: "create" });
+    sendMessageToJava({
+      type: "FxAccount:Create",
+      data: data,
+    });
   },
 
   onVerified: function (data) {
     log.debug("Received: 'verified'. Data:" + JSON.stringify(data));
     this.injectData("message", { status: "verified" });
+    sendMessageToJava({
+      type: "FxAccount:Verified",
+      data: data,
+    });
   },
 
   get accountsURI() {
     delete this.accountsURI;
     return this.accountsURI = Services.urlFormatter.formatURLPref("firefox.accounts.remoteUrl");
   },
 
   handleRemoteCommand: function (evt) {