Skip to content
This repository was archived by the owner on Oct 18, 2018. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,62 @@
package org.mozilla.gecko.fxa;

import java.io.File;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;

import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
import org.mozilla.gecko.sync.ThreadPool;
import org.mozilla.gecko.sync.Utils;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;

/**
* Simple public accessors for Firefox account objects.
*/
public class FirefoxAccounts {
private static final String LOG_TAG = FirefoxAccounts.class.getSimpleName();

public enum SyncHint {
/**
* Hint that a requested sync is preferred immediately.
* <p>
* On many devices, not including <code>SCHEDULE_NOW</code> means a delay of
* at least 30 seconds.
*/
SCHEDULE_NOW,

/**
* Hint that a requested sync may ignore local rate limiting.
* <p>
* This is just a hint; the actual requested sync may not obey the hint.
*/
IGNORE_LOCAL_RATE_LIMIT,

/**
* Hint that a requested sync may ignore remote server backoffs.
* <p>
* This is just a hint; the actual requested sync may not obey the hint.
*/
IGNORE_REMOTE_SERVER_BACKOFF,
}

public static final EnumSet<SyncHint> SOON = EnumSet.noneOf(SyncHint.class);

public static final EnumSet<SyncHint> NOW = EnumSet.of(
SyncHint.SCHEDULE_NOW);

public static final EnumSet<SyncHint> FORCE = EnumSet.of(
SyncHint.SCHEDULE_NOW,
SyncHint.IGNORE_LOCAL_RATE_LIMIT,
SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);

/**
* Returns true if a FirefoxAccount exists, false otherwise.
*
Expand Down Expand Up @@ -104,4 +143,86 @@ public static Account getFirefoxAccount(final Context context) {
}
return null;
}

protected static void putHintsToSync(final Bundle extras, EnumSet<SyncHint> syncHints) {
// stagesToSync and stagesToSkip are allowed to be null.
if (syncHints == null) {
throw new IllegalArgumentException("syncHints must not be null");
}

final boolean scheduleNow = syncHints.contains(SyncHint.SCHEDULE_NOW);
final boolean ignoreLocalRateLimit = syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
final boolean ignoreRemoteServerBackoff = syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);

extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, scheduleNow);
// The default when manually syncing is to ignore the local rate limit and
// any remote server backoff requests. Since we can't add flags to a manual
// sync instigated by the user, we have to reverse the natural conditionals.
// See also the FORCE EnumSet.
extras.putBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT, !ignoreLocalRateLimit);
extras.putBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF, !ignoreRemoteServerBackoff);
}

public static EnumSet<SyncHint> getHintsToSyncFromBundle(final Bundle extras) {
final EnumSet<SyncHint> syncHints = EnumSet.noneOf(SyncHint.class);

final boolean scheduleNow = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
final boolean ignoreLocalRateLimit = !extras.getBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT, false);
final boolean ignoreRemoteServerBackoff = !extras.getBoolean(FxAccountSyncAdapter.SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF, false);

if (scheduleNow) {
syncHints.add(SyncHint.SCHEDULE_NOW);
}
if (ignoreLocalRateLimit) {
syncHints.add(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
}
if (ignoreRemoteServerBackoff) {
syncHints.add(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
}

return syncHints;
}

public static void logSyncHints(EnumSet<SyncHint> syncHints) {
final boolean scheduleNow = syncHints.contains(SyncHint.SCHEDULE_NOW);
final boolean ignoreLocalRateLimit = syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT);
final boolean ignoreRemoteServerBackoff = syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);

Logger.info(LOG_TAG, "Sync hints" +
"; scheduling now: " + scheduleNow +
"; ignoring local rate limit: " + ignoreLocalRateLimit +
"; ignoring remote server backoff: " + ignoreRemoteServerBackoff + ".");
}

/**
* Request a sync for the given Android Account.
* <p>
* Any hints are strictly optional: the actual requested sync is scheduled by
* the Android sync scheduler, and the sync mechanism may ignore hints as it
* sees fit.
*
* @param account to sync.
* @param syncHints to pass to sync.
* @param stagesToSync stage names to sync.
* @param stagesToSkip stage names to skip.
*/
public static void requestSync(Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
if (account == null) {
throw new IllegalArgumentException("account must not be null");
}
if (syncHints == null) {
throw new IllegalArgumentException("syncHints must not be null");
}

final Bundle extras = new Bundle();
putHintsToSync(extras, syncHints);
Utils.putStageNamesToSync(extras, stagesToSync, stagesToSkip);

Logger.info(LOG_TAG, "Requesting sync.");
logSyncHints(syncHints);

for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
ContentResolver.requestSync(account, authority, extras);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Engaged;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.fxa.login.State.Action;
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;

import android.app.Activity;
Expand All @@ -41,6 +42,8 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
// Set in onResume.
protected AndroidFxAccount fxAccount;

protected final SyncStatusDelegate syncStatusDelegate = new SyncStatusDelegate();

public FxAccountConfirmAccountActivity() {
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
}
Expand Down Expand Up @@ -80,11 +83,62 @@ public void onResume() {
finish();
return;
}
State state = fxAccount.getState();
if (state.getStateLabel() != StateLabel.Engaged) {
Logger.warn(LOG_TAG, "Cannot confirm Firefox Account in state: " + state.getStateLabel());

FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate);

refresh();
}

@Override
public void onPause() {
super.onPause();
FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
}

protected class SyncStatusDelegate implements FxAccountSyncStatusHelper.Delegate {
protected final Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
refresh();
}
};

@Override
public AndroidFxAccount getAccount() {
return fxAccount;
}

@Override
public void handleSyncStarted() {
Logger.info(LOG_TAG, "Got sync started message; ignoring.");
}

@Override
public void handleSyncFinished() {
if (fxAccount == null) {
return;
}
Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
runOnUiThread(refreshRunnable);
}
}

protected void refresh() {
final State state = fxAccount.getState();
final Action neededAction = state.getNeededAction();
switch (neededAction) {
case NeedsVerification:
// This is what we're here to handle.
break;
case NeedsPassword:
case NeedsUpgrade:
case None:
default:
// We're not in the right place! Redirect to status.
Logger.warn(LOG_TAG, "No need to verifiy Firefox Account that needs action " + neededAction.toString() +
" (in state " + state.getStateLabel() + ").");
setResult(RESULT_CANCELED);
finish();
this.redirectToActivity(FxAccountStatusActivity.class);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.preferences.PreferenceFragment;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
import org.mozilla.gecko.sync.SyncConfiguration;

import android.content.ContentResolver;
Expand Down Expand Up @@ -71,6 +72,8 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
// single account. (That is, it does not capture a single account instance.)
protected Runnable requestSyncRunnable;

protected final SyncStatusDelegate syncStatusDelegate = new SyncStatusDelegate();

protected Preference ensureFindPreference(String key) {
Preference preference = findPreference(key);
if (preference == null) {
Expand Down Expand Up @@ -237,6 +240,38 @@ protected void showConnected() {
setCheckboxesEnabled(true);
}

protected class SyncStatusDelegate implements FxAccountSyncStatusHelper.Delegate {
protected final Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
refresh();
}
};

@Override
public AndroidFxAccount getAccount() {
return fxAccount;
}

@Override
public void handleSyncStarted() {
if (fxAccount == null) {
return;
}
Logger.info(LOG_TAG, "Got sync started message; refreshing.");
getActivity().runOnUiThread(refreshRunnable);
}

@Override
public void handleSyncFinished() {
if (fxAccount == null) {
return;
}
Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
getActivity().runOnUiThread(refreshRunnable);
}
}

/**
* Notify the fragment that a new AndroidFxAccount instance is current.
* <p>
Expand All @@ -259,9 +294,23 @@ public void refresh(AndroidFxAccount fxAccount) {
// serviced very quickly, so this is not an issue.
requestSyncRunnable = new RequestSyncRunnable();

// We would very much like register these status observers in bookended
// onResume/onPause calls, but because the Fragment gets onResume during the
// Activity's super.onResume, it hasn't yet been told its Firefox Account.
// So we register the observer here (and remove it in onPause), and open
// ourselves to the possibility that we don't have properly paired
// register/unregister calls.
FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate);

refresh();
}

@Override
public void onPause() {
super.onPause();
FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
}

protected void refresh() {
// refresh is called from our onResume, which can happen before the owning
// Activity tells us about an account (via our public
Expand Down Expand Up @@ -440,9 +489,7 @@ public void run() {
return;
}
Logger.info(LOG_TAG, "Requesting a sync sometime soon.");
// Request a sync, but not necessarily an immediate sync.
ContentResolver.requestSync(fxAccount.getAndroidAccount(), BrowserContract.AUTHORITY, Bundle.EMPTY);
// SyncAdapter.requestImmediateSync(fxAccount.getAndroidAccount(), null);
fxAccount.requestSync();
}
}

Expand All @@ -459,10 +506,8 @@ public boolean onPreferenceClick(Preference preference) {
} else if ("debug_dump".equals(key)) {
fxAccount.dump();
} else if ("debug_force_sync".equals(key)) {
Logger.info(LOG_TAG, "Syncing.");
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
fxAccount.requestSync(extras);
Logger.info(LOG_TAG, "Force syncing.");
fxAccount.requestSync(FirefoxAccounts.FORCE);
// No sense refreshing, since the sync will complete in the future.
} else if ("debug_forget_certificate".equals(key)) {
State state = fxAccount.getState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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.FxAccountConstants;
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
Expand Down Expand Up @@ -149,6 +150,7 @@ public void handleSuccess(LoginResponse result) {
return;
}
fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
fxAccount.requestSync(FirefoxAccounts.FORCE);

// For great debugging.
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
Expand Down
Loading