Bug 951304 - Part 2: first cut at native UI for Firefox Accounts on Android. r=rnewman

This commit is contained in:
Nick Alexander 2014-01-16 18:35:09 -08:00
parent 0b7eb17cf0
commit 084dc693bf
54 changed files with 2197 additions and 253 deletions

View File

@ -541,7 +541,15 @@ sync_java_files = [
'browserid/verifier/BrowserIDVerifierDelegate.java',
'browserid/verifier/BrowserIDVerifierException.java',
'browserid/VerifyingPublicKey.java',
'fxa/activities/FxAccountSetupActivity.java',
'fxa/activities/FxAccountAbstractActivity.java',
'fxa/activities/FxAccountAbstractSetupActivity.java',
'fxa/activities/FxAccountCreateAccountActivity.java',
'fxa/activities/FxAccountCreateAccountFragment.java',
'fxa/activities/FxAccountCreateSuccessActivity.java',
'fxa/activities/FxAccountGetStartedActivity.java',
'fxa/activities/FxAccountSetupTask.java',
'fxa/activities/FxAccountSignInActivity.java',
'fxa/activities/FxAccountStatusActivity.java',
'fxa/authenticator/AbstractFxAccount.java',
'fxa/authenticator/AndroidFxAccount.java',
'fxa/authenticator/FxAccountAuthenticator.java',
@ -549,7 +557,6 @@ sync_java_files = [
'fxa/authenticator/FxAccountLoginDelegate.java',
'fxa/authenticator/FxAccountLoginException.java',
'fxa/authenticator/FxAccountLoginPolicy.java',
'fxa/sync/FxAccount.java',
'fxa/sync/FxAccountGlobalSession.java',
'fxa/sync/FxAccountSyncAdapter.java',
'fxa/sync/FxAccountSyncService.java',

View File

@ -0,0 +1,80 @@
/* 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.background.common.log.Logger;
import android.app.Activity;
import android.content.Intent;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
public abstract class FxAccountAbstractActivity extends Activity {
private static final String LOG_TAG = FxAccountAbstractActivity.class.getSimpleName();
protected void launchActivity(Class<? extends Activity> activityClass) {
Intent intent = new Intent(this, activityClass);
// 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);
}
protected void redirectToActivity(Class<? extends Activity> activityClass) {
launchActivity(activityClass);
finish();
}
/**
* 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;
}
public void linkifyTextViews(View view, int[] textViews) {
for (int id : textViews) {
TextView textView;
if (view != null) {
textView = (TextView) view.findViewById(id);
} else {
textView = (TextView) findViewById(id);
}
if (textView == null) {
Logger.warn(LOG_TAG, "Could not process links for view with id " + id + ".");
continue;
}
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setText(Html.fromHtml(textView.getText().toString()));
}
}
protected void launchActivityOnClick(final View view, final Class<? extends Activity> activityClass) {
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FxAccountAbstractActivity.this.launchActivity(activityClass);
}
});
}
}

View File

@ -0,0 +1,122 @@
/* 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 android.app.AlertDialog;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity {
private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
protected int minimumPasswordLength = 8;
protected TextView localErrorTextView;
protected EditText emailEdit;
protected EditText passwordEdit;
protected Button showPasswordButton;
protected Button button;
protected void createShowPasswordButton() {
showPasswordButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
boolean isShown = 0 == (passwordEdit.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD);
// Changing input type loses position in edit text; let's try to maintain it.
int start = passwordEdit.getSelectionStart();
int stop = passwordEdit.getSelectionEnd();
passwordEdit.setInputType(passwordEdit.getInputType() ^ InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordEdit.setSelection(start, stop);
if (isShown) {
showPasswordButton.setText(R.string.fxaccount_password_show);
} else {
showPasswordButton.setText(R.string.fxaccount_password_hide);
}
}
});
}
protected void showRemoteError(Exception e) {
new AlertDialog.Builder(this).setTitle("Remote error!").setMessage(e.toString()).show();
}
protected void addListeners() {
TextChangedListener textChangedListener = new TextChangedListener();
EditorActionListener editorActionListener = new EditorActionListener();
FocusChangeListener focusChangeListener = new FocusChangeListener();
emailEdit.addTextChangedListener(textChangedListener);
emailEdit.setOnEditorActionListener(editorActionListener);
emailEdit.setOnFocusChangeListener(focusChangeListener);
passwordEdit.addTextChangedListener(textChangedListener);
passwordEdit.setOnEditorActionListener(editorActionListener);
passwordEdit.setOnFocusChangeListener(focusChangeListener);
}
protected class FocusChangeListener implements OnFocusChangeListener {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
return;
}
updateButtonState();
}
}
protected class EditorActionListener implements OnEditorActionListener {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
updateButtonState();
return false;
}
}
protected class TextChangedListener implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
updateButtonState();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing.
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Do nothing.
}
}
protected boolean updateButtonState() {
final String email = emailEdit.getText().toString();
final String password = passwordEdit.getText().toString();
boolean enabled =
(email.length() > 0) &&
Patterns.EMAIL_ADDRESS.matcher(email).matches() &&
(password.length() >= minimumPasswordLength);
if (enabled != button.isEnabled()) {
Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
button.setEnabled(enabled);
}
return enabled;
}
}

View File

@ -0,0 +1,184 @@
/* 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.fxa.activities.FxAccountSetupTask.FxAccountSignUpTask;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
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;
/**
* Activity which displays create account screen to the user.
*/
public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivity {
protected static final String LOG_TAG = FxAccountCreateAccountActivity.class.getSimpleName();
protected EditText yearEdit;
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_create_account);
linkifyTextViews(null, new int[] { R.id.policy });
localErrorTextView = (TextView) ensureFindViewById(null, R.id.local_error, "local error text view");
emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
yearEdit = (EditText) ensureFindViewById(null, R.id.year_edit, "year edit");
button = (Button) ensureFindViewById(null, R.id.create_account_button, "create account button");
createCreateAccountButton();
createYearEdit();
addListeners();
updateButtonState();
createShowPasswordButton();
launchActivityOnClick(ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link"), FxAccountSignInActivity.class);
}
/**
* {@inheritDoc}
*/
@Override
public void onResume() {
super.onResume();
if (FxAccountAuthenticator.getFirefoxAccounts(this).length > 0) {
redirectToActivity(FxAccountStatusActivity.class);
return;
}
}
protected void createYearEdit() {
yearEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String[] years = new String[20];
for (int i = 0; i < years.length; i++) {
years[i] = Integer.toString(2014 - i);
}
android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
yearEdit.setText(years[which]);
}
};
AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
.setTitle(R.string.fxaccount_when_were_you_born)
.setItems(years, listener)
.setIcon(R.drawable.fxaccount_icon)
.create();
dialog.show();
}
});
}
protected class CreateAccountDelegate implements RequestDelegate<String> {
public final String email;
public final String password;
public final String serverURI;
public CreateAccountDelegate(String email, String password, String serverURI) {
this.email = email;
this.password = password;
this.serverURI = serverURI;
}
@Override
public void handleError(Exception e) {
showRemoteError(e);
}
@Override
public void handleFailure(int status, HttpResponse response) {
handleError(new HTTPFailureException(new SyncStorageResponse(response)));
}
@Override
public void handleSuccess(String result) {
Activity activity = FxAccountCreateAccountActivity.this;
Logger.info(LOG_TAG, "Got success creating account.");
// We're on the UI thread, but it's okay to create the account here.
Account account;
try {
account = AndroidFxAccount.addAndroidAccount(activity, email, password,
serverURI, null, null, false);
if (account == null) {
throw new RuntimeException("XXX what?");
}
} catch (Exception e) {
handleError(e);
return;
}
// For great debugging.
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
new AndroidFxAccount(activity, account).dump();
}
Toast.makeText(getApplicationContext(), "Got success creating account.", Toast.LENGTH_LONG).show();
redirectToActivity(FxAccountStatusActivity.class);
}
}
public void createAccount(String email, String password) {
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
RequestDelegate<String> delegate = new CreateAccountDelegate(email, password, serverURI);
Executor executor = Executors.newSingleThreadExecutor();
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
try {
new FxAccountSignUpTask(this, email, password, client, delegate).execute();
} catch (Exception e) {
showRemoteError(e);
}
}
protected void createCreateAccountButton() {
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!updateButtonState()) {
return;
}
final String email = emailEdit.getText().toString();
final String password = passwordEdit.getText().toString();
createAccount(email, password);
}
});
}
}

View File

@ -0,0 +1,191 @@
/* 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 android.support.v4.app.Fragment;
public class FxAccountCreateAccountFragment extends Fragment { // implements OnClickListener {
protected static final String LOG_TAG = FxAccountCreateAccountFragment.class.getSimpleName();
//
// protected FxAccountSetupActivity activity;
//
// protected EditText emailEdit;
// protected EditText passwordEdit;
// protected EditText password2Edit;
// protected Button button;
//
// protected TextView emailError;
// protected TextView passwordError;
//
// protected TextChangedListener textChangedListener;
// protected EditorActionListener editorActionListener;
// protected OnFocusChangeListener focusChangeListener;
// @Override
// public void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// // Retain this fragment across configuration changes. See, for example,
// // http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
// // This fragment will own AsyncTask instances which should not be
// // interrupted by configuration changes (and activity changes).
// setRetainInstance(true);
// }
// @Override
// public View onCreateView(LayoutInflater inflater, ViewGroup container,
// Bundle savedInstanceState) {
// View v = inflater.inflate(R.layout.fxaccount_create_account_fragment, container, false);
//
// FxAccountSetupActivity.linkifyTextViews(v, new int[] { R.id.description, R.id.policy });
//
// emailEdit = (EditText) ensureFindViewById(v, R.id.email, "email");
// passwordEdit = (EditText) ensureFindViewById(v, R.id.password, "password");
// // Second password can be null.
// password2Edit = (EditText) v.findViewById(R.id.password2);
//
// emailError = (TextView) ensureFindViewById(v, R.id.email_error, "email error");
// passwordError = (TextView) ensureFindViewById(v, R.id.password_error, "password error");
//
// textChangedListener = new TextChangedListener();
// editorActionListener = new EditorActionListener();
// focusChangeListener = new FocusChangeListener();
//
// addListeners(emailEdit);
// addListeners(passwordEdit);
// if (password2Edit != null) {
// addListeners(password2Edit);
// }
//
// button = (Button) ensureFindViewById(v, R.id.create_account_button, "button");
// button.setOnClickListener(this);
// return v;
// }
// protected void onCreateAccount(View button) {
// Logger.debug(LOG_TAG, "onCreateAccount: Asking for username/password for new account.");
// String email = emailEdit.getText().toString();
// String password = passwordEdit.getText().toString();
// activity.signUp(email, password);
// }
//
// @Override
// public void onClick(View v) {
// switch (v.getId()) {
// case R.id.create_account_button:
// if (!validate(false)) {
// return;
// }
// onCreateAccount(v);
// break;
// }
// }
//
// protected void addListeners(EditText editText) {
// editText.addTextChangedListener(textChangedListener);
// editText.setOnEditorActionListener(editorActionListener);
// editText.setOnFocusChangeListener(focusChangeListener);
// }
//
// protected class FocusChangeListener implements OnFocusChangeListener {
// @Override
// public void onFocusChange(View v, boolean hasFocus) {
// if (hasFocus) {
// return;
// }
// validate(false);
// }
// }
//
// protected class EditorActionListener implements OnEditorActionListener {
// @Override
// public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// validate(false);
// return false;
// }
// }
//
// protected class TextChangedListener implements TextWatcher {
// @Override
// public void afterTextChanged(Editable s) {
// validate(true);
// }
//
// @Override
// public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// // Do nothing.
// }
//
// @Override
// public void onTextChanged(CharSequence s, int start, int before, int count) {
// // Do nothing.
// }
// }
//
// /**
// * Show or hide error messaging.
// *
// * @param removeOnly
// * if true, possibly remove existing error messages but do not set an
// * error message if one was not present.
// * @param errorResourceId
// * of error string, or -1 to hide.
// * @param errorView
// * <code>TextView</code> instance to display error message in.
// * @param edits
// * <code>EditText</code> instances to style.
// */
// protected void setError(boolean removeOnly, int errorResourceId, TextView errorView, EditText... edits) {
// if (removeOnly && errorResourceId != -1) {
// return;
// }
//
// int res = errorResourceId == -1 ? R.drawable.fxaccount_textfield_background : R.drawable.fxaccount_textfield_error_background;
// for (EditText edit : edits) {
// if (edit == null) {
// continue;
// }
// edit.setBackgroundResource(res);
// }
// if (errorResourceId == -1) {
// errorView.setVisibility(View.GONE);
// errorView.setText(null);
// } else {
// errorView.setText(errorResourceId);
// errorView.setVisibility(View.VISIBLE);
// }
// }
//
// protected boolean validate(boolean removeOnly) {
// boolean enabled = true;
// final String email = emailEdit.getText().toString();
// final String password = passwordEdit.getText().toString();
//
// if (email.length() == 0 || Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
// setError(removeOnly, -1, emailError, emailEdit);
// } else {
// enabled = false;
// setError(removeOnly, R.string.fxaccount_bad_email, emailError, emailEdit);
// }
//
// if (password2Edit != null) {
// final String password2 = password2Edit.getText().toString();
// enabled = enabled && password2.length() > 0;
//
// boolean passwordsMatch = password.equals(password2);
// if (passwordsMatch) {
// setError(removeOnly, -1, passwordError, passwordEdit, password2Edit);
// } else {
// enabled = false;
// setError(removeOnly, R.string.fxaccount_bad_passwords, passwordError, passwordEdit, password2Edit);
// }
// }
//
// if (enabled != button.isEnabled()) {
// Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
// button.setEnabled(enabled);
// }
//
// return enabled;
// }
}

View File

@ -0,0 +1,60 @@
/* 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 android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
/**
* Activity which displays sign up/sign in screen to the user.
*/
public class FxAccountCreateSuccessActivity extends Activity {
protected static final String LOG_TAG = FxAccountCreateSuccessActivity.class.getSimpleName();
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_create_success);
emailText = (TextView) ensureFindViewById(null, R.id.email, "email text");
if (getIntent() != null && getIntent().getExtras() != null) {
emailText.setText(getIntent().getStringExtra("email"));
}
}
}

View File

@ -0,0 +1,32 @@
/* 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 android.os.Bundle;
/**
* Activity which displays sign up/sign in screen to the user.
*/
public class FxAccountGetStartedActivity extends FxAccountAbstractActivity {
protected static final String LOG_TAG = FxAccountGetStartedActivity.class.getSimpleName();
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_get_started);
linkifyTextViews(null, new int[] { R.id.old_firefox });
launchActivityOnClick(ensureFindViewById(null, R.id.get_started_button, "get started button"), FxAccountCreateAccountActivity.class);
}
}

View File

@ -1,48 +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 org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
/**
* Activity which displays login screen to the user.
*/
public class FxAccountSetupActivity extends Activity {
protected static final String LOG_TAG = FxAccountSetupActivity.class.getSimpleName();
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_setup);
}
@Override
public void onResume() {
Logger.debug(LOG_TAG, "onResume()");
super.onResume();
// Start Fennec at about:accounts page.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
AppConstants.ANDROID_PACKAGE_NAME + ".App");
intent.setData(Uri.parse("about:accounts"));
startActivity(intent);
finish();
}
}

View File

@ -0,0 +1,171 @@
/* 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.security.GeneralSecurityException;
import java.util.concurrent.CountDownLatch;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import ch.boye.httpclientandroidlib.HttpResponse;
/**
* An <code>AsyncTask</code> wrapper around signing up for, and signing in to, a
* Firefox Account.
* <p>
* It's strange to add explicit blocking to callback-threading code, but we do
* it here to take advantage of Android's built in support for background work.
* We really want to avoid making a threading mistake that brings down the whole
* process.
*/
abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> {
protected static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName();
protected final Context context;
protected final String email;
protected final byte[] emailUTF8;
protected final String password;
protected final byte[] quickStretchedPW;
protected final FxAccountClient20 client;
protected ProgressDialog progressDialog = null;
// AsyncTask's are one-time-use, so final members are fine.
protected final CountDownLatch latch = new CountDownLatch(1);
protected final InnerRequestDelegate<T> innerDelegate = new InnerRequestDelegate<T>(latch);
protected final RequestDelegate<T> delegate;
public FxAccountSetupTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<T> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
this.context = context;
this.email = email;
this.emailUTF8 = email.getBytes("UTF-8");
this.password = password;
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes("UTF-8"));
this.client = client;
this.delegate = delegate;
}
@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();
}
@Override
protected void onPostExecute(InnerRequestDelegate<T> result) {
if (progressDialog != null) {
progressDialog.dismiss();
}
// 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) {
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."));
}
}
@Override
protected void onCancelled(InnerRequestDelegate<T> result) {
if (progressDialog != null) {
progressDialog.dismiss();
}
delegate.handleError(new IllegalStateException("Task was cancelled."));
}
protected static class InnerRequestDelegate<T> implements RequestDelegate<T> {
protected final CountDownLatch latch;
public T response = null;
public Exception exception = null;
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(int status, HttpResponse response) {
Logger.warn(LOG_TAG, "Got failure.");
this.exception = new HTTPFailureException(new SyncStorageResponse(response));
latch.countDown();
}
@Override
public void handleSuccess(T result) {
Logger.info(LOG_TAG, "Got success.");
this.response = result;
latch.countDown();
}
}
public static class FxAccountSignUpTask extends FxAccountSetupTask<String> {
protected static final String LOG_TAG = FxAccountSignUpTask.class.getSimpleName();
public FxAccountSignUpTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
super(context, email, password, client, delegate);
}
@Override
protected InnerRequestDelegate<String> doInBackground(Void... arg0) {
try {
client.createAccount(emailUTF8, quickStretchedPW, false, innerDelegate);
latch.await();
return innerDelegate;
} catch (InterruptedException e) {
Logger.error(LOG_TAG, "Got exception logging in.", e);
delegate.handleError(e);
}
return null;
}
}
public static class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> {
protected static final String LOG_TAG = FxAccountSignUpTask.class.getSimpleName();
public FxAccountSignInTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
super(context, email, password, client, delegate);
}
@Override
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
try {
client.loginAndGetKeys(emailUTF8, quickStretchedPW, innerDelegate);
latch.await();
return innerDelegate;
} catch (InterruptedException e) {
Logger.error(LOG_TAG, "Got exception signing in.", e);
delegate.handleError(e);
}
return null;
}
}
}

View File

@ -0,0 +1,149 @@
/* 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.background.fxa.FxAccountClient20.LoginResponse;
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.authenticator.FxAccountAuthenticator;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import android.accounts.Account;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
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;
/**
* Activity which displays sign in screen to the user.
*/
public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
protected static final String LOG_TAG = FxAccountSignInActivity.class.getSimpleName();
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_sign_in);
localErrorTextView = (TextView) ensureFindViewById(null, R.id.local_error, "local error text view");
emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
button = (Button) ensureFindViewById(null, R.id.sign_in_button, "sign in button");
minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
createSignInButton();
addListeners();
updateButtonState();
createShowPasswordButton();
this.launchActivityOnClick(ensureFindViewById(null, R.id.create_account_link, "create account instead link"), FxAccountCreateAccountActivity.class);
// Not yet implemented.
this.launchActivityOnClick(ensureFindViewById(null, R.id.forgot_password_link, "forgot password link"), null);
}
/**
* {@inheritDoc}
*/
@Override
public void onResume() {
super.onResume();
if (FxAccountAuthenticator.getFirefoxAccounts(this).length > 0) {
redirectToActivity(FxAccountStatusActivity.class);
return;
}
}
protected class SignInDelegate implements RequestDelegate<LoginResponse> {
public final String email;
public final String password;
public final String serverURI;
public SignInDelegate(String email, String password, String serverURI) {
this.email = email;
this.password = password;
this.serverURI = serverURI;
}
@Override
public void handleError(Exception e) {
showRemoteError(e);
}
@Override
public void handleFailure(int status, HttpResponse response) {
showRemoteError(new HTTPFailureException(new SyncStorageResponse(response)));
}
@Override
public void handleSuccess(LoginResponse result) {
Activity activity = FxAccountSignInActivity.this;
Logger.info(LOG_TAG, "Got success signing in.");
// We're on the UI thread, but it's okay to create the account here.
Account account;
try {
account = AndroidFxAccount.addAndroidAccount(activity, email, password,
serverURI, result.sessionToken, result.keyFetchToken, result.verified);
if (account == null) {
throw new RuntimeException("XXX what?");
}
} catch (Exception e) {
handleError(e);
return;
}
// For great debugging.
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
new AndroidFxAccount(activity, account).dump();
}
Toast.makeText(getApplicationContext(), "Got success creating account.", Toast.LENGTH_LONG).show();
redirectToActivity(FxAccountStatusActivity.class);
}
}
public void signIn(String email, String password) {
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
RequestDelegate<LoginResponse> delegate = new SignInDelegate(email, password, serverURI);
Executor executor = Executors.newSingleThreadExecutor();
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
try {
new FxAccountSignInTask(this, email, password, client, delegate).execute();
} catch (Exception e) {
showRemoteError(e);
}
}
protected void createSignInButton() {
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String email = emailEdit.getText().toString();
final String password = passwordEdit.getText().toString();
signIn(email, password);
}
});
}
}

View File

@ -0,0 +1,150 @@
/* 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.authenticator.FxAccountAuthenticator;
import android.accounts.Account;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
/**
* Activity which displays account status.
*/
public class FxAccountStatusActivity extends FxAccountAbstractActivity {
protected static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
protected View connectionStatusUnverifiedView;
protected View connectionStatusSignInView;
protected View connectionStatusSyncingView;
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
setContentView(R.layout.fxaccount_status);
connectionStatusUnverifiedView = ensureFindViewById(null, R.id.unverified_view, "unverified view");
connectionStatusSignInView = ensureFindViewById(null, R.id.sign_in_view, "sign in view");
connectionStatusSyncingView = ensureFindViewById(null, R.id.syncing_view, "syncing view");
}
@Override
public void onResume() {
super.onResume();
refresh();
}
protected void refresh(Account account) {
TextView email = (TextView) findViewById(R.id.email);
if (account == null) {
redirectToActivity(FxAccountGetStartedActivity.class);
return;
}
AndroidFxAccount fxAccount = new AndroidFxAccount(this, account);
email.setText(account.name);
// Not as good as interrogating state machine, but will do for now.
if (!fxAccount.isVerified()) {
connectionStatusUnverifiedView.setVisibility(View.VISIBLE);
connectionStatusSignInView.setVisibility(View.GONE);
connectionStatusSyncingView.setVisibility(View.GONE);
return;
}
if (fxAccount.getQuickStretchedPW() == null) {
connectionStatusUnverifiedView.setVisibility(View.GONE);
connectionStatusSignInView.setVisibility(View.VISIBLE);
connectionStatusSyncingView.setVisibility(View.GONE);
return;
}
connectionStatusUnverifiedView.setVisibility(View.GONE);
connectionStatusSignInView.setVisibility(View.GONE);
connectionStatusSyncingView.setVisibility(View.VISIBLE);
}
protected void refresh() {
Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
if (accounts.length < 1) {
refresh(null);
return;
}
refresh(accounts[0]);
}
protected void dumpAccountDetails() {
Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
if (accounts.length < 1) {
return;
}
AndroidFxAccount fxAccount = new AndroidFxAccount(this, accounts[0]);
fxAccount.dump();
}
protected void forgetAccountTokens() {
Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
if (accounts.length < 1) {
return;
}
AndroidFxAccount fxAccount = new AndroidFxAccount(this, accounts[0]);
fxAccount.forgetAccountTokens();
fxAccount.dump();
}
protected void forgetQuickStretchedPW() {
Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
if (accounts.length < 1) {
return;
}
AndroidFxAccount fxAccount = new AndroidFxAccount(this, accounts[0]);
fxAccount.forgetQuickstretchedPW();
fxAccount.dump();
}
public void onClickRefresh(View view) {
Logger.debug(LOG_TAG, "Refreshing.");
refresh();
}
public void onClickForgetAccountTokens(View view) {
Logger.debug(LOG_TAG, "Forgetting account tokens.");
forgetAccountTokens();
}
public void onClickForgetPassword(View view) {
Logger.debug(LOG_TAG, "Forgetting quickStretchedPW.");
forgetQuickStretchedPW();
}
public void onClickDumpAccountDetails(View view) {
Logger.debug(LOG_TAG, "Dumping account details.");
dumpAccountDetails();
}
public void onClickGetStarted(View view) {
Logger.debug(LOG_TAG, "Launching get started activity.");
redirectToActivity(FxAccountGetStartedActivity.class);
}
public void onClickVerify(View view) {
Logger.debug(LOG_TAG, "Launching verification activity.");
}
public void onClickSignIn(View view) {
Logger.debug(LOG_TAG, "Launching sign in again activity.");
}
}

View File

@ -83,7 +83,8 @@ public class AndroidFxAccount implements AbstractFxAccount {
@Override
public byte[] getQuickStretchedPW() {
return Utils.hex2Byte(accountManager.getPassword(account));
String quickStretchedPW = accountManager.getPassword(account);
return quickStretchedPW == null ? null : Utils.hex2Byte(quickStretchedPW);
}
@Override
@ -305,8 +306,15 @@ public class AndroidFxAccount implements AbstractFxAccount {
/**
* <b>For debugging only!</b>
*/
public void resetAccountTokens() {
public void forgetAccountTokens() {
accountManager.setUserData(account, ACCOUNT_KEY_SESSION_TOKEN, null);
accountManager.setUserData(account, ACCOUNT_KEY_KEY_FETCH_TOKEN, null);
}
/**
* <b>For debugging only!</b>
*/
public void forgetQuickstretchedPW() {
accountManager.setPassword(account, null);
}
}

View File

@ -7,7 +7,7 @@ package org.mozilla.gecko.fxa.authenticator;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.activities.FxAccountSetupActivity;
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
@ -81,7 +81,7 @@ public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
return res;
}
Intent intent = new Intent(context, FxAccountSetupActivity.class);
Intent intent = new Intent(context, FxAccountGetStartedActivity.class);
res.putParcelable(AccountManager.KEY_INTENT, intent);
return res;
}

View File

@ -1,175 +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.sync;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClient10;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
import org.mozilla.gecko.tokenserver.TokenServerClient;
import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
import org.mozilla.gecko.tokenserver.TokenServerException;
import org.mozilla.gecko.tokenserver.TokenServerToken;
import android.content.Context;
import ch.boye.httpclientandroidlib.HttpResponse;
/**
* Represent a Firefox Account.
*
* This is the FxAccounts equivalent of {@link SyncAccountParameters}.
*/
public class FxAccount {
protected static final String LOG_TAG = FxAccount.class.getSimpleName();
public interface Delegate {
public void handleSuccess(String uid, String endpoint, AuthHeaderProvider authHeaderProvider);
public void handleError(Exception e);
}
protected final String email;
protected final byte[] sessionTokenBytes;
protected final byte[] kA;
protected final byte[] kB;
protected final String idpEndpoint;
protected final String authEndpoint;
protected final Executor executor;
public FxAccount(String email, byte[] sessionTokenBytes, byte[] kA, byte[] kB, String idpEndpoint, String authEndpoint) {
this.email = email;
this.sessionTokenBytes = sessionTokenBytes;
this.kA = kA;
this.kB = kB;
this.idpEndpoint = idpEndpoint;
this.authEndpoint = authEndpoint;
this.executor = Executors.newSingleThreadExecutor();
}
protected static class InnerFxAccountClientRequestDelegate implements FxAccountClient10.RequestDelegate<String> {
protected final Executor executor;
protected final String audience;
protected final String tokenServerEndpoint;
protected final BrowserIDKeyPair keyPair;
protected final Delegate delegate;
protected InnerFxAccountClientRequestDelegate(Executor executor, String audience, String tokenServerEndpoint, BrowserIDKeyPair keyPair, Delegate delegate) {
this.executor = executor;
this.audience = audience;
this.tokenServerEndpoint = tokenServerEndpoint;
this.keyPair = keyPair;
this.delegate = delegate;
}
@Override
public void handleError(Exception e) {
Logger.error(LOG_TAG, "Failed to sign.", e);
delegate.handleError(e);
}
@Override
public void handleFailure(int status, HttpResponse response) {
HTTPFailureException e = new HTTPFailureException(new SyncStorageResponse(response));
Logger.error(LOG_TAG, "Failed to sign.", e);
delegate.handleError(e);
}
@Override
public void handleSuccess(String certificate) {
Logger.pii(LOG_TAG, "Got certificate " + certificate);
try {
String assertion = JSONWebTokenUtils.createAssertion(keyPair.getPrivate(), certificate, audience);
if (Logger.LOG_PERSONAL_INFORMATION) {
Logger.pii(LOG_TAG, "Generated assertion " + assertion);
JSONWebTokenUtils.dumpAssertion(assertion);
}
TokenServerClient tokenServerclient = new TokenServerClient(new URI(tokenServerEndpoint), executor);
tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, new InnerTokenServerClientDelegate(delegate));
} catch (Exception e) {
Logger.error(LOG_TAG, "Got error doing stuff.", e);
delegate.handleError(e);
}
}
}
protected static class InnerTokenServerClientDelegate implements TokenServerClientDelegate {
protected final Delegate delegate;
public InnerTokenServerClientDelegate(Delegate delegate) {
this.delegate = delegate;
}
@Override
public void handleSuccess(TokenServerToken token) {
AuthHeaderProvider authHeaderProvider;
try {
authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false);
} catch (UnsupportedEncodingException e) {
Logger.error(LOG_TAG, "Failed to sync.", e);
delegate.handleError(e);
return;
}
delegate.handleSuccess(token.uid, token.endpoint, authHeaderProvider);
}
@Override
public void handleFailure(TokenServerException e) {
Logger.error(LOG_TAG, "Failed fetching server token.", e);
delegate.handleError(e);
}
@Override
public void handleError(Exception e) {
Logger.error(LOG_TAG, "Got error fetching token server token.", e);
delegate.handleError(e);
}
}
/**
* Request a signed certificate and exchange it for a token.
*
* This is temporary code that does not do production-ready caching. This
* should be made obsolete by a fully featured {@link FxAccountAuthenticator}.
*
* @param context to use.
* @param tokenServerEndpoint to get token from.
* @param keyPair to sign certificate for.
* @param delegate to callback to.
*/
public void login(final Context context, final String tokenServerEndpoint,
final BrowserIDKeyPair keyPair, final Delegate delegate) {
ExtendedJSONObject publicKeyObject;
try {
publicKeyObject = keyPair.getPublic().toJSONObject();
} catch (Exception e) {
delegate.handleError(e);
return;
}
// We have nested executors in play here. Since we control the executor and
// the delegates, we can guarantee that there is no dead-lock between the
// inner FxAccountClient delegate, the outer TokenServerClient delegate, and
// the user supplied delegate.
FxAccountClient10 fxAccountClient = new FxAccountClient10(idpEndpoint, executor);
fxAccountClient.sign(sessionTokenBytes, publicKeyObject,
JSONWebTokenUtils.DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS,
new InnerFxAccountClientRequestDelegate(executor, authEndpoint, tokenServerEndpoint, keyPair, delegate));
}
}

View File

@ -57,12 +57,17 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
*/
protected static class SessionCallback implements BaseGlobalSessionCallback {
protected final CountDownLatch latch;
protected final SyncResult syncResult;
public SessionCallback(CountDownLatch latch) {
public SessionCallback(CountDownLatch latch, SyncResult syncResult) {
if (latch == null) {
throw new IllegalArgumentException("latch must not be null");
}
if (syncResult == null) {
throw new IllegalArgumentException("syncResult must not be null");
}
this.latch = latch;
this.syncResult = syncResult;
}
@Override
@ -86,20 +91,47 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
public void handleStageCompleted(Stage currentState, GlobalSession globalSession) {
}
/**
* No error! Say that we made progress.
*/
protected void setSyncResultSuccess() {
syncResult.stats.numUpdates += 1;
}
/**
* Soft error. Say that we made progress, so that Android will sync us again
* after exponential backoff.
*/
protected void setSyncResultSoftError() {
syncResult.stats.numUpdates += 1;
syncResult.stats.numIoExceptions += 1;
}
/**
* Hard error. We don't want Android to sync us again, even if we make
* progress, until the user intervenes.
*/
protected void setSyncResultHardError() {
syncResult.stats.numAuthExceptions += 1;
}
@Override
public void handleSuccess(GlobalSession globalSession) {
Logger.info(LOG_TAG, "Successfully synced!");
setSyncResultSuccess();
Logger.info(LOG_TAG, "Sync succeeded.");
latch.countDown();
}
@Override
public void handleError(GlobalSession globalSession, Exception ex) {
Logger.warn(LOG_TAG, "Sync failed.", ex);
public void handleError(GlobalSession globalSession, Exception e) {
setSyncResultSoftError();
Logger.warn(LOG_TAG, "Sync failed.", e);
latch.countDown();
}
@Override
public void handleAborted(GlobalSession globalSession, String reason) {
setSyncResultSoftError();
Logger.warn(LOG_TAG, "Sync aborted: " + reason);
latch.countDown();
}
@ -122,6 +154,9 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
" with instance " + this + ".");
final CountDownLatch latch = new CountDownLatch(1);
final BaseGlobalSessionCallback callback = new SessionCallback(latch, syncResult);
try {
final String authEndpoint = FxAccountConstants.DEFAULT_AUTH_ENDPOINT;
final String tokenServerEndpoint = authEndpoint + (authEndpoint.endsWith("/") ? "" : "/") + "1.0/sync/1.1";
@ -150,7 +185,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
sharedPrefs.edit().putLong("tokenFailures", 0).commit();
final BaseGlobalSessionCallback callback = new SessionCallback(latch);
FxAccountGlobalSession globalSession = null;
try {
ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
@ -187,7 +221,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void handleError(Exception e) {
Logger.error(LOG_TAG, "Failed to get token.", e);
latch.countDown();
callback.handleError(null, e);
}
});
}
@ -195,15 +229,17 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void handleError(FxAccountLoginException e) {
Logger.error(LOG_TAG, "Got error logging in.", e);
latch.countDown();
callback.handleError(null, e);
}
});
latch.await();
} catch (Exception e) {
Logger.error(LOG_TAG, "Got error syncing.", e);
latch.countDown();
callback.handleError(null, e);
}
Logger.error(LOG_TAG, "Syncing done.");
}
protected void debugAssertion(String audience, String assertion) {

View File

@ -108,4 +108,52 @@
<!ENTITY sync.text.tab.not.sent.label 'There was a problem sending your tab.'>
<!-- Firefox Account strings -->
<!ENTITY firefox.accounts 'Firefox Accounts'>
<!ENTITY fxaccount.label '&fxaccountBrand.fullName.label;'>
<!ENTITY fxaccount.policy 'By proceeding you agree with the &lt;a
href="http://mozilla.com"&gt;Terms of Service&lt;/a&gt; and &lt;a
href="http://mozilla.com"&gt;Privacy Policy&lt;/a&gt;.'>
<!ENTITY fxaccount.forgot.password 'Forgot password?'>
<!ENTITY fxaccount.create.account.button.label 'Create Account'>
<!ENTITY fxaccount.sign.in.button.label 'Sign In'>
<!ENTITY fxaccount.email.hint 'Email'>
<!ENTITY fxaccount.password.hint 'Password'>
<!ENTITY fxaccount.password2.hint 'Confirm password'>
<!ENTITY fxaccount.bad.email 'We need a real email address.'>
<!ENTITY fxaccount.bad.passwords 'Your passwords must match.'>
<!ENTITY fxaccount.sign.in.instead 'Already have an account? Sign in!'>
<!ENTITY fxaccount.sign.up.instead 'Don\&apos;t have an account? Sign up instead.'>
<!ENTITY fxaccount.sign.in 'Sign in'>
<!ENTITY fxaccount.create.account 'Create an account'>
<!ENTITY fxaccount.old.firefox '&lt;a href="http://mozilla.com"&gt;Using &syncBrand.shortName.label; with an older version of &brandShortName;?&lt;/a&gt;'>
<!ENTITY fxaccount.when.were.you.born 'When were you born?'>
<!ENTITY fxaccount.year.of.birth 'Year of birth'>
<!ENTITY fxaccount.icon.contentDescription 'Firefox Accounts icon'>
<!ENTITY fxaccount.password.hide 'Hide'>
<!ENTITY fxaccount.password.show 'Show'>
<!ENTITY fxaccount.get.started 'Get started'>
<!ENTITY fxaccount.description 'Sign in to backup and sync all your settings and bookmarks!'>
<!ENTITY fxaccount.sign.up 'Sign up'>
<!-- Android > Accounts > [Firefox Account] Screen -->
<!ENTITY fxaccount.options.title 'Options'>
<!ENTITY fxaccount.options.configure.title 'Configure your Firefox Account'>
<!ENTITY fxaccount.intro.contentDescription 'Firefox Accounts introduction graphics'>
<!ENTITY fxaccount.confirmation.email.sent 'Confirmation email sent!'>
<!ENTITY fxaccount.mail.contentDescription 'Firefox Accounts envelope graphic'>
<!ENTITY fxaccount.confirmation.description 'Your confirmation link awaits at:'>
<!ENTITY fxaccount.offer.resend.confirmation.email 'Don\&apos;t see an email? Resend.'>
<!ENTITY fxaccount.password.length.restriction 'Must be at least 8 characters.'>
<!ENTITY fxaccount.change.password 'Change password'>
<!ENTITY fxaccount.status.needs.verification 'Your account needs to be verified. Tap here to verify and start syncing.'>
<!ENTITY fxaccount.status.needs.credentials 'Your account details are out of date. Tap here to sign in again and resume syncing.'>
<!ENTITY fxaccount.status.syncing 'Firefox is syncing...'>
<!ENTITY fxaccount.status.sync 'Sync' >
<!ENTITY fxaccount.status.bookmarks 'Bookmarks'>
<!ENTITY fxaccount.status.history 'History'>
<!ENTITY fxaccount.status.passwords 'Passwords'>
<!ENTITY fxaccount.status.tabs 'Open tabs'>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/fxaccount_button_background_pressed" />
<item android:state_enabled="false"
android:drawable="@drawable/fxaccount_button_background_disabled" />
<item android:state_enabled="true"
android:drawable="@drawable/fxaccount_button_background_enabled" />
</selector>

View File

@ -0,0 +1,11 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="@color/fxaccount_button_background_inactive" />
<corners
android:radius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,11 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="@color/fxaccount_button_background_active" />
<corners
android:radius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,11 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="@color/fxaccount_button_background_hit" />
<corners
android:radius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/fxaccount_button_textColor" />
</selector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<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,19 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/transparent" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="@dimen/fxaccount_corner_radius"
android:topRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
android:bottomRightRadius="0dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_focused="false"
android:drawable="@drawable/fxaccount_password_inactive" />
<item
android:drawable="@drawable/fxaccount_password_active" />
</selector>

View File

@ -0,0 +1,19 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/transparent" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="0dp"
android:topRightRadius="@dimen/fxaccount_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,12 @@
<?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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_focused="false"
android:drawable="@drawable/fxaccount_password_button_inactive" />
<item
android:drawable="@drawable/fxaccount_password_button_active" />
</selector>

View File

@ -0,0 +1,19 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/transparent" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderInactive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="0dp"
android:topRightRadius="@dimen/fxaccount_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,19 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/transparent" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderInactive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="@dimen/fxaccount_corner_radius"
android:topRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
android:bottomRightRadius="0dp" />
</shape>

View File

@ -0,0 +1,15 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/transparent" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<corners
android:radius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,12 @@
<?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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_focused="false"
android:drawable="@drawable/fxaccount_textfield_inactive" />
<item
android:drawable="@drawable/fxaccount_textfield_active" />
</selector>

View File

@ -0,0 +1,13 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderInactive" />
<corners
android:radius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,9 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="@color/fxaccount_error" />
</shape>

View File

@ -0,0 +1,79 @@
<?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/create_account_view"
style="@style/FxAccountMiddle" >
<TextView
style="@style/FxAccountHeaderItem"
android:text="@string/firefox_accounts" />
<TextView
style="@style/FxAccountSubHeaderItem"
android:text="@string/fxaccount_create_account" />
<include layout="@layout/fxaccount_email_password_view" />
<TextView
style="@style/FxAccountLinkifiedItem"
android:layout_marginTop="10dp"
android:text="@string/fxaccount_password_length_restriction" />
<TextView
style="@style/FxAccountTextItem"
android:layout_marginTop="15dp"
android:text="@string/fxaccount_when_were_you_born"
android:textSize="18sp" />
<!-- Per http://stackoverflow.com/questions/2359176/android-edittext-onclicklistener, not allowing focus allows us to highjack the click. -->
<EditText
android:id="@+id/year_edit"
style="@style/FxAccountEditItem"
android:layout_marginTop="15dp"
android:drawableRight="@drawable/fxaccount_ddarrow_inactive"
android:focusable="false"
android:hint="@string/fxaccount_year_of_birth"
android:inputType="none" />
<TextView
android:id="@+id/local_error"
style="@style/FxAccountErrorItem" />
<Button
android:id="@+id/create_account_button"
style="@style/FxAccountButton"
android:layout_marginBottom="20dp"
android:layout_marginTop="45dp"
android:text="@string/fxaccount_create_account_button_label" />
<TextView
android:id="@+id/sign_in_instead_link"
style="@style/FxAccountLinkItem"
android:layout_marginBottom="20dp"
android:focusable="true"
android:text="@string/fxaccount_sign_in_instead" />
<TextView
android:id="@+id/policy"
style="@style/FxAccountLinkifiedItem"
android:text="@string/fxaccount_policy" />
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
</LinearLayout>
</ScrollView>

View File

@ -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/create_success_view"
style="@style/FxAccountMiddle" >
<TextView
style="@style/FxAccountHeaderItem"
android:text="@string/firefox_accounts" >
</TextView>
<TextView
style="@style/FxAccountSubHeaderItem"
android:text="@string/fxaccount_confirmation_email_sent" >
</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_mail_contentDescription"
android:src="@drawable/fxaccount_mail" >
</ImageView>
<TextView
style="@style/FxAccountTextItem"
android:layout_marginBottom="15dp"
android:text="@string/fxaccount_confirmation_description"
android:textSize="18sp" >
</TextView>
<TextView
android:id="@+id/email"
style="@style/FxAccountTextItem"
android:layout_marginBottom="45dp"
android:textSize="20sp"
android:textStyle="bold" >
</TextView>
<TextView
style="@style/FxAccountLinkItem"
android:text="@string/fxaccount_offer_resend_confirmation_email" />
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
</LinearLayout>
</ScrollView>

View File

@ -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/.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/local_error"
style="@style/FxAccountErrorItem" />
<EditText
android:id="@+id/email"
style="@style/FxAccountEditItem"
android:layout_marginBottom="15dp"
android:ems="10"
android:hint="@string/fxaccount_email_hint"
android:inputType="textEmailAddress" >
<requestFocus />
</EditText>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/password"
style="@style/FxAccountEditItem"
android:background="@drawable/fxaccount_password_background"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:hint="@string/fxaccount_password_hint"
android:inputType="textPassword" />
<Button
android:id="@+id/show_password"
style="@style/FxAccountButton"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="0"
android:background="@drawable/fxaccount_password_button_background"
android:minHeight="0dp"
android:padding="0dp"
android:text="@string/fxaccount_password_show"
android:textColor="@color/fxaccount_input_textColor" >
</Button>
</LinearLayout>
</LinearLayout>
</merge>

View File

@ -0,0 +1,60 @@
<?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/intro_view"
style="@style/FxAccountMiddle" >
<TextView
style="@style/FxAccountHeaderItem"
android:text="@string/firefox_accounts" >
</TextView>
<TextView
style="@style/FxAccountSubHeaderItem"
android:text="@string/fxaccount_get_started" >
</TextView>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="20dp"
android:contentDescription="@string/fxaccount_intro_contentDescription"
android:src="@drawable/fxaccount_intro" >
</ImageView>
<TextView
style="@style/FxAccountTextItem"
android:layout_marginBottom="45dp"
android:text="@string/fxaccount_description" >
</TextView>
<Button
android:id="@+id/get_started_button"
style="@style/FxAccountButton"
android:layout_marginBottom="20dp"
android:onClick="onClickGetStarted"
android:text="@string/fxaccount_get_started" />
<TextView
android:id="@+id/old_firefox"
style="@style/FxAccountLinkifiedItem"
android:text="@string/fxaccount_old_firefox" />
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,65 @@
<?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/sign_in_view"
style="@style/FxAccountMiddle" >
<TextView
style="@style/FxAccountHeaderItem"
android:text="@string/firefox_accounts" />
<TextView
style="@style/FxAccountSubHeaderItem"
android:text="@string/fxaccount_sign_in" />
<include layout="@layout/fxaccount_email_password_view" />
<Button
android:id="@+id/sign_in_button"
style="@style/FxAccountButton"
android:layout_marginBottom="20dp"
android:layout_marginTop="45dp"
android:text="@string/fxaccount_sign_in_button_label" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp" >
<TextView
android:id="@+id/forgot_password_link"
style="@style/FxAccountLinkItem"
android:layout_gravity="left"
android:layout_weight="1"
android:gravity="left"
android:text="@string/fxaccount_forgot_password" />
<TextView
android:id="@+id/create_account_link"
style="@style/FxAccountLinkItem"
android:layout_gravity="right"
android:layout_weight="1"
android:gravity="right"
android:text="@string/fxaccount_create_account" />
</LinearLayout>
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,159 @@
<?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/existing_user"
style="@style/FxAccountMiddle" >
<TextView
style="@style/FxAccountHeaderItem"
android:text="@string/firefox_accounts" >
</TextView>
<TextView
android:id="@+id/email"
style="@style/FxAccountSubHeaderItem" >
</TextView>
<TextView
android:id="@+id/change_password"
style="@style/FxAccountLinkItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/fxaccount_change_password" >
</TextView>
<FrameLayout
android:id="@+id/connection_status_view"
style="@style/FxAccountTextItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" >
<TextView
android:id="@+id/unverified_view"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="10dp"
android:background="#fad4d2"
android:drawablePadding="10dp"
android:drawableStart="@drawable/fxaccount_sync_error"
android:gravity="center_vertical"
android:padding="10dp"
android:text="@string/fxaccount_status_needs_verification"
android:visibility="gone" >
</TextView>
<TextView
android:id="@+id/sign_in_view"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="10dp"
android:background="#fad4d2"
android:drawablePadding="10dp"
android:drawableStart="@drawable/fxaccount_sync_error"
android:gravity="center_vertical"
android:padding="10dp"
android:text="@string/fxaccount_status_needs_credentials"
android:visibility="gone" >
</TextView>
<TextView
android:id="@+id/syncing_view"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="10dp"
android:background="#d1e7fe"
android:drawablePadding="10dp"
android:drawableStart="@drawable/fxaccount_sync_icon"
android:gravity="center_vertical"
android:padding="10dp"
android:text="@string/fxaccount_status_syncing"
android:visibility="visible" >
</TextView>
</FrameLayout>
<TextView
style="@style/FxAccountHeaderItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/fxaccount_status_sync" >
</TextView>
<CheckBox
android:id="@+id/bookmarks_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/fxaccount_status_bookmarks" />
<CheckBox
android:id="@+id/history_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/fxaccount_status_history" />
<CheckBox
android:id="@+id/passwords_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/fxaccount_status_passwords" />
<CheckBox
android:id="@+id/tabs_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/fxaccount_status_tabs" />
<LinearLayout
android:id="@+id/debug_buttons"
style="@style/FxAccountMiddle"
android:background="#7f7f7f" >
<Button
style="@style/FxAccountButton"
android:layout_marginBottom="10dp"
android:onClick="onClickRefresh"
android:text="Refresh" />
<Button
style="@style/FxAccountButton"
android:layout_marginBottom="10dp"
android:onClick="onClickDumpAccountDetails"
android:text="Dump Account Details" />
<Button
style="@style/FxAccountButton"
android:layout_marginBottom="10dp"
android:onClick="onClickForgetAccountTokens"
android:text="Forget sessionToken and keyFetchToken" />
<Button
style="@style/FxAccountButton"
android:layout_marginBottom="10dp"
android:onClick="onClickForgetPassword"
android:text="Forget password" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,27 @@
<?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/. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<color name="fxaccount_error_dark">#FDEBE5</color>
<color name="fxaccount_error_light">#E24B03</color>
<color name="fxaccount_textColor">#3c454d</color>
<color name="fxaccount_linkified_textColor">#c0c9d0</color>
<color name="fxaccount_linkified_textColorLink">#0096dd</color>
<color name="fxaccount_error">#d63920</color>
<color name="fxaccount_button_textColor">#ffffff</color>
<color name="fxaccount_button_background_active">#e66000</color>
<color name="fxaccount_button_background_hit">#fd9500</color>
<color name="fxaccount_button_background_loading">#424f59</color>
<color name="fxaccount_button_background_inactive">#c0c9d0</color>
<color name="fxaccount_input_textColor">#424f59</color>
<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_input_borderActive">#6a7b86</color>
<color name="fxaccount_input_borderInactive">#c0c9d0</color>
</resources>

View File

@ -3,9 +3,8 @@
- 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/. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
</LinearLayout>
<resources>
<dimen name="fxaccount_stroke_width">1dp</dimen>
<dimen name="fxaccount_corner_radius">5dp</dimen>
<dimen name="fxaccount_input_padding">10dp</dimen>
</resources>

View File

@ -0,0 +1,105 @@
<?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/.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="FxAccountTheme" parent="@style/Gecko" />
<style name="FxAccountMiddle">
<item name="android:orientation">vertical</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:paddingTop">25dp</item>
<item name="android:paddingLeft">12dp</item>
<item name="android:paddingRight">12dp</item>
<item name="android:paddingBottom">15dp</item>
</style>
<style name="FxAccountSpacer">
<item name="android:orientation">vertical</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">0dp</item>
<item name="android:layout_weight">1</item>
</style>
<style name="FxAccountHeaderItem" parent="@style/FxAccountTextItem">
<item name="android:textStyle">bold</item>
<item name="android:textSize">24sp</item>
<item name="android:layout_marginBottom">10dp</item>
</style>
<style name="FxAccountSubHeaderItem" parent="@style/FxAccountTextItem">
<item name="android:textSize">20sp</item>
<item name="android:layout_marginBottom">45dp</item>
</style>
<style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/fxaccount_textColor</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:textSize">14sp</item>
</style>
<style name="FxAccountLinkItem" parent="@style/FxAccountTextItem">
<item name="android:clickable">true</item>
<item name="android:focusable">false</item>
<item name="android:textColor">@drawable/fxaccount_linkitem_textcolor</item>
</style>
<style name="FxAccountButton" parent="@android:style/Widget.Button">
<item name="android:background">@drawable/fxaccount_button_background</item>
<item name="android:textColor">@drawable/fxaccount_button_color</item>
<item name="android:textSize">18sp</item>
<item name="android:padding">20dp</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="FxAccountEditItem" parent="@android:style/Widget.EditText">
<item name="android:padding">@dimen/fxaccount_input_padding</item>
<item name="android:background">@drawable/fxaccount_textfield_background</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">@color/fxaccount_input_textColor</item>
<item name="android:textColorHint">@color/fxaccount_input_textColorHint</item>
</style>
<style name="FxAccountLinkifiedItem" parent="@android:style/TextAppearance.Small">
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:textColor">@color/fxaccount_linkified_textColor</item>
<item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item>
</style>
<style name="FxAccountIcon">
<item name="android:layout_marginTop">20dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:src">@drawable/fxaccount_icon</item>
</style>
<style name="FxAccountErrorItem">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">left</item>
<item name="android:layout_margin">5dp</item>
<item name="android:background">@drawable/fxaccount_textview_error_background</item>
<item name="android:padding">5dp</item>
<item name="android:text">Error</item>
<item name="android:textColor">@android:color/white</item>
<item name="android:textSize">18sp</item>
<item name="android:visibility">invisible</item>
</style>
</resources>

View File

@ -8,4 +8,4 @@
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/fxaccount_label"
/>
android:accountPreferences="@xml/fxaccount_options" />

View File

@ -0,0 +1,18 @@
<?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/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/fxaccount_options_title" />
<PreferenceScreen
android:key="options"
android:title="@string/fxaccount_options_configure_title">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="@string/android_package_name_for_ui"
android:targetClass="org.mozilla.gecko.fxa.activities.FxAccountStatusActivity">
</intent>
</PreferenceScreen>
</PreferenceScreen>

View File

@ -1,9 +1,9 @@
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/icon"
android:label="@string/fxaccount_label"
android:icon="@drawable/fxaccount_icon"
android:label="Status"
android:clearTaskOnLaunch="true"
android:name="org.mozilla.gecko.fxa.activities.FxAccountSetupActivity"
android:name="org.mozilla.gecko.fxa.activities.FxAccountStatusActivity"
android:windowSoftInputMode="adjustResize">
<!-- Adding a launcher will make this activity appear on the
Apps screen, which we only want when testing. -->
@ -12,3 +12,48 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"
android:label="Setup"
android:clearTaskOnLaunch="true"
android:name="org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity"
android:windowSoftInputMode="adjustResize">
<!-- Adding a launcher will make this activity appear on the
Apps screen, which we only want when testing. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
</intent-filter>
</activity>
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateSuccessActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:theme="@style/FxAccountTheme"
android:icon="@drawable/fxaccount_icon"
android:name="org.mozilla.gecko.fxa.activities.FxAccountSignInActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>

View File

@ -18,7 +18,7 @@
<string name="sync_link_advancedsetup"><u>&sync.link.advancedsetup.label;</u></string>
<!-- J-PAKE Waiting Screen -->
<string name="sync_jpake_subtitle_waiting">&sync.jpake.subtitle.waiting.label;</string>
<!-- Account Login Screen -->
@ -28,7 +28,7 @@
<string name="sync_input_key">&sync.input.key.label;</string>
<string name="sync_checkbox_server">&sync.checkbox.server.label;</string>
<string name="sync_input_server">&sync.input.server.label;</string>
<!-- Setup Fail -->
<string name="sync_title_fail">&sync.title.fail.label;</string>
<string name="sync_subtitle_fail">&sync.subtitle.fail.label;</string>
@ -37,13 +37,13 @@
<string name="sync_subtitle_nointernet">&sync.subtitle.nointernet.label;</string>
<string name="sync_subtitle_failaccount">&sync.subtitle.failaccount.label;</string>
<string name="sync_subtitle_failmultiple">&sync.subtitle.failmultiple.label;</string>
<!-- Setup Success -->
<string name="sync_title_success">&sync.title.success.label;</string>
<string name="sync_subtitle_success">&sync.subtitle.success.label1;</string>
<string name="sync_settings">&sync.settings.label;</string>
<string name="sync_subtitle_manage">&sync.subtitle.manage.label1;</string>
<!-- Pair Device -->
<string name="sync_pair_tryagain">&sync.pair.tryagain.label;</string>
@ -63,10 +63,10 @@
<string name="sync_button_cancel">&sync.button.cancel.label;</string>
<string name="sync_button_connect">&sync.button.connect.label;</string>
<string name="sync_button_ok">&sync.button.ok.label;</string>
<!-- Account strings -->
<string name="sync_account_label">&sync.account.label.label;</string>
<!-- Bookmark folder strings -->
<string name="bookmarks_folder_menu">&bookmarks.folder.menu.label;</string>
<string name="bookmarks_folder_places">&bookmarks.folder.places.label;</string>
@ -99,4 +99,54 @@
<string name="sync_text_tab_not_sent">&sync.text.tab.not.sent.label;</string>
<!-- Firefox Account strings -->
<string name="firefox_accounts">&firefox.accounts;</string>
<string name="fxaccount_label">&fxaccount.label;</string>
<string name="fxaccount_policy">&fxaccount.policy;</string>
<string name="fxaccount_forgot_password">&fxaccount.forgot.password;</string>
<string name="fxaccount_create_account_button_label">&fxaccount.create.account.button.label;</string>
<string name="fxaccount_sign_in_button_label">&fxaccount.sign.in.button.label;</string>
<string name="fxaccount_when_were_you_born">&fxaccount.when.were.you.born;</string>
<string name="fxaccount_year_of_birth">&fxaccount.year.of.birth;</string>
<string name="fxaccount_email_hint">&fxaccount.email.hint;</string>
<string name="fxaccount_password_hint">&fxaccount.password.hint;</string>
<string name="fxaccount_password2_hint">&fxaccount.password2.hint;</string>
<string name="fxaccount_bad_email">&fxaccount.bad.email;</string>
<string name="fxaccount_bad_passwords">&fxaccount.bad.passwords;</string>
<string name="fxaccount_sign_in_instead">&fxaccount.sign.in.instead;</string>
<string name="fxaccount_sign_up_instead">&fxaccount.sign.up.instead;</string>
<string name="fxaccount_old_firefox">&fxaccount.old.firefox;</string>
<string name="fxaccount_options_title">&fxaccount.options.title;</string>
<string name="fxaccount_options_configure_title">&fxaccount.options.configure.title;</string>
<string name="fxaccount_sign_in">&fxaccount.sign.in;</string>
<string name="fxaccount_create_account">&fxaccount.create.account;</string>
<string name="fxaccount_icon_contentDescription">&fxaccount.icon.contentDescription;</string>
<string name="fxaccount_password_hide">&fxaccount.password.hide;</string>
<string name="fxaccount_password_show">&fxaccount.password.show;</string>
<string name="fxaccount_get_started">&fxaccount.get.started;</string>
<string name="fxaccount_description">&fxaccount.description;</string>
<string name="fxaccount_sign_up">&fxaccount.sign.up;</string>
<string name="fxaccount_intro_contentDescription">&fxaccount.intro.contentDescription;</string>
<string name="fxaccount_offer_resend_confirmation_email">&fxaccount.offer.resend.confirmation.email;</string>
<string name="fxaccount_confirmation_description">&fxaccount.confirmation.description;</string>
<string name="fxaccount_mail_contentDescription">&fxaccount.mail.contentDescription;</string>
<string name="fxaccount_confirmation_email_sent">&fxaccount.confirmation.email.sent;</string>
<string name="fxaccount_password_length_restriction">&fxaccount.password.length.restriction;</string>
<string name="fxaccount_change_password">&fxaccount.change.password;</string>
<string name="fxaccount_status_needs_verification">&fxaccount.status.needs.verification;</string>
<string name="fxaccount_status_needs_credentials">&fxaccount.status.needs.credentials;</string>
<string name="fxaccount_status_syncing">&fxaccount.status.syncing;</string>
<string name="fxaccount_status_sync">&fxaccount.status.sync;</string>
<string name="fxaccount_status_bookmarks">&fxaccount.status.bookmarks;</string>
<string name="fxaccount_status_history">&fxaccount.status.history;</string>
<string name="fxaccount_status_passwords">&fxaccount.status.passwords;</string>
<string name="fxaccount_status_tabs">&fxaccount.status.tabs;</string>