mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 22:05:44 +00:00
Merge f-t to m-c, a=merge
This commit is contained in:
commit
f713acf7ea
@ -838,10 +838,13 @@ sync_java_files = [
|
|||||||
'fxa/AccountLoader.java',
|
'fxa/AccountLoader.java',
|
||||||
'fxa/activities/FxAccountAbstractActivity.java',
|
'fxa/activities/FxAccountAbstractActivity.java',
|
||||||
'fxa/activities/FxAccountAbstractSetupActivity.java',
|
'fxa/activities/FxAccountAbstractSetupActivity.java',
|
||||||
|
'fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java',
|
||||||
'fxa/activities/FxAccountConfirmAccountActivity.java',
|
'fxa/activities/FxAccountConfirmAccountActivity.java',
|
||||||
'fxa/activities/FxAccountCreateAccountActivity.java',
|
'fxa/activities/FxAccountCreateAccountActivity.java',
|
||||||
'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
|
'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
|
||||||
|
'fxa/activities/FxAccountFinishMigratingActivity.java',
|
||||||
'fxa/activities/FxAccountGetStartedActivity.java',
|
'fxa/activities/FxAccountGetStartedActivity.java',
|
||||||
|
'fxa/activities/FxAccountMigrationFinishedActivity.java',
|
||||||
'fxa/activities/FxAccountSignInActivity.java',
|
'fxa/activities/FxAccountSignInActivity.java',
|
||||||
'fxa/activities/FxAccountStatusActivity.java',
|
'fxa/activities/FxAccountStatusActivity.java',
|
||||||
'fxa/activities/FxAccountStatusFragment.java',
|
'fxa/activities/FxAccountStatusFragment.java',
|
||||||
@ -861,6 +864,7 @@ sync_java_files = [
|
|||||||
'fxa/login/FxAccountLoginStateMachine.java',
|
'fxa/login/FxAccountLoginStateMachine.java',
|
||||||
'fxa/login/FxAccountLoginTransition.java',
|
'fxa/login/FxAccountLoginTransition.java',
|
||||||
'fxa/login/Married.java',
|
'fxa/login/Married.java',
|
||||||
|
'fxa/login/MigratedFromSync11.java',
|
||||||
'fxa/login/Separated.java',
|
'fxa/login/Separated.java',
|
||||||
'fxa/login/State.java',
|
'fxa/login/State.java',
|
||||||
'fxa/login/StateFactory.java',
|
'fxa/login/StateFactory.java',
|
||||||
@ -879,6 +883,7 @@ sync_java_files = [
|
|||||||
'fxa/tasks/FxAccountCreateAccountTask.java',
|
'fxa/tasks/FxAccountCreateAccountTask.java',
|
||||||
'fxa/tasks/FxAccountSetupTask.java',
|
'fxa/tasks/FxAccountSetupTask.java',
|
||||||
'fxa/tasks/FxAccountSignInTask.java',
|
'fxa/tasks/FxAccountSignInTask.java',
|
||||||
|
'fxa/tasks/FxAccountUnlockCodeResender.java',
|
||||||
'sync/AlreadySyncingException.java',
|
'sync/AlreadySyncingException.java',
|
||||||
'sync/BackoffHandler.java',
|
'sync/BackoffHandler.java',
|
||||||
'sync/BadRequiredFieldJSONException.java',
|
'sync/BadRequiredFieldJSONException.java',
|
||||||
|
@ -17,4 +17,5 @@ public interface FxAccountClient {
|
|||||||
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
|
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
|
||||||
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
|
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
|
||||||
public void resendCode(byte[] sessionToken, RequestDelegate<Void> delegate);
|
public void resendCode(byte[] sessionToken, RequestDelegate<Void> delegate);
|
||||||
|
public void resendUnlockCode(byte[] emailUTF8, RequestDelegate<Void> delegate);
|
||||||
}
|
}
|
||||||
|
@ -770,4 +770,46 @@ public class FxAccountClient10 {
|
|||||||
};
|
};
|
||||||
post(resource, new JSONObject(), delegate);
|
post(resource, new JSONObject(), delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a fresh unlock code be sent to the account email.
|
||||||
|
* <p>
|
||||||
|
* Since the account can be locked before the device can connect to it, the
|
||||||
|
* only reasonable identifier is the account email. Since the account is
|
||||||
|
* locked out, this request is un-authenticated.
|
||||||
|
*
|
||||||
|
* @param emailUTF8
|
||||||
|
* identifying account.
|
||||||
|
* @param delegate
|
||||||
|
* to invoke callbacks.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void resendUnlockCode(final byte[] emailUTF8, final RequestDelegate<Void> delegate) {
|
||||||
|
final BaseResource resource;
|
||||||
|
final JSONObject body = new JSONObject();
|
||||||
|
try {
|
||||||
|
resource = new BaseResource(new URI(serverURI + "account/unlock/resend_code"));
|
||||||
|
body.put("email", new String(emailUTF8, "UTF-8"));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
invokeHandleError(delegate, e);
|
||||||
|
return;
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
invokeHandleError(delegate, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.delegate = new ResourceDelegate<Void>(resource, delegate) {
|
||||||
|
@Override
|
||||||
|
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||||
|
try {
|
||||||
|
delegate.handleSuccess(null);
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
delegate.handleError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
post(resource, body, delegate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,10 @@ public class FxAccountClientException extends Exception {
|
|||||||
return apiErrorNumber == FxAccountRemoteError.INCORRECT_EMAIL_CASE;
|
return apiErrorNumber == FxAccountRemoteError.INCORRECT_EMAIL_CASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAccountLocked() {
|
||||||
|
return apiErrorNumber == FxAccountRemoteError.ACCOUNT_LOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
public int getErrorMessageStringResource() {
|
public int getErrorMessageStringResource() {
|
||||||
if (isUpgradeRequired()) {
|
if (isUpgradeRequired()) {
|
||||||
return R.string.fxaccount_remote_error_UPGRADE_REQUIRED;
|
return R.string.fxaccount_remote_error_UPGRADE_REQUIRED;
|
||||||
@ -111,6 +115,8 @@ public class FxAccountClientException extends Exception {
|
|||||||
return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
|
return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
|
||||||
} else if (isServerUnavailable()) {
|
} else if (isServerUnavailable()) {
|
||||||
return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;
|
return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;
|
||||||
|
} else if (isAccountLocked()) {
|
||||||
|
return R.string.fxaccount_remote_error_ACCOUNT_LOCKED;
|
||||||
} else {
|
} else {
|
||||||
return R.string.fxaccount_remote_error_UNKNOWN_ERROR;
|
return R.string.fxaccount_remote_error_UNKNOWN_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ public interface FxAccountRemoteError {
|
|||||||
public static final int INVALID_REQUEST_SIGNATURE = 109;
|
public static final int INVALID_REQUEST_SIGNATURE = 109;
|
||||||
public static final int INVALID_AUTHENTICATION_TOKEN = 110;
|
public static final int INVALID_AUTHENTICATION_TOKEN = 110;
|
||||||
public static final int INVALID_AUTHENTICATION_TIMESTAMP = 111;
|
public static final int INVALID_AUTHENTICATION_TIMESTAMP = 111;
|
||||||
public static final int INVALID_AUTHENTICATION_NONCE = 115;
|
|
||||||
public static final int CONTENT_LENGTH_HEADER_WAS_NOT_PROVIDED = 112;
|
public static final int CONTENT_LENGTH_HEADER_WAS_NOT_PROVIDED = 112;
|
||||||
public static final int REQUEST_BODY_TOO_LARGE = 113;
|
public static final int REQUEST_BODY_TOO_LARGE = 113;
|
||||||
public static final int CLIENT_HAS_SENT_TOO_MANY_REQUESTS = 114;
|
public static final int CLIENT_HAS_SENT_TOO_MANY_REQUESTS = 114;
|
||||||
@ -26,6 +25,7 @@ public interface FxAccountRemoteError {
|
|||||||
public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118;
|
public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118;
|
||||||
public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
|
public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
|
||||||
public static final int INCORRECT_EMAIL_CASE = 120;
|
public static final int INCORRECT_EMAIL_CASE = 120;
|
||||||
|
public static final int ACCOUNT_LOCKED = 121;
|
||||||
public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
|
public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
|
||||||
public static final int UNKNOWN_ERROR = 999;
|
public static final int UNKNOWN_ERROR = 999;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,8 @@ public class Distribution {
|
|||||||
// Corresponds to the high value in Histograms.json.
|
// Corresponds to the high value in Histograms.json.
|
||||||
private static final long MAX_DOWNLOAD_TIME_MSEC = 40000; // 40 seconds.
|
private static final long MAX_DOWNLOAD_TIME_MSEC = 40000; // 40 seconds.
|
||||||
|
|
||||||
|
// Wait just a little while for the system to send a referrer intent after install.
|
||||||
|
private static final long DELAY_WAIT_FOR_REFERRER_MSEC = 400;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -385,7 +387,16 @@ public class Distribution {
|
|||||||
*/
|
*/
|
||||||
private boolean checkIntentDistribution() {
|
private boolean checkIntentDistribution() {
|
||||||
if (referrer == null) {
|
if (referrer == null) {
|
||||||
return false;
|
// Wait a predetermined time and try again.
|
||||||
|
// Just block the thread, because it's the simplest solution.
|
||||||
|
try {
|
||||||
|
Thread.sleep(DELAY_WAIT_FOR_REFERRER_MSEC);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Good enough.
|
||||||
|
}
|
||||||
|
if (referrer == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
URI uri = getReferredDistribution(referrer);
|
URI uri = getReferredDistribution(referrer);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package org.mozilla.gecko.fxa.activities;
|
package org.mozilla.gecko.fxa.activities;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -23,8 +24,10 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
|||||||
import org.mozilla.gecko.fxa.login.Engaged;
|
import org.mozilla.gecko.fxa.login.Engaged;
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
|
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
|
||||||
|
import org.mozilla.gecko.fxa.tasks.FxAccountUnlockCodeResender;
|
||||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||||
|
import org.mozilla.gecko.sync.Utils;
|
||||||
import org.mozilla.gecko.sync.setup.Constants;
|
import org.mozilla.gecko.sync.setup.Constants;
|
||||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||||
|
|
||||||
@ -35,9 +38,12 @@ import android.content.Intent;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.Spannable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
import android.text.method.SingleLineTransformationMethod;
|
import android.text.method.SingleLineTransformationMethod;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -154,7 +160,35 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void showClientRemoteException(final FxAccountClientRemoteException e) {
|
protected void showClientRemoteException(final FxAccountClientRemoteException e) {
|
||||||
remoteErrorTextView.setText(e.getErrorMessageStringResource());
|
if (!e.isAccountLocked()) {
|
||||||
|
remoteErrorTextView.setText(e.getErrorMessageStringResource());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This horrible bit of special-casing is because we want this error message
|
||||||
|
// to contain a clickable, extra chunk of text, but we don't want to pollute
|
||||||
|
// the exception class with Android specifics.
|
||||||
|
final int messageId = e.getErrorMessageStringResource();
|
||||||
|
final int clickableId = R.string.fxaccount_resend_unlock_code_button_label;
|
||||||
|
final Spannable span = Utils.interpolateClickableSpan(this, messageId, clickableId, new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {
|
||||||
|
// It would be best to capture the email address sent to the server
|
||||||
|
// and use it here, but this will do for now. If the user modifies
|
||||||
|
// the email address entered, the error text is hidden, so sending a
|
||||||
|
// changed email address would be the result of an unusual race.
|
||||||
|
final String email = emailEdit.getText().toString();
|
||||||
|
byte[] emailUTF8 = null;
|
||||||
|
try {
|
||||||
|
emailUTF8 = email.getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
// It's okay, we'll fail in the code resender.
|
||||||
|
}
|
||||||
|
FxAccountUnlockCodeResender.resendUnlockCode(FxAccountAbstractSetupActivity.this, getAuthServerEndpoint(), emailUTF8);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
remoteErrorTextView.setText(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addListeners() {
|
protected void addListeners() {
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.fxa.activities;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||||
|
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||||
|
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||||
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
|
import org.mozilla.gecko.fxa.login.Engaged;
|
||||||
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
|
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||||
|
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.AutoCompleteTextView;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract activity which displays a screen for updating the local password.
|
||||||
|
*/
|
||||||
|
public abstract class FxAccountAbstractUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
|
||||||
|
protected static final String LOG_TAG = FxAccountAbstractUpdateCredentialsActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
protected AndroidFxAccount fxAccount;
|
||||||
|
|
||||||
|
protected final int layoutResourceId;
|
||||||
|
|
||||||
|
public FxAccountAbstractUpdateCredentialsActivity(int layoutResourceId) {
|
||||||
|
// We want to share code with the other setup activities, but this activity
|
||||||
|
// doesn't create a new Android Account, it modifies an existing one. If you
|
||||||
|
// manage to get an account, and somehow be locked out too, we'll let you
|
||||||
|
// update it.
|
||||||
|
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||||
|
this.layoutResourceId = layoutResourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle icicle) {
|
||||||
|
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||||
|
|
||||||
|
super.onCreate(icicle);
|
||||||
|
setContentView(layoutResourceId);
|
||||||
|
|
||||||
|
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
|
||||||
|
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
|
||||||
|
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
|
||||||
|
remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
|
||||||
|
button = (Button) ensureFindViewById(null, R.id.button, "update credentials");
|
||||||
|
progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
|
||||||
|
|
||||||
|
minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
|
||||||
|
createButton();
|
||||||
|
addListeners();
|
||||||
|
updateButtonState();
|
||||||
|
createShowPasswordButton();
|
||||||
|
|
||||||
|
emailEdit.setEnabled(false);
|
||||||
|
|
||||||
|
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
||||||
|
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
||||||
|
|
||||||
|
updateFromIntentExtras();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
|
||||||
|
public final String email;
|
||||||
|
public final String serverURI;
|
||||||
|
public final PasswordStretcher passwordStretcher;
|
||||||
|
|
||||||
|
public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
|
||||||
|
this.email = email;
|
||||||
|
this.serverURI = serverURI;
|
||||||
|
this.passwordStretcher = passwordStretcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(Exception e) {
|
||||||
|
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFailure(FxAccountClientRemoteException e) {
|
||||||
|
if (e.isUpgradeRequired()) {
|
||||||
|
Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state.");
|
||||||
|
final State state = fxAccount.getState();
|
||||||
|
fxAccount.setState(state.makeDoghouseState());
|
||||||
|
// The status activity will say that the user needs to upgrade.
|
||||||
|
redirectToActivity(FxAccountStatusActivity.class);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSuccess(LoginResponse result) {
|
||||||
|
Logger.info(LOG_TAG, "Got success signing in.");
|
||||||
|
|
||||||
|
if (fxAccount == null) {
|
||||||
|
this.handleError(new IllegalStateException("fxAccount must not be null"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] unwrapkB;
|
||||||
|
try {
|
||||||
|
// It is crucial that we use the email address provided by the server
|
||||||
|
// (rather than whatever the user entered), because the user's keys are
|
||||||
|
// wrapped and salted with the initial email they provided to
|
||||||
|
// /create/account. Of course, we want to pass through what the user
|
||||||
|
// entered locally as much as possible.
|
||||||
|
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
|
||||||
|
unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.handleError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
|
||||||
|
fxAccount.requestSync(FirefoxAccounts.FORCE);
|
||||||
|
|
||||||
|
// For great debugging.
|
||||||
|
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||||
|
fxAccount.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
|
||||||
|
// Maybe show success activity.
|
||||||
|
final Intent successIntent = makeSuccessIntent(email, result);
|
||||||
|
if (successIntent != null) {
|
||||||
|
startActivity(successIntent);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCredentials(String email, String password) {
|
||||||
|
String serverURI = fxAccount.getAccountServerURI();
|
||||||
|
Executor executor = Executors.newSingleThreadExecutor();
|
||||||
|
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||||
|
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
||||||
|
try {
|
||||||
|
hideRemoteError();
|
||||||
|
RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
|
||||||
|
new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
|
||||||
|
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createButton() {
|
||||||
|
button.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final String email = emailEdit.getText().toString();
|
||||||
|
final String password = passwordEdit.getText().toString();
|
||||||
|
updateCredentials(email, password);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ package org.mozilla.gecko.fxa.activities;
|
|||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.background.common.log.Logger;
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||||
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
|
|
||||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
import org.mozilla.gecko.fxa.login.Engaged;
|
import org.mozilla.gecko.fxa.login.Engaged;
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
@ -141,9 +140,6 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
|
|||||||
case NeedsVerification:
|
case NeedsVerification:
|
||||||
// This is what we're here to handle.
|
// This is what we're here to handle.
|
||||||
break;
|
break;
|
||||||
case NeedsPassword:
|
|
||||||
case NeedsUpgrade:
|
|
||||||
case None:
|
|
||||||
default:
|
default:
|
||||||
// We're not in the right place! Redirect to status.
|
// We're not in the right place! Redirect to status.
|
||||||
Logger.warn(LOG_TAG, "No need to verify Firefox Account that needs action " + neededAction.toString() +
|
Logger.warn(LOG_TAG, "No need to verify Firefox Account that needs action " + neededAction.toString() +
|
||||||
|
@ -22,6 +22,7 @@ import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClient
|
|||||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||||
import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask;
|
import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask;
|
||||||
|
import org.mozilla.gecko.sync.Utils;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@ -30,7 +31,6 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -124,13 +124,10 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
|||||||
// This horrible bit of special-casing is because we want this error message to
|
// This horrible bit of special-casing is because we want this error message to
|
||||||
// contain a clickable, extra chunk of text, but we don't want to pollute
|
// contain a clickable, extra chunk of text, but we don't want to pollute
|
||||||
// the exception class with Android specifics.
|
// the exception class with Android specifics.
|
||||||
final String clickablePart = getString(R.string.fxaccount_sign_in_button_label);
|
final int messageId = e.getErrorMessageStringResource();
|
||||||
final String message = getString(e.getErrorMessageStringResource(), clickablePart);
|
final int clickableId = R.string.fxaccount_sign_in_button_label;
|
||||||
final int clickableStart = message.lastIndexOf(clickablePart);
|
|
||||||
final int clickableEnd = clickableStart + clickablePart.length();
|
|
||||||
|
|
||||||
final Spannable span = Spannable.Factory.getInstance().newSpannable(message);
|
final Spannable span = Utils.interpolateClickableSpan(this, messageId, clickableId, new ClickableSpan() {
|
||||||
span.setSpan(new ClickableSpan() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View widget) {
|
public void onClick(View widget) {
|
||||||
// Pass through the email address that already existed.
|
// Pass through the email address that already existed.
|
||||||
@ -143,7 +140,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
|||||||
final Bundle extras = makeExtrasBundle(email, password);
|
final Bundle extras = makeExtrasBundle(email, password);
|
||||||
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
|
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
|
||||||
}
|
}
|
||||||
}, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
});
|
||||||
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
remoteErrorTextView.setText(span);
|
remoteErrorTextView.setText(span);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.fxa.activities;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||||
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
|
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity which displays a screen for inputting the password and finishing
|
||||||
|
* migrating to Firefox Accounts / Sync 1.5.
|
||||||
|
*/
|
||||||
|
public class FxAccountFinishMigratingActivity extends FxAccountAbstractUpdateCredentialsActivity {
|
||||||
|
protected static final String LOG_TAG = FxAccountFinishMigratingActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public FxAccountFinishMigratingActivity() {
|
||||||
|
super(R.layout.fxaccount_finish_migrating);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
this.fxAccount = getAndroidFxAccount();
|
||||||
|
if (fxAccount == null) {
|
||||||
|
Logger.warn(LOG_TAG, "Could not get Firefox Account.");
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final State state = fxAccount.getState();
|
||||||
|
if (state.getStateLabel() != StateLabel.MigratedFromSync11) {
|
||||||
|
Logger.warn(LOG_TAG, "Cannot finish migrating from Firefox Account in state: " + state.getStateLabel());
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emailEdit.setText(fxAccount.getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent makeSuccessIntent(String email, LoginResponse result) {
|
||||||
|
final Intent successIntent = new Intent(this, FxAccountMigrationFinishedActivity.class);
|
||||||
|
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||||
|
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||||
|
successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
return successIntent;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.fxa.activities;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
|
import org.mozilla.gecko.fxa.login.State.Action;
|
||||||
|
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity which displays "Upgrade finished" success screen.
|
||||||
|
*/
|
||||||
|
public class FxAccountMigrationFinishedActivity extends FxAccountAbstractActivity {
|
||||||
|
private static final String LOG_TAG = FxAccountMigrationFinishedActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
protected AndroidFxAccount fxAccount;
|
||||||
|
|
||||||
|
public FxAccountMigrationFinishedActivity() {
|
||||||
|
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle icicle) {
|
||||||
|
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||||
|
|
||||||
|
super.onCreate(icicle);
|
||||||
|
setContentView(R.layout.fxaccount_migration_finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
this.fxAccount = getAndroidFxAccount();
|
||||||
|
if (fxAccount == null) {
|
||||||
|
Logger.warn(LOG_TAG, "Could not get Firefox Account.");
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final State state = fxAccount.getState();
|
||||||
|
if (state.getNeededAction() == Action.NeedsFinishMigrating) {
|
||||||
|
Logger.warn(LOG_TAG, "Firefox Account needs to finish migrating; not displaying migration finished activity.");
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button");
|
||||||
|
backToBrowsingButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
ActivityUtils.openURLInFennec(v.getContext(), null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,12 @@
|
|||||||
package org.mozilla.gecko.fxa.activities;
|
package org.mozilla.gecko.fxa.activities;
|
||||||
|
|
||||||
import org.mozilla.gecko.AppConstants;
|
import org.mozilla.gecko.AppConstants;
|
||||||
|
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.background.common.log.Logger;
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
import org.mozilla.gecko.sync.Utils;
|
import org.mozilla.gecko.sync.Utils;
|
||||||
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
|
|
||||||
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.accounts.AccountManager;
|
import android.accounts.AccountManager;
|
||||||
|
@ -77,6 +77,7 @@ public class FxAccountStatusFragment
|
|||||||
protected Preference needsVerificationPreference;
|
protected Preference needsVerificationPreference;
|
||||||
protected Preference needsMasterSyncAutomaticallyEnabledPreference;
|
protected Preference needsMasterSyncAutomaticallyEnabledPreference;
|
||||||
protected Preference needsAccountEnabledPreference;
|
protected Preference needsAccountEnabledPreference;
|
||||||
|
protected Preference needsFinishMigratingPreference;
|
||||||
|
|
||||||
protected PreferenceCategory syncCategory;
|
protected PreferenceCategory syncCategory;
|
||||||
|
|
||||||
@ -138,6 +139,7 @@ public class FxAccountStatusFragment
|
|||||||
needsVerificationPreference = ensureFindPreference("needs_verification");
|
needsVerificationPreference = ensureFindPreference("needs_verification");
|
||||||
needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
|
needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
|
||||||
needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled");
|
needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled");
|
||||||
|
needsFinishMigratingPreference = ensureFindPreference("needs_finish_migrating");
|
||||||
|
|
||||||
syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
|
syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
|
||||||
|
|
||||||
@ -157,6 +159,7 @@ public class FxAccountStatusFragment
|
|||||||
needsPasswordPreference.setOnPreferenceClickListener(this);
|
needsPasswordPreference.setOnPreferenceClickListener(this);
|
||||||
needsVerificationPreference.setOnPreferenceClickListener(this);
|
needsVerificationPreference.setOnPreferenceClickListener(this);
|
||||||
needsAccountEnabledPreference.setOnPreferenceClickListener(this);
|
needsAccountEnabledPreference.setOnPreferenceClickListener(this);
|
||||||
|
needsFinishMigratingPreference.setOnPreferenceClickListener(this);
|
||||||
|
|
||||||
bookmarksPreference.setOnPreferenceClickListener(this);
|
bookmarksPreference.setOnPreferenceClickListener(this);
|
||||||
historyPreference.setOnPreferenceClickListener(this);
|
historyPreference.setOnPreferenceClickListener(this);
|
||||||
@ -204,6 +207,20 @@ public class FxAccountStatusFragment
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preference == needsFinishMigratingPreference) {
|
||||||
|
final Intent intent = new Intent(getActivity(), FxAccountFinishMigratingActivity.class);
|
||||||
|
final Bundle extras = getExtrasForAccount();
|
||||||
|
if (extras != null) {
|
||||||
|
intent.putExtras(extras);
|
||||||
|
}
|
||||||
|
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||||
|
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
startActivity(intent);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (preference == needsVerificationPreference) {
|
if (preference == needsVerificationPreference) {
|
||||||
FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
|
FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
|
||||||
|
|
||||||
@ -280,6 +297,7 @@ public class FxAccountStatusFragment
|
|||||||
this.needsVerificationPreference,
|
this.needsVerificationPreference,
|
||||||
this.needsMasterSyncAutomaticallyEnabledPreference,
|
this.needsMasterSyncAutomaticallyEnabledPreference,
|
||||||
this.needsAccountEnabledPreference,
|
this.needsAccountEnabledPreference,
|
||||||
|
this.needsFinishMigratingPreference,
|
||||||
};
|
};
|
||||||
for (Preference errorPreference : errorPreferences) {
|
for (Preference errorPreference : errorPreferences) {
|
||||||
final boolean currentlyShown = null != findPreference(errorPreference.getKey());
|
final boolean currentlyShown = null != findPreference(errorPreference.getKey());
|
||||||
@ -325,6 +343,12 @@ public class FxAccountStatusFragment
|
|||||||
setCheckboxesEnabled(false);
|
setCheckboxesEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void showNeedsFinishMigrating() {
|
||||||
|
syncCategory.setTitle(R.string.fxaccount_status_sync);
|
||||||
|
showOnlyOneErrorPreference(needsFinishMigratingPreference);
|
||||||
|
setCheckboxesEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
protected void showConnected() {
|
protected void showConnected() {
|
||||||
syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
|
syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
|
||||||
showOnlyOneErrorPreference(null);
|
showOnlyOneErrorPreference(null);
|
||||||
@ -464,8 +488,12 @@ public class FxAccountStatusFragment
|
|||||||
case NeedsVerification:
|
case NeedsVerification:
|
||||||
showNeedsVerification();
|
showNeedsVerification();
|
||||||
break;
|
break;
|
||||||
default:
|
case NeedsFinishMigrating:
|
||||||
|
showNeedsFinishMigrating();
|
||||||
|
break;
|
||||||
|
case None:
|
||||||
showConnected();
|
showConnected();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We check for the master setting last, since it is not strictly
|
// We check for the master setting last, since it is not strictly
|
||||||
@ -703,6 +731,11 @@ public class FxAccountStatusFragment
|
|||||||
State state = fxAccount.getState();
|
State state = fxAccount.getState();
|
||||||
fxAccount.setState(state.makeDoghouseState());
|
fxAccount.setState(state.makeDoghouseState());
|
||||||
refresh();
|
refresh();
|
||||||
|
} else if ("debug_migrated_from_sync11".equals(key)) {
|
||||||
|
Logger.info(LOG_TAG, "Moving to MigratedFromSync11 state: Requiring password.");
|
||||||
|
State state = fxAccount.getState();
|
||||||
|
fxAccount.setState(state.makeMigratedFromSync11State(null));
|
||||||
|
refresh();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -729,7 +762,8 @@ public class FxAccountStatusFragment
|
|||||||
"debug_force_sync",
|
"debug_force_sync",
|
||||||
"debug_forget_certificate",
|
"debug_forget_certificate",
|
||||||
"debug_require_password",
|
"debug_require_password",
|
||||||
"debug_require_upgrade" };
|
"debug_require_upgrade",
|
||||||
|
"debug_migrated_from_sync11" };
|
||||||
for (String debugKey : debugKeys) {
|
for (String debugKey : debugKeys) {
|
||||||
final Preference button = ensureFindPreference(debugKey);
|
final Preference button = ensureFindPreference(debugKey);
|
||||||
button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
|
button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
|
||||||
|
@ -4,80 +4,22 @@
|
|||||||
|
|
||||||
package org.mozilla.gecko.fxa.activities;
|
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.R;
|
||||||
import org.mozilla.gecko.background.common.log.Logger;
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
|
||||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
|
||||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
|
||||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
|
||||||
import org.mozilla.gecko.fxa.login.Engaged;
|
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
|
||||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.content.Intent;
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.AutoCompleteTextView;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity which displays a screen for updating the local password.
|
* Activity which displays a screen for updating the local password.
|
||||||
*/
|
*/
|
||||||
public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
|
public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractUpdateCredentialsActivity {
|
||||||
protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName();
|
protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName();
|
||||||
|
|
||||||
protected AndroidFxAccount fxAccount;
|
|
||||||
|
|
||||||
public FxAccountUpdateCredentialsActivity() {
|
public FxAccountUpdateCredentialsActivity() {
|
||||||
// We want to share code with the other setup activities, but this activity
|
super(R.layout.fxaccount_update_credentials);
|
||||||
// doesn't create a new Android Account, it modifies an existing one. If you
|
|
||||||
// manage to get an account, and somehow be locked out too, we'll let you
|
|
||||||
// update it.
|
|
||||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
|
||||||
|
|
||||||
super.onCreate(icicle);
|
|
||||||
setContentView(R.layout.fxaccount_update_credentials);
|
|
||||||
|
|
||||||
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
|
|
||||||
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
|
|
||||||
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
|
|
||||||
remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
|
|
||||||
button = (Button) ensureFindViewById(null, R.id.button, "update credentials");
|
|
||||||
progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
|
|
||||||
|
|
||||||
minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
|
|
||||||
createButton();
|
|
||||||
addListeners();
|
|
||||||
updateButtonState();
|
|
||||||
createShowPasswordButton();
|
|
||||||
|
|
||||||
emailEdit.setEnabled(false);
|
|
||||||
|
|
||||||
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
|
||||||
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
|
||||||
|
|
||||||
updateFromIntentExtras();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,7 +32,7 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
|||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
State state = fxAccount.getState();
|
final State state = fxAccount.getState();
|
||||||
if (state.getStateLabel() != StateLabel.Separated) {
|
if (state.getStateLabel() != StateLabel.Separated) {
|
||||||
Logger.warn(LOG_TAG, "Cannot update credentials from Firefox Account in state: " + state.getStateLabel());
|
Logger.warn(LOG_TAG, "Cannot update credentials from Firefox Account in state: " + state.getStateLabel());
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
@ -100,93 +42,11 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
|||||||
emailEdit.setText(fxAccount.getEmail());
|
emailEdit.setText(fxAccount.getEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
|
@Override
|
||||||
public final String email;
|
public Intent makeSuccessIntent(String email, LoginResponse result) {
|
||||||
public final String serverURI;
|
// We don't show anything after updating credentials. The updating Activity
|
||||||
public final PasswordStretcher passwordStretcher;
|
// sets its result to OK and the user is returned to the previous task,
|
||||||
|
// which is often the Status Activity.
|
||||||
public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
|
return null;
|
||||||
this.email = email;
|
|
||||||
this.serverURI = serverURI;
|
|
||||||
this.passwordStretcher = passwordStretcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleError(Exception e) {
|
|
||||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleFailure(FxAccountClientRemoteException e) {
|
|
||||||
if (e.isUpgradeRequired()) {
|
|
||||||
Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state.");
|
|
||||||
final State state = fxAccount.getState();
|
|
||||||
fxAccount.setState(state.makeDoghouseState());
|
|
||||||
// The status activity will say that the user needs to upgrade.
|
|
||||||
redirectToActivity(FxAccountStatusActivity.class);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleSuccess(LoginResponse result) {
|
|
||||||
Logger.info(LOG_TAG, "Got success signing in.");
|
|
||||||
|
|
||||||
if (fxAccount == null) {
|
|
||||||
this.handleError(new IllegalStateException("fxAccount must not be null"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] unwrapkB;
|
|
||||||
try {
|
|
||||||
// It is crucial that we use the email address provided by the server
|
|
||||||
// (rather than whatever the user entered), because the user's keys are
|
|
||||||
// wrapped and salted with the initial email they provided to
|
|
||||||
// /create/account. Of course, we want to pass through what the user
|
|
||||||
// entered locally as much as possible.
|
|
||||||
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
|
|
||||||
unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
|
||||||
} catch (Exception e) {
|
|
||||||
this.handleError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
|
|
||||||
fxAccount.requestSync(FirefoxAccounts.FORCE);
|
|
||||||
|
|
||||||
// For great debugging.
|
|
||||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
|
||||||
fxAccount.dump();
|
|
||||||
}
|
|
||||||
|
|
||||||
setResult(RESULT_OK);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateCredentials(String email, String password) {
|
|
||||||
String serverURI = fxAccount.getAccountServerURI();
|
|
||||||
Executor executor = Executors.newSingleThreadExecutor();
|
|
||||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
|
||||||
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
|
||||||
try {
|
|
||||||
hideRemoteError();
|
|
||||||
RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
|
|
||||||
new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
|
|
||||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void createButton() {
|
|
||||||
button.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
final String email = emailEdit.getText().toString();
|
|
||||||
final String password = passwordEdit.getText().toString();
|
|
||||||
updateCredentials(email, password);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
mobile/android/base/fxa/login/MigratedFromSync11.java
Normal file
28
mobile/android/base/fxa/login/MigratedFromSync11.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.fxa.login;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
|
||||||
|
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.PasswordRequired;
|
||||||
|
|
||||||
|
public class MigratedFromSync11 extends State {
|
||||||
|
public final String password;
|
||||||
|
|
||||||
|
public MigratedFromSync11(String email, String uid, boolean verified, String password) {
|
||||||
|
super(StateLabel.MigratedFromSync11, email, uid, verified);
|
||||||
|
// Null password is allowed.
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(final ExecuteDelegate delegate) {
|
||||||
|
delegate.handleTransition(new PasswordRequired(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Action getNeededAction() {
|
||||||
|
return Action.NeedsFinishMigrating;
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ import org.mozilla.gecko.sync.ExtendedJSONObject;
|
|||||||
import org.mozilla.gecko.sync.Utils;
|
import org.mozilla.gecko.sync.Utils;
|
||||||
|
|
||||||
public abstract class State {
|
public abstract class State {
|
||||||
public static final long CURRENT_VERSION = 2L;
|
public static final long CURRENT_VERSION = 3L;
|
||||||
|
|
||||||
public enum StateLabel {
|
public enum StateLabel {
|
||||||
Engaged,
|
Engaged,
|
||||||
@ -17,12 +17,14 @@ public abstract class State {
|
|||||||
Married,
|
Married,
|
||||||
Separated,
|
Separated,
|
||||||
Doghouse,
|
Doghouse,
|
||||||
|
MigratedFromSync11,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Action {
|
public enum Action {
|
||||||
NeedsUpgrade,
|
NeedsUpgrade,
|
||||||
NeedsPassword,
|
NeedsPassword,
|
||||||
NeedsVerification,
|
NeedsVerification,
|
||||||
|
NeedsFinishMigrating,
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +62,10 @@ public abstract class State {
|
|||||||
return new Doghouse(email, uid, verified);
|
return new Doghouse(email, uid, verified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public State makeMigratedFromSync11State(String password) {
|
||||||
|
return new MigratedFromSync11(email, uid, verified, password);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void execute(ExecuteDelegate delegate);
|
public abstract void execute(ExecuteDelegate delegate);
|
||||||
|
|
||||||
public abstract Action getNeededAction();
|
public abstract Action getNeededAction();
|
||||||
|
@ -54,8 +54,11 @@ public class StateFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int v = version.intValue();
|
final int v = version.intValue();
|
||||||
if (v == 2) {
|
if (v == 3) {
|
||||||
// The most common case is the most recent version.
|
// The most common case is the most recent version.
|
||||||
|
return fromJSONObjectV3(stateLabel, o);
|
||||||
|
}
|
||||||
|
if (v == 2) {
|
||||||
return fromJSONObjectV2(stateLabel, o);
|
return fromJSONObjectV2(stateLabel, o);
|
||||||
}
|
}
|
||||||
if (v == 1) {
|
if (v == 1) {
|
||||||
@ -134,6 +137,23 @@ public class StateFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exactly the same as {@link fromJSONObjectV2}, except that there's a new
|
||||||
|
* MigratedFromSyncV11 state.
|
||||||
|
*/
|
||||||
|
protected static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
|
||||||
|
switch (stateLabel) {
|
||||||
|
case MigratedFromSync11:
|
||||||
|
return new MigratedFromSync11(
|
||||||
|
o.getString("email"),
|
||||||
|
o.getString("uid"),
|
||||||
|
o.getBoolean("verified"),
|
||||||
|
o.getString("password"));
|
||||||
|
default:
|
||||||
|
return fromJSONObjectV2(stateLabel, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static void logMigration(State from, State to) {
|
protected static void logMigration(State from, State to) {
|
||||||
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||||
return;
|
return;
|
||||||
|
@ -8,6 +8,7 @@ import org.mozilla.gecko.BrowserLocaleManager;
|
|||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.background.common.log.Logger;
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||||
|
import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
|
||||||
import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
||||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
@ -67,12 +68,21 @@ public class FxAccountNotificationManager {
|
|||||||
BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
|
BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
final String title;
|
||||||
final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
final String text;
|
||||||
|
final Intent notificationIntent;
|
||||||
|
if (action == Action.NeedsFinishMigrating) {
|
||||||
|
title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
|
||||||
|
text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
|
||||||
|
notificationIntent = new Intent(context, FxAccountFinishMigratingActivity.class);
|
||||||
|
} else {
|
||||||
|
title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
||||||
|
text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||||
|
notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
||||||
|
}
|
||||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
||||||
FxAccountUtils.pii(LOG_TAG, "And text: " + text);
|
FxAccountUtils.pii(LOG_TAG, "And text: " + text);
|
||||||
|
|
||||||
final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
|
||||||
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||||
|
|
||||||
final Builder builder = new NotificationCompat.Builder(context);
|
final Builder builder = new NotificationCompat.Builder(context);
|
||||||
|
@ -118,6 +118,7 @@ public class FxAccountSchedulePolicy implements SchedulePolicy {
|
|||||||
switch (needed) {
|
switch (needed) {
|
||||||
case NeedsPassword:
|
case NeedsPassword:
|
||||||
case NeedsUpgrade:
|
case NeedsUpgrade:
|
||||||
|
case NeedsFinishMigrating:
|
||||||
requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
|
requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
|
||||||
break;
|
break;
|
||||||
case NeedsVerification:
|
case NeedsVerification:
|
||||||
|
105
mobile/android/base/fxa/tasks/FxAccountUnlockCodeResender.java
Normal file
105
mobile/android/base/fxa/tasks/FxAccountUnlockCodeResender.java
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.fxa.tasks;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||||
|
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class that provides a simple interface for requesting a Firefox
|
||||||
|
* Account unlock account email be (re)-sent.
|
||||||
|
*/
|
||||||
|
public class FxAccountUnlockCodeResender {
|
||||||
|
private static final String LOG_TAG = FxAccountUnlockCodeResender.class.getSimpleName();
|
||||||
|
|
||||||
|
private static class FxAccountUnlockCodeTask extends FxAccountSetupTask<Void> {
|
||||||
|
protected static final String LOG_TAG = FxAccountUnlockCodeTask.class.getSimpleName();
|
||||||
|
|
||||||
|
protected final byte[] emailUTF8;
|
||||||
|
|
||||||
|
public FxAccountUnlockCodeTask(Context context, byte[] emailUTF8, FxAccountClient client, RequestDelegate<Void> delegate) {
|
||||||
|
super(context, null, client, delegate);
|
||||||
|
this.emailUTF8 = emailUTF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InnerRequestDelegate<Void> doInBackground(Void... arg0) {
|
||||||
|
try {
|
||||||
|
client.resendUnlockCode(emailUTF8, innerDelegate);
|
||||||
|
latch.await();
|
||||||
|
return innerDelegate;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error(LOG_TAG, "Got exception signing in.", e);
|
||||||
|
delegate.handleError(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ResendUnlockCodeDelegate implements RequestDelegate<Void> {
|
||||||
|
public final Context context;
|
||||||
|
|
||||||
|
public ResendUnlockCodeDelegate(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(Exception e) {
|
||||||
|
Logger.warn(LOG_TAG, "Got exception requesting fresh unlock code; ignoring.", e);
|
||||||
|
Toast.makeText(context, R.string.fxaccount_unlock_code_not_sent, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFailure(FxAccountClientRemoteException e) {
|
||||||
|
handleError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSuccess(Void result) {
|
||||||
|
Toast.makeText(context, R.string.fxaccount_unlock_code_sent, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends the account unlock email, and displays an appropriate toast on both
|
||||||
|
* send success and failure. Note that because the underlying implementation
|
||||||
|
* uses {@link AsyncTask}, the provided context must be UI-capable and this
|
||||||
|
* method called from the UI thread.
|
||||||
|
*
|
||||||
|
* Note that it may actually be possible to run this (and the
|
||||||
|
* {@link AsyncTask}) method from a background thread - but this hasn't been
|
||||||
|
* tested.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* A UI-capable Android context.
|
||||||
|
* @param authServerURI
|
||||||
|
* to send request to.
|
||||||
|
* @param emailUTF8
|
||||||
|
* bytes of email address identifying account; null indicates a local failure.
|
||||||
|
*/
|
||||||
|
public static void resendUnlockCode(Context context, String authServerURI, byte[] emailUTF8) {
|
||||||
|
RequestDelegate<Void> delegate = new ResendUnlockCodeDelegate(context);
|
||||||
|
|
||||||
|
if (emailUTF8 == null) {
|
||||||
|
delegate.handleError(new IllegalArgumentException("emailUTF8 must not be null"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Executor executor = Executors.newSingleThreadExecutor();
|
||||||
|
final FxAccountClient client = new FxAccountClient20(authServerURI, executor);
|
||||||
|
new FxAccountUnlockCodeTask(context, emailUTF8, client, delegate).execute();
|
||||||
|
}
|
||||||
|
}
|
@ -157,6 +157,8 @@ public class RemoteTabsPanel extends HomeFragment {
|
|||||||
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
|
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
|
||||||
case NeedsUpgrade:
|
case NeedsUpgrade:
|
||||||
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_upgrade);
|
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_upgrade);
|
||||||
|
case NeedsFinishMigrating:
|
||||||
|
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_finish_migrating);
|
||||||
default:
|
default:
|
||||||
// This should never happen, but we're confident we have a Firefox
|
// This should never happen, but we're confident we have a Firefox
|
||||||
// Account at this point, so let's show the needs password screen.
|
// Account at this point, so let's show the needs password screen.
|
||||||
|
@ -10,6 +10,7 @@ import java.util.Locale;
|
|||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||||
import org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity;
|
import org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity;
|
||||||
|
import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
|
||||||
import org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity;
|
import org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity;
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
|
|
||||||
@ -90,7 +91,8 @@ public class RemoteTabsStaticFragment extends HomeFragment implements OnClickLis
|
|||||||
R.id.remote_tabs_setup_old_sync_link,
|
R.id.remote_tabs_setup_old_sync_link,
|
||||||
R.id.remote_tabs_needs_verification_resend_email,
|
R.id.remote_tabs_needs_verification_resend_email,
|
||||||
R.id.remote_tabs_needs_verification_help,
|
R.id.remote_tabs_needs_verification_help,
|
||||||
R.id.remote_tabs_needs_password_sign_in, }) {
|
R.id.remote_tabs_needs_password_sign_in,
|
||||||
|
R.id.remote_tabs_needs_finish_migrating_sign_in, }) {
|
||||||
maybeSetOnClickListener(view, resourceId);
|
maybeSetOnClickListener(view, resourceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,10 +118,13 @@ public class RemoteTabsStaticFragment extends HomeFragment implements OnClickLis
|
|||||||
final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
|
final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
|
||||||
mUrlOpenListener.onUrlOpen(CONFIRM_ACCOUNT_SUPPORT_URL, flags);
|
mUrlOpenListener.onUrlOpen(CONFIRM_ACCOUNT_SUPPORT_URL, flags);
|
||||||
} else if (id == R.id.remote_tabs_needs_password_sign_in) {
|
} else if (id == R.id.remote_tabs_needs_password_sign_in) {
|
||||||
// This Activity will redirect to the correct Activity as needed.
|
|
||||||
final Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
final Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
} else if (id == R.id.remote_tabs_needs_finish_migrating_sign_in) {
|
||||||
|
final Intent intent = new Intent(getActivity(), FxAccountFinishMigratingActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,6 +429,8 @@ size. -->
|
|||||||
<!ENTITY home_remote_tabs_empty "Your tabs from other devices show up here.">
|
<!ENTITY home_remote_tabs_empty "Your tabs from other devices show up here.">
|
||||||
<!ENTITY home_remote_tabs_unable_to_connect "Unable to connect">
|
<!ENTITY home_remote_tabs_unable_to_connect "Unable to connect">
|
||||||
<!ENTITY home_remote_tabs_need_to_sign_in "Please sign in to reconnect your Firefox Account and continue syncing.">
|
<!ENTITY home_remote_tabs_need_to_sign_in "Please sign in to reconnect your Firefox Account and continue syncing.">
|
||||||
|
<!ENTITY home_remote_tabs_need_to_finish_migrating "Your new Firefox Account is ready!">
|
||||||
|
|
||||||
<!ENTITY home_remote_tabs_trouble_verifying "Trouble verifying your account?">
|
<!ENTITY home_remote_tabs_trouble_verifying "Trouble verifying your account?">
|
||||||
<!ENTITY home_remote_tabs_need_to_verify "Please verify your Firefox Account to start syncing.">
|
<!ENTITY home_remote_tabs_need_to_verify "Please verify your Firefox Account to start syncing.">
|
||||||
|
|
||||||
|
@ -170,6 +170,9 @@
|
|||||||
<!ENTITY fxaccount_confirm_account_change_email 'Forget this email address?'>
|
<!ENTITY fxaccount_confirm_account_change_email 'Forget this email address?'>
|
||||||
<!ENTITY fxaccount_confirm_account_verification_link_sent2 'Verification email sent'>
|
<!ENTITY fxaccount_confirm_account_verification_link_sent2 'Verification email sent'>
|
||||||
<!ENTITY fxaccount_confirm_account_verification_link_not_sent2 'Couldn\'t send verification email'>
|
<!ENTITY fxaccount_confirm_account_verification_link_not_sent2 'Couldn\'t send verification email'>
|
||||||
|
<!ENTITY fxaccount_resend_unlock_code_button_label 'Resend unlock email'>
|
||||||
|
<!ENTITY fxaccount_unlock_code_sent 'Account unlock email sent'>
|
||||||
|
<!ENTITY fxaccount_unlock_code_not_sent 'Couldn\'t send account unlock email'>
|
||||||
|
|
||||||
<!ENTITY fxaccount_sign_in_sub_header 'Sign in'>
|
<!ENTITY fxaccount_sign_in_sub_header 'Sign in'>
|
||||||
<!ENTITY fxaccount_sign_in_button_label 'Sign in'>
|
<!ENTITY fxaccount_sign_in_button_label 'Sign in'>
|
||||||
@ -180,10 +183,16 @@
|
|||||||
<!ENTITY fxaccount_account_verified_sub_header 'Account verified'>
|
<!ENTITY fxaccount_account_verified_sub_header 'Account verified'>
|
||||||
<!ENTITY fxaccount_account_verified_description2 'Your data will begin syncing momentarily.'>
|
<!ENTITY fxaccount_account_verified_description2 'Your data will begin syncing momentarily.'>
|
||||||
|
|
||||||
|
<!ENTITY fxaccount_migration_finished_header 'Upgrade finished'>
|
||||||
|
|
||||||
<!ENTITY fxaccount_update_credentials_header 'Sign in'>
|
<!ENTITY fxaccount_update_credentials_header 'Sign in'>
|
||||||
<!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
|
<!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
|
||||||
<!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
|
<!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
|
||||||
|
|
||||||
|
<!ENTITY fxaccount_finish_migrating_header 'Sign in to finish upgrading'>
|
||||||
|
<!ENTITY fxaccount_finish_migrating_button_label 'Finish upgrading'>
|
||||||
|
<!ENTITY fxaccount_finish_migrating_description 'Upgrading can transfer a lot of data. It\'s best to be on a WiFi network.'>
|
||||||
|
|
||||||
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
||||||
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
||||||
<!ENTITY fxaccount_status_auth_server 'Account server'>
|
<!ENTITY fxaccount_status_auth_server 'Account server'>
|
||||||
@ -198,6 +207,7 @@
|
|||||||
<!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
|
<!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
|
||||||
<!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings > Data Usage.'>
|
<!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings > Data Usage.'>
|
||||||
<!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
|
<!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
|
||||||
|
<!ENTITY fxaccount_status_needs_finish_migrating 'Tap to sign in to your new Firefox Account.'>
|
||||||
<!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
|
<!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
|
||||||
<!ENTITY fxaccount_status_history 'History'>
|
<!ENTITY fxaccount_status_history 'History'>
|
||||||
<!ENTITY fxaccount_status_passwords 'Passwords'>
|
<!ENTITY fxaccount_status_passwords 'Passwords'>
|
||||||
@ -245,8 +255,14 @@
|
|||||||
<!ENTITY fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD 'Server busy, try again soon'>
|
<!ENTITY fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD 'Server busy, try again soon'>
|
||||||
<!ENTITY fxaccount_remote_error_UNKNOWN_ERROR 'There was a problem'>
|
<!ENTITY fxaccount_remote_error_UNKNOWN_ERROR 'There was a problem'>
|
||||||
<!ENTITY fxaccount_remote_error_COULD_NOT_CONNECT 'Cannot connect to network'>
|
<!ENTITY fxaccount_remote_error_COULD_NOT_CONNECT 'Cannot connect to network'>
|
||||||
|
<!ENTITY fxaccount_remote_error_ACCOUNT_LOCKED 'Account is locked. &formatS1;'>
|
||||||
|
|
||||||
<!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
|
<!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
|
||||||
<!-- Localization note: the format string below will be replaced
|
<!-- Localization note: the format string below will be replaced
|
||||||
with the Firefox Account's email address. -->
|
with the Firefox Account's email address. -->
|
||||||
<!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
|
<!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
|
||||||
|
|
||||||
|
<!ENTITY fxaccount_sync_finish_migrating_notification_title 'Finish upgrading &syncBrand.shortName.label;?'>
|
||||||
|
<!-- Localization note: the format string below will be replaced
|
||||||
|
with the Firefox Account's email address. -->
|
||||||
|
<!ENTITY fxaccount_sync_finish_migrating_notification_text 'Tap to sign in as &formatS;'>
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:fillViewport="true" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/update_credentials_view"
|
||||||
|
style="@style/FxAccountMiddle" >
|
||||||
|
|
||||||
|
<LinearLayout style="@style/FxAccountSpacer" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/FxAccountHeaderItem"
|
||||||
|
android:text="@string/fxaccount_finish_migrating_header" />
|
||||||
|
|
||||||
|
<include layout="@layout/fxaccount_custom_server_view" />
|
||||||
|
|
||||||
|
<include layout="@layout/fxaccount_email_password_view" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/FxAccountTextItem"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="@string/fxaccount_finish_migrating_description" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/remote_error"
|
||||||
|
style="@style/FxAccountErrorItem" />
|
||||||
|
|
||||||
|
<RelativeLayout style="@style/FxAccountButtonLayout" >
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress"
|
||||||
|
style="@style/FxAccountProgress" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
style="@style/FxAccountButton"
|
||||||
|
android:text="@string/fxaccount_finish_migrating_button_label" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/forgot_password_link"
|
||||||
|
style="@style/FxAccountLinkifiedItem"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="@string/fxaccount_sign_in_forgot_password" />
|
||||||
|
|
||||||
|
<LinearLayout style="@style/FxAccountSpacer" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
style="@style/FxAccountIcon"
|
||||||
|
android:contentDescription="@string/fxaccount_empty_contentDescription" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:fillViewport="true" >
|
||||||
|
|
||||||
|
<LinearLayout style="@style/FxAccountMiddle" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/FxAccountHeaderItem"
|
||||||
|
android:text="@string/fxaccount_migration_finished_header" >
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="45dp"
|
||||||
|
android:contentDescription="@string/fxaccount_empty_contentDescription"
|
||||||
|
android:src="@drawable/fxaccount_checkbox" >
|
||||||
|
</ImageView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/FxAccountTextItem"
|
||||||
|
android:layout_marginBottom="40dp"
|
||||||
|
android:text="@string/fxaccount_migration_finished_description"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
style="@style/FxAccountButton"
|
||||||
|
android:text="@string/fxaccount_back_to_browsing" />
|
||||||
|
|
||||||
|
<LinearLayout style="@style/FxAccountSpacer" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
style="@style/FxAccountIcon"
|
||||||
|
android:contentDescription="@string/fxaccount_empty_contentDescription" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -0,0 +1,34 @@
|
|||||||
|
<?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="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
|
|
||||||
|
<LinearLayout style="@style/RemoteTabsPanelFrame" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/RemoteTabsPanelItem.TextAppearance.Header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/fxaccount_finish_migrating_header" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/RemoteTabsPanelItem.TextAppearance"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/home_remote_tabs_need_to_finish_migrating" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/remote_tabs_needs_finish_migrating_sign_in"
|
||||||
|
style="@style/RemoteTabsPanelItem.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/fxaccount_finish_migrating_button_label" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -54,6 +54,13 @@
|
|||||||
android:layout="@layout/fxaccount_status_error_preference"
|
android:layout="@layout/fxaccount_status_error_preference"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/fxaccount_status_needs_account_enabled" />
|
android:title="@string/fxaccount_status_needs_account_enabled" />
|
||||||
|
<Preference
|
||||||
|
android:editable="false"
|
||||||
|
android:icon="@drawable/fxaccount_sync_error"
|
||||||
|
android:key="needs_finish_migrating"
|
||||||
|
android:layout="@layout/fxaccount_status_error_preference"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/fxaccount_status_needs_finish_migrating" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:editable="false"
|
android:editable="false"
|
||||||
@ -125,6 +132,7 @@
|
|||||||
<Preference android:key="debug_forget_certificate" />
|
<Preference android:key="debug_forget_certificate" />
|
||||||
<Preference android:key="debug_require_password" />
|
<Preference android:key="debug_require_password" />
|
||||||
<Preference android:key="debug_require_upgrade" />
|
<Preference android:key="debug_require_upgrade" />
|
||||||
|
<Preference android:key="debug_migrated_from_sync11" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@ -369,6 +369,7 @@
|
|||||||
<string name="home_remote_tabs_empty">&home_remote_tabs_empty;</string>
|
<string name="home_remote_tabs_empty">&home_remote_tabs_empty;</string>
|
||||||
<string name="home_remote_tabs_unable_to_connect">&home_remote_tabs_unable_to_connect;</string>
|
<string name="home_remote_tabs_unable_to_connect">&home_remote_tabs_unable_to_connect;</string>
|
||||||
<string name="home_remote_tabs_need_to_sign_in">&home_remote_tabs_need_to_sign_in;</string>
|
<string name="home_remote_tabs_need_to_sign_in">&home_remote_tabs_need_to_sign_in;</string>
|
||||||
|
<string name="home_remote_tabs_need_to_finish_migrating">&home_remote_tabs_need_to_finish_migrating;</string>
|
||||||
<string name="home_remote_tabs_trouble_verifying">&home_remote_tabs_trouble_verifying;</string>
|
<string name="home_remote_tabs_trouble_verifying">&home_remote_tabs_trouble_verifying;</string>
|
||||||
<string name="home_remote_tabs_need_to_verify">&home_remote_tabs_need_to_verify;</string>
|
<string name="home_remote_tabs_need_to_verify">&home_remote_tabs_need_to_verify;</string>
|
||||||
<string name="home_remote_tabs_one_hidden_device">&home_remote_tabs_one_hidden_device;</string>
|
<string name="home_remote_tabs_one_hidden_device">&home_remote_tabs_one_hidden_device;</string>
|
||||||
|
@ -35,6 +35,9 @@ import android.annotation.SuppressLint;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
@ -614,4 +617,26 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
return language + "-" + country;
|
return language + "-" + country;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a span with a clickable chunk of text interpolated in.
|
||||||
|
*
|
||||||
|
* @param context Android context.
|
||||||
|
* @param messageId of string containing clickable chunk.
|
||||||
|
* @param clickableId of string to make clickable.
|
||||||
|
* @param clickableSpan to activate on click.
|
||||||
|
* @return Spannable.
|
||||||
|
*/
|
||||||
|
public static Spannable interpolateClickableSpan(Context context, int messageId, int clickableId, ClickableSpan clickableSpan) {
|
||||||
|
// This horrible bit of special-casing is because we want this error message to
|
||||||
|
// contain a clickable, extra chunk of text, but we don't want to pollute
|
||||||
|
// the exception class with Android specifics.
|
||||||
|
final String clickablePart = context.getString(clickableId);
|
||||||
|
final String message = context.getString(messageId, clickablePart);
|
||||||
|
final int clickableStart = message.lastIndexOf(clickablePart);
|
||||||
|
final int clickableEnd = clickableStart + clickablePart.length();
|
||||||
|
final Spannable span = Spannable.Factory.getInstance().newSpannable(message);
|
||||||
|
span.setSpan(clickableSpan, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
return span;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,21 @@
|
|||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:theme="@style/FxAccountTheme"
|
||||||
|
android:name="org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity"
|
||||||
|
android:configChanges="locale|layoutDirection"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:theme="@style/FxAccountTheme"
|
||||||
|
android:name="org.mozilla.gecko.fxa.activities.FxAccountMigrationFinishedActivity"
|
||||||
|
android:configChanges="locale|layoutDirection"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:theme="@style/FxAccountTheme"
|
android:theme="@style/FxAccountTheme"
|
||||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
|
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
|
||||||
|
@ -157,6 +157,9 @@
|
|||||||
<string name="fxaccount_confirm_account_change_email">&fxaccount_confirm_account_change_email;</string>
|
<string name="fxaccount_confirm_account_change_email">&fxaccount_confirm_account_change_email;</string>
|
||||||
<string name="fxaccount_confirm_account_verification_link_sent">&fxaccount_confirm_account_verification_link_sent2;</string>
|
<string name="fxaccount_confirm_account_verification_link_sent">&fxaccount_confirm_account_verification_link_sent2;</string>
|
||||||
<string name="fxaccount_confirm_account_verification_link_not_sent">&fxaccount_confirm_account_verification_link_not_sent2;</string>
|
<string name="fxaccount_confirm_account_verification_link_not_sent">&fxaccount_confirm_account_verification_link_not_sent2;</string>
|
||||||
|
<string name="fxaccount_resend_unlock_code_button_label">&fxaccount_resend_unlock_code_button_label;</string>
|
||||||
|
<string name="fxaccount_unlock_code_sent">&fxaccount_unlock_code_sent;</string>
|
||||||
|
<string name="fxaccount_unlock_code_not_sent">&fxaccount_unlock_code_not_sent;</string>
|
||||||
|
|
||||||
<string name="fxaccount_sign_in_sub_header">&fxaccount_sign_in_sub_header;</string>
|
<string name="fxaccount_sign_in_sub_header">&fxaccount_sign_in_sub_header;</string>
|
||||||
<string name="fxaccount_sign_in_button_label">&fxaccount_sign_in_button_label;</string>
|
<string name="fxaccount_sign_in_button_label">&fxaccount_sign_in_button_label;</string>
|
||||||
@ -167,10 +170,17 @@
|
|||||||
<string name="fxaccount_account_verified_sub_header">&fxaccount_account_verified_sub_header;</string>
|
<string name="fxaccount_account_verified_sub_header">&fxaccount_account_verified_sub_header;</string>
|
||||||
<string name="fxaccount_account_verified_description">&fxaccount_account_verified_description2;</string>
|
<string name="fxaccount_account_verified_description">&fxaccount_account_verified_description2;</string>
|
||||||
|
|
||||||
|
<string name="fxaccount_migration_finished_header">&fxaccount_migration_finished_header;</string>
|
||||||
|
<string name="fxaccount_migration_finished_description">&fxaccount_account_verified_description2;</string>
|
||||||
|
|
||||||
<string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
|
<string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
|
||||||
<string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
|
<string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
|
||||||
<string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
|
<string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
|
||||||
|
|
||||||
|
<string name="fxaccount_finish_migrating_header">&fxaccount_finish_migrating_header;</string>
|
||||||
|
<string name="fxaccount_finish_migrating_button_label">&fxaccount_finish_migrating_button_label;</string>
|
||||||
|
<string name="fxaccount_finish_migrating_description">&fxaccount_finish_migrating_description;</string>
|
||||||
|
|
||||||
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
|
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
|
||||||
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
||||||
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
||||||
@ -187,6 +197,7 @@
|
|||||||
<string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
|
<string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
|
||||||
<string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
|
<string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
|
||||||
<string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
|
<string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
|
||||||
|
<string name="fxaccount_status_needs_finish_migrating">&fxaccount_status_needs_finish_migrating;</string>
|
||||||
<string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
|
<string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
|
||||||
<string name="fxaccount_status_history">&fxaccount_status_history;</string>
|
<string name="fxaccount_status_history">&fxaccount_status_history;</string>
|
||||||
<string name="fxaccount_status_passwords">&fxaccount_status_passwords;</string>
|
<string name="fxaccount_status_passwords">&fxaccount_status_passwords;</string>
|
||||||
@ -210,6 +221,7 @@
|
|||||||
<string name="fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD">&fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;</string>
|
<string name="fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD">&fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;</string>
|
||||||
<string name="fxaccount_remote_error_UNKNOWN_ERROR">&fxaccount_remote_error_UNKNOWN_ERROR;</string>
|
<string name="fxaccount_remote_error_UNKNOWN_ERROR">&fxaccount_remote_error_UNKNOWN_ERROR;</string>
|
||||||
<string name="fxaccount_remote_error_COULD_NOT_CONNECT">&fxaccount_remote_error_COULD_NOT_CONNECT;</string>
|
<string name="fxaccount_remote_error_COULD_NOT_CONNECT">&fxaccount_remote_error_COULD_NOT_CONNECT;</string>
|
||||||
|
<string name="fxaccount_remote_error_ACCOUNT_LOCKED">&fxaccount_remote_error_ACCOUNT_LOCKED;</string>
|
||||||
|
|
||||||
<string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
|
<string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
|
||||||
<string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
|
<string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
|
||||||
@ -219,3 +231,6 @@
|
|||||||
<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
|
<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
|
||||||
<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
|
<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
|
||||||
<string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
|
<string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
|
||||||
|
|
||||||
|
<string name="fxaccount_sync_finish_migrating_notification_title">&fxaccount_sync_finish_migrating_notification_title;</string>
|
||||||
|
<string name="fxaccount_sync_finish_migrating_notification_text">&fxaccount_sync_finish_migrating_notification_text;</string>
|
||||||
|
51
testing/xpcshell/dbg-actors.js
Normal file
51
testing/xpcshell/dbg-actors.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||||
|
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||||
|
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||||
|
const { RootActor } = devtools.require("devtools/server/actors/root");
|
||||||
|
const { BrowserTabList } = devtools.require("devtools/server/actors/webbrowser");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* xpcshell-test (XPCST) specific actors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a root actor appropriate for use in a server running xpcshell
|
||||||
|
* tests. <snip boilerplate> :)
|
||||||
|
*/
|
||||||
|
function createRootActor(connection)
|
||||||
|
{
|
||||||
|
let parameters = {
|
||||||
|
tabList: new XPCSTTabList(connection),
|
||||||
|
globalActorFactories: DebuggerServer.globalActorFactories,
|
||||||
|
onShutdown() {
|
||||||
|
// If the user never switches to the "debugger" tab we might get a
|
||||||
|
// shutdown before we've attached.
|
||||||
|
Services.obs.notifyObservers(null, "xpcshell-test-devtools-shutdown", null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new RootActor(connection, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "stub" TabList implementation that provides no tabs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function XPCSTTabList(connection)
|
||||||
|
{
|
||||||
|
BrowserTabList.call(this, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
XPCSTTabList.prototype = Object.create(BrowserTabList.prototype);
|
||||||
|
|
||||||
|
XPCSTTabList.prototype.constructor = XPCSTTabList;
|
||||||
|
|
||||||
|
XPCSTTabList.prototype.getList = function() {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
};
|
@ -336,7 +336,101 @@ function _register_modules_protocol_handler() {
|
|||||||
protocolHandler.setSubstitution("testing-common", modulesURI);
|
protocolHandler.setSubstitution("testing-common", modulesURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _initDebugging(port) {
|
||||||
|
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||||
|
.getService(Components.interfaces.nsIPrefBranch);
|
||||||
|
|
||||||
|
// Always allow remote debugging.
|
||||||
|
prefs.setBoolPref("devtools.debugger.remote-enabled", true);
|
||||||
|
|
||||||
|
// for debugging-the-debugging, let an env var cause log spew.
|
||||||
|
let env = Components.classes["@mozilla.org/process/environment;1"]
|
||||||
|
.getService(Components.interfaces.nsIEnvironment);
|
||||||
|
if (env.get("DEVTOOLS_DEBUGGER_LOG")) {
|
||||||
|
prefs.setBoolPref("devtools.debugger.log", true);
|
||||||
|
}
|
||||||
|
if (env.get("DEVTOOLS_DEBUGGER_LOG_VERBOSE")) {
|
||||||
|
prefs.setBoolPref("devtools.debugger.log.verbose", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let {DebuggerServer} = Components.utils.import('resource://gre/modules/devtools/dbg-server.jsm', {});
|
||||||
|
DebuggerServer.init(() => true);
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
DebuggerServer.addActors("resource://testing-common/dbg-actors.js");
|
||||||
|
|
||||||
|
// An observer notification that tells us when we can "resume" script
|
||||||
|
// execution.
|
||||||
|
let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
|
||||||
|
getService(Components.interfaces.nsIObserverService);
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
const TOPICS = ["devtools-thread-resumed", "xpcshell-test-devtools-shutdown"];
|
||||||
|
let observe = function(subject, topic, data) {
|
||||||
|
switch (topic) {
|
||||||
|
case "devtools-thread-resumed":
|
||||||
|
// Exceptions in here aren't reported and block the debugger from
|
||||||
|
// resuming, so...
|
||||||
|
try {
|
||||||
|
// Add a breakpoint for the first line in our test files.
|
||||||
|
let threadActor = subject.wrappedJSObject;
|
||||||
|
let location = { line: 1 };
|
||||||
|
for (let file of _TEST_FILE) {
|
||||||
|
let sourceActor = threadActor.sources.source({originalUrl: file});
|
||||||
|
sourceActor.createAndStoreBreakpoint(location);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
do_print("Failed to initialize breakpoints: " + ex + "\n" + ex.stack);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "xpcshell-test-devtools-shutdown":
|
||||||
|
// the debugger has shutdown before we got a resume event - nothing
|
||||||
|
// special to do here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
for (let topicToRemove of TOPICS) {
|
||||||
|
obsSvc.removeObserver(observe, topicToRemove);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let topic of TOPICS) {
|
||||||
|
obsSvc.addObserver(observe, topic, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_print("");
|
||||||
|
do_print("*******************************************************************");
|
||||||
|
do_print("Waiting for the debugger to connect on port " + port)
|
||||||
|
do_print("")
|
||||||
|
do_print("To connect the debugger, open a Firefox instance, select 'Connect'");
|
||||||
|
do_print("from the Developer menu and specify the port as " + port);
|
||||||
|
do_print("*******************************************************************");
|
||||||
|
do_print("")
|
||||||
|
|
||||||
|
DebuggerServer.openListener(port);
|
||||||
|
|
||||||
|
// spin an event loop until the debugger connects.
|
||||||
|
let thr = Components.classes["@mozilla.org/thread-manager;1"]
|
||||||
|
.getService().currentThread;
|
||||||
|
while (!initialized) {
|
||||||
|
do_print("Still waiting for debugger to connect...");
|
||||||
|
thr.processNextEvent(true);
|
||||||
|
}
|
||||||
|
// NOTE: if you want to debug the harness itself, you can now add a 'debugger'
|
||||||
|
// statement anywhere and it will stop - but we've already added a breakpoint
|
||||||
|
// for the first line of the test scripts, so we just continue...
|
||||||
|
do_print("Debugger connected, starting test execution");
|
||||||
|
}
|
||||||
|
|
||||||
function _execute_test() {
|
function _execute_test() {
|
||||||
|
// _JSDEBUGGER_PORT is dynamically defined by <runxpcshelltests.py>.
|
||||||
|
if (_JSDEBUGGER_PORT) {
|
||||||
|
try {
|
||||||
|
_initDebugging(_JSDEBUGGER_PORT);
|
||||||
|
} catch (ex) {
|
||||||
|
do_print("Failed to initialize debugging: " + ex + "\n" + ex.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_register_protocol_handlers();
|
_register_protocol_handlers();
|
||||||
|
|
||||||
// Override idle service by default.
|
// Override idle service by default.
|
||||||
@ -1072,6 +1166,8 @@ function do_load_child_test_harness()
|
|||||||
+ "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
|
+ "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
|
||||||
+ "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
|
+ "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
|
||||||
+ "const _TEST_NAME=" + uneval(_TEST_NAME) + "; "
|
+ "const _TEST_NAME=" + uneval(_TEST_NAME) + "; "
|
||||||
|
// We'll need more magic to get the debugger working in the child
|
||||||
|
+ "const _JSDEBUGGER_PORT=0; "
|
||||||
+ "const _XPCSHELL_PROCESS='child';";
|
+ "const _XPCSHELL_PROCESS='child';";
|
||||||
|
|
||||||
if (this._TESTING_MODULES_DIR) {
|
if (this._TESTING_MODULES_DIR) {
|
||||||
|
@ -65,6 +65,7 @@ class XPCShellRunner(MozbuildObject):
|
|||||||
def run_test(self, test_paths, interactive=False,
|
def run_test(self, test_paths, interactive=False,
|
||||||
keep_going=False, sequential=False, shuffle=False,
|
keep_going=False, sequential=False, shuffle=False,
|
||||||
debugger=None, debuggerArgs=None, debuggerInteractive=None,
|
debugger=None, debuggerArgs=None, debuggerInteractive=None,
|
||||||
|
jsDebugger=False, jsDebuggerPort=None,
|
||||||
rerun_failures=False, test_objects=None, verbose=False,
|
rerun_failures=False, test_objects=None, verbose=False,
|
||||||
log=None,
|
log=None,
|
||||||
# ignore parameters from other platforms' options
|
# ignore parameters from other platforms' options
|
||||||
@ -83,6 +84,7 @@ class XPCShellRunner(MozbuildObject):
|
|||||||
keep_going=keep_going, shuffle=shuffle, sequential=sequential,
|
keep_going=keep_going, shuffle=shuffle, sequential=sequential,
|
||||||
debugger=debugger, debuggerArgs=debuggerArgs,
|
debugger=debugger, debuggerArgs=debuggerArgs,
|
||||||
debuggerInteractive=debuggerInteractive,
|
debuggerInteractive=debuggerInteractive,
|
||||||
|
jsDebugger=jsDebugger, jsDebuggerPort=jsDebuggerPort,
|
||||||
rerun_failures=rerun_failures,
|
rerun_failures=rerun_failures,
|
||||||
verbose=verbose, log=log)
|
verbose=verbose, log=log)
|
||||||
return
|
return
|
||||||
@ -113,6 +115,8 @@ class XPCShellRunner(MozbuildObject):
|
|||||||
'debugger': debugger,
|
'debugger': debugger,
|
||||||
'debuggerArgs': debuggerArgs,
|
'debuggerArgs': debuggerArgs,
|
||||||
'debuggerInteractive': debuggerInteractive,
|
'debuggerInteractive': debuggerInteractive,
|
||||||
|
'jsDebugger': jsDebugger,
|
||||||
|
'jsDebuggerPort': jsDebuggerPort,
|
||||||
'rerun_failures': rerun_failures,
|
'rerun_failures': rerun_failures,
|
||||||
'manifest': manifest,
|
'manifest': manifest,
|
||||||
'verbose': verbose,
|
'verbose': verbose,
|
||||||
@ -125,6 +129,7 @@ class XPCShellRunner(MozbuildObject):
|
|||||||
test_path=None, shuffle=False, interactive=False,
|
test_path=None, shuffle=False, interactive=False,
|
||||||
keep_going=False, sequential=False,
|
keep_going=False, sequential=False,
|
||||||
debugger=None, debuggerArgs=None, debuggerInteractive=None,
|
debugger=None, debuggerArgs=None, debuggerInteractive=None,
|
||||||
|
jsDebugger=False, jsDebuggerPort=None,
|
||||||
rerun_failures=False, verbose=False, log=None):
|
rerun_failures=False, verbose=False, log=None):
|
||||||
|
|
||||||
# Obtain a reference to the xpcshell test runner.
|
# Obtain a reference to the xpcshell test runner.
|
||||||
@ -161,6 +166,8 @@ class XPCShellRunner(MozbuildObject):
|
|||||||
'debugger': debugger,
|
'debugger': debugger,
|
||||||
'debuggerArgs': debuggerArgs,
|
'debuggerArgs': debuggerArgs,
|
||||||
'debuggerInteractive': debuggerInteractive,
|
'debuggerInteractive': debuggerInteractive,
|
||||||
|
'jsDebugger': jsDebugger,
|
||||||
|
'jsDebuggerPort': jsDebuggerPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
if test_path is not None:
|
if test_path is not None:
|
||||||
@ -417,6 +424,13 @@ class MachCommands(MachCommandBase):
|
|||||||
dest = "debuggerInteractive",
|
dest = "debuggerInteractive",
|
||||||
help = "prevents the test harness from redirecting "
|
help = "prevents the test harness from redirecting "
|
||||||
"stdout and stderr for interactive debuggers")
|
"stdout and stderr for interactive debuggers")
|
||||||
|
@CommandArgument("--jsdebugger", dest="jsDebugger", action="store_true",
|
||||||
|
help="Waits for a devtools JS debugger to connect before "
|
||||||
|
"starting the test.")
|
||||||
|
@CommandArgument("--jsdebugger-port", dest="jsDebuggerPort",
|
||||||
|
type=int, default=6000,
|
||||||
|
help="The port to listen on for a debugger connection if "
|
||||||
|
"--jsdebugger is specified (default=6000).")
|
||||||
@CommandArgument('--interactive', '-i', action='store_true',
|
@CommandArgument('--interactive', '-i', action='store_true',
|
||||||
help='Open an xpcshell prompt before running tests.')
|
help='Open an xpcshell prompt before running tests.')
|
||||||
@CommandArgument('--keep-going', '-k', action='store_true',
|
@CommandArgument('--keep-going', '-k', action='store_true',
|
||||||
|
@ -9,3 +9,7 @@ TEST_DIRS += ['example']
|
|||||||
PYTHON_UNIT_TESTS += [
|
PYTHON_UNIT_TESTS += [
|
||||||
'selftest.py',
|
'selftest.py',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TESTING_JS_MODULES += [
|
||||||
|
'dbg-actors.js',
|
||||||
|
]
|
||||||
|
@ -19,7 +19,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque, namedtuple
|
||||||
from distutils import dir_util
|
from distutils import dir_util
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
@ -111,6 +111,7 @@ class XPCShellTestThread(Thread):
|
|||||||
self.xrePath = kwargs.get('xrePath')
|
self.xrePath = kwargs.get('xrePath')
|
||||||
self.testingModulesDir = kwargs.get('testingModulesDir')
|
self.testingModulesDir = kwargs.get('testingModulesDir')
|
||||||
self.debuggerInfo = kwargs.get('debuggerInfo')
|
self.debuggerInfo = kwargs.get('debuggerInfo')
|
||||||
|
self.jsDebuggerInfo = kwargs.get('jsDebuggerInfo')
|
||||||
self.pluginsPath = kwargs.get('pluginsPath')
|
self.pluginsPath = kwargs.get('pluginsPath')
|
||||||
self.httpdManifest = kwargs.get('httpdManifest')
|
self.httpdManifest = kwargs.get('httpdManifest')
|
||||||
self.httpdJSPath = kwargs.get('httpdJSPath')
|
self.httpdJSPath = kwargs.get('httpdJSPath')
|
||||||
@ -366,10 +367,15 @@ class XPCShellTestThread(Thread):
|
|||||||
for f in headfiles])
|
for f in headfiles])
|
||||||
cmdT = ", ".join(['"' + f.replace('\\', '/') + '"'
|
cmdT = ", ".join(['"' + f.replace('\\', '/') + '"'
|
||||||
for f in tailfiles])
|
for f in tailfiles])
|
||||||
|
|
||||||
|
dbgport = 0 if self.jsDebuggerInfo is None else self.jsDebuggerInfo.port
|
||||||
|
|
||||||
return xpcscmd + \
|
return xpcscmd + \
|
||||||
['-e', 'const _SERVER_ADDR = "localhost"',
|
['-e', 'const _SERVER_ADDR = "localhost"',
|
||||||
'-e', 'const _HEAD_FILES = [%s];' % cmdH,
|
'-e', 'const _HEAD_FILES = [%s];' % cmdH,
|
||||||
'-e', 'const _TAIL_FILES = [%s];' % cmdT]
|
'-e', 'const _TAIL_FILES = [%s];' % cmdT,
|
||||||
|
'-e', 'const _JSDEBUGGER_PORT = %d;' % dbgport,
|
||||||
|
]
|
||||||
|
|
||||||
def getHeadAndTailFiles(self, test_object):
|
def getHeadAndTailFiles(self, test_object):
|
||||||
"""Obtain the list of head and tail files.
|
"""Obtain the list of head and tail files.
|
||||||
@ -632,7 +638,7 @@ class XPCShellTestThread(Thread):
|
|||||||
testTimeoutInterval *= int(self.test_object['requesttimeoutfactor'])
|
testTimeoutInterval *= int(self.test_object['requesttimeoutfactor'])
|
||||||
|
|
||||||
testTimer = None
|
testTimer = None
|
||||||
if not self.interactive and not self.debuggerInfo:
|
if not self.interactive and not self.debuggerInfo and not self.jsDebuggerInfo:
|
||||||
testTimer = Timer(testTimeoutInterval, lambda: self.testTimeout(proc))
|
testTimer = Timer(testTimeoutInterval, lambda: self.testTimeout(proc))
|
||||||
testTimer.start()
|
testTimer.start()
|
||||||
|
|
||||||
@ -1004,7 +1010,8 @@ class XPCShellTests(object):
|
|||||||
profileName=None, mozInfo=None, sequential=False, shuffle=False,
|
profileName=None, mozInfo=None, sequential=False, shuffle=False,
|
||||||
testsRootDir=None, testingModulesDir=None, pluginsPath=None,
|
testsRootDir=None, testingModulesDir=None, pluginsPath=None,
|
||||||
testClass=XPCShellTestThread, failureManifest=None,
|
testClass=XPCShellTestThread, failureManifest=None,
|
||||||
log=None, stream=None, **otherOptions):
|
log=None, stream=None, jsDebugger=False, jsDebuggerPort=0,
|
||||||
|
**otherOptions):
|
||||||
"""Run xpcshell tests.
|
"""Run xpcshell tests.
|
||||||
|
|
||||||
|xpcshell|, is the xpcshell executable to use to run the tests.
|
|xpcshell|, is the xpcshell executable to use to run the tests.
|
||||||
@ -1075,6 +1082,12 @@ class XPCShellTests(object):
|
|||||||
if debugger:
|
if debugger:
|
||||||
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debuggerArgs, debuggerInteractive)
|
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debuggerArgs, debuggerInteractive)
|
||||||
|
|
||||||
|
self.jsDebuggerInfo = None
|
||||||
|
if jsDebugger:
|
||||||
|
# A namedtuple let's us keep .port instead of ['port']
|
||||||
|
JSDebuggerInfo = namedtuple('JSDebuggerInfo', ['port'])
|
||||||
|
self.jsDebuggerInfo = JSDebuggerInfo(port=jsDebuggerPort)
|
||||||
|
|
||||||
self.xpcshell = xpcshell
|
self.xpcshell = xpcshell
|
||||||
self.xrePath = xrePath
|
self.xrePath = xrePath
|
||||||
self.appPath = appPath
|
self.appPath = appPath
|
||||||
@ -1161,6 +1174,7 @@ class XPCShellTests(object):
|
|||||||
'xrePath': self.xrePath,
|
'xrePath': self.xrePath,
|
||||||
'testingModulesDir': self.testingModulesDir,
|
'testingModulesDir': self.testingModulesDir,
|
||||||
'debuggerInfo': self.debuggerInfo,
|
'debuggerInfo': self.debuggerInfo,
|
||||||
|
'jsDebuggerInfo': self.jsDebuggerInfo,
|
||||||
'pluginsPath': self.pluginsPath,
|
'pluginsPath': self.pluginsPath,
|
||||||
'httpdManifest': self.httpdManifest,
|
'httpdManifest': self.httpdManifest,
|
||||||
'httpdJSPath': self.httpdJSPath,
|
'httpdJSPath': self.httpdJSPath,
|
||||||
@ -1190,6 +1204,13 @@ class XPCShellTests(object):
|
|||||||
if self.debuggerInfo.interactive:
|
if self.debuggerInfo.interactive:
|
||||||
signal.signal(signal.SIGINT, lambda signum, frame: None)
|
signal.signal(signal.SIGINT, lambda signum, frame: None)
|
||||||
|
|
||||||
|
if self.jsDebuggerInfo:
|
||||||
|
# The js debugger magic needs more work to do the right thing
|
||||||
|
# if debugging multiple files.
|
||||||
|
if len(self.alltests) != 1:
|
||||||
|
self.log.error("Error: --jsdebugger can only be used with a single test!")
|
||||||
|
return False
|
||||||
|
|
||||||
# create a queue of all tests that will run
|
# create a queue of all tests that will run
|
||||||
tests_queue = deque()
|
tests_queue = deque()
|
||||||
# also a list for the tests that need to be run sequentially
|
# also a list for the tests that need to be run sequentially
|
||||||
@ -1434,6 +1455,13 @@ class XPCShellOptions(OptionParser):
|
|||||||
action = "store_true", dest = "debuggerInteractive",
|
action = "store_true", dest = "debuggerInteractive",
|
||||||
help = "prevents the test harness from redirecting "
|
help = "prevents the test harness from redirecting "
|
||||||
"stdout and stderr for interactive debuggers")
|
"stdout and stderr for interactive debuggers")
|
||||||
|
self.add_option("--jsdebugger", dest="jsDebugger", action="store_true",
|
||||||
|
help="Waits for a devtools JS debugger to connect before "
|
||||||
|
"starting the test.")
|
||||||
|
self.add_option("--jsdebugger-port", type="int", dest="jsDebuggerPort",
|
||||||
|
default=6000,
|
||||||
|
help="The port to listen on for a debugger connection if "
|
||||||
|
"--jsdebugger is specified.")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = XPCShellOptions()
|
parser = XPCShellOptions()
|
||||||
|
@ -602,6 +602,9 @@ function ThreadActor(aParent, aGlobal)
|
|||||||
this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
|
this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
|
||||||
this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
|
this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
|
||||||
this.onNewScript = this.onNewScript.bind(this);
|
this.onNewScript = this.onNewScript.bind(this);
|
||||||
|
// Set a wrappedJSObject property so |this| can be sent via the observer svc
|
||||||
|
// for the xpcshell harness.
|
||||||
|
this.wrappedJSObject = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadActor.prototype = {
|
ThreadActor.prototype = {
|
||||||
@ -1178,6 +1181,11 @@ ThreadActor.prototype = {
|
|||||||
|
|
||||||
let packet = this._resumed();
|
let packet = this._resumed();
|
||||||
this._popThreadPause();
|
this._popThreadPause();
|
||||||
|
// Tell anyone who cares of the resume (as of now, that's the xpcshell
|
||||||
|
// harness)
|
||||||
|
if (Services.obs) {
|
||||||
|
Services.obs.notifyObservers(this, "devtools-thread-resumed", null);
|
||||||
|
}
|
||||||
return packet;
|
return packet;
|
||||||
}, error => {
|
}, error => {
|
||||||
return error instanceof Error
|
return error instanceof Error
|
||||||
@ -1322,7 +1330,7 @@ ThreadActor.prototype = {
|
|||||||
for (let line = 0, n = offsets.length; line < n; line++) {
|
for (let line = 0, n = offsets.length; line < n; line++) {
|
||||||
if (offsets[line]) {
|
if (offsets[line]) {
|
||||||
let location = { line: line };
|
let location = { line: line };
|
||||||
let resp = sourceActor._createAndStoreBreakpoint(location);
|
let resp = sourceActor.createAndStoreBreakpoint(location);
|
||||||
dbg_assert(!resp.actualLocation, "No actualLocation should be returned");
|
dbg_assert(!resp.actualLocation, "No actualLocation should be returned");
|
||||||
if (resp.error) {
|
if (resp.error) {
|
||||||
reportError(new Error("Unable to set breakpoint on event listener"));
|
reportError(new Error("Unable to set breakpoint on event listener"));
|
||||||
@ -2516,11 +2524,10 @@ SourceActor.prototype = {
|
|||||||
let sourceFetched = fetch(this.url, { loadFromCache: !this.source });
|
let sourceFetched = fetch(this.url, { loadFromCache: !this.source });
|
||||||
|
|
||||||
// Record the contentType we just learned during fetching
|
// Record the contentType we just learned during fetching
|
||||||
sourceFetched.then(({ contentType }) => {
|
return sourceFetched.then(result => {
|
||||||
this._contentType = contentType;
|
this._contentType = result.contentType;
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
return sourceFetched;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -2848,7 +2855,7 @@ SourceActor.prototype = {
|
|||||||
|
|
||||||
_createBreakpoint: function(loc, originalLoc, condition) {
|
_createBreakpoint: function(loc, originalLoc, condition) {
|
||||||
return resolve(null).then(() => {
|
return resolve(null).then(() => {
|
||||||
return this._createAndStoreBreakpoint({
|
return this.createAndStoreBreakpoint({
|
||||||
line: loc.line,
|
line: loc.line,
|
||||||
column: loc.column,
|
column: loc.column,
|
||||||
condition: condition
|
condition: condition
|
||||||
@ -2915,12 +2922,14 @@ SourceActor.prototype = {
|
|||||||
* Create a breakpoint at the specified location and store it in the
|
* Create a breakpoint at the specified location and store it in the
|
||||||
* cache. Takes ownership of `aRequest`. This is the
|
* cache. Takes ownership of `aRequest`. This is the
|
||||||
* generated location if this source is sourcemapped.
|
* generated location if this source is sourcemapped.
|
||||||
|
* Used by the XPCShell test harness to set breakpoints in a script before
|
||||||
|
* it has loaded.
|
||||||
*
|
*
|
||||||
* @param Object aRequest
|
* @param Object aRequest
|
||||||
* An object of the form { line[, column, condition] }. The
|
* An object of the form { line[, column, condition] }. The
|
||||||
* location is in the generated source, if sourcemapped.
|
* location is in the generated source, if sourcemapped.
|
||||||
*/
|
*/
|
||||||
_createAndStoreBreakpoint: function (aRequest) {
|
createAndStoreBreakpoint: function (aRequest) {
|
||||||
let bp = update({}, aRequest, { source: this.form() });
|
let bp = update({}, aRequest, { source: this.form() });
|
||||||
this.breakpointStore.addBreakpoint(bp);
|
this.breakpointStore.addBreakpoint(bp);
|
||||||
return this._setBreakpoint(aRequest);
|
return this._setBreakpoint(aRequest);
|
||||||
|
@ -13,10 +13,19 @@ const { Cc, Ci } = require("chrome");
|
|||||||
Object.defineProperty(this, "addonManager", {
|
Object.defineProperty(this, "addonManager", {
|
||||||
get: (function () {
|
get: (function () {
|
||||||
let cached;
|
let cached;
|
||||||
return () => cached
|
return () => {
|
||||||
? cached
|
if (cached === undefined) {
|
||||||
: (cached = Cc["@mozilla.org/addons/integration;1"]
|
// catch errors as the addonManager might not exist in this environment
|
||||||
.getService(Ci.amIAddonManager))
|
// (eg, xpcshell)
|
||||||
|
try {
|
||||||
|
cached = Cc["@mozilla.org/addons/integration;1"]
|
||||||
|
.getService(Ci.amIAddonManager);
|
||||||
|
} catch (ex) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
}())
|
}())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user