Bug 959784 - Show spinner and native error UI as appropriate. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Thu, 23 Jan 2014 18:32:34 -0800
changeset 181058 2779e0f04195b868c512a7fdec038d123da7dacb
parent 181057 87aeb1c80b871eecff229da1c3d4bc97a406fe19
child 181059 b21ef8cb0db658ae277beb1b6a6e81c3f10bc2a1
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)
reviewersrnewman
bugs959784
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 959784 - Show spinner and native error UI as appropriate. r=rnewman
mobile/android/base/background/fxa/FxAccountClientException.java
mobile/android/base/background/fxa/FxAccountRemoteError.java
mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
mobile/android/base/fxa/activities/FxAccountSetupTask.java
mobile/android/base/fxa/activities/FxAccountSignInActivity.java
mobile/android/base/fxa/activities/FxAccountStatusActivity.java
mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
mobile/android/base/resources/layout/fxaccount_create_account.xml
mobile/android/base/resources/layout/fxaccount_email_password_view.xml
mobile/android/base/resources/layout/fxaccount_sign_in.xml
mobile/android/base/resources/layout/fxaccount_status.xml
mobile/android/base/resources/layout/fxaccount_update_credentials.xml
mobile/android/base/resources/values/fxaccount_styles.xml
mobile/android/services/strings.xml.in
--- a/mobile/android/base/background/fxa/FxAccountClientException.java
+++ b/mobile/android/base/background/fxa/FxAccountClientException.java
@@ -1,14 +1,15 @@
 /* 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.background.fxa;
 
+import org.mozilla.gecko.R;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.HttpStatus;
 
 /**
  * From <a href="https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md">https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md</a>.
@@ -41,17 +42,17 @@ public class FxAccountClientException ex
       this.apiErrorNumber = apiErrorNumber;
       this.error = error;
       this.message = message;
       this.info = info;
     }
 
     @Override
     public String toString() {
-      return "" + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message;
+      return "<FxAccountClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
     }
 
     public boolean isInvalidAuthentication() {
       return httpStatusCode == HttpStatus.SC_UNAUTHORIZED;
     }
 
     public boolean isAccountAlreadyExists() {
       return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;
@@ -67,16 +68,38 @@ public class FxAccountClientException ex
 
     public boolean isUpgradeRequired() {
       return
           apiErrorNumber == FxAccountRemoteError.ENDPOINT_IS_NO_LONGER_SUPPORTED ||
           apiErrorNumber == FxAccountRemoteError.INCORRECT_LOGIN_METHOD_FOR_THIS_ACCOUNT ||
           apiErrorNumber == FxAccountRemoteError.INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT ||
           apiErrorNumber == FxAccountRemoteError.INCORRECT_API_VERSION_FOR_THIS_ACCOUNT;
     }
+
+    public int getErrorMessageStringResource() {
+      if (isUpgradeRequired()) {
+        return R.string.fxaccount_remote_error_UPGRADE_REQUIRED;
+      }
+      switch ((int) apiErrorNumber) {
+      case FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS:
+        return R.string.fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS;
+      case FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST:
+        return R.string.fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;
+      case FxAccountRemoteError.INCORRECT_PASSWORD:
+        return R.string.fxaccount_remote_error_INCORRECT_PASSWORD;
+      case FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT:
+        return R.string.fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT;
+      case FxAccountRemoteError.CLIENT_HAS_SENT_TOO_MANY_REQUESTS:
+        return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
+      case FxAccountRemoteError.SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD:
+        return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;
+      default:
+        return R.string.fxaccount_remote_error_UNKNOWN_ERROR;
+      }
+    }
   }
 
 
   public static class FxAccountClientMalformedResponseException extends FxAccountClientRemoteException {
     private static final long serialVersionUID = 2209313149952001098L;
 
     public FxAccountClientMalformedResponseException(HttpResponse response) {
       super(response, 0, FxAccountRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", "Response malformed");
--- a/mobile/android/base/background/fxa/FxAccountRemoteError.java
+++ b/mobile/android/base/background/fxa/FxAccountRemoteError.java
@@ -20,11 +20,11 @@ public interface FxAccountRemoteError {
   public static final int CONTENT_LENGTH_HEADER_WAS_NOT_PROVIDED = 112;
   public static final int REQUEST_BODY_TOO_LARGE = 113;
   public static final int CLIENT_HAS_SENT_TOO_MANY_REQUESTS = 114;
   public static final int INVALID_NONCE_IN_REQUEST_SIGNATURE = 115;
   public static final int ENDPOINT_IS_NO_LONGER_SUPPORTED = 116;
   public static final int INCORRECT_LOGIN_METHOD_FOR_THIS_ACCOUNT = 117;
   public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118;
   public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
-  public static final int SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD = 201;
+  public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
   public static final int UNKNOWN_ERROR = 999;
 }
--- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
@@ -1,49 +1,54 @@
 /* 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.fxa.activities;
 
+import java.io.IOException;
+
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
+import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay;
 
-import android.app.AlertDialog;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.TextWatcher;
 import android.util.Patterns;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
-abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity {
+abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
   public FxAccountAbstractSetupActivity() {
     super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
   }
 
   protected FxAccountAbstractSetupActivity(int resume) {
     super(resume);
   }
 
   private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
 
   protected int minimumPasswordLength = 8;
 
-  protected TextView localErrorTextView;
   protected EditText emailEdit;
   protected EditText passwordEdit;
   protected Button showPasswordButton;
+  protected TextView remoteErrorTextView;
   protected Button button;
+  protected ProgressBar progressBar;
 
   protected void createShowPasswordButton() {
     showPasswordButton.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
         boolean isShown = 0 == (passwordEdit.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD);
         // Changing input type loses position in edit text; let's try to maintain it.
         int start = passwordEdit.getSelectionStart();
@@ -54,18 +59,30 @@ abstract public class FxAccountAbstractS
           showPasswordButton.setText(R.string.fxaccount_password_show);
         } else {
           showPasswordButton.setText(R.string.fxaccount_password_hide);
         }
       }
     });
   }
 
-  protected void showRemoteError(Exception e) {
-    new AlertDialog.Builder(this).setTitle("Remote error!").setMessage(e.toString()).show();
+  protected void hideRemoteError() {
+    remoteErrorTextView.setVisibility(View.INVISIBLE);
+  }
+
+  protected void showRemoteError(Exception e, int defaultResourceId) {
+    if (e instanceof IOException) {
+      remoteErrorTextView.setText(R.string.fxaccount_remote_error_COULD_NOT_CONNECT);
+    } else if (e instanceof FxAccountClientRemoteException) {
+      remoteErrorTextView.setText(((FxAccountClientRemoteException) e).getErrorMessageStringResource());
+    } else {
+      remoteErrorTextView.setText(defaultResourceId);
+    }
+    Logger.warn(LOG_TAG, "Got exception; showing error message: " + remoteErrorTextView.getText().toString(), e);
+    remoteErrorTextView.setVisibility(View.VISIBLE);
   }
 
   protected void addListeners() {
     TextChangedListener textChangedListener = new TextChangedListener();
     EditorActionListener editorActionListener = new EditorActionListener();
     FocusChangeListener focusChangeListener = new FocusChangeListener();
 
     emailEdit.addTextChangedListener(textChangedListener);
@@ -119,16 +136,32 @@ abstract public class FxAccountAbstractS
         (email.length() > 0) &&
         Patterns.EMAIL_ADDRESS.matcher(email).matches() &&
         (password.length() >= minimumPasswordLength);
     return enabled;
   }
 
   protected boolean updateButtonState() {
     boolean enabled = shouldButtonBeEnabled();
+    if (!enabled) {
+      // The user needs to do something before you can interact with the button;
+      // presumably that interaction will fix whatever error is shown.
+      hideRemoteError();
+    }
     if (enabled != button.isEnabled()) {
       Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
       button.setEnabled(enabled);
     }
-
     return enabled;
   }
+
+  @Override
+  public void showProgress() {
+    progressBar.setVisibility(View.VISIBLE);
+    button.setVisibility(View.INVISIBLE);
+  }
+
+  @Override
+  public void dismissProgress() {
+    progressBar.setVisibility(View.INVISIBLE);
+    button.setVisibility(View.VISIBLE);
+  }
 }
--- a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
@@ -81,17 +81,17 @@ public class FxAccountConfirmAccountActi
   }
 
   public static class FxAccountResendCodeTask extends FxAccountSetupTask<Void> {
     protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName();
 
     protected final byte[] sessionToken;
 
     public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient20 client, RequestDelegate<Void> delegate) {
-      super(context, false, client, delegate);
+      super(context, null, client, delegate);
       this.sessionToken = sessionToken;
     }
 
     @Override
     protected InnerRequestDelegate<Void> doInBackground(Void... arg0) {
       try {
         client.resendCode(sessionToken, innerDelegate);
         latch.await();
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -28,16 +28,17 @@ import android.app.Dialog;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 /**
  * Activity which displays create account screen to the user.
  */
 public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivity {
   protected static final String LOG_TAG = FxAccountCreateAccountActivity.class.getSimpleName();
 
@@ -53,22 +54,23 @@ public class FxAccountCreateAccountActiv
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_create_account);
 
     linkifyTextViews(null, new int[] { R.id.policy });
 
-    localErrorTextView = (TextView) ensureFindViewById(null, R.id.local_error, "local error text view");
     emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
     yearEdit = (EditText) ensureFindViewById(null, R.id.year_edit, "year edit");
-    button = (Button) ensureFindViewById(null, R.id.create_account_button, "create account button");
+    remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
+    button = (Button) ensureFindViewById(null, R.id.button, "create account button");
+    progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
 
     createCreateAccountButton();
     createYearEdit();
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
     View signInInsteadLink = ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link");
@@ -140,22 +142,22 @@ public class FxAccountCreateAccountActiv
     public CreateAccountDelegate(String email, String password, String serverURI) {
       this.email = email;
       this.password = password;
       this.serverURI = serverURI;
     }
 
     @Override
     public void handleError(Exception e) {
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
     }
 
     @Override
     public void handleFailure(final FxAccountClientRemoteException e) {
-      handleError(e);
+      showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
     }
 
     @Override
     public void handleSuccess(String uid) {
       Activity activity = FxAccountCreateAccountActivity.this;
       Logger.info(LOG_TAG, "Got success creating account.");
 
       // We're on the UI thread, but it's okay to create the account here.
@@ -204,19 +206,20 @@ public class FxAccountCreateAccountActiv
   }
 
   public void createAccount(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
     RequestDelegate<String> delegate = new CreateAccountDelegate(email, password, serverURI);
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
     try {
-      new FxAccountCreateAccountTask(this, email, password, client, delegate).execute();
+      hideRemoteError();
+      new FxAccountCreateAccountTask(this, this, email, password, client, delegate).execute();
     } catch (Exception e) {
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
     }
   }
 
   @Override
   protected boolean shouldButtonBeEnabled() {
     return super.shouldButtonBeEnabled() &&
         (yearEdit.length() > 0);
   }
--- a/mobile/android/base/fxa/activities/FxAccountSetupTask.java
+++ b/mobile/android/base/fxa/activities/FxAccountSetupTask.java
@@ -11,127 +11,127 @@ import java.util.concurrent.CountDownLat
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate;
 
-import android.app.ProgressDialog;
 import android.content.Context;
 import android.os.AsyncTask;
 
 /**
  * An <code>AsyncTask</code> wrapper around signing up for, and signing in to, a
  * Firefox Account.
  * <p>
  * It's strange to add explicit blocking to callback-threading code, but we do
  * it here to take advantage of Android's built in support for background work.
  * We really want to avoid making a threading mistake that brings down the whole
  * process.
  */
 abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> {
-  protected static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName();
+  private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName();
+
+  public interface ProgressDisplay {
+    public void showProgress();
+    public void dismissProgress();
+  }
 
   protected final Context context;
   protected final FxAccountClient20 client;
-
-  protected ProgressDialog progressDialog = null;
+  protected final ProgressDisplay progressDisplay;
 
   // Initialized lazily.
   protected byte[] quickStretchedPW;
 
   // AsyncTask's are one-time-use, so final members are fine.
   protected final CountDownLatch latch = new CountDownLatch(1);
   protected final InnerRequestDelegate<T> innerDelegate = new InnerRequestDelegate<T>(latch);
 
   protected final RequestDelegate<T> delegate;
 
-  public FxAccountSetupTask(Context context, boolean shouldShowProgressDialog, FxAccountClient20 client, RequestDelegate<T> delegate) {
+  public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient20 client, RequestDelegate<T> delegate) {
     this.context = context;
     this.client = client;
     this.delegate = delegate;
-    if (shouldShowProgressDialog) {
-      progressDialog = new ProgressDialog(context);
-    }
+    this.progressDisplay = progressDisplay;
   }
 
   @Override
   protected void onPreExecute() {
-    if (progressDialog != null) {
-      progressDialog.setTitle("Firefox Account..."); // XXX.
-      progressDialog.setMessage("Please wait.");
-      progressDialog.setCancelable(false);
-      progressDialog.setIndeterminate(true);
-      progressDialog.show();
+    if (progressDisplay != null) {
+      progressDisplay.showProgress();
     }
   }
 
   @Override
   protected void onPostExecute(InnerRequestDelegate<T> result) {
-    if (progressDialog != null) {
-      progressDialog.dismiss();
+    if (progressDisplay != null) {
+      progressDisplay.dismissProgress();
     }
 
     // We are on the UI thread, and need to invoke these callbacks here to allow UI updating.
-    if (innerDelegate.exception != null) {
+    if (innerDelegate.failure != null) {
+      delegate.handleFailure(innerDelegate.failure);
+    } else if (innerDelegate.exception != null) {
       delegate.handleError(innerDelegate.exception);
     } else {
       delegate.handleSuccess(result.response);
     }
   }
 
   @Override
   protected void onCancelled(InnerRequestDelegate<T> result) {
-    if (progressDialog != null) {
-      progressDialog.dismiss();
+    if (progressDisplay != null) {
+      progressDisplay.dismissProgress();
     }
     delegate.handleError(new IllegalStateException("Task was cancelled."));
   }
 
   protected static class InnerRequestDelegate<T> implements RequestDelegate<T> {
     protected final CountDownLatch latch;
     public T response = null;
     public Exception exception = null;
+    public FxAccountClientRemoteException failure = null;
 
     protected InnerRequestDelegate(CountDownLatch latch) {
       this.latch = latch;
     }
 
     @Override
     public void handleError(Exception e) {
       Logger.error(LOG_TAG, "Got exception.");
       this.exception = e;
       latch.countDown();
     }
 
     @Override
     public void handleFailure(FxAccountClientRemoteException e) {
       Logger.warn(LOG_TAG, "Got failure.");
-      this.exception = e;
+      this.failure = e;
       latch.countDown();
     }
 
     @Override
     public void handleSuccess(T result) {
       Logger.info(LOG_TAG, "Got success.");
       this.response = result;
       latch.countDown();
     }
   }
 
   public static class FxAccountCreateAccountTask extends FxAccountSetupTask<String> {
-    protected static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
+    private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
 
     protected final byte[] emailUTF8;
     protected final byte[] passwordUTF8;
 
-    public FxAccountCreateAccountTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException {
-      super(context, true, client, delegate);
+    public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException {
+      super(context, progressDisplay, client, delegate);
       this.emailUTF8 = email.getBytes("UTF-8");
       this.passwordUTF8 = password.getBytes("UTF-8");
     }
 
     /**
      * Stretching the password is expensive, so we compute the stretched value lazily.
      *
      * @return stretched password.
@@ -155,23 +155,23 @@ abstract class FxAccountSetupTask<T> ext
         Logger.error(LOG_TAG, "Got exception logging in.", e);
         delegate.handleError(e);
       }
       return null;
     }
   }
 
   public static class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> {
-    protected static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
+    protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName();
 
     protected final byte[] emailUTF8;
     protected final byte[] passwordUTF8;
 
-    public FxAccountSignInTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
-      super(context, true, client, delegate);
+    public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
+      super(context, progressDisplay, client, delegate);
       this.emailUTF8 = email.getBytes("UTF-8");
       this.passwordUTF8 = password.getBytes("UTF-8");
     }
 
     /**
      * Stretching the password is expensive, so we compute the stretched value lazily.
      *
      * @return stretched password.
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -24,16 +24,17 @@ import org.mozilla.gecko.sync.setup.Cons
 import android.accounts.AccountManager;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 /**
  * Activity which displays sign in screen to the user.
  */
 public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
   protected static final String LOG_TAG = FxAccountSignInActivity.class.getSimpleName();
 
@@ -44,21 +45,22 @@ public class FxAccountSignInActivity ext
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_sign_in);
 
-    localErrorTextView = (TextView) ensureFindViewById(null, R.id.local_error, "local error text view");
     emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
-    button = (Button) ensureFindViewById(null, R.id.sign_in_button, "sign in button");
+    remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
+    button = (Button) ensureFindViewById(null, R.id.button, "sign in button");
+    progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
 
     minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
     createSignInButton();
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
     View signInInsteadLink = ensureFindViewById(null, R.id.create_account_link, "create account instead link");
@@ -109,22 +111,22 @@ public class FxAccountSignInActivity ext
     public SignInDelegate(String email, String password, String serverURI) {
       this.email = email;
       this.password = password;
       this.serverURI = serverURI;
     }
 
     @Override
     public void handleError(Exception e) {
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
     }
 
     @Override
     public void handleFailure(FxAccountClientRemoteException e) {
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
     }
 
     @Override
     public void handleSuccess(LoginResponse result) {
       Activity activity = FxAccountSignInActivity.this;
       Logger.info(LOG_TAG, "Got success signing in.");
 
       // We're on the UI thread, but it's okay to create the account here.
@@ -179,19 +181,20 @@ public class FxAccountSignInActivity ext
   }
 
   public void signIn(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
     RequestDelegate<LoginResponse> delegate = new SignInDelegate(email, password, serverURI);
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
     try {
-      new FxAccountSignInTask(this, email, password, client, delegate).execute();
+      hideRemoteError();
+      new FxAccountSignInTask(this, this, email, password, client, delegate).execute();
     } catch (Exception e) {
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
     }
   }
 
   protected void createSignInButton() {
     button.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
         final String email = emailEdit.getText().toString();
--- a/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
@@ -14,23 +14,25 @@ import org.mozilla.gecko.fxa.login.Marri
 import org.mozilla.gecko.fxa.login.State;
 
 import android.accounts.Account;
 import android.content.ContentResolver;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.TextView;
+import android.widget.ViewFlipper;
 
 /**
  * Activity which displays account status.
  */
 public class FxAccountStatusActivity extends FxAccountAbstractActivity {
   protected static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
 
+  protected ViewFlipper connectionStatusViewFlipper;
   protected View connectionStatusUnverifiedView;
   protected View connectionStatusSignInView;
   protected View connectionStatusSyncingView;
   protected TextView emailTextView;
 
   public FxAccountStatusActivity() {
     super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
   }
@@ -41,17 +43,18 @@ public class FxAccountStatusActivity ext
   @Override
   public void onCreate(Bundle icicle) {
     Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_status);
 
-    connectionStatusUnverifiedView = ensureFindViewById(null, R.id.unverified_view, "unverified view");
+    connectionStatusViewFlipper = (ViewFlipper) ensureFindViewById(null, R.id.connection_status_view, "connection status frame layout");
+    connectionStatusUnverifiedView = ensureFindViewById(null, R.id.unverified_view, "unverified vie w");
     connectionStatusSignInView = ensureFindViewById(null, R.id.sign_in_view, "sign in view");
     connectionStatusSyncingView = ensureFindViewById(null, R.id.syncing_view, "syncing view");
 
     launchActivityOnClick(connectionStatusSignInView, FxAccountUpdateCredentialsActivity.class);
 
     emailTextView = (TextView) findViewById(R.id.email);
 
     if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
@@ -156,39 +159,29 @@ public class FxAccountStatusActivity ext
 
   @Override
   public void onResume() {
     super.onResume();
     refresh();
   }
 
   protected void showNeedsUpgrade() {
-    connectionStatusUnverifiedView.setVisibility(View.GONE);
-    connectionStatusSignInView.setVisibility(View.GONE);
-    connectionStatusSyncingView.setVisibility(View.GONE);
-  }
-
-  protected void showNeedsVerification() {
-    connectionStatusUnverifiedView.setVisibility(View.VISIBLE);
-    connectionStatusSignInView.setVisibility(View.GONE);
-    connectionStatusSyncingView.setVisibility(View.GONE);
+    connectionStatusViewFlipper.setDisplayedChild(0);
   }
 
   protected void showNeedsPassword() {
-    connectionStatusUnverifiedView.setVisibility(View.GONE);
-    connectionStatusSignInView.setVisibility(View.VISIBLE);
-    connectionStatusSyncingView.setVisibility(View.GONE);
-    return;
+    connectionStatusViewFlipper.setDisplayedChild(1);
+  }
+
+  protected void showNeedsVerification() {
+    connectionStatusViewFlipper.setDisplayedChild(2);
   }
 
   protected void showConnected() {
-    connectionStatusUnverifiedView.setVisibility(View.GONE);
-    connectionStatusSignInView.setVisibility(View.GONE);
-    connectionStatusSyncingView.setVisibility(View.VISIBLE);
-    return;
+    connectionStatusViewFlipper.setDisplayedChild(3);
   }
 
   protected void refresh(Account account) {
     if (account == null) {
       redirectToActivity(FxAccountGetStartedActivity.class);
       return;
     }
     emailTextView.setText(account.name);
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -26,16 +26,17 @@ import org.mozilla.gecko.fxa.login.State
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 
 import android.accounts.Account;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 /**
  * Activity which displays a screen for updating the local password.
  */
 public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
   protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName();
 
@@ -55,21 +56,22 @@ public class FxAccountUpdateCredentialsA
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_update_credentials);
 
-    localErrorTextView = (TextView) ensureFindViewById(null, R.id.local_error, "local error text view");
     emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
+    remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
     button = (Button) ensureFindViewById(null, R.id.button, "update credentials");
+    progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
 
     minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
     createButton();
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
     emailEdit.setEnabled(false);
@@ -117,23 +119,23 @@ public class FxAccountUpdateCredentialsA
       this.password = password;
       this.serverURI = serverURI;
       // XXX This needs to be calculated lazily.
       this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8"));
     }
 
     @Override
     public void handleError(Exception e) {
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
     }
 
     @Override
     public void handleFailure(FxAccountClientRemoteException e) {
       // TODO On isUpgradeRequired, transition to Doghouse state.
-      showRemoteError(e);
+      showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
     }
 
     @Override
     public void handleSuccess(LoginResponse result) {
       Logger.info(LOG_TAG, "Got success signing in.");
 
       if (fxAccount == null) {
         this.handleError(new IllegalStateException("fxAccount must not be null"));
@@ -158,20 +160,22 @@ public class FxAccountUpdateCredentialsA
     }
   }
 
   public void updateCredentials(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
     try {
+      hideRemoteError();
       RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, password, serverURI);
-      new FxAccountSignInTask(this, email, password, client, delegate).execute();
+      new FxAccountSignInTask(this, this, email, password, client, delegate).execute();
     } catch (Exception e) {
-      showRemoteError(e);
+      Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
+      showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
     }
   }
 
   protected void createButton() {
     button.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
         final String email = emailEdit.getText().toString();
--- a/mobile/android/base/resources/layout/fxaccount_create_account.xml
+++ b/mobile/android/base/resources/layout/fxaccount_create_account.xml
@@ -44,25 +44,30 @@
             style="@style/FxAccountEditItem"
             android:layout_marginTop="15dp"
             android:drawableRight="@drawable/fxaccount_ddarrow_inactive"
             android:focusable="false"
             android:hint="@string/fxaccount_year_of_birth"
             android:inputType="none" />
 
         <TextView
-            android:id="@+id/local_error"
+            android:id="@+id/remote_error"
             style="@style/FxAccountErrorItem" />
 
-        <Button
-            android:id="@+id/create_account_button"
-            style="@style/FxAccountButton"
-            android:layout_marginBottom="20dp"
-            android:layout_marginTop="45dp"
-            android:text="@string/fxaccount_create_account_button_label" />
+        <FrameLayout style="@style/FxAccountButtonLayout" >
+
+            <ProgressBar
+                android:id="@+id/progress"
+                style="@style/FxAccountProgress" />
+
+            <Button
+                android:id="@+id/button"
+                style="@style/FxAccountButton"
+                android:text="@string/fxaccount_create_account_button_label" />
+        </FrameLayout>
 
         <TextView
             android:id="@+id/sign_in_instead_link"
             style="@style/FxAccountLinkItem"
             android:layout_marginBottom="20dp"
             android:focusable="true"
             android:text="@string/fxaccount_sign_in_instead" />
 
@@ -73,9 +78,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_icon_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
+</ScrollView>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/fxaccount_email_password_view.xml
+++ b/mobile/android/base/resources/layout/fxaccount_email_password_view.xml
@@ -7,20 +7,16 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <LinearLayout
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical" >
 
-        <TextView
-            android:id="@+id/local_error"
-            style="@style/FxAccountErrorItem" />
-
         <EditText
             android:id="@+id/email"
             style="@style/FxAccountEditItem"
             android:layout_marginBottom="15dp"
             android:ems="10"
             android:hint="@string/fxaccount_email_hint"
             android:inputType="textEmailAddress" >
 
--- a/mobile/android/base/resources/layout/fxaccount_sign_in.xml
+++ b/mobile/android/base/resources/layout/fxaccount_sign_in.xml
@@ -21,22 +21,31 @@
             android:text="@string/firefox_accounts" />
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_sign_in" />
 
         <include layout="@layout/fxaccount_email_password_view" />
 
-        <Button
-            android:id="@+id/sign_in_button"
-            style="@style/FxAccountButton"
-            android:layout_marginBottom="20dp"
-            android:layout_marginTop="45dp"
-            android:text="@string/fxaccount_sign_in_button_label" />
+        <TextView
+            android:id="@+id/remote_error"
+            style="@style/FxAccountErrorItem" />
+
+        <FrameLayout style="@style/FxAccountButtonLayout" >
+
+            <ProgressBar
+                android:id="@+id/progress"
+                style="@style/FxAccountProgress" />
+
+            <Button
+                android:id="@+id/button"
+                style="@style/FxAccountButton"
+                android:text="@string/fxaccount_sign_in_button_label" />
+        </FrameLayout>
 
         <LinearLayout
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
             android:paddingLeft="10dp"
             android:paddingRight="10dp" >
 
@@ -59,9 +68,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_icon_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
+</ScrollView>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/fxaccount_status.xml
+++ b/mobile/android/base/resources/layout/fxaccount_status.xml
@@ -28,17 +28,17 @@
             android:id="@+id/change_password"
             style="@style/FxAccountLinkItem"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="10dp"
             android:text="@string/fxaccount_change_password" >
         </TextView>
 
-        <FrameLayout
+        <ViewFlipper
             android:id="@+id/connection_status_view"
             style="@style/FxAccountTextItem"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="10dp" >
 
             <TextView
                 android:id="@+id/unverified_view"
@@ -47,52 +47,64 @@
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
                 android:layout_marginBottom="10dp"
                 android:background="#fad4d2"
                 android:drawablePadding="10dp"
                 android:drawableStart="@drawable/fxaccount_sync_error"
                 android:gravity="center_vertical"
                 android:padding="10dp"
-                android:text="@string/fxaccount_status_needs_verification"
-                android:visibility="gone" >
+                android:text="@string/fxaccount_status_needs_upgrade" >
             </TextView>
 
             <TextView
                 android:id="@+id/sign_in_view"
                 style="@style/FxAccountTextItem"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
                 android:layout_marginBottom="10dp"
                 android:background="#fad4d2"
                 android:drawablePadding="10dp"
                 android:drawableStart="@drawable/fxaccount_sync_error"
                 android:gravity="center_vertical"
                 android:padding="10dp"
-                android:text="@string/fxaccount_status_needs_credentials"
-                android:visibility="gone" >
+                android:text="@string/fxaccount_status_needs_credentials" >
+            </TextView>
+
+            <TextView
+                android:id="@+id/unverified_view"
+                style="@style/FxAccountTextItem"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginBottom="10dp"
+                android:background="#fad4d2"
+                android:drawablePadding="10dp"
+                android:drawableStart="@drawable/fxaccount_sync_error"
+                android:gravity="center_vertical"
+                android:padding="10dp"
+                android:text="@string/fxaccount_status_needs_verification" >
             </TextView>
 
             <TextView
                 android:id="@+id/syncing_view"
                 style="@style/FxAccountTextItem"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
                 android:layout_marginBottom="10dp"
                 android:background="#d1e7fe"
                 android:drawablePadding="10dp"
                 android:drawableStart="@drawable/fxaccount_sync_icon"
                 android:gravity="center_vertical"
                 android:padding="10dp"
-                android:text="@string/fxaccount_status_syncing"
-                android:visibility="visible" >
+                android:text="@string/fxaccount_status_syncing" >
             </TextView>
-        </FrameLayout>
+        </ViewFlipper>
 
         <TextView
             style="@style/FxAccountHeaderItem"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="10dp"
             android:text="@string/fxaccount_status_sync" >
         </TextView>
--- a/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
+++ b/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
@@ -21,28 +21,37 @@
             android:text="@string/firefox_accounts" />
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_update_credentials" />
 
         <include layout="@layout/fxaccount_email_password_view" />
 
-        <Button
-            android:id="@+id/button"
-            style="@style/FxAccountButton"
-            android:layout_marginBottom="20dp"
-            android:layout_marginTop="45dp"
-            android:text="@string/fxaccount_update_credentials_button_label" />
+        <TextView
+            android:id="@+id/remote_error"
+            style="@style/FxAccountErrorItem" />
+
+        <FrameLayout style="@style/FxAccountButtonLayout" >
+
+            <ProgressBar
+                android:id="@+id/progress"
+                style="@style/FxAccountProgress" />
+
+            <Button
+                android:id="@+id/button"
+                style="@style/FxAccountButton"
+                android:text="@string/fxaccount_update_credentials_button_label" />
+        </FrameLayout>
 
         <TextView
             android:id="@+id/forgot_password_link"
             style="@style/FxAccountLinkItem"
             android:text="@string/fxaccount_forgot_password" />
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_icon_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
+</ScrollView>
\ No newline at end of file
--- a/mobile/android/base/resources/values/fxaccount_styles.xml
+++ b/mobile/android/base/resources/values/fxaccount_styles.xml
@@ -86,21 +86,38 @@
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_gravity">center_horizontal</item>
         <item name="android:src">@drawable/fxaccount_icon</item>
     </style>
 
     <style name="FxAccountErrorItem">
-        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_marginBottom">10dp</item>
+        <item name="android:layout_marginLeft">@dimen/fxaccount_corner_radius</item>
+        <item name="android:layout_marginRight">@dimen/fxaccount_corner_radius</item>
+        <item name="android:layout_marginTop">30dp</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_gravity">left</item>
-        <item name="android:layout_margin">5dp</item>
         <item name="android:background">@drawable/fxaccount_textview_error_background</item>
         <item name="android:padding">5dp</item>
         <item name="android:text">Error</item>
         <item name="android:textColor">@android:color/white</item>
         <item name="android:textSize">18sp</item>
         <item name="android:visibility">invisible</item>
     </style>
 
-</resources>
+    <style name="FxAccountProgress">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:background">@drawable/fxaccount_button_background</item>
+        <item name="android:visibility">invisible</item>
+    </style>
+
+    <style name="FxAccountButtonLayout">
+        <item name="android:orientation">vertical</item>
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginBottom">20dp</item>
+    </style>
+
+</resources>
\ No newline at end of file
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -137,16 +137,17 @@
   <string name="fxaccount_sign_up">&fxaccount.sign.up;</string>
   <string name="fxaccount_intro_contentDescription">&fxaccount.intro.contentDescription;</string>
   <string name="fxaccount_offer_resend_confirmation_email">&fxaccount.offer.resend.confirmation.email;</string>
   <string name="fxaccount_confirmation_description">&fxaccount.confirmation.description;</string>
   <string name="fxaccount_mail_contentDescription">&fxaccount.mail.contentDescription;</string>
   <string name="fxaccount_confirmation_email_sent">&fxaccount.confirmation.email.sent;</string>
   <string name="fxaccount_password_length_restriction">&fxaccount.password.length.restriction;</string>
   <string name="fxaccount_change_password">&fxaccount.change.password;</string>
+  <string name="fxaccount_status_needs_upgrade">You need to upgrade Firefox to log in to this account.</string>
   <string name="fxaccount_status_needs_verification">&fxaccount.status.needs.verification;</string>
   <string name="fxaccount_status_needs_credentials">&fxaccount.status.needs.credentials;</string>
   <string name="fxaccount_status_syncing">&fxaccount.status.syncing;</string>
   <string name="fxaccount_status_sync">&fxaccount.status.sync;</string>
   <string name="fxaccount_status_bookmarks">&fxaccount.status.bookmarks;</string>
   <string name="fxaccount_status_history">&fxaccount.status.history;</string>
   <string name="fxaccount_status_passwords">&fxaccount.status.passwords;</string>
   <string name="fxaccount_status_tabs">&fxaccount.status.tabs;</string>
@@ -182,8 +183,20 @@
   <string name="fxaccount_confirm_verification_link_sent">Sent fresh verification link</string>
   <string name="fxaccount_confirm_verification_link_not_sent">Couldn\&apos;t send a fresh verification link</string>
   <string name="fxaccount_status_debug_refresh_button_label">Refresh status view</string>
   <string name="fxaccount_status_debug_dump_button_label">Dump account details</string>
   <string name="fxaccount_status_debug_sync_button_label">Force sync</string>
   <string name="fxaccount_status_debug_forget_certificate_button_label">Forget certificate (if applicable)</string>
   <string name="fxaccount_status_debug_require_password_button_label">Require password re-entry</string>
   <string name="fxaccount_status_debug_require_upgrade_button_label">Require upgrade</string>
+  <string name="fxaccount_create_account_unknown_error">Could not create account</string>
+  <string name="fxaccount_sign_in_unknown_error">Could not sign in</string>
+  <string name="fxaccount_update_credentials_unknown_error">Could not update password</string>
+  <string name="fxaccount_remote_error_UPGRADE_REQUIRED">You need to upgrade Firefox</string>
+  <string name="fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS">Account already exists</string>
+  <string name="fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST">Account does not exist</string>
+  <string name="fxaccount_remote_error_INCORRECT_PASSWORD">Bad password</string>
+  <string name="fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT">Account is not verified</string>
+  <string name="fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS">Try again later</string>
+  <string name="fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD">Try again later</string>
+  <string name="fxaccount_remote_error_UNKNOWN_ERROR">There was a problem</string>
+  <string name="fxaccount_remote_error_COULD_NOT_CONNECT">Couldn\&apos;t connect to the internet</string>