diff -ruN mozilla-esr31/mobile/android/base/fxa/FirefoxAccounts.java mozilla-release/mobile/android/base/fxa/FirefoxAccounts.java --- mozilla-esr31/mobile/android/base/fxa/FirefoxAccounts.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/FirefoxAccounts.java 2014-09-24 03:05:32.000000000 +0200 @@ -6,13 +6,18 @@ import java.io.File; import java.util.EnumSet; +import java.util.Locale; import java.util.concurrent.CountDownLatch; +import org.mozilla.gecko.AppConstants; +import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.fxa.authenticator.AccountPickler; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; +import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; +import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; import org.mozilla.gecko.sync.ThreadPool; import org.mozilla.gecko.sync.Utils; @@ -20,6 +25,7 @@ import android.accounts.AccountManager; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Resources; import android.os.Bundle; /** @@ -152,6 +158,38 @@ return null; } + /** + * @return + * the {@link State} instance associated with the current account, or null if + * no accounts exist. + */ + public static State getFirefoxAccountState(final Context context) { + final Account account = getFirefoxAccount(context); + if (account == null) { + return null; + } + + final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); + try { + return fxAccount.getState(); + } catch (final Exception ex) { + Logger.warn(LOG_TAG, "Could not get FX account state.", ex); + return null; + } + } + + /* + * @param context Android context + * @return the email address associated with the configured Firefox account if one exists; null otherwise. + */ + public static String getFirefoxAccountEmail(final Context context) { + final Account account = getFirefoxAccount(context); + if (account == null) { + return null; + } + return account.name; + } + protected static void putHintsToSync(final Bundle extras, EnumSet syncHints) { // stagesToSync and stagesToSkip are allowed to be null. if (syncHints == null) { @@ -264,4 +302,33 @@ // stopObserving null-checks its argument. FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener); } + + public static String getOldSyncUpgradeURL(final Resources res, final Locale locale) { + final String VERSION = AppConstants.MOZ_APP_VERSION; + final String OS = AppConstants.OS_TARGET; + final String LOCALE = Utils.getLanguageTag(locale); + return res.getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); + } + + /** + * Resends the account verification email, and displays an appropriate + * toast on both send success and failure. Note that because the underlying implementation + * uses {@link AsyncTask}, the provided context must be UI-capable, and this + * method called from the UI thread (see + * {@link org.mozilla.gecko.fxa.tasks.FxAccountCodeResender#resendCode(Context, AndroidFxAccount)} + * for more). + * + * @param context a UI-capable Android context. + * @return true if an account exists, false otherwise. + */ + public static boolean resendVerificationEmail(final Context context) { + final Account account = getFirefoxAccount(context); + if (account == null) { + return false; + } + + final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); + FxAccountCodeResender.resendCode(context, fxAccount); + return true; + } } diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java 2014-09-24 03:05:32.000000000 +0200 @@ -19,10 +19,10 @@ 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.activities.FxAccountSetupTask.ProgressDisplay; 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.SyncConfiguration; import org.mozilla.gecko.sync.setup.Constants; import org.mozilla.gecko.sync.setup.activities.ActivityUtils; diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java 2014-09-24 03:05:32.000000000 +0200 @@ -4,21 +4,15 @@ 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.FxAccountClientException.FxAccountClientRemoteException; 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.Action; import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; +import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; import org.mozilla.gecko.sync.setup.activities.ActivityUtils; import android.accounts.Account; @@ -28,7 +22,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; -import android.widget.Toast; /** * Activity which displays account created successfully screen to the user, and @@ -164,76 +157,8 @@ resendLink.setClickable(resendLinkShouldBeEnabled); } - public static class FxAccountResendCodeTask extends FxAccountSetupTask { - protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); - - protected final byte[] sessionToken; - - public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { - super(context, null, client, delegate); - this.sessionToken = sessionToken; - } - - @Override - protected InnerRequestDelegate doInBackground(Void... arg0) { - try { - client.resendCode(sessionToken, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception signing in.", e); - delegate.handleError(e); - } - return null; - } - } - - protected static class ResendCodeDelegate implements RequestDelegate { - public final Context context; - - public ResendCodeDelegate(Context context) { - this.context = context; - } - - @Override - public void handleError(Exception e) { - Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); - Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - handleError(e); - } - - @Override - public void handleSuccess(Void result) { - Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); - } - } - - public static void resendCode(Context context, AndroidFxAccount fxAccount) { - RequestDelegate delegate = new ResendCodeDelegate(context); - - byte[] sessionToken; - try { - sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); - } catch (Exception e) { - delegate.handleError(e); - return; - } - if (sessionToken == null) { - delegate.handleError(new IllegalStateException("sessionToken should not be null")); - return; - } - - Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); - new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); - } - @Override public void onClick(View v) { - resendCode(this, fxAccount); + FxAccountCodeResender.resendCode(this, fxAccount); } } diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java 2014-09-24 03:05:32.000000000 +0200 @@ -21,7 +21,7 @@ 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.activities.FxAccountSetupTask.FxAccountCreateAccountTask; +import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask; import android.app.AlertDialog; import android.app.Dialog; diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java 2014-09-24 03:05:32.000000000 +0200 @@ -6,13 +6,11 @@ import java.util.Locale; -import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; import org.mozilla.gecko.fxa.FirefoxAccounts; import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.setup.activities.ActivityUtils; import org.mozilla.gecko.sync.setup.activities.LocaleAware; @@ -109,11 +107,7 @@ protected void linkifyOldFirefoxLink() { TextView oldFirefox = (TextView) findViewById(R.id.old_firefox); String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox); - String VERSION = AppConstants.MOZ_APP_VERSION; - String OS = AppConstants.OS_TARGET; - - String LOCALE = Utils.getLanguageTag(Locale.getDefault()); - String url = getResources().getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); + final String url = FirefoxAccounts.getOldSyncUpgradeURL(getResources(), Locale.getDefault()); FxAccountConstants.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular. ActivityUtils.linkTextView(oldFirefox, text, url); } diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSetupTask.java mozilla-release/mobile/android/base/fxa/activities/FxAccountSetupTask.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSetupTask.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountSetupTask.java 1970-01-01 01:00:00.000000000 +0100 @@ -1,172 +0,0 @@ -/* 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.UnsupportedEncodingException; -import java.util.concurrent.CountDownLatch; - -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.LoginResponse; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.PasswordStretcher; -import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate; - -import android.content.Context; -import android.os.AsyncTask; - -/** - * An AsyncTask wrapper around signing up for, and signing in to, a - * Firefox Account. - *

- * 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 extends AsyncTask> { - private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); - - public interface ProgressDisplay { - public void showProgress(); - public void dismissProgress(); - } - - protected final Context context; - protected final FxAccountClient client; - 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 innerDelegate = new InnerRequestDelegate(latch); - - protected final RequestDelegate delegate; - - public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { - this.context = context; - this.client = client; - this.delegate = delegate; - this.progressDisplay = progressDisplay; - } - - @Override - protected void onPreExecute() { - if (progressDisplay != null) { - progressDisplay.showProgress(); - } - } - - @Override - protected void onPostExecute(InnerRequestDelegate result) { - if (progressDisplay != null) { - progressDisplay.dismissProgress(); - } - - // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. - 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 result) { - if (progressDisplay != null) { - progressDisplay.dismissProgress(); - } - delegate.handleError(new IllegalStateException("Task was cancelled.")); - } - - protected static class InnerRequestDelegate implements RequestDelegate { - 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.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 { - private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); - - protected final byte[] emailUTF8; - protected final PasswordStretcher passwordStretcher; - - public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { - super(context, progressDisplay, client, delegate); - this.emailUTF8 = email.getBytes("UTF-8"); - this.passwordStretcher = passwordStretcher; - } - - @Override - protected InnerRequestDelegate doInBackground(Void... arg0) { - try { - client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception logging in.", e); - delegate.handleError(e); - } - return null; - } - } - - public static class FxAccountSignInTask extends FxAccountSetupTask { - protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); - - protected final byte[] emailUTF8; - protected final PasswordStretcher passwordStretcher; - - public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { - super(context, progressDisplay, client, delegate); - this.emailUTF8 = email.getBytes("UTF-8"); - this.passwordStretcher = passwordStretcher; - } - - @Override - protected InnerRequestDelegate doInBackground(Void... arg0) { - try { - client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception signing in.", e); - delegate.handleError(e); - } - return null; - } - } -} diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSignInActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountSignInActivity.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSignInActivity.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountSignInActivity.java 2014-09-24 03:05:32.000000000 +0200 @@ -16,7 +16,7 @@ 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.activities.FxAccountSetupTask.FxAccountSignInTask; +import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; import org.mozilla.gecko.sync.setup.activities.ActivityUtils; import android.content.Intent; diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountStatusFragment.java mozilla-release/mobile/android/base/fxa/activities/FxAccountStatusFragment.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountStatusFragment.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountStatusFragment.java 2014-09-24 03:05:32.000000000 +0200 @@ -17,6 +17,8 @@ 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.SharedPreferencesClientsDataDelegate; import org.mozilla.gecko.sync.SyncConfiguration; import android.accounts.Account; @@ -27,10 +29,13 @@ import android.os.Bundle; import android.os.Handler; import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; +import android.text.TextUtils; /** * A fragment that displays the status of an AndroidFxAccount. @@ -38,7 +43,9 @@ * The owning activity is responsible for providing an AndroidFxAccount at * appropriate times. */ -public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener { +public class FxAccountStatusFragment + extends PreferenceFragment + implements OnPreferenceClickListener, OnPreferenceChangeListener { private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); // When a checkbox is toggled, wait 5 seconds (for other checkbox actions) @@ -64,7 +71,12 @@ protected CheckBoxPreference tabsPreference; protected CheckBoxPreference passwordsPreference; + protected EditTextPreference deviceNamePreference; + protected volatile AndroidFxAccount fxAccount; + // The contract is: when fxAccount is non-null, then clientsDataDelegate is + // non-null. If violated then an IllegalStateException is thrown. + protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate; // Used to post delayed sync requests. protected Handler handler; @@ -87,6 +99,10 @@ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + addPreferences(); + } + + protected void addPreferences() { addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); emailPreference = ensureFindPreference("email"); @@ -118,6 +134,9 @@ historyPreference.setOnPreferenceClickListener(this); tabsPreference.setOnPreferenceClickListener(this); passwordsPreference.setOnPreferenceClickListener(this); + + deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name"); + deviceNamePreference.setOnPreferenceChangeListener(this); } /** @@ -142,7 +161,7 @@ } if (preference == needsVerificationPreference) { - FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount); + FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount); Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class); // Per http://stackoverflow.com/a/8992365, this triggers a known bug with @@ -176,6 +195,8 @@ 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); } /** @@ -293,6 +314,14 @@ throw new IllegalArgumentException("fxAccount must not be null"); } this.fxAccount = fxAccount; + try { + this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs()); + } catch (Exception e) { + Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e); + // Something is terribly wrong; best to get a stack trace rather than + // continue with a null clients delegate. + throw new IllegalStateException(e); + } handler = new Handler(); // Attached to current (assumed to be UI) thread. @@ -318,6 +347,17 @@ FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); } + protected void hardRefresh() { + // This is the only way to guarantee that the EditText dialogs created by + // EditTextPreferences are re-created. This works around the issue described + // at http://androiddev.orkitra.com/?p=112079. + final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); + statusScreen.removeAll(); + addPreferences(); + + refresh(); + } + protected void refresh() { // refresh is called from our onResume, which can happen before the owning // Activity tells us about an account (via our public @@ -371,6 +411,10 @@ // No matter our state, we should update the checkboxes. updateSelectedEngines(); } + + final String clientName = clientsDataDelegate.getClientName(); + deviceNamePreference.setSummary(clientName); + deviceNamePreference.setText(clientName); } /** @@ -570,4 +614,22 @@ button.setOnPreferenceClickListener(listener); } } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == deviceNamePreference) { + String newClientName = (String) newValue; + if (TextUtils.isEmpty(newClientName)) { + newClientName = clientsDataDelegate.getDefaultClientName(); + } + final long now = System.currentTimeMillis(); + clientsDataDelegate.setClientName(newClientName, now); + requestDelayedSync(); // Try to update our remote client record. + hardRefresh(); // Updates the value displayed to the user, among other things. + return true; + } + + // For everything else, accept the change. + return true; + } } diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java --- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java 2014-09-24 03:05:32.000000000 +0200 @@ -18,11 +18,11 @@ import org.mozilla.gecko.background.fxa.PasswordStretcher; import org.mozilla.gecko.fxa.FirefoxAccounts; import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; 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; diff -ruN mozilla-esr31/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java mozilla-release/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java --- mozilla-esr31/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java 2015-02-25 19:21:06.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java 2014-09-24 03:05:33.000000000 +0200 @@ -357,7 +357,11 @@ FxAccountGlobalSession globalSession = null; try { - ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs); + final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs); + if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { + FxAccountConstants.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'."); + FxAccountConstants.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp()); + } // We compute skew over time using SkewHandler. This yields an unchanging // skew adjustment that the HawkAuthHeaderProvider uses to adjust its diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCodeResender.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountCodeResender.java --- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCodeResender.java 1970-01-01 01:00:00.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountCodeResender.java 2014-09-24 03:05:33.000000000 +0200 @@ -0,0 +1,108 @@ +/* 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.tasks; + +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.FxAccountClientException.FxAccountClientRemoteException; +import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; +import org.mozilla.gecko.fxa.login.Engaged; + +import android.content.Context; +import android.widget.Toast; + +/** + * A helper class that provides a simple interface for requesting + * a Firefox Account verification email to be resent. + */ +public class FxAccountCodeResender { + private static final String LOG_TAG = FxAccountCodeResender.class.getSimpleName(); + + private static class FxAccountResendCodeTask extends FxAccountSetupTask { + protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); + + protected final byte[] sessionToken; + + public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { + super(context, null, client, delegate); + this.sessionToken = sessionToken; + } + + @Override + protected InnerRequestDelegate doInBackground(Void... arg0) { + try { + client.resendCode(sessionToken, innerDelegate); + latch.await(); + return innerDelegate; + } catch (Exception e) { + Logger.error(LOG_TAG, "Got exception signing in.", e); + delegate.handleError(e); + } + return null; + } + } + + private static class ResendCodeDelegate implements RequestDelegate { + public final Context context; + + public ResendCodeDelegate(Context context) { + this.context = context; + } + + @Override + public void handleError(Exception e) { + Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); + Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); + } + + @Override + public void handleFailure(FxAccountClientRemoteException e) { + handleError(e); + } + + @Override + public void handleSuccess(Void result) { + Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); + } + } + + /** + * Resends the account verification email, and displays an appropriate + * toast on both send success and failure. Note that because the underlying implementation + * uses {@link AsyncTask}, the provided context must be UI-capable and + * this method called from the UI thread. + * + * Note that it may actually be possible to run this (and the {@link AsyncTask}) method + * from a background thread - but this hasn't been tested. + * + * @param context A UI-capable Android context. + * @param fxAccount The Firefox Account to resend the code to. + */ + public static void resendCode(Context context, AndroidFxAccount fxAccount) { + RequestDelegate delegate = new ResendCodeDelegate(context); + + byte[] sessionToken; + try { + sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); + } catch (Exception e) { + delegate.handleError(e); + return; + } + if (sessionToken == null) { + delegate.handleError(new IllegalStateException("sessionToken should not be null")); + return; + } + + Executor executor = Executors.newSingleThreadExecutor(); + FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); + new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); + } +} diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java --- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java 1970-01-01 01:00:00.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java 2014-09-24 03:05:33.000000000 +0200 @@ -0,0 +1,41 @@ +/* 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.tasks; + +import java.io.UnsupportedEncodingException; + +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.LoginResponse; +import org.mozilla.gecko.background.fxa.PasswordStretcher; + +import android.content.Context; + +public class FxAccountCreateAccountTask extends FxAccountSetupTask { + private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); + + protected final byte[] emailUTF8; + protected final PasswordStretcher passwordStretcher; + + public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { + super(context, progressDisplay, client, delegate); + this.emailUTF8 = email.getBytes("UTF-8"); + this.passwordStretcher = passwordStretcher; + } + + @Override + protected InnerRequestDelegate doInBackground(Void... arg0) { + try { + client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); + latch.await(); + return innerDelegate; + } catch (Exception e) { + Logger.error(LOG_TAG, "Got exception logging in.", e); + delegate.handleError(e); + } + return null; + } +} diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSetupTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountSetupTask.java --- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSetupTask.java 1970-01-01 01:00:00.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountSetupTask.java 2014-09-24 03:05:33.000000000 +0200 @@ -0,0 +1,117 @@ +/* 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.tasks; + +import java.util.concurrent.CountDownLatch; + +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.FxAccountClientException.FxAccountClientRemoteException; +import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.InnerRequestDelegate; + +import android.content.Context; +import android.os.AsyncTask; + +/** + * An AsyncTask wrapper around signing up for, and signing in to, a + * Firefox Account. + *

+ * 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. + */ +public abstract class FxAccountSetupTask extends AsyncTask> { + private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); + + public interface ProgressDisplay { + public void showProgress(); + public void dismissProgress(); + } + + protected final Context context; + protected final FxAccountClient client; + 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 innerDelegate = new InnerRequestDelegate(latch); + + protected final RequestDelegate delegate; + + public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { + this.context = context; + this.client = client; + this.delegate = delegate; + this.progressDisplay = progressDisplay; + } + + @Override + protected void onPreExecute() { + if (progressDisplay != null) { + progressDisplay.showProgress(); + } + } + + @Override + protected void onPostExecute(InnerRequestDelegate result) { + if (progressDisplay != null) { + progressDisplay.dismissProgress(); + } + + // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. + 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 result) { + if (progressDisplay != null) { + progressDisplay.dismissProgress(); + } + delegate.handleError(new IllegalStateException("Task was cancelled.")); + } + + protected static class InnerRequestDelegate implements RequestDelegate { + 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.failure = e; + latch.countDown(); + } + + @Override + public void handleSuccess(T result) { + Logger.info(LOG_TAG, "Got success."); + this.response = result; + latch.countDown(); + } + } +} diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSignInTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountSignInTask.java --- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSignInTask.java 1970-01-01 01:00:00.000000000 +0100 +++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountSignInTask.java 2014-09-24 03:05:33.000000000 +0200 @@ -0,0 +1,41 @@ +/* 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.tasks; + +import java.io.UnsupportedEncodingException; + +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.LoginResponse; +import org.mozilla.gecko.background.fxa.PasswordStretcher; + +import android.content.Context; + +public class FxAccountSignInTask extends FxAccountSetupTask { + protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); + + protected final byte[] emailUTF8; + protected final PasswordStretcher passwordStretcher; + + public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { + super(context, progressDisplay, client, delegate); + this.emailUTF8 = email.getBytes("UTF-8"); + this.passwordStretcher = passwordStretcher; + } + + @Override + protected InnerRequestDelegate doInBackground(Void... arg0) { + try { + client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); + latch.await(); + return innerDelegate; + } catch (Exception e) { + Logger.error(LOG_TAG, "Got exception signing in.", e); + delegate.handleError(e); + } + return null; + } +} --- mozilla-esr31/mobile/android/base/android-services.mozbuild 2015-02-25 19:21:05.000000000 +0100 +++ mozilla-release/mobile/android/base/android-services.mozbuild 2014-09-24 03:05:32.000000000 +0200 @@ -555,7 +557,6 @@ 'fxa/activities/FxAccountCreateAccountActivity.java', 'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java', 'fxa/activities/FxAccountGetStartedActivity.java', - 'fxa/activities/FxAccountSetupTask.java', 'fxa/activities/FxAccountSignInActivity.java', 'fxa/activities/FxAccountStatusActivity.java', 'fxa/activities/FxAccountStatusFragment.java', @@ -589,6 +590,10 @@ 'fxa/sync/FxAccountSyncService.java', 'fxa/sync/FxAccountSyncStatusHelper.java', 'fxa/sync/SchedulePolicy.java', + 'fxa/tasks/FxAccountCodeResender.java', + 'fxa/tasks/FxAccountCreateAccountTask.java', + 'fxa/tasks/FxAccountSetupTask.java', + 'fxa/tasks/FxAccountSignInTask.java', 'sync/AlreadySyncingException.java', 'sync/BackoffHandler.java', 'sync/BadRequiredFieldJSONException.java',