/* 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; import java.io.File; import java.util.EnumSet; import java.util.Locale; import java.util.concurrent.CountDownLatch; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.fxa.authenticator.AccountPickler; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; import org.mozilla.gecko.sync.ThreadPool; import org.mozilla.gecko.sync.Utils; import android.accounts.Account; import android.accounts.AccountManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; 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. *
* On many devices, not including SCHEDULE_NOW
means a delay of
* at least 30 seconds.
*/
SCHEDULE_NOW,
/**
* Hint that a requested sync may ignore local rate limiting.
*
* 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. *
* This is just a hint; the actual requested sync may not obey the hint.
*/
IGNORE_REMOTE_SERVER_BACKOFF,
}
public static final EnumSet
* If no accounts exist in the AccountManager, one may be created
* via a pickled FirefoxAccount, if available, and that account
* will be added to the AccountManager and returned.
*
* Note that this can be called from any thread.
*
* @param context Android context.
* @return Firefox account objects.
*/
public static Account[] getFirefoxAccounts(final Context context) {
final Account[] accounts =
AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
if (accounts.length > 0) {
return accounts;
}
final Account pickledAccount = getPickledAccount(context);
return (pickledAccount != null) ? new Account[] {pickledAccount} : new Account[0];
}
private static Account getPickledAccount(final Context context) {
// To avoid a StrictMode violation for disk access, we call this from a background thread.
// We do this every time, so the caller doesn't have to care.
final CountDownLatch latch = new CountDownLatch(1);
final Account[] accounts = new Account[1];
ThreadPool.run(new Runnable() {
@Override
public void run() {
try {
final File file = context.getFileStreamPath(FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
if (!file.exists()) {
accounts[0] = null;
return;
}
// There is a small race window here: if the user creates a new Firefox account
// between our checks, this could erroneously report that no Firefox accounts
// exist.
final AndroidFxAccount fxAccount =
AccountPickler.unpickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
accounts[0] = fxAccount.getAndroidAccount();
} finally {
latch.countDown();
}
}
});
try {
latch.await(); // Wait for the background thread to return.
} catch (InterruptedException e) {
Logger.warn(LOG_TAG,
"Foreground thread unexpectedly interrupted while getting pickled account", e);
return null;
}
return accounts[0];
}
/**
* @param context Android context.
* @return the configured Firefox account if one exists, or null otherwise.
*/
public static Account getFirefoxAccount(final Context context) {
Account[] accounts = getFirefoxAccounts(context);
if (accounts.length > 0) {
return accounts[0];
}
return null;
}
/**
* @return
* the {@link State} instance associated with the current account, or
* 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.
*
* It is safe to call this method from any thread.
*
* @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(final Account account, EnumSet
* Only a weak reference to null
if
* no accounts exist.
*/
public static State getFirefoxAccountState(final Context context) {
final Account account = getFirefoxAccount(context);
if (account == null) {
return null;
}
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
try {
return fxAccount.getState();
} catch (final Exception ex) {
Logger.warn(LOG_TAG, "Could not get FX account state.", ex);
return null;
}
}
/*
* @param context Android context
* @return the email address associated with the configured Firefox account if one exists; null otherwise.
*/
public static String getFirefoxAccountEmail(final Context context) {
final Account account = getFirefoxAccount(context);
if (account == null) {
return null;
}
return account.name;
}
protected static void putHintsToSync(final Bundle extras, EnumSetsyncStatusListener
of sync status changes.
* syncStatusListener
is held.
*
* @param syncStatusListener to start notifying.
*/
public static void addSyncStatusListener(SyncStatusListener syncStatusListener) {
// startObserving null-checks its argument.
FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusListener);
}
/**
* Stop notifying syncStatusListener
of sync status changes.
*
* @param syncStatusListener to stop notifying.
*/
public static void removeSyncStatusListener(SyncStatusListener syncStatusListener) {
// stopObserving null-checks its argument.
FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener);
}
public static String getOldSyncUpgradeURL(final Resources res, final Locale locale) {
final String VERSION = AppConstants.MOZ_APP_VERSION;
final String OS = AppConstants.OS_TARGET;
final String LOCALE = Utils.getLanguageTag(locale);
return res.getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE);
}
/**
* Resends the account verification email, and displays an appropriate
* toast on both send success and failure. Note that because the underlying implementation
* uses {@link AsyncTask}, the provided context must be UI-capable, and this
* method called from the UI thread (see
* {@link org.mozilla.gecko.fxa.tasks.FxAccountCodeResender#resendCode(Context, AndroidFxAccount)}
* for more).
*
* @param context a UI-capable Android context.
* @return true if an account exists, false otherwise.
*/
public static boolean resendVerificationEmail(final Context context) {
final Account account = getFirefoxAccount(context);
if (account == null) {
return false;
}
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
FxAccountCodeResender.resendCode(context, fxAccount);
return true;
}
}