Bug 1098667 - Part 1: Import from android-sync. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Tue, 25 Nov 2014 16:18:53 -0800
changeset 243920 ae7ca57ce7609cabeed7cbb87bb6cdf23d174ea1
parent 243919 bb478d8622ab3a701c9138d1b020bc22249481e7
child 243921 3397a68199cc4cd46fdb217f00dd2237807a5683
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1098667
milestone37.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 1098667 - Part 1: Import from android-sync. r=rnewman
mobile/android/base/android-services.mozbuild
mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
mobile/android/base/fxa/activities/FxAccountFinishMigratingActivity.java
mobile/android/base/fxa/activities/FxAccountMigrationFinishedActivity.java
mobile/android/base/fxa/activities/FxAccountStatusActivity.java
mobile/android/base/fxa/activities/FxAccountStatusFragment.java
mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
mobile/android/base/fxa/login/MigratedFromSync11.java
mobile/android/base/fxa/login/State.java
mobile/android/base/fxa/login/StateFactory.java
mobile/android/base/fxa/sync/FxAccountNotificationManager.java
mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java
mobile/android/base/locales/en-US/sync_strings.dtd
mobile/android/base/resources/layout/fxaccount_finish_migrating.xml
mobile/android/base/resources/layout/fxaccount_migration_finished.xml
mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
mobile/android/services/strings.xml.in
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -833,20 +833,23 @@ sync_java_files = [
     'browserid/verifier/BrowserIDRemoteVerifierClient.java',
     'browserid/verifier/BrowserIDVerifierClient.java',
     'browserid/verifier/BrowserIDVerifierDelegate.java',
     'browserid/verifier/BrowserIDVerifierException.java',
     'browserid/VerifyingPublicKey.java',
     'fxa/AccountLoader.java',
     'fxa/activities/FxAccountAbstractActivity.java',
     'fxa/activities/FxAccountAbstractSetupActivity.java',
+    'fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java',
     'fxa/activities/FxAccountConfirmAccountActivity.java',
     'fxa/activities/FxAccountCreateAccountActivity.java',
     'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
+    'fxa/activities/FxAccountFinishMigratingActivity.java',
     'fxa/activities/FxAccountGetStartedActivity.java',
+    'fxa/activities/FxAccountMigrationFinishedActivity.java',
     'fxa/activities/FxAccountSignInActivity.java',
     'fxa/activities/FxAccountStatusActivity.java',
     'fxa/activities/FxAccountStatusFragment.java',
     'fxa/activities/FxAccountUpdateCredentialsActivity.java',
     'fxa/activities/FxAccountVerifiedAccountActivity.java',
     'fxa/authenticator/AccountPickler.java',
     'fxa/authenticator/AndroidFxAccount.java',
     'fxa/authenticator/FxAccountAuthenticator.java',
@@ -856,16 +859,17 @@ sync_java_files = [
     'fxa/FirefoxAccounts.java',
     'fxa/login/BaseRequestDelegate.java',
     'fxa/login/Cohabiting.java',
     'fxa/login/Doghouse.java',
     'fxa/login/Engaged.java',
     'fxa/login/FxAccountLoginStateMachine.java',
     'fxa/login/FxAccountLoginTransition.java',
     'fxa/login/Married.java',
+    'fxa/login/MigratedFromSync11.java',
     'fxa/login/Separated.java',
     'fxa/login/State.java',
     'fxa/login/StateFactory.java',
     'fxa/login/TokensAndKeysState.java',
     'fxa/receivers/FxAccountDeletedReceiver.java',
     'fxa/receivers/FxAccountDeletedService.java',
     'fxa/receivers/FxAccountUpgradeReceiver.java',
     'fxa/sync/FxAccountGlobalSession.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
@@ -0,0 +1,181 @@
+/* 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.util.concurrent.Executor;
+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.FxAccountUtils;
+import org.mozilla.gecko.background.fxa.PasswordStretcher;
+import org.mozilla.gecko.fxa.FirefoxAccounts;
+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.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;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Abstract activity which displays a screen for updating the local password.
+ */
+public abstract class FxAccountAbstractUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
+  protected static final String LOG_TAG = FxAccountAbstractUpdateCredentialsActivity.class.getSimpleName();
+
+  protected AndroidFxAccount fxAccount;
+
+  protected final int layoutResourceId;
+
+  public FxAccountAbstractUpdateCredentialsActivity(int layoutResourceId) {
+    // We want to share code with the other setup activities, but this activity
+    // doesn't create a new Android Account, it modifies an existing one. If you
+    // manage to get an account, and somehow be locked out too, we'll let you
+    // update it.
+    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
+    this.layoutResourceId = layoutResourceId;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onCreate(Bundle icicle) {
+    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
+
+    super.onCreate(icicle);
+    setContentView(layoutResourceId);
+
+    emailEdit = (AutoCompleteTextView) 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);
+
+    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();
+  }
+
+  protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
+    public final String email;
+    public final String serverURI;
+    public final PasswordStretcher passwordStretcher;
+
+    public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
+      this.email = email;
+      this.serverURI = serverURI;
+      this.passwordStretcher = passwordStretcher;
+    }
+
+    @Override
+    public void handleError(Exception e) {
+      showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
+    }
+
+    @Override
+    public void handleFailure(FxAccountClientRemoteException e) {
+      if (e.isUpgradeRequired()) {
+        Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state.");
+        final State state = fxAccount.getState();
+        fxAccount.setState(state.makeDoghouseState());
+        // The status activity will say that the user needs to upgrade.
+        redirectToActivity(FxAccountStatusActivity.class);
+        return;
+      }
+      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"));
+        return;
+      }
+
+      byte[] unwrapkB;
+      try {
+        // 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.
+        byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
+        unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
+      } catch (Exception e) {
+        this.handleError(e);
+        return;
+      }
+      fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
+      fxAccount.requestSync(FirefoxAccounts.FORCE);
+
+      // For great debugging.
+      if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
+        fxAccount.dump();
+      }
+
+      setResult(RESULT_OK);
+
+      // Maybe show success activity.
+      final Intent successIntent = makeSuccessIntent(email, result);
+      if (successIntent != null) {
+        startActivity(successIntent);
+      }
+      finish();
+    }
+  }
+
+  public void updateCredentials(String email, String password) {
+    String serverURI = fxAccount.getAccountServerURI();
+    Executor executor = Executors.newSingleThreadExecutor();
+    FxAccountClient client = new FxAccountClient20(serverURI, executor);
+    PasswordStretcher passwordStretcher = makePasswordStretcher(password);
+    try {
+      hideRemoteError();
+      RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
+      new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
+    } catch (Exception 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();
+        final String password = passwordEdit.getText().toString();
+        updateCredentials(email, password);
+      }
+    });
+  }
+}
--- a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
@@ -2,17 +2,16 @@
  * 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 org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
 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.login.State.Action;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
@@ -136,19 +135,16 @@ public class FxAccountConfirmAccountActi
 
   protected void refresh() {
     final State state = fxAccount.getState();
     final Action neededAction = state.getNeededAction();
     switch (neededAction) {
     case NeedsVerification:
       // This is what we're here to handle.
       break;
-    case NeedsPassword:
-    case NeedsUpgrade:
-    case None:
     default:
       // We're not in the right place!  Redirect to status.
       Logger.warn(LOG_TAG, "No need to verify Firefox Account that needs action " + neededAction.toString() +
           " (in state " + state.getStateLabel() + ").");
       setResult(RESULT_CANCELED);
       this.redirectToActivity(FxAccountStatusActivity.class);
       return;
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/activities/FxAccountFinishMigratingActivity.java
@@ -0,0 +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 org.mozilla.gecko.R;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
+import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.fxa.login.State.StateLabel;
+
+import android.content.Intent;
+
+/**
+ * Activity which displays a screen for inputting the password and finishing
+ * migrating to Firefox Accounts / Sync 1.5.
+ */
+public class FxAccountFinishMigratingActivity extends FxAccountAbstractUpdateCredentialsActivity {
+  protected static final String LOG_TAG = FxAccountFinishMigratingActivity.class.getSimpleName();
+
+  public FxAccountFinishMigratingActivity() {
+    super(R.layout.fxaccount_finish_migrating);
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+    this.fxAccount = getAndroidFxAccount();
+    if (fxAccount == null) {
+      Logger.warn(LOG_TAG, "Could not get Firefox Account.");
+      setResult(RESULT_CANCELED);
+      finish();
+      return;
+    }
+    final State state = fxAccount.getState();
+    if (state.getStateLabel() != StateLabel.MigratedFromSync11) {
+      Logger.warn(LOG_TAG, "Cannot finish migrating from Firefox Account in state: " + state.getStateLabel());
+      setResult(RESULT_CANCELED);
+      finish();
+      return;
+    }
+    emailEdit.setText(fxAccount.getEmail());
+  }
+
+  @Override
+  public Intent makeSuccessIntent(String email, LoginResponse result) {
+    final Intent successIntent = new Intent(this, FxAccountMigrationFinishedActivity.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?
+    successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+    return successIntent;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/activities/FxAccountMigrationFinishedActivity.java
@@ -0,0 +1,67 @@
+/* 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 org.mozilla.gecko.R;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.fxa.login.State.Action;
+import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+/**
+ * Activity which displays "Upgrade finished" success screen.
+ */
+public class FxAccountMigrationFinishedActivity extends FxAccountAbstractActivity {
+  private static final String LOG_TAG = FxAccountMigrationFinishedActivity.class.getSimpleName();
+
+  protected AndroidFxAccount fxAccount;
+
+  public FxAccountMigrationFinishedActivity() {
+    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onCreate(Bundle icicle) {
+    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
+
+    super.onCreate(icicle);
+    setContentView(R.layout.fxaccount_migration_finished);
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+    this.fxAccount = getAndroidFxAccount();
+    if (fxAccount == null) {
+      Logger.warn(LOG_TAG, "Could not get Firefox Account.");
+      setResult(RESULT_CANCELED);
+      finish();
+      return;
+    }
+    final State state = fxAccount.getState();
+    if (state.getNeededAction() == Action.NeedsFinishMigrating) {
+      Logger.warn(LOG_TAG, "Firefox Account needs to finish migrating; not displaying migration finished activity.");
+      setResult(RESULT_CANCELED);
+      finish();
+      return;
+    }
+
+    final View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button");
+    backToBrowsingButton.setOnClickListener(new OnClickListener() {
+      @Override
+      public void onClick(View v) {
+        ActivityUtils.openURLInFennec(v.getContext(), null);
+      }
+    });
+  }
+}
--- a/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
@@ -1,22 +1,21 @@
 /* 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 org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
-import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
 import android.accounts.AccountManagerFuture;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.ActionBar;
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -72,16 +72,17 @@ public class FxAccountStatusFragment
   protected Preference emailPreference;
   protected Preference authServerPreference;
 
   protected Preference needsPasswordPreference;
   protected Preference needsUpgradePreference;
   protected Preference needsVerificationPreference;
   protected Preference needsMasterSyncAutomaticallyEnabledPreference;
   protected Preference needsAccountEnabledPreference;
+  protected Preference needsFinishMigratingPreference;
 
   protected PreferenceCategory syncCategory;
 
   protected CheckBoxPreference bookmarksPreference;
   protected CheckBoxPreference historyPreference;
   protected CheckBoxPreference tabsPreference;
   protected CheckBoxPreference passwordsPreference;
 
@@ -133,16 +134,17 @@ public class FxAccountStatusFragment
     emailPreference = ensureFindPreference("email");
     authServerPreference = ensureFindPreference("auth_server");
 
     needsPasswordPreference = ensureFindPreference("needs_credentials");
     needsUpgradePreference = ensureFindPreference("needs_upgrade");
     needsVerificationPreference = ensureFindPreference("needs_verification");
     needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
     needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled");
+    needsFinishMigratingPreference = ensureFindPreference("needs_finish_migrating");
 
     syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
 
     bookmarksPreference = (CheckBoxPreference) ensureFindPreference("bookmarks");
     historyPreference = (CheckBoxPreference) ensureFindPreference("history");
     tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs");
     passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords");
 
@@ -152,16 +154,17 @@ public class FxAccountStatusFragment
       connectDebugButtons();
       ALWAYS_SHOW_AUTH_SERVER = true;
       ALWAYS_SHOW_SYNC_SERVER = true;
     }
 
     needsPasswordPreference.setOnPreferenceClickListener(this);
     needsVerificationPreference.setOnPreferenceClickListener(this);
     needsAccountEnabledPreference.setOnPreferenceClickListener(this);
+    needsFinishMigratingPreference.setOnPreferenceClickListener(this);
 
     bookmarksPreference.setOnPreferenceClickListener(this);
     historyPreference.setOnPreferenceClickListener(this);
     tabsPreference.setOnPreferenceClickListener(this);
     passwordsPreference.setOnPreferenceClickListener(this);
 
     deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
     deviceNamePreference.setOnPreferenceChangeListener(this);
@@ -199,16 +202,30 @@ public class FxAccountStatusFragment
       // 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;
     }
 
+    if (preference == needsFinishMigratingPreference) {
+      final Intent intent = new Intent(getActivity(), FxAccountFinishMigratingActivity.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;
+    }
+
     if (preference == needsVerificationPreference) {
       FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
 
       Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.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);
       startActivity(intent);
@@ -275,16 +292,17 @@ public class FxAccountStatusFragment
    */
   protected void showOnlyOneErrorPreference(Preference errorPreferenceToShow) {
     final Preference[] errorPreferences = new Preference[] {
         this.needsPasswordPreference,
         this.needsUpgradePreference,
         this.needsVerificationPreference,
         this.needsMasterSyncAutomaticallyEnabledPreference,
         this.needsAccountEnabledPreference,
+        this.needsFinishMigratingPreference,
     };
     for (Preference errorPreference : errorPreferences) {
       final boolean currentlyShown = null != findPreference(errorPreference.getKey());
       final boolean shouldBeShown = errorPreference == errorPreferenceToShow;
       if (currentlyShown == shouldBeShown) {
         continue;
       }
       if (shouldBeShown) {
@@ -320,16 +338,22 @@ public class FxAccountStatusFragment
   }
 
   protected void showNeedsAccountEnabled() {
     syncCategory.setTitle(R.string.fxaccount_status_sync);
     showOnlyOneErrorPreference(needsAccountEnabledPreference);
     setCheckboxesEnabled(false);
   }
 
+  protected void showNeedsFinishMigrating() {
+    syncCategory.setTitle(R.string.fxaccount_status_sync);
+    showOnlyOneErrorPreference(needsFinishMigratingPreference);
+    setCheckboxesEnabled(false);
+  }
+
   protected void showConnected() {
     syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
     showOnlyOneErrorPreference(null);
     setCheckboxesEnabled(true);
   }
 
   protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener {
     protected final Runnable refreshRunnable = new Runnable() {
@@ -459,18 +483,22 @@ public class FxAccountStatusFragment
         showNeedsUpgrade();
         break;
       case NeedsPassword:
         showNeedsPassword();
         break;
       case NeedsVerification:
         showNeedsVerification();
         break;
-      default:
+      case NeedsFinishMigrating:
+        showNeedsFinishMigrating();
+        break;
+      case None:
         showConnected();
+        break;
       }
 
       // We check for the master setting last, since it is not strictly
       // necessary for the user to address this error state: it's really a
       // warning state. We surface it for the user's convenience, and to prevent
       // confused folks wondering why Sync is not working at all.
       final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
       if (!masterSyncAutomatically) {
@@ -698,16 +726,21 @@ public class FxAccountStatusFragment
         State state = fxAccount.getState();
         fxAccount.setState(state.makeSeparatedState());
         refresh();
       } else if ("debug_require_upgrade".equals(key)) {
         Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade.");
         State state = fxAccount.getState();
         fxAccount.setState(state.makeDoghouseState());
         refresh();
+      } else if ("debug_migrated_from_sync11".equals(key)) {
+        Logger.info(LOG_TAG, "Moving to MigratedFromSync11 state: Requiring password.");
+        State state = fxAccount.getState();
+        fxAccount.setState(state.makeMigratedFromSync11State(null));
+        refresh();
       } else {
         return false;
       }
       return true;
     }
   }
 
   /**
@@ -724,17 +757,18 @@ public class FxAccountStatusFragment
     debugCategory.setTitle(debugCategory.getKey());
 
     String[] debugKeys = new String[] {
         "debug_refresh",
         "debug_dump",
         "debug_force_sync",
         "debug_forget_certificate",
         "debug_require_password",
-        "debug_require_upgrade" };
+        "debug_require_upgrade",
+        "debug_migrated_from_sync11" };
     for (String debugKey : debugKeys) {
       final Preference button = ensureFindPreference(debugKey);
       button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
       button.setOnPreferenceClickListener(listener);
     }
   }
 
   @Override
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -1,192 +1,52 @@
 /* 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.util.concurrent.Executor;
-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.FxAccountUtils;
-import org.mozilla.gecko.background.fxa.PasswordStretcher;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-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.login.State.StateLabel;
-import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
-import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.AutoCompleteTextView;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.TextView;
+import android.content.Intent;
 
 /**
  * Activity which displays a screen for updating the local password.
  */
-public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
+public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractUpdateCredentialsActivity {
   protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName();
 
-  protected AndroidFxAccount fxAccount;
-
   public FxAccountUpdateCredentialsActivity() {
-    // We want to share code with the other setup activities, but this activity
-    // doesn't create a new Android Account, it modifies an existing one. If you
-    // manage to get an account, and somehow be locked out too, we'll let you
-    // update it.
-    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void onCreate(Bundle icicle) {
-    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
-
-    super.onCreate(icicle);
-    setContentView(R.layout.fxaccount_update_credentials);
-
-    emailEdit = (AutoCompleteTextView) 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);
-
-    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();
+    super(R.layout.fxaccount_update_credentials);
   }
 
   @Override
   public void onResume() {
     super.onResume();
     this.fxAccount = getAndroidFxAccount();
     if (fxAccount == null) {
       Logger.warn(LOG_TAG, "Could not get Firefox Account.");
       setResult(RESULT_CANCELED);
       finish();
       return;
     }
-    State state = fxAccount.getState();
+    final State state = fxAccount.getState();
     if (state.getStateLabel() != StateLabel.Separated) {
       Logger.warn(LOG_TAG, "Cannot update credentials from Firefox Account in state: " + state.getStateLabel());
       setResult(RESULT_CANCELED);
       finish();
       return;
     }
     emailEdit.setText(fxAccount.getEmail());
   }
 
-  protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
-    public final String email;
-    public final String serverURI;
-    public final PasswordStretcher passwordStretcher;
-
-    public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
-      this.email = email;
-      this.serverURI = serverURI;
-      this.passwordStretcher = passwordStretcher;
-    }
-
-    @Override
-    public void handleError(Exception e) {
-      showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
-    }
-
-    @Override
-    public void handleFailure(FxAccountClientRemoteException e) {
-      if (e.isUpgradeRequired()) {
-        Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state.");
-        final State state = fxAccount.getState();
-        fxAccount.setState(state.makeDoghouseState());
-        // The status activity will say that the user needs to upgrade.
-        redirectToActivity(FxAccountStatusActivity.class);
-        return;
-      }
-      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"));
-        return;
-      }
-
-      byte[] unwrapkB;
-      try {
-        // 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.
-        byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
-        unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
-      } catch (Exception e) {
-        this.handleError(e);
-        return;
-      }
-      fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
-      fxAccount.requestSync(FirefoxAccounts.FORCE);
-
-      // For great debugging.
-      if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-        fxAccount.dump();
-      }
-
-      setResult(RESULT_OK);
-      finish();
-    }
-  }
-
-  public void updateCredentials(String email, String password) {
-    String serverURI = fxAccount.getAccountServerURI();
-    Executor executor = Executors.newSingleThreadExecutor();
-    FxAccountClient client = new FxAccountClient20(serverURI, executor);
-    PasswordStretcher passwordStretcher = makePasswordStretcher(password);
-    try {
-      hideRemoteError();
-      RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
-      new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
-    } catch (Exception 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();
-        final String password = passwordEdit.getText().toString();
-        updateCredentials(email, password);
-      }
-    });
+  @Override
+  public Intent makeSuccessIntent(String email, LoginResponse result) {
+    // We don't show anything after updating credentials. The updating Activity
+    // sets its result to OK and the user is returned to the previous task,
+    // which is often the Status Activity.
+    return null;
   }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/login/MigratedFromSync11.java
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.fxa.login;
+
+import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
+import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.PasswordRequired;
+
+public class MigratedFromSync11 extends State {
+  public final String password;
+
+  public MigratedFromSync11(String email, String uid, boolean verified, String password) {
+    super(StateLabel.MigratedFromSync11, email, uid, verified);
+    // Null password is allowed.
+    this.password = password;
+  }
+
+  @Override
+  public void execute(final ExecuteDelegate delegate) {
+    delegate.handleTransition(new PasswordRequired(), this);
+  }
+
+  @Override
+  public Action getNeededAction() {
+    return Action.NeedsFinishMigrating;
+  }
+}
--- a/mobile/android/base/fxa/login/State.java
+++ b/mobile/android/base/fxa/login/State.java
@@ -4,30 +4,32 @@
 
 package org.mozilla.gecko.fxa.login;
 
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 
 public abstract class State {
-  public static final long CURRENT_VERSION = 2L;
+  public static final long CURRENT_VERSION = 3L;
 
   public enum StateLabel {
     Engaged,
     Cohabiting,
     Married,
     Separated,
     Doghouse,
+    MigratedFromSync11,
   }
 
   public enum Action {
     NeedsUpgrade,
     NeedsPassword,
     NeedsVerification,
+    NeedsFinishMigrating,
     None,
   }
 
   protected final StateLabel stateLabel;
   public final String email;
   public final String uid;
   public final boolean verified;
 
@@ -55,12 +57,16 @@ public abstract class State {
   public State makeSeparatedState() {
     return new Separated(email, uid, verified);
   }
 
   public State makeDoghouseState() {
     return new Doghouse(email, uid, verified);
   }
 
+  public State makeMigratedFromSync11State(String password) {
+    return new MigratedFromSync11(email, uid, verified, password);
+  }
+
   public abstract void execute(ExecuteDelegate delegate);
 
   public abstract Action getNeededAction();
 }
--- a/mobile/android/base/fxa/login/StateFactory.java
+++ b/mobile/android/base/fxa/login/StateFactory.java
@@ -49,18 +49,21 @@ public class StateFactory {
 
   public static State fromJSONObject(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
     Long version = o.getLong("version");
     if (version == null) {
       throw new IllegalStateException("version must not be null");
     }
 
     final int v = version.intValue();
+    if (v == 3) {
+      // The most common case is the most recent version.
+      return fromJSONObjectV3(stateLabel, o);
+    }
     if (v == 2) {
-      // The most common case is the most recent version.
       return fromJSONObjectV2(stateLabel, o);
     }
     if (v == 1) {
       final State state = fromJSONObjectV1(stateLabel, o);
       return migrateV1toV2(stateLabel, state);
     }
     throw new IllegalStateException("version must be in {1, 2}");
   }
@@ -129,16 +132,33 @@ public class StateFactory {
           Utils.hex2Byte(o.getString("kB")),
           keyPairFromJSONObjectV2(o.getObject("keyPair")),
           o.getString("certificate"));
     default:
       return fromJSONObjectV1(stateLabel, o);
     }
   }
 
+  /**
+   * Exactly the same as {@link fromJSONObjectV2}, except that there's a new
+   * MigratedFromSyncV11 state.
+   */
+  protected static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
+    switch (stateLabel) {
+    case MigratedFromSync11:
+      return new MigratedFromSync11(
+          o.getString("email"),
+          o.getString("uid"),
+          o.getBoolean("verified"),
+          o.getString("password"));
+    default:
+      return fromJSONObjectV2(stateLabel, o);
+    }
+  }
+
   protected static void logMigration(State from, State to) {
     if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
       return;
     }
     try {
       FxAccountUtils.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
     } catch (Exception e) {
       Logger.warn(LOG_TAG, "Error producing JSON representation of V1 state.", e);
--- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
+++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.fxa.sync;
 
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
+import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
 import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.Action;
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -62,22 +63,31 @@ public class FxAccountNotificationManage
       return;
     }
 
     if (!localeUpdated) {
       localeUpdated = true;
       BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
     }
 
-    final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
-    final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
+    final String title;
+    final String text;
+    final Intent notificationIntent;
+    if (action == Action.NeedsFinishMigrating) {
+      title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
+      text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
+      notificationIntent = new Intent(context, FxAccountFinishMigratingActivity.class);
+    } else {
+      title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
+      text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
+      notificationIntent = new Intent(context, FxAccountStatusActivity.class);
+    }
     Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
     FxAccountUtils.pii(LOG_TAG, "And text: " + text);
 
-    final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
     final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
 
     final Builder builder = new NotificationCompat.Builder(context);
     builder
     .setContentTitle(title)
     .setContentText(text)
     .setSmallIcon(R.drawable.ic_status_logo)
     .setAutoCancel(true)
--- a/mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java
+++ b/mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java
@@ -113,16 +113,17 @@ public class FxAccountSchedulePolicy imp
     requestPeriodicSync(interval);
   }
 
   @Override
   public void onHandleFinal(Action needed) {
     switch (needed) {
     case NeedsPassword:
     case NeedsUpgrade:
+    case NeedsFinishMigrating:
       requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
       break;
     case NeedsVerification:
       requestPeriodicSync(POLL_INTERVAL_PENDING_VERIFICATION);
       break;
     case None:
       // No action needed: we'll set the periodic sync interval
       // when the sync finishes, via the SessionCallback.
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -175,34 +175,41 @@
 <!ENTITY fxaccount_sign_in_button_label 'Sign in'>
 <!ENTITY fxaccount_sign_in_forgot_password 'Forgot password?'>
 <!ENTITY fxaccount_sign_in_create_account_instead 'Create an account'>
 <!ENTITY fxaccount_sign_in_unknown_error 'Could not sign in'>
 
 <!ENTITY fxaccount_account_verified_sub_header 'Account verified'>
 <!ENTITY fxaccount_account_verified_description2 'Your data will begin syncing momentarily.'>
 
+<!ENTITY fxaccount_migration_finished_header 'Upgrade finished'>
+
 <!ENTITY fxaccount_update_credentials_header 'Sign in'>
 <!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
 <!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
 
+<!ENTITY fxaccount_finish_migrating_header 'Sign in to finish upgrading'>
+<!ENTITY fxaccount_finish_migrating_button_label 'Finish upgrading'>
+<!ENTITY fxaccount_finish_migrating_description 'Upgrading can transfer a lot of data. It\&apos;s best to be on a WiFi network.'>
+
 <!ENTITY fxaccount_status_header2 'Firefox Account'>
 <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
 <!ENTITY fxaccount_status_auth_server 'Account server'>
 <!ENTITY fxaccount_status_sync_now 'Sync now'>
 <!ENTITY fxaccount_status_syncing2 'Syncing…'>
 <!ENTITY fxaccount_status_device_name 'Device name'>
 <!ENTITY fxaccount_status_sync_server 'Sync server'>
 <!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
 <!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
 <!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
 <!ENTITY fxaccount_status_needs_credentials 'Cannot connect. Tap to sign in.'>
 <!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
 <!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings &gt; Data Usage.'>
 <!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
+<!ENTITY fxaccount_status_needs_finish_migrating 'Tap to sign in to your new Firefox Account.'>
 <!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
 <!ENTITY fxaccount_status_history 'History'>
 <!ENTITY fxaccount_status_passwords 'Passwords'>
 <!ENTITY fxaccount_status_tabs 'Open tabs'>
 <!ENTITY fxaccount_status_legal 'Legal' >
 <!-- Localization note: when tapped, the following two strings link to
      external web pages.  Compare fxaccount_policy_{linktos,linkprivacy}:
      these strings are separated to accommodate languages that decline
@@ -245,8 +252,13 @@
 <!ENTITY fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD 'Server busy, try again soon'>
 <!ENTITY fxaccount_remote_error_UNKNOWN_ERROR 'There was a problem'>
 <!ENTITY fxaccount_remote_error_COULD_NOT_CONNECT 'Cannot connect to network'>
 
 <!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
 <!-- Localization note: the format string below will be replaced
      with the Firefox Account's email address. -->
 <!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
+
+<!ENTITY fxaccount_sync_finish_migrating_notification_title 'Finish upgrading &syncBrand.shortName.label;?'>
+<!-- Localization note: the format string below will be replaced
+     with the Firefox Account's email address. -->
+<!ENTITY fxaccount_sync_finish_migrating_notification_text 'Tap to sign in as &formatS;'>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/fxaccount_finish_migrating.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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/.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:fillViewport="true" >
+
+    <LinearLayout
+        android:id="@+id/update_credentials_view"
+        style="@style/FxAccountMiddle" >
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <TextView
+            style="@style/FxAccountHeaderItem"
+            android:text="@string/fxaccount_finish_migrating_header" />
+
+        <include layout="@layout/fxaccount_custom_server_view" />
+
+        <include layout="@layout/fxaccount_email_password_view" />
+
+        <TextView
+            style="@style/FxAccountTextItem"
+            android:layout_marginTop="10dp"
+            android:text="@string/fxaccount_finish_migrating_description" />
+
+        <TextView
+            android:id="@+id/remote_error"
+            style="@style/FxAccountErrorItem" />
+
+        <RelativeLayout style="@style/FxAccountButtonLayout" >
+
+            <ProgressBar
+                android:id="@+id/progress"
+                style="@style/FxAccountProgress" />
+
+            <Button
+                android:id="@+id/button"
+                style="@style/FxAccountButton"
+                android:text="@string/fxaccount_finish_migrating_button_label" />
+        </RelativeLayout>
+
+        <TextView
+            android:id="@+id/forgot_password_link"
+            style="@style/FxAccountLinkifiedItem"
+            android:layout_marginTop="10dp"
+            android:text="@string/fxaccount_sign_in_forgot_password" />
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <ImageView
+            style="@style/FxAccountIcon"
+            android:contentDescription="@string/fxaccount_empty_contentDescription" />
+    </LinearLayout>
+
+</ScrollView>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/fxaccount_migration_finished.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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/.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:fillViewport="true" >
+
+    <LinearLayout style="@style/FxAccountMiddle" >
+
+        <TextView
+            style="@style/FxAccountHeaderItem"
+            android:text="@string/fxaccount_migration_finished_header" >
+        </TextView>
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginBottom="45dp"
+            android:contentDescription="@string/fxaccount_empty_contentDescription"
+            android:src="@drawable/fxaccount_checkbox" >
+        </ImageView>
+
+        <TextView
+            style="@style/FxAccountTextItem"
+            android:layout_marginBottom="40dp"
+            android:text="@string/fxaccount_migration_finished_description"
+            android:textSize="18sp" />
+
+        <Button
+            android:id="@+id/button"
+            style="@style/FxAccountButton"
+            android:text="@string/fxaccount_back_to_browsing" />
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <ImageView
+            style="@style/FxAccountIcon"
+            android:contentDescription="@string/fxaccount_empty_contentDescription" />
+    </LinearLayout>
+
+</ScrollView>
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -49,16 +49,23 @@
             android:title="@string/fxaccount_status_needs_master_sync_automatically_enabled" />
         <Preference
             android:editable="false"
             android:icon="@drawable/fxaccount_sync_error"
             android:key="needs_account_enabled"
             android:layout="@layout/fxaccount_status_error_preference"
             android:persistent="false"
             android:title="@string/fxaccount_status_needs_account_enabled" />
+        <Preference
+            android:editable="false"
+            android:icon="@drawable/fxaccount_sync_error"
+            android:key="needs_finish_migrating"
+            android:layout="@layout/fxaccount_status_error_preference"
+            android:persistent="false"
+            android:title="@string/fxaccount_status_needs_finish_migrating" />
 
         <Preference
             android:editable="false"
             android:key="sync_now"
             android:defaultValue=""
             android:persistent="false"
             android:title="Sync now"
             android:summary="" />
@@ -120,11 +127,12 @@
     <PreferenceCategory
         android:key="debug_category" >
         <Preference android:key="debug_refresh" />
         <Preference android:key="debug_dump" />
         <Preference android:key="debug_force_sync" />
         <Preference android:key="debug_forget_certificate" />
         <Preference android:key="debug_require_password" />
         <Preference android:key="debug_require_upgrade" />
+        <Preference android:key="debug_migrated_from_sync11" />
     </PreferenceCategory>
 
 </PreferenceScreen>
--- a/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
+++ b/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
@@ -64,16 +64,31 @@
             android:theme="@style/FxAccountTheme"
             android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity"
             android:configChanges="locale|layoutDirection"
             android:windowSoftInputMode="adjustResize">
         </activity>
 
         <activity
             android:theme="@style/FxAccountTheme"
+            android:name="org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity"
+            android:configChanges="locale|layoutDirection"
+            android:windowSoftInputMode="adjustResize">
+        </activity>
+
+        <activity
+            android:theme="@style/FxAccountTheme"
+            android:name="org.mozilla.gecko.fxa.activities.FxAccountMigrationFinishedActivity"
+            android:configChanges="locale|layoutDirection"
+            android:noHistory="true"
+            android:windowSoftInputMode="adjustResize">
+        </activity>
+
+        <activity
+            android:theme="@style/FxAccountTheme"
             android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
             android:configChanges="locale|layoutDirection"
             android:noHistory="true"
             android:windowSoftInputMode="adjustResize">
         </activity>
 
         <receiver
             android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedReceiver"
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -162,36 +162,44 @@
 <string name="fxaccount_sign_in_button_label">&fxaccount_sign_in_button_label;</string>
 <string name="fxaccount_sign_in_forgot_password">&fxaccount_sign_in_forgot_password;</string>
 <string name="fxaccount_sign_in_create_account_instead">&fxaccount_sign_in_create_account_instead;</string>
 <string name="fxaccount_sign_in_unknown_error">&fxaccount_sign_in_unknown_error;</string>
 
 <string name="fxaccount_account_verified_sub_header">&fxaccount_account_verified_sub_header;</string>
 <string name="fxaccount_account_verified_description">&fxaccount_account_verified_description2;</string>
 
+<string name="fxaccount_migration_finished_header">&fxaccount_migration_finished_header;</string>
+<string name="fxaccount_migration_finished_description">&fxaccount_account_verified_description2;</string>
+
 <string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
 <string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
 <string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
 
+<string name="fxaccount_finish_migrating_header">&fxaccount_finish_migrating_header;</string>
+<string name="fxaccount_finish_migrating_button_label">&fxaccount_finish_migrating_button_label;</string>
+<string name="fxaccount_finish_migrating_description">&fxaccount_finish_migrating_description;</string>
+
 <string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
 <string name="fxaccount_status_header">&fxaccount_status_header2;</string>
 <string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
 <string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
 <string name="fxaccount_status_sync_now">&fxaccount_status_sync_now;</string>
 <string name="fxaccount_status_syncing">&fxaccount_status_syncing2;</string>
 <string name="fxaccount_status_last_synced">&remote_tabs_last_synced;</string>
 <string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
 <string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
 <string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
 <string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
 <string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
 <string name="fxaccount_status_needs_credentials">&fxaccount_status_needs_credentials;</string>
 <string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
 <string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
 <string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
+<string name="fxaccount_status_needs_finish_migrating">&fxaccount_status_needs_finish_migrating;</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>
 <string name="fxaccount_status_legal">&fxaccount_status_legal;</string>
 <string name="fxaccount_status_linktos">&fxaccount_status_linktos;</string>
 <string name="fxaccount_status_linkprivacy">&fxaccount_status_linkprivacy;</string>
 <string name="fxaccount_status_more">&fxaccount_status_more;</string>
@@ -214,8 +222,11 @@
 <string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
 <string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
 
 <!-- Remove Account -->
 <string name="fxaccount_remove_account_dialog_title">&fxaccount_remove_account_dialog_title;</string>
 <string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
 <string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
 <string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
+
+<string name="fxaccount_sync_finish_migrating_notification_title">&fxaccount_sync_finish_migrating_notification_title;</string>
+<string name="fxaccount_sync_finish_migrating_notification_text">&fxaccount_sync_finish_migrating_notification_text;</string>