Bug 961184 - Show activity after signing in to account. r=rnewman

Also (partially) implements resending verification codes, but doesn't
yet implement resending after account creation.
This commit is contained in:
Nick Alexander 2014-01-20 21:26:57 -08:00
parent 17697835ca
commit 4bc912955d
14 changed files with 409 additions and 49 deletions

View File

@ -545,15 +545,16 @@ sync_java_files = [
'browserid/VerifyingPublicKey.java',
'fxa/activities/FxAccountAbstractActivity.java',
'fxa/activities/FxAccountAbstractSetupActivity.java',
'fxa/activities/FxAccountConfirmAccountActivity.java',
'fxa/activities/FxAccountCreateAccountActivity.java',
'fxa/activities/FxAccountCreateAccountFragment.java',
'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
'fxa/activities/FxAccountCreateSuccessActivity.java',
'fxa/activities/FxAccountGetStartedActivity.java',
'fxa/activities/FxAccountSetupTask.java',
'fxa/activities/FxAccountSignInActivity.java',
'fxa/activities/FxAccountStatusActivity.java',
'fxa/activities/FxAccountUpdateCredentialsActivity.java',
'fxa/activities/FxAccountVerifiedAccountActivity.java',
'fxa/authenticator/AbstractFxAccount.java',
'fxa/authenticator/AndroidFxAccount.java',
'fxa/authenticator/FxAccountAuthenticator.java',

View File

@ -651,4 +651,46 @@ public class FxAccountClient10 {
};
post(resource, body, delegate);
}
/**
* Request a verification link be sent to the account email, given a valid session token.
*
* @param sessionToken
* to authenticate with.
* @param delegate
* to invoke callbacks.
*/
public void resendCode(byte[] sessionToken, final RequestDelegate<Void> delegate) {
final byte[] tokenId = new byte[32];
final byte[] reqHMACKey = new byte[32];
final byte[] requestKey = new byte[32];
try {
HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
} catch (Exception e) {
invokeHandleError(delegate, e);
return;
}
BaseResource resource;
try {
resource = new BaseResource(new URI(serverURI + "recovery_email/resend_code"));
} catch (URISyntaxException e) {
invokeHandleError(delegate, e);
return;
}
resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey, false) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
try {
delegate.handleSuccess(null);
return;
} catch (Exception e) {
delegate.handleError(e);
return;
}
}
};
post(resource, new JSONObject(), delegate);
}
}

View File

@ -0,0 +1,139 @@
/* 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.FxAccountClient10.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;
import ch.boye.httpclientandroidlib.HttpResponse;
/**
* Activity which displays account created successfully screen to the user, and
* starts them on the email verification path.
*/
public class FxAccountConfirmAccountActivity extends Activity implements OnClickListener {
protected static final String LOG_TAG = FxAccountConfirmAccountActivity.class.getSimpleName();
protected byte[] sessionToken;
protected TextView emailText;
/**
* Helper to find view or error if it is missing.
*
* @param id of view to find.
* @param description to print in error.
* @return non-null <code>View</code> instance.
*/
public View ensureFindViewById(View v, int id, String description) {
View view;
if (v != null) {
view = v.findViewById(id);
} else {
view = findViewById(id);
}
if (view == null) {
String message = "Could not find view " + description + ".";
Logger.error(LOG_TAG, message);
throw new RuntimeException(message);
}
return view;
}
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_confirm_account);
emailText = (TextView) ensureFindViewById(null, R.id.email, "email text");
if (getIntent() != null && getIntent().getExtras() != null) {
Bundle extras = getIntent().getExtras();
emailText.setText(extras.getString("email"));
sessionToken = extras.getByteArray("sessionToken");
}
View resendLink = ensureFindViewById(null, R.id.resend_confirmation_email_link, "resend confirmation email link");
resendLink.setOnClickListener(this);
if (sessionToken == null) {
resendLink.setEnabled(false);
resendLink.setClickable(false);
}
}
public static class FxAccountResendCodeTask extends FxAccountSetupTask<Void> {
protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName();
protected final byte[] sessionToken;
public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient20 client, RequestDelegate<Void> delegate) {
super(context, false, client, delegate);
this.sessionToken = sessionToken;
}
@Override
protected InnerRequestDelegate<Void> 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 class ResendCodeDelegate implements RequestDelegate<Void> {
@Override
public void handleError(Exception e) {
Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e);
Toast.makeText(getApplicationContext(), R.string.fxaccount_confirm_verification_link_not_sent, Toast.LENGTH_LONG).show();
}
@Override
public void handleFailure(int status, HttpResponse response) {
handleError(new HTTPFailureException(new SyncStorageResponse(response)));
}
@Override
public void handleSuccess(Void result) {
Toast.makeText(getApplicationContext(), R.string.fxaccount_confirm_verification_link_sent, Toast.LENGTH_SHORT).show();
}
}
protected void resendCode(byte[] sessionToken) {
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
RequestDelegate<Void> delegate = new ResendCodeDelegate();
Executor executor = Executors.newSingleThreadExecutor();
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
new FxAccountResendCodeTask(this, sessionToken, client, delegate).execute();
}
@Override
public void onClick(View v) {
resendCode(sessionToken);
}
}

View File

@ -13,7 +13,7 @@ import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignUpTask;
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
@ -182,6 +182,14 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
// intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
setResult(RESULT_OK, intent);
finish();
// Show success activity.
Intent successIntent = new Intent(FxAccountCreateAccountActivity.this, FxAccountConfirmAccountActivity.class);
successIntent.putExtra("email", email);
// 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);
startActivity(successIntent);
}
}
@ -191,7 +199,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
Executor executor = Executors.newSingleThreadExecutor();
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
try {
new FxAccountSignUpTask(this, email, password, client, delegate).execute();
new FxAccountCreateAccountTask(this, email, password, client, delegate).execute();
} catch (Exception e) {
showRemoteError(e);
}

View File

@ -35,10 +35,6 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
protected static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName();
protected final Context context;
protected final String email;
protected final byte[] emailUTF8;
protected final String password;
protected final byte[] passwordUTF8;
protected final FxAccountClient20 client;
protected ProgressDialog progressDialog = null;
@ -52,38 +48,24 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
protected final RequestDelegate<T> delegate;
public FxAccountSetupTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<T> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
public FxAccountSetupTask(Context context, boolean shouldShowProgressDialog, FxAccountClient20 client, RequestDelegate<T> delegate) {
this.context = context;
this.email = email;
this.emailUTF8 = email.getBytes("UTF-8");
this.password = password;
this.passwordUTF8 = password.getBytes("UTF-8");
this.client = client;
this.delegate = delegate;
}
/**
* Stretching the password is expensive, so we compute the stretched value lazily.
*
* @return stretched password.
* @throws GeneralSecurityException
* @throws UnsupportedEncodingException
*/
public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
if (this.quickStretchedPW == null) {
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
if (shouldShowProgressDialog) {
progressDialog = new ProgressDialog(context);
}
return this.quickStretchedPW;
}
@Override
protected void onPreExecute() {
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Firefox Account..."); // XXX.
progressDialog.setMessage("Please wait.");
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.show();
if (progressDialog != null) {
progressDialog.setTitle("Firefox Account..."); // XXX.
progressDialog.setMessage("Please wait.");
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.show();
}
}
@Override
@ -93,15 +75,13 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
}
// We are on the UI thread, and need to invoke these callbacks here to allow UI updating.
if (result.response != null) {
delegate.handleSuccess(result.response);
} else if (result.exception instanceof HTTPFailureException) {
if (result.exception instanceof HTTPFailureException) {
HTTPFailureException e = (HTTPFailureException) result.exception;
delegate.handleFailure(e.response.getStatusCode(), e.response.httpResponse());
} else if (innerDelegate.exception != null) {
delegate.handleError(innerDelegate.exception);
} else {
delegate.handleError(new IllegalStateException("Got bad state."));
delegate.handleSuccess(result.response);
}
}
@ -144,11 +124,30 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
}
}
public static class FxAccountSignUpTask extends FxAccountSetupTask<String> {
protected static final String LOG_TAG = FxAccountSignUpTask.class.getSimpleName();
public static class FxAccountCreateAccountTask extends FxAccountSetupTask<String> {
protected static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
public FxAccountSignUpTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
super(context, email, password, client, delegate);
protected final byte[] emailUTF8;
protected final byte[] passwordUTF8;
public FxAccountCreateAccountTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException {
super(context, true, client, delegate);
this.emailUTF8 = email.getBytes("UTF-8");
this.passwordUTF8 = password.getBytes("UTF-8");
}
/**
* Stretching the password is expensive, so we compute the stretched value lazily.
*
* @return stretched password.
* @throws GeneralSecurityException
* @throws UnsupportedEncodingException
*/
public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
if (this.quickStretchedPW == null) {
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
}
return this.quickStretchedPW;
}
@Override
@ -166,10 +165,29 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
}
public static class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> {
protected static final String LOG_TAG = FxAccountSignUpTask.class.getSimpleName();
protected static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
public FxAccountSignInTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
super(context, email, password, client, delegate);
protected final byte[] emailUTF8;
protected final byte[] passwordUTF8;
public FxAccountSignInTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
super(context, true, client, delegate);
this.emailUTF8 = email.getBytes("UTF-8");
this.passwordUTF8 = password.getBytes("UTF-8");
}
/**
* Stretching the password is expensive, so we compute the stretched value lazily.
*
* @return stretched password.
* @throws GeneralSecurityException
* @throws UnsupportedEncodingException
*/
public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
if (this.quickStretchedPW == null) {
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
}
return this.quickStretchedPW;
}
@Override

View File

@ -151,6 +151,20 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
// intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
setResult(RESULT_OK, intent);
finish();
// Show success activity depending on verification status.
Intent successIntent;
if (result.verified) {
successIntent = new Intent(FxAccountSignInActivity.this, FxAccountVerifiedAccountActivity.class);
} else {
successIntent = new Intent(FxAccountSignInActivity.this, FxAccountConfirmAccountActivity.class);
successIntent.putExtra("sessionToken", result.sessionToken);
}
successIntent.putExtra("email", email);
// 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);
startActivity(successIntent);
}
}

View File

@ -30,7 +30,6 @@ import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import ch.boye.httpclientandroidlib.HttpResponse;
/**
@ -132,7 +131,6 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
fxAccount.dump();
}
Toast.makeText(getApplicationContext(), "Got success updating account credential.", Toast.LENGTH_LONG).show();
redirectToActivity(FxAccountStatusActivity.class);
}
}

View File

@ -13,10 +13,10 @@ import android.view.View;
import android.widget.TextView;
/**
* Activity which displays sign up/sign in screen to the user.
* Activity which displays "Account verified" success screen.
*/
public class FxAccountCreateSuccessActivity extends Activity {
protected static final String LOG_TAG = FxAccountCreateSuccessActivity.class.getSimpleName();
public class FxAccountVerifiedAccountActivity extends Activity {
protected static final String LOG_TAG = FxAccountVerifiedAccountActivity.class.getSimpleName();
protected TextView emailText;
@ -50,7 +50,7 @@ public class FxAccountCreateSuccessActivity extends Activity {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_create_success);
setContentView(R.layout.fxaccount_account_verified);
emailText = (TextView) ensureFindViewById(null, R.id.email, "email text");
if (getIntent() != null && getIntent().getExtras() != null) {

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/fxaccount_link_textColor_inactive" />
<item android:state_pressed="true" android:color="@color/fxaccount_link_textColor_pressed" />
<item android:color="@color/fxaccount_link_textColor" />
</selector>

View File

@ -0,0 +1,55 @@
<?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/firefox_accounts" >
</TextView>
<TextView
style="@style/FxAccountSubHeaderItem"
android:text="@string/fxaccount_account_verified" >
</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_checkbox_contentDescription"
android:src="@drawable/fxaccount_checkbox" >
</ImageView>
<TextView
style="@style/FxAccountTextItem"
android:layout_marginBottom="15dp"
android:text="@string/fxaccount_account_verified_description"
android:textSize="18sp" >
</TextView>
<TextView
android:id="@+id/email"
style="@style/FxAccountTextItem"
android:layout_marginBottom="45dp"
android:textSize="20sp"
android:textStyle="bold" >
</TextView>
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,64 @@
<?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" >
<LinearLayout style="@style/FxAccountSpacer" />
<TextView
style="@style/FxAccountHeaderItem"
android:text="@string/firefox_accounts" >
</TextView>
<TextView
style="@style/FxAccountSubHeaderItem"
android:text="@string/fxaccount_confirm_your_account" >
</TextView>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="45dp"
android:background="@android:color/transparent"
android:contentDescription="@string/fxaccount_mail_contentDescription"
android:src="@drawable/fxaccount_mail" >
</ImageView>
<TextView
style="@style/FxAccountTextItem"
android:layout_marginBottom="15dp"
android:text="@string/fxaccount_verification_link_awaits_at"
android:textSize="18sp" >
</TextView>
<TextView
android:id="@+id/email"
style="@style/FxAccountTextItem"
android:layout_marginBottom="45dp"
android:textSize="20sp"
android:textStyle="bold" >
</TextView>
<TextView
android:id="@+id/resend_confirmation_email_link"
style="@style/FxAccountLinkItem"
android:text="@string/fxaccount_verification_link_not_showing_up_offer_resend" />
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
</LinearLayout>
</ScrollView>

View File

@ -22,6 +22,7 @@
<color name="fxaccount_input_textColorHint">#c0c9d0</color>
<color name="fxaccount_link_textColor">#0096dd</color>
<color name="fxaccount_link_textColor_pressed">#00767d</color>
<color name="fxaccount_link_textColor_inactive">#c0c9d0</color>
<color name="fxaccount_input_borderActive">#6a7b86</color>
<color name="fxaccount_input_borderInactive">#c0c9d0</color>
</resources>

View File

@ -38,7 +38,7 @@
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateSuccessActivity"
android:name="org.mozilla.gecko.fxa.activities.FxAccountConfirmAccountActivity"
android:windowSoftInputMode="adjustResize">
</activity>
@ -49,6 +49,13 @@
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"
android:name="org.mozilla.gecko.fxa.activities.FxAccountVerifiedAccountActivity"
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"

View File

@ -166,6 +166,18 @@
<string name="fxaccount_create_account_age_2005">&fxaccount.create.account.age.2005;</string>
<string name="fxaccount_create_account_age_2006">&fxaccount.create.account.age.2006;</string>
<string name="fxaccount_create_account_age_2007">&fxaccount.create.account.age.2007;</string>
<!-- Temporary Firefox Accounts strings. -->
<!-- We haven't yet decided how to linkify, so to save translation
time we're holding this back. -->
<string name="fxaccount_account_create_not_allowed_learn_more">&lt;a href="http://www.ftc.gov/news-events/media-resources/protecting-consumer-privacy/kids-privacy-coppa"&gt;Learn more&lt;/a&gt;</string>
<string name="fxaccount_sign_in_success">Account verified</string>
<string name="fxaccount_sign_in_success_description">You are now ready to use Sync!</string>
<string name="fxaccount_checkbox_contentDescription">Firefox Accounts checkbox graphic</string>
<string name="fxaccount_account_verified">Account verified</string>
<string name="fxaccount_account_verified_description">You are now ready to use Sync!</string>
<string name="fxaccount_confirm_your_account">Confirm your account</string>
<string name="fxaccount_verification_link_awaits_at">A verification link awaits at:</string>
<string name="fxaccount_verification_link_not_showing_up_offer_resend">Email not showing up? Send again</string>
<!-- TODO: add email address to toast? -->
<string name="fxaccount_confirm_verification_link_sent">Sent fresh verification link</string>
<string name="fxaccount_confirm_verification_link_not_sent">Couldn\&apos;t send a fresh verification link</string>