Bug 1022748 - Part 2: Use custom server URLs in setup activities. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Sat, 21 Jun 2014 16:21:41 -0700
changeset 189832 e1f0b5ef74dc75e45b39def9efe13a1dbfe600ce
parent 189831 9da481fd4b5d72d0ef5c09ed8c1aeec3fc2d109e
child 189833 3908f68f75901f50f920d986c9af6780229236f6
push id26998
push userttaubert@mozilla.com
push dateMon, 23 Jun 2014 12:37:15 +0000
treeherdermozilla-central@335b6610fe0c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1022748
milestone33.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 1022748 - Part 2: Use custom server URLs in setup activities. r=rnewman ======== https://github.com/mozilla-services/android-sync/commit/2582a39b7fa4d0c7acf8f01f09da1dc6e7dc2c42 Author: Nick Alexander <nalexander@mozilla.com> Bug 1022748 - Review comments. ======== https://github.com/mozilla-services/android-sync/commit/8c9a8df84fef42ac257e825aeadb1ce1831a7eb6 Author: Nick Alexander <nalexander@mozilla.com> Bug 1022748 - Part 4: Don't show Get Started activity when custom server URLs are passed. The Get Started activity provides no feedback about custom URLs, and it just gets in the way of power users, so skip it entirely. ======== https://github.com/mozilla-services/android-sync/commit/2701a9ea521a43d55080e6eb88b64dbae2e3b85f Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Jun 9 17:25:51 2014 -0700 Bug 1022748 - Part 3: Pass through server details to update credentials activity. This information can be extracted from the AndroidFxAccount object in the activity, but feeding the information through extras keeps the processing and display pipeline uniform across all setup activities. ======== https://github.com/mozilla-services/android-sync/commit/b9dd884764d8a5bc683e04be580151010b302556 Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Jun 9 17:17:57 2014 -0700 Bug 1022748 - Part 2: Use custom server URLs passed through extras in setup activities. ======== https://github.com/mozilla-services/android-sync/commit/be8284ea25bf1709263ac034cf54f141ab61aac7 Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Jun 9 15:06:03 2014 -0700 Bug 1022748 - Part 1: Pass extras through setup activities. This also passes the password button's show/hide state between create and set up.
mobile/android/base/fxa/FxAccountConstants.java.in
mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
mobile/android/base/fxa/activities/FxAccountSignInActivity.java
mobile/android/base/fxa/activities/FxAccountStatusFragment.java
mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
--- a/mobile/android/base/fxa/FxAccountConstants.java.in
+++ b/mobile/android/base/fxa/FxAccountConstants.java.in
@@ -10,16 +10,17 @@ import org.mozilla.gecko.background.comm
 
 public class FxAccountConstants {
   public static final String GLOBAL_LOG_TAG = "FxAccounts";
   public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
 
   public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
   public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
 
+  public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
   public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
 
   // For extra debugging.  Not final so it can be changed from Fennec, or from
   // an add-on.
   public static boolean LOG_PERSONAL_INFORMATION = false;
 
   public static void pii(String tag, String message) {
     if (LOG_PERSONAL_INFORMATION) {
--- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
@@ -18,25 +18,27 @@ import org.mozilla.gecko.background.fxa.
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
 import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
 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.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.SingleLineTransformationMethod;
 import android.util.Patterns;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -45,16 +47,26 @@ import android.widget.ArrayAdapter;
 import android.widget.AutoCompleteTextView;
 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 implements ProgressDisplay {
+  public static final String EXTRA_EMAIL = "email";
+  public static final String EXTRA_PASSWORD = "password";
+  public static final String EXTRA_PASSWORD_SHOWN = "password_shown";
+  public static final String EXTRA_YEAR = "year";
+  public static final String EXTRA_EXTRAS = "extras";
+
+  public static final String JSON_KEY_AUTH = "auth";
+  public static final String JSON_KEY_SERVICES = "services";
+  public static final String JSON_KEY_SYNC = "sync";
+
   public FxAccountAbstractSetupActivity() {
     super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
   }
 
   protected FxAccountAbstractSetupActivity(int resume) {
     super(resume);
   }
 
@@ -64,43 +76,57 @@ abstract public class FxAccountAbstractS
 
   protected AutoCompleteTextView emailEdit;
   protected EditText passwordEdit;
   protected Button showPasswordButton;
   protected TextView remoteErrorTextView;
   protected Button button;
   protected ProgressBar progressBar;
 
+  private String authServerEndpoint;
+  private String syncServerEndpoint;
+
+  protected String getAuthServerEndpoint() {
+    return authServerEndpoint;
+  }
+
+  protected String getTokenServerEndpoint() {
+    return syncServerEndpoint;
+  }
+
   protected void createShowPasswordButton() {
     showPasswordButton.setOnClickListener(new OnClickListener() {
-      @SuppressWarnings("deprecation")
       @Override
       public void onClick(View v) {
         boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
-
-        // Changing input type loses position in edit text; let's try to maintain it.
-        int start = passwordEdit.getSelectionStart();
-        int stop = passwordEdit.getSelectionEnd();
-
-        if (isShown) {
-          passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
-          showPasswordButton.setText(R.string.fxaccount_password_show);
-          showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
-          showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
-        } else {
-          passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
-          showPasswordButton.setText(R.string.fxaccount_password_hide);
-          showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
-          showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
-        }
-        passwordEdit.setSelection(start, stop);
+        setPasswordButtonShown(!isShown);
       }
     });
   }
 
+  @SuppressWarnings("deprecation")
+  protected void setPasswordButtonShown(boolean shouldShow) {
+    // Changing input type loses position in edit text; let's try to maintain it.
+    int start = passwordEdit.getSelectionStart();
+    int stop = passwordEdit.getSelectionEnd();
+
+    if (!shouldShow) {
+      passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
+      showPasswordButton.setText(R.string.fxaccount_password_show);
+      showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
+      showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
+    } else {
+      passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
+      showPasswordButton.setText(R.string.fxaccount_password_hide);
+      showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
+      showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
+    }
+    passwordEdit.setSelection(start, stop);
+  }
+
   protected void linkifyPolicy() {
     TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
     final String linkTerms = getString(R.string.fxaccount_link_tos);
     final String linkPrivacy = getString(R.string.fxaccount_link_pn);
     final String linkedTOS = "<a href=\"" + linkTerms + "\">" + getString(R.string.fxaccount_policy_linktos) + "</a>";
     final String linkedPN = "<a href=\"" + linkPrivacy + "\">" + getString(R.string.fxaccount_policy_linkprivacy) + "</a>";
     policyView.setText(getString(R.string.fxaccount_create_account_policy_text, linkedTOS, linkedPN));
     final boolean underlineLinks = true;
@@ -257,17 +283,17 @@ abstract public class FxAccountAbstractS
     @Override
     public void handleSuccess(LoginResponse result) {
       Logger.info(LOG_TAG, "Got success response; adding Android account.");
 
       // We're on the UI thread, but it's okay to create the account here.
       AndroidFxAccount fxAccount;
       try {
         final String profile = Constants.DEFAULT_PROFILE;
-        final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
+        final String tokenServerURI = getTokenServerEndpoint();
         // It is crucial that we use the email address provided by the server
         // (rather than whatever the user entered), because the user's keys are
         // wrapped and salted with the initial email they provided to
         // /create/account. Of course, we want to pass through what the user
         // entered locally as much as possible, so we create the Android account
         // with their entered email address, etc.
         // The passwordStretcher should have seen this email address before, so
         // we shouldn't be calculating the expensive stretch twice.
@@ -352,22 +378,127 @@ abstract public class FxAccountAbstractS
     final String[] sortedEmails = emails.toArray(new String[0]);
     Arrays.sort(sortedEmails);
 
     final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, sortedEmails);
     emailEdit.setAdapter(adapter);
   }
 
   @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+  }
+
+  protected void updateFromIntentExtras() {
+    // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
+    if (getIntent() != null && getIntent().getExtras() != null) {
+      Bundle bundle = getIntent().getExtras();
+      emailEdit.setText(bundle.getString(EXTRA_EMAIL));
+      passwordEdit.setText(bundle.getString(EXTRA_PASSWORD));
+      setPasswordButtonShown(bundle.getBoolean(EXTRA_PASSWORD_SHOWN, false));
+    }
+
+    // This sets defaults as well as extracting from extras, so it's not conditional.
+    updateServersFromIntentExtras(getIntent());
+
+    if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
+      FxAccountConstants.pii(LOG_TAG, "Using auth server: " + authServerEndpoint);
+      FxAccountConstants.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint);
+    }
+  }
+
+  @Override
   public void onResume() {
     super.onResume();
 
     // Getting Accounts accesses databases on disk, so needs to be done on a
     // background thread.
     final GetAccountsAsyncTask task = new GetAccountsAsyncTask(this) {
       @Override
       public void onPostExecute(Account[] accounts) {
         populateEmailAddressAutocomplete(accounts);
       }
     };
     task.execute();
   }
+
+  protected Bundle makeExtrasBundle(String email, String password) {
+    final Bundle bundle = new Bundle();
+
+    // Pass through any extras that we were started with.
+    if (getIntent() != null && getIntent().getExtras() != null) {
+      bundle.putAll(getIntent().getExtras());
+    }
+
+    // Overwrite with current settings.
+    if (email == null) {
+      email = emailEdit.getText().toString();
+    }
+    if (password == null) {
+      password = passwordEdit.getText().toString();
+    }
+    bundle.putString(EXTRA_EMAIL, email);
+    bundle.putString(EXTRA_PASSWORD, password);
+
+    boolean isPasswordShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
+    bundle.putBoolean(EXTRA_PASSWORD_SHOWN, isPasswordShown);
+
+    return bundle;
+  }
+
+  protected void startActivityInstead(Class<?> cls, int requestCode, Bundle extras) {
+    Intent intent = new Intent(this, cls);
+    if (extras != null) {
+      intent.putExtras(extras);
+    }
+    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
+    // the soft keyboard not being shown for the started activity. Why, Android, why?
+    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+    startActivityForResult(intent, requestCode);
+  }
+
+  protected void updateServersFromIntentExtras(Intent intent) {
+    // Start with defaults.
+    this.authServerEndpoint = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
+    this.syncServerEndpoint = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
+
+    if (intent == null) {
+      Logger.warn(LOG_TAG, "Intent is null; ignoring and using default servers.");
+      return;
+    }
+
+    final String extrasString = intent.getStringExtra(EXTRA_EXTRAS);
+
+    if (extrasString == null) {
+      return;
+    }
+
+    final ExtendedJSONObject extras;
+    final ExtendedJSONObject services;
+    try {
+      extras = new ExtendedJSONObject(extrasString);
+      services = extras.getObject(JSON_KEY_SERVICES);
+    } catch (Exception e) {
+      Logger.warn(LOG_TAG, "Got exception parsing extras; ignoring and using default servers.");
+      return;
+    }
+
+    String authServer = extras.getString(JSON_KEY_AUTH);
+    String syncServer = services == null ? null : services.getString(JSON_KEY_SYNC);
+
+    if (authServer != null) {
+      this.authServerEndpoint = authServer;
+    }
+    if (syncServer != null) {
+      this.syncServerEndpoint = syncServer;
+    }
+
+    if (FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint) &&
+        !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint)) {
+      // We really don't want to hard-code assumptions about server
+      // configurations into client code in such a way that if and when the
+      // situation is relaxed, the client code stops valid usage. Instead, we
+      // warn. This configuration should present itself as an auth exception at
+      // Sync time.
+      Logger.warn(LOG_TAG, "Mozilla's Sync token servers only works with Mozilla's auth servers. Sync will likely be mis-configured.");
+    }
+  }
 }
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -84,42 +84,39 @@ public class FxAccountCreateAccountActiv
     createShowPasswordButton();
     linkifyPolicy();
     createChooseCheckBox();
 
     View signInInsteadLink = ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link");
     signInInsteadLink.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        final String email = emailEdit.getText().toString();
-        final String password = passwordEdit.getText().toString();
-        doSigninInstead(email, password);
+        final Bundle extras = makeExtrasBundle(null, null);
+        startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
       }
     });
 
-    // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
-    if (getIntent() != null && getIntent().getExtras() != null) {
-      Bundle bundle = getIntent().getExtras();
-      emailEdit.setText(bundle.getString("email"));
-      passwordEdit.setText(bundle.getString("password"));
-    }
+    updateFromIntentExtras();
   }
 
-  protected void doSigninInstead(final String email, final String password) {
-    Intent intent = new Intent(this, FxAccountSignInActivity.class);
-    if (email != null) {
-      intent.putExtra("email", email);
+  @Override
+  protected Bundle makeExtrasBundle(String email, String password) {
+    final Bundle extras = super.makeExtrasBundle(email, password);
+    final String year = yearEdit.getText().toString();
+    extras.putString(EXTRA_YEAR, year);
+    return extras;
+  }
+
+  @Override
+  protected void updateFromIntentExtras() {
+    super.updateFromIntentExtras();
+
+    if (getIntent() != null) {
+      yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR));
     }
-    if (password != null) {
-      intent.putExtra("password", password);
-    }
-    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-    // the soft keyboard not being shown for the started activity. Why, Android, why?
-    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-    startActivityForResult(intent, CHILD_REQUEST_CODE);
   }
 
   @Override
   protected void showClientRemoteException(final FxAccountClientRemoteException e) {
     if (!e.isAccountAlreadyExists()) {
       super.showClientRemoteException(e);
       return;
     }
@@ -137,17 +134,19 @@ public class FxAccountCreateAccountActiv
       @Override
       public void onClick(View widget) {
         // Pass through the email address that already existed.
         String email = e.body.getString("email");
         if (email == null) {
             email = emailEdit.getText().toString();
         }
         final String password = passwordEdit.getText().toString();
-        doSigninInstead(email, password);
+
+        final Bundle extras = makeExtrasBundle(email, password);
+        startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
       }
     }, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
     remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
     remoteErrorTextView.setText(span);
   }
 
   /**
    * We might have switched to the SignIn activity; if that activity
@@ -200,17 +199,17 @@ public class FxAccountCreateAccountActiv
         .create();
 
         dialog.show();
       }
     });
   }
 
   public void createAccount(String email, String password, Map<String, Boolean> engines) {
-    String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
+    String serverURI = getAuthServerEndpoint();
     PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     // This delegate creates a new Android account on success, opens the
     // appropriate "success!" activity, and finishes this activity.
     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI, engines) {
       @Override
       public void handleError(Exception e) {
         showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
       }
--- a/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
@@ -45,25 +45,36 @@ public class FxAccountGetStartedActivity
     setContentView(R.layout.fxaccount_get_started);
 
     linkifyOldFirefoxLink();
 
     View button = findViewById(R.id.get_started_button);
     button.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        Intent intent = new Intent(FxAccountGetStartedActivity.this, FxAccountCreateAccountActivity.class);
-        // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-        // the soft keyboard not being shown for the started activity. Why, Android, why?
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        startActivityForResult(intent, CHILD_REQUEST_CODE);
+        Bundle extras = null; // startFlow accepts null.
+        if (getIntent() != null) {
+          extras = getIntent().getExtras();
+        }
+        startFlow(extras);
       }
     });
   }
 
+  protected void startFlow(Bundle extras) {
+    final Intent intent = new Intent(this, FxAccountCreateAccountActivity.class);
+    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
+    // the soft keyboard not being shown for the started activity. Why, Android, why?
+    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+    if (extras != null) {
+        intent.putExtras(extras);
+    }
+    startActivityForResult(intent, CHILD_REQUEST_CODE);
+  }
+
   @Override
   public void onResume() {
     super.onResume();
 
     Intent intent = null;
     if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) {
       intent = new Intent(this, FxAccountCreateAccountNotAllowedActivity.class);
     } else if (FirefoxAccounts.firefoxAccountsExist(this)) {
@@ -74,16 +85,30 @@ public class FxAccountGetStartedActivity
       this.setAccountAuthenticatorResult(null);
       setResult(RESULT_CANCELED);
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
       this.startActivity(intent);
       this.finish();
     }
+
+    // If we've been launched with extras (namely custom server URLs), continue
+    // past go and collect 200 dollars. If we ever get back here (for example,
+    // if the user hits the back button), forget that we had extras entirely, so
+    // that we don't enter a loop.
+    Bundle extras = null;
+    if (getIntent() != null) {
+      extras = getIntent().getExtras();
+    }
+    if (extras != null && extras.containsKey(FxAccountAbstractSetupActivity.EXTRA_EXTRAS)) {
+      getIntent().replaceExtras(Bundle.EMPTY);
+      startFlow((Bundle) extras.clone());
+      return;
+    }
   }
 
   /**
    * We started the CreateAccount activity for a result; this returns it to the
    * authenticator.
    */
   @Override
   public void onActivityResult(int requestCode, int resultCode, Intent data) {
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -10,17 +10,16 @@ import java.util.concurrent.Executors;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 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.PasswordStretcher;
-import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.AutoCompleteTextView;
@@ -60,32 +59,22 @@ public class FxAccountSignInActivity ext
     updateButtonState();
     createShowPasswordButton();
     linkifyPolicy();
 
     View createAccountInsteadLink = ensureFindViewById(null, R.id.create_account_link, "create account instead link");
     createAccountInsteadLink.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        Intent intent = new Intent(FxAccountSignInActivity.this, FxAccountCreateAccountActivity.class);
-        intent.putExtra("email", emailEdit.getText().toString());
-        intent.putExtra("password", passwordEdit.getText().toString());
-        // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-        // the soft keyboard not being shown for the started activity. Why, Android, why?
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        startActivityForResult(intent, CHILD_REQUEST_CODE);
+        final Bundle extras = makeExtrasBundle(null, null);
+        startActivityInstead(FxAccountCreateAccountActivity.class, CHILD_REQUEST_CODE, extras);
       }
     });
 
-    // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
-    if (getIntent() != null && getIntent().getExtras() != null) {
-      Bundle bundle = getIntent().getExtras();
-      emailEdit.setText(bundle.getString("email"));
-      passwordEdit.setText(bundle.getString("password"));
-    }
+    updateFromIntentExtras();
 
     TextView view = (TextView) findViewById(R.id.forgot_password_link);
     ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
   }
 
   /**
    * We might have switched to the CreateAccount activity; if that activity
    * succeeds, feed its result back to the authenticator.
@@ -97,17 +86,17 @@ public class FxAccountSignInActivity ext
       super.onActivityResult(requestCode, resultCode, data);
       return;
     }
     this.setResult(resultCode, data);
     this.finish();
   }
 
   public void signIn(String email, String password) {
-    String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
+    String serverURI = getAuthServerEndpoint();
     PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     // This delegate creates a new Android account on success, opens the
     // appropriate "success!" activity, and finishes this activity.
     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) {
       @Override
       public void handleError(Exception e) {
         showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
       }
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -13,16 +13,17 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.preferences.PreferenceFragment;
 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.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 
 import android.accounts.Account;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -147,16 +148,20 @@ public class FxAccountStatusFragment
   public void onResume() {
     super.onResume();
   }
 
   @Override
   public boolean onPreferenceClick(Preference preference) {
     if (preference == needsPasswordPreference) {
       Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
+      final Bundle extras = getExtrasForAccount();
+      if (extras != null) {
+        intent.putExtras(extras);
+      }
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
       startActivity(intent);
 
       return true;
     }
 
@@ -185,16 +190,27 @@ public class FxAccountStatusFragment
         preference == tabsPreference) {
       saveEngineSelections();
       return true;
     }
 
     return false;
   }
 
+  protected Bundle getExtrasForAccount() {
+    final Bundle extras = new Bundle();
+    final ExtendedJSONObject o = new ExtendedJSONObject();
+    o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI());
+    final ExtendedJSONObject services = new ExtendedJSONObject();
+    services.put(FxAccountAbstractSetupActivity.JSON_KEY_SYNC, fxAccount.getTokenServerURI());
+    o.put(FxAccountAbstractSetupActivity.JSON_KEY_SERVICES, services);
+    extras.putString(FxAccountAbstractSetupActivity.EXTRA_EXTRAS, o.toJSONString());
+    return extras;
+  }
+
   protected void setCheckboxesEnabled(boolean enabled) {
     bookmarksPreference.setEnabled(enabled);
     historyPreference.setEnabled(enabled);
     tabsPreference.setEnabled(enabled);
     passwordsPreference.setEnabled(enabled);
     // Since we can't sync, we can't update our remote client record.
     deviceNamePreference.setEnabled(enabled);
   }
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -72,16 +72,18 @@ public class FxAccountUpdateCredentialsA
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
     emailEdit.setEnabled(false);
 
     TextView view = (TextView) findViewById(R.id.forgot_password_link);
     ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
+
+    updateFromIntentExtras();
   }
 
   @Override
   public void onResume() {
     super.onResume();
     this.fxAccount = getAndroidFxAccount();
     if (fxAccount == null) {
       Logger.warn(LOG_TAG, "Could not get Firefox Account.");