Bug 1191064 - Part 3: Handle removing Android Accounts from fxa-content-server. r=markh
authorNick Alexander <nalexander@mozilla.com>
Mon, 14 Sep 2015 17:21:19 -0400
changeset 296880 6fed2baa96c9e0321a4cebe480ebb11059aa81c6
parent 296879 e80ad43fbb4971cb35874bb86767c59ad8f24ea6
child 296881 a88abc5e76ed7fc53b826531232df9ae3b939a58
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs1191064
milestone43.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 1191064 - Part 3: Handle removing Android Accounts from fxa-content-server. r=markh This adds a new JS to Java ping-pong; exposes it via Accounts.jsm; and uses it in response to the fxa-content-server message.
mobile/android/base/AccountsHelper.java
mobile/android/modules/Accounts.jsm
mobile/android/modules/FxAccountsWebChannel.jsm
--- a/mobile/android/base/AccountsHelper.java
+++ b/mobile/android/base/AccountsHelper.java
@@ -1,32 +1,38 @@
 /* -*- 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 android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+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.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 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.sync.Utils;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 
 /**
  * Helper class to manage Android Accounts corresponding to Firefox Accounts.
  */
 public class AccountsHelper implements NativeEventListener {
@@ -43,34 +49,36 @@ public class AccountsHelper implements N
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.registerGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
                 "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
+                "Accounts:DeleteFirefoxAccount",
                 "Accounts:Exist");
     }
 
     public synchronized void uninit() {
         EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.unregisterGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
                 "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
+                "Accounts:DeleteFirefoxAccount",
                 "Accounts:Exist");
     }
 
     @Override
-    public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
+    public void handleMessage(String event, NativeJSObject message, final EventCallback callback) {
         if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
             AndroidFxAccount fxAccount = null;
             try {
                 final NativeJSObject json = message.getObject("json");
                 final String email = json.getString("email");
                 final String uid = json.getString("uid");
                 final boolean verified = json.optBoolean("verified", false);
                 final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
@@ -152,16 +160,53 @@ public class AccountsHelper implements N
             final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             final NativeJSObject extras = message.optObject("extras", null);
             if (extras != null) {
                 intent.putExtra("extras", extras.toString());
             }
             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>() {
+                    @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);
+                            if (callback != null) {
+                                callback.sendSuccess(result);
+                            }
+                        } catch (OperationCanceledException | IOException | AuthenticatorException e) {
+                            if (callback != null) {
+                                callback.sendError("Could not delete Firefox Account: " + e.toString());
+                            }
+                        }
+                    }
+                };
+
+                AccountManager.get(mContext).removeAccount(account, accountManagerCallback, null);
+            } 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());
+                    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();
--- a/mobile/android/modules/Accounts.jsm
+++ b/mobile/android/modules/Accounts.jsm
@@ -126,10 +126,23 @@ let Accounts = Object.freeze({
       kind: "fxa",
     }).then(data => {
       if (!data || !data.exists) {
         return null;
       }
       delete data.exists;
       return data;
     });
+  },
+
+  /**
+   * 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({
+      type: "Accounts:DeleteFirefoxAccount",
+    });
   }
 });
--- a/mobile/android/modules/FxAccountsWebChannel.jsm
+++ b/mobile/android/modules/FxAccountsWebChannel.jsm
@@ -23,16 +23,17 @@ Cu.import("resource://gre/modules/XPCOMU
 const log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccounts");
 
 const WEBCHANNEL_ID = "account_updates";
 
 const COMMAND_LOADED               = "fxaccounts:loaded";
 const COMMAND_CAN_LINK_ACCOUNT     = "fxaccounts:can_link_account";
 const COMMAND_LOGIN                = "fxaccounts:login";
 const COMMAND_CHANGE_PASSWORD      = "fxaccounts:change_password";
+const COMMAND_DELETE_ACCOUNT       = "fxaccounts:delete_account";
 
 const PREF_LAST_FXA_USER           = "identity.fxaccounts.lastSignedInUserHash";
 
 XPCOMUtils.defineLazyGetter(this, "strings",
                             () => Services.strings.createBundle("chrome://browser/locale/aboutAccounts.properties")); /*global strings */
 
 Object.defineProperty(this, "NativeWindow",
                       { get: () => Services.wm.getMostRecentWindow("navigator:browser").NativeWindow }); /*global NativeWindow */
@@ -310,16 +311,36 @@ this.FxAccountsWebChannel.prototype = {
               }
               log.i("Changed Firefox Account password.");
             })
             .catch(e => {
               log.e(e.toString());
             });
             break;
 
+          case COMMAND_DELETE_ACCOUNT:
+            // The fxa-content-server has already confirmed the user's intent.
+            // Bombs away.  There's no recovery from failure, and not even a
+            // real need to check an account exists (although we do, for error
+            // messaging only).
+            Accounts.getFirefoxAccount().then(account => {
+              if (!account) {
+                throw new Error("Can't delete non-existent Firefox Account!");
+              }
+              return Accounts.deleteFirefoxAccount().then(success => {
+                if (!success) {
+                  throw new Error("Could not delete Firefox Account!");
+                }
+                log.i("Firefox Account deleted.");
+              });
+            }).catch(e => {
+              log.e(e.toString());
+            });
+            break;
+
           default:
             log.w("Ignoring unrecognized FxAccountsWebChannel command: " + JSON.stringify(command));
             break;
         }
       }
     };
 
     this._channelCallback = listener;