Bug 917942 - Create a JS interface to Sync configuration. r=margaret, a=sledru
authorRichard Newman <rnewman@mozilla.com>
Tue, 18 Feb 2014 19:20:27 -0800
changeset 182991 8e7a694ef27bfb3d76838e9982e7c36d96e5bb82
parent 182990 939571a11ebbc5985892dab5e4f025f0f69fd7fd
child 182992 f8ec94d3ab7cf16f270756560df884c675690535
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, sledru
bugs917942
milestone29.0a2
Bug 917942 - Create a JS interface to Sync configuration. r=margaret, a=sledru
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoApp.java
mobile/android/base/tests/robocop.ini
mobile/android/base/tests/testAccounts.java
mobile/android/base/tests/testAccounts.js
mobile/android/modules/Accounts.jsm
mobile/android/modules/moz.build
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -8,29 +8,32 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
+import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
+import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerMarginsAnimator;
 import org.mozilla.gecko.health.BrowserHealthRecorder;
 import org.mozilla.gecko.health.BrowserHealthReporter;
 import org.mozilla.gecko.home.BrowserSearch;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.Prompt;
+import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
@@ -531,16 +534,18 @@ abstract public class BrowserApp extends
         registerEventListener("CharEncoding:Data");
         registerEventListener("CharEncoding:State");
         registerEventListener("Feedback:LastUrl");
         registerEventListener("Feedback:OpenPlayStore");
         registerEventListener("Feedback:MaybeLater");
         registerEventListener("Telemetry:Gather");
         registerEventListener("Settings:Show");
         registerEventListener("Updater:Launch");
+        registerEventListener("Accounts:Create");
+        registerEventListener("Accounts:Exist");
 
         Distribution.init(this);
         JavaAddonManager.getInstance().init(getApplicationContext());
         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
         mBrowserHealthReporter = new BrowserHealthReporter();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
@@ -845,16 +850,18 @@ abstract public class BrowserApp extends
         unregisterEventListener("CharEncoding:Data");
         unregisterEventListener("CharEncoding:State");
         unregisterEventListener("Feedback:LastUrl");
         unregisterEventListener("Feedback:OpenPlayStore");
         unregisterEventListener("Feedback:MaybeLater");
         unregisterEventListener("Telemetry:Gather");
         unregisterEventListener("Settings:Show");
         unregisterEventListener("Updater:Launch");
+        unregisterEventListener("Accounts:Create");
+        unregisterEventListener("Accounts:Exist");
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 // null this out even though the docs say it's not needed,
                 // because the source code looks like it will only do this
                 // automatically on API 14+
                 nfc.setNdefPushMessageCallback(null, this);
@@ -1223,24 +1230,54 @@ abstract public class BrowserApp extends
             } else if (event.equals("Updater:Launch")) {
                 handleUpdaterLaunch();
             } else if (event.equals("Prompt:ShowTop")) {
                 // Bring this activity to front so the prompt is visible..
                 Intent bringToFrontIntent = new Intent();
                 bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS);
                 bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                 startActivity(bringToFrontIntent);
+            } else if (event.equals("Accounts:Create")) {
+                // Do exactly the same thing as if you tapped 'Sync'
+                // in Settings.
+                final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                getContext().startActivity(intent);
+            } else if (event.equals("Accounts:Exist")) {
+                final String kind = message.getString("kind");
+                final JSONObject response = new JSONObject();
+
+                if ("any".equals(kind)) {
+                    response.put("exists", SyncAccounts.syncAccountsExist(getContext()) ||
+                                           FirefoxAccounts.firefoxAccountsExist(getContext()));
+                } else if ("fxa".equals(kind)) {
+                    response.put("exists", FirefoxAccounts.firefoxAccountsExist(getContext()));
+                } else if ("sync11".equals(kind)) {
+                    response.put("exists", SyncAccounts.syncAccountsExist(getContext()));
+                } else {
+                    response.put("error", "Unknown kind");
+                }
+                mCurrentResponse = response.toString();
             } else {
                 super.handleMessage(event, message);
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
+    private String mCurrentResponse;
+
+    @Override
+    public String getResponse(JSONObject origMessage) {
+        String res = mCurrentResponse;
+        mCurrentResponse = "";
+        return res;
+    }
+
     @Override
     public void addTab() {
         // Always load about:home when opening a new tab.
         Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
     }
 
     @Override
     public void addPrivateTab() {
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -691,16 +691,17 @@ public abstract class GeckoApp
             } else if (event.equals("SystemUI:Visibility")) {
                 setSystemUiVisible(message.getBoolean("visible"));
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
+    @Override
     public String getResponse(JSONObject origMessage) {
         String res = mCurrentResponse;
         mCurrentResponse = "";
         return res;
     }
 
     void onStatePurged() { }
 
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -72,16 +72,17 @@ skip-if = processor == "x86"
 [testSystemPages]
 # disabled on x86 only; bug 907383
 skip-if = processor == "x86"
 # [testThumbnails] # see bug 813107
 [testTitleBar]
 # [testVkbOverlap] # see bug 907274
 
 # Using JavascriptTest
+[testAccounts]
 [testBrowserDiscovery]
 [testDeviceSearchEngine]
 [testJNI]
 # [testMozPay] # see bug 945675
 [testOrderedBroadcast]
 [testSharedPreferences]
 [testSimpleDiscovery]
 [testUITelemetry]
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAccounts.java
@@ -0,0 +1,28 @@
+/* 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.tests;
+
+import org.mozilla.gecko.*;
+import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
+
+public class testAccounts extends JavascriptTest {
+    public testAccounts() {
+        super("testAccounts.js");
+    }
+
+    @Override
+    public void testJavascript() throws Exception {
+        super.testJavascript();
+
+        // Rather than waiting for the JS call to message
+        // Java and wait for the Activity to launch, we just
+        // don't test these.
+        /*
+        android.app.Activity activity = mSolo.getCurrentActivity();
+        System.out.println("Current activity: " + activity);
+        mAsserter.ok(activity instanceof FxAccountGetStartedActivity, "checking activity", "setup activity launched");
+        */
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAccounts.js
@@ -0,0 +1,24 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Accounts.jsm");
+
+add_task(function test_Accounts() {
+  let syncExists = yield Accounts.syncAccountsExist();
+  dump("Sync account exists? " + syncExists + "\n");
+  let firefoxExists = yield Accounts.firefoxAccountsExist();
+  dump("Firefox account exists? " + firefoxExists + "\n");
+  let anyExists = yield Accounts.anySyncAccountsExist();
+  dump("Any accounts exist? " + anyExists + "\n");
+
+  // Only one account should exist.
+  do_check_true(!syncExists || !firefoxExists);
+  do_check_eq(anyExists, firefoxExists || syncExists);
+
+  dump("Launching setup.\n");
+  Accounts.launchSetup();
+});
+
+run_next_test();
new file mode 100644
--- /dev/null
+++ b/mobile/android/modules/Accounts.jsm
@@ -0,0 +1,100 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Accounts"];
+
+const { utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+function sendMessageToJava(aMessage, aCallback) {
+  let data, error;
+  try {
+    data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
+  } catch (ex) {
+    error = ex;
+  }
+
+  if (aCallback) {
+    aCallback(data, error);
+  }
+}
+
+/**
+ * A promise-based API for querying the existence of Sync accounts,
+ * and accessing the Sync setup wizard.
+ *
+ * Usage:
+ *
+ *   Cu.import("resource://gre/modules/Accounts.jsm");
+ *   Accounts.anySyncAccountsExist().then(
+ *     (exist) => {
+ *       console.log("Accounts exist? " + exist);
+ *       if (!exist) {
+ *         Accounts.launchSetup();
+ *       }
+ *     },
+ *     (err) => {
+ *       console.log("We failed so hard.");
+ *     }
+ *   );
+ */
+let Accounts = Object.freeze({
+  _accountsExist: function (kind) {
+    let deferred = Promise.defer();
+
+    sendMessageToJava({
+      type: "Accounts:Exist",
+      kind: kind,
+    }, (data, error) => {
+      if (error) {
+        deferred.reject(error);
+        return;
+      }
+
+      if (!data) {
+        deferred.resolve(null);
+        return;
+      }
+
+      let json = JSON.parse(data);
+      if (json.error) {
+        deferred.reject(json.error);
+        return;
+      }
+
+      deferred.resolve(json.exists);
+    });
+
+    return deferred.promise;
+  },
+
+  firefoxAccountsExist: function () {
+    return this._accountsExist("fxa");
+  },
+
+  syncAccountsExist: function () {
+    return this._accountsExist("sync11");
+  },
+
+  anySyncAccountsExist: function () {
+    return this._accountsExist("any");
+  },
+
+  /**
+   * Fire-and-forget: open the Firefox accounts activity, which
+   * will be the Getting Started screen if FxA isn't yet set up.
+   *
+   * There is no return value from this method.
+   */
+  launchSetup: function () {
+    sendMessageToJava({
+      type: "Accounts:Create",
+    });
+  },
+});
+
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_JS_MODULES += [
+    'Accounts.jsm',
     'ContactService.jsm',
     'HelperApps.jsm',
     'Home.jsm',
     'HomeProvider.jsm',
     'JNI.jsm',
     'LightweightThemeConsumer.jsm',
     'Notifications.jsm',
     'OrderedBroadcast.jsm',