summaryrefslogtreecommitdiffstats
path: root/sync
diff options
context:
space:
mode:
authordsmyers@chromium.org <dsmyers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-24 22:17:40 +0000
committerdsmyers@chromium.org <dsmyers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-24 22:17:40 +0000
commit44d5119a63db796e1fd99c7af9119237076b4e78 (patch)
treea894bd1ce4751ca0288ba2a9bd449bdf1a19c033 /sync
parent5b29403640e6f7b45d27db1825af78e0fd4bf29e (diff)
downloadchromium_src-44d5119a63db796e1fd99c7af9119237076b4e78.zip
chromium_src-44d5119a63db796e1fd99c7af9119237076b4e78.tar.gz
chromium_src-44d5119a63db796e1fd99c7af9119237076b4e78.tar.bz2
Upstreams SyncStatusHelper and dependencies.
Upstreams SyncStatusHelper, AccountManagerHelper, SharedPreferencesUtil, and files on which they depened. These changes are required to ultimately upstream the sync notification client. BUG=159221 Review URL: https://chromiumcodereview.appspot.com/11929040 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@178670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/SyncContentResolverDelegate.java33
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java337
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/SystemSyncContentResolverDelegate.java53
-rw-r--r--sync/android/java/src/org/chromium/sync/signin/AccountManagerDelegate.java52
-rw-r--r--sync/android/java/src/org/chromium/sync/signin/AccountManagerHelper.java225
-rw-r--r--sync/android/java/src/org/chromium/sync/signin/SystemAccountManagerDelegate.java105
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/signin/SyncStatusHelperTest.java44
7 files changed, 849 insertions, 0 deletions
diff --git a/sync/android/java/src/org/chromium/sync/notifier/SyncContentResolverDelegate.java b/sync/android/java/src/org/chromium/sync/notifier/SyncContentResolverDelegate.java
new file mode 100644
index 0000000..d0e3edc
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/notifier/SyncContentResolverDelegate.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.notifier;
+
+
+import android.accounts.Account;
+import android.content.SyncStatusObserver;
+
+/**
+ * Since the ContentResolver in Android has a lot of static methods, it is hard to
+ * mock out for tests. This interface wraps all the sync-related methods we use from
+ * the Android ContentResolver.
+ */
+public interface SyncContentResolverDelegate {
+
+ Object addStatusChangeListener(int mask, SyncStatusObserver callback);
+
+ void removeStatusChangeListener(Object handle);
+
+ void setMasterSyncAutomatically(boolean sync);
+
+ boolean getMasterSyncAutomatically();
+
+ void setSyncAutomatically(Account account, String authority, boolean sync);
+
+ boolean getSyncAutomatically(Account account, String authority);
+
+ void setIsSyncable(Account account, String authority, int syncable);
+
+ int getIsSyncable(Account account, String authority);
+}
diff --git a/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java b/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java
new file mode 100644
index 0000000..2239d5c
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java
@@ -0,0 +1,337 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.notifier;
+
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SyncStatusObserver;
+import android.os.StrictMode;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import org.chromium.sync.signin.AccountManagerHelper;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * A helper class to handle the current status of sync for Chrome in Android-land.
+ *
+ * It also has a helper class to be used by observers whenever sync settings change.
+ *
+ * To retrieve an instance of this class, call SyncStatusHelper.get(someContext).
+ */
+public class SyncStatusHelper {
+
+ public interface Listener {
+ /**
+ * Called when the user signs out of Chrome.
+ */
+ void onClearSignedInUser();
+ }
+
+ @VisibleForTesting
+ public static final String SIGNED_IN_ACCOUNT_KEY = "google.services.username";
+
+ public static final String TAG = "SyncStatusHelper";
+
+ private final Context mApplicationContext;
+
+ private final SyncContentResolverDelegate mSyncContentResolverWrapper;
+
+ private static final Object lock = new Object();
+
+ private static SyncStatusHelper sSyncStatusHelper;
+
+ private ArrayList<Listener> mListeners;
+
+ /**
+ * @param context the context
+ * @param syncContentResolverWrapper an implementation of SyncContentResolverWrapper
+ */
+ private SyncStatusHelper(Context context,
+ SyncContentResolverDelegate syncContentResolverWrapper) {
+ mApplicationContext = context.getApplicationContext();
+ mSyncContentResolverWrapper = syncContentResolverWrapper;
+ mListeners = new ArrayList<Listener>();
+ }
+
+ /**
+ * A factory method for the SyncStatusHelper.
+ *
+ * It is possible to override the SyncContentResolverWrapper to use in tests for the
+ * instance of the SyncStatusHelper by calling overrideSyncStatusHelperForTests(...) with
+ * your SyncContentResolverWrapper.
+ *
+ * @param context the ApplicationContext is retreived from the context used as an argument.
+ * @return a singleton instance of the SyncStatusHelper
+ */
+ public static SyncStatusHelper get(Context context) {
+ synchronized (lock) {
+ if (sSyncStatusHelper == null) {
+ Context applicationContext = context.getApplicationContext();
+ sSyncStatusHelper = new SyncStatusHelper(applicationContext,
+ new SystemSyncContentResolverDelegate());
+ }
+ }
+ return sSyncStatusHelper;
+ }
+
+ /**
+ * Tests might want to consider overriding the context and SyncContentResolverWrapper so they
+ * do not use the real ContentResolver in Android.
+ *
+ * @param context the context to use
+ * @param syncContentResolverWrapper the SyncContentResolverWrapper to use
+ */
+ @VisibleForTesting
+ public static void overrideSyncStatusHelperForTests(Context context,
+ SyncContentResolverDelegate syncContentResolverWrapper) {
+ synchronized (lock) {
+ if (sSyncStatusHelper != null) {
+ throw new IllegalStateException("SyncStatusHelper already exists");
+ }
+ sSyncStatusHelper = new SyncStatusHelper(context, syncContentResolverWrapper);
+ }
+ }
+
+ /**
+ * Wrapper method for the ContentResolver.addStatusChangeListener(...) when we are only
+ * interested in the settings type.
+ */
+ public Object registerContentResolverObserver(SyncStatusObserver observer) {
+ return mSyncContentResolverWrapper.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, observer);
+ }
+
+ /**
+ * Wrapper method for the ContentResolver.removeStatusChangeListener(...).
+ */
+ public void unregisterContentResolverObserver(Object observerHandle) {
+ mSyncContentResolverWrapper.removeStatusChangeListener(observerHandle);
+ }
+
+ /**
+ * Checks whether sync is currently enabled from Chrome for a given account.
+ *
+ * It checks both the master sync for the device, and Chrome sync setting for the given account.
+ *
+ * @param account the account to check if Chrome sync is enabled on.
+ * @return true if sync is on, false otherwise
+ */
+ public boolean isSyncEnabled(Account account) {
+ StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
+ String contractAuthority =
+ InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ boolean enabled = account != null &&
+ mSyncContentResolverWrapper.getMasterSyncAutomatically() &&
+ mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority);
+ StrictMode.setThreadPolicy(oldPolicy);
+ return enabled;
+ }
+
+ /**
+ * Checks whether sync is currently enabled from Chrome for the currently signed in account.
+ *
+ * It checks both the master sync for the device, and Chrome sync setting for the given account.
+ * If no user is currently signed in it returns false.
+ *
+ * @return true if sync is on, false otherwise
+ */
+ public boolean isSyncEnabled() {
+ return isSyncEnabled(getSignedInUser());
+ }
+
+ /**
+ * Checks whether sync is currently enabled from Chrome for a given account.
+ *
+ * It checks only Chrome sync setting for the given account,
+ * and ignores the master sync setting.
+ *
+ * @param account the account to check if Chrome sync is enabled on.
+ * @return true if sync is on, false otherwise
+ */
+ public boolean isSyncEnabledForChrome(Account account) {
+ StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
+ String contractAuthority =
+ InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ boolean enabled = account != null &&
+ mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority);
+ StrictMode.setThreadPolicy(oldPolicy);
+ return enabled;
+ }
+
+ /**
+ * Checks whether the master sync flag for Android is currently set.
+ *
+ * @return true if the global master sync is on, false otherwise
+ */
+ public boolean isMasterSyncAutomaticallyEnabled() {
+ StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
+ boolean enabled = mSyncContentResolverWrapper.getMasterSyncAutomatically();
+ StrictMode.setThreadPolicy(oldPolicy);
+ return enabled;
+ }
+
+ /**
+ * Make sure Chrome is syncable, and enable sync.
+ *
+ * @param account the account to enable sync on
+ */
+ public void enableAndroidSync(Account account) {
+ StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
+ makeSyncable(account);
+ String contractAuthority =
+ InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ if (!mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority)) {
+ mSyncContentResolverWrapper.setSyncAutomatically(account, contractAuthority, true);
+ }
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
+ /**
+ * Disables Android Chrome sync
+ *
+ * @param account the account to disable Chrome sync on
+ */
+ public void disableAndroidSync(Account account) {
+ StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
+ String contractAuthority =
+ InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ if (mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority)) {
+ mSyncContentResolverWrapper.setSyncAutomatically(account, contractAuthority, false);
+ }
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
+ public Account getSignedInUser() {
+ String syncAccountName = getSignedInAccountName();
+ if (syncAccountName == null) {
+ return null;
+ }
+ return AccountManagerHelper.createAccountFromName(syncAccountName);
+ }
+
+ public boolean isSignedIn() {
+ return getSignedInAccountName() != null;
+ }
+
+ public void setSignedInAccountName(String accountName) {
+ getPreferences().edit()
+ .putString(SIGNED_IN_ACCOUNT_KEY, accountName)
+ .apply();
+ }
+
+ public void clearSignedInUser() {
+ Log.d(TAG, "Clearing user signed in to Chrome");
+ setSignedInAccountName(null);
+
+ for (Listener listener : mListeners) {
+ listener.onClearSignedInUser();
+ }
+ }
+
+ private String getSignedInAccountName() {
+ return getPreferences().getString(SIGNED_IN_ACCOUNT_KEY, null);
+ }
+
+ /**
+ * Register with Android Sync Manager. This is what causes the "Chrome" option to appear in
+ * Settings -> Accounts / Sync .
+ *
+ * @param account the account to enable Chrome sync on
+ */
+ private void makeSyncable(Account account) {
+ String contractAuthority =
+ InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ if (hasFinishedFirstSync(account)) {
+ mSyncContentResolverWrapper.setIsSyncable(account, contractAuthority, 1);
+ }
+ // Disable the syncability of Chrome for all other accounts
+ Account[] googleAccounts = AccountManagerHelper.get(mApplicationContext).
+ getGoogleAccounts();
+ for (Account accountToSetNotSyncable : googleAccounts) {
+ if (!accountToSetNotSyncable.equals(account) &&
+ mSyncContentResolverWrapper.getIsSyncable(
+ accountToSetNotSyncable, contractAuthority) > 0) {
+ mSyncContentResolverWrapper.setIsSyncable(accountToSetNotSyncable,
+ contractAuthority, 0);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the given account has ever been synced.
+ */
+ boolean hasFinishedFirstSync(Account account) {
+ String contractAuthority =
+ InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ return mSyncContentResolverWrapper.getIsSyncable(account, contractAuthority) <= 0;
+ }
+
+ /**
+ * Helper class to be used by observers whenever sync settings change.
+ *
+ * To register the observer, call SyncStatusHelper.registerObserver(...).
+ */
+ public static abstract class SyncSettingsChangedObserver implements SyncStatusObserver {
+
+ @Override
+ public void onStatusChanged(int which) {
+ if (ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS == which) {
+ syncSettingsChanged();
+ }
+ }
+
+ protected abstract void syncSettingsChanged();
+ }
+
+ /**
+ * Returns the default shared preferences.
+ */
+ private SharedPreferences getPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(mApplicationContext);
+ }
+
+ /**
+ * Sets a new StrictMode.ThreadPolicy based on the current one, but allows disk reads
+ * and disk writes.
+ *
+ * The return value is the old policy, which must be applied after the disk access is finished,
+ * by using StrictMode.setThreadPolicy(oldPolicy).
+ *
+ * @return the policy before allowing reads and writes.
+ */
+ private static StrictMode.ThreadPolicy temporarilyAllowDiskWritesAndDiskReads() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ StrictMode.ThreadPolicy.Builder newPolicy =
+ new StrictMode.ThreadPolicy.Builder(oldPolicy);
+ newPolicy.permitDiskReads();
+ newPolicy.permitDiskWrites();
+ StrictMode.setThreadPolicy(newPolicy.build());
+ return oldPolicy;
+ }
+
+ /**
+ * Adds a Listener.
+ * @param listener Listener to add.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a Listener.
+ * @param listener Listener to remove from the list.
+ * @returns whether or not the Listener was removed.
+ */
+ public boolean removeListener(Listener listener) {
+ return mListeners.remove(listener);
+ }
+}
diff --git a/sync/android/java/src/org/chromium/sync/notifier/SystemSyncContentResolverDelegate.java b/sync/android/java/src/org/chromium/sync/notifier/SystemSyncContentResolverDelegate.java
new file mode 100644
index 0000000..ce00c8c
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/notifier/SystemSyncContentResolverDelegate.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.notifier;
+
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.SyncStatusObserver;
+
+public class SystemSyncContentResolverDelegate implements SyncContentResolverDelegate {
+
+ @Override
+ public Object addStatusChangeListener(int mask, SyncStatusObserver callback) {
+ return ContentResolver.addStatusChangeListener(mask, callback);
+ }
+
+ @Override
+ public void removeStatusChangeListener(Object handle) {
+ ContentResolver.removeStatusChangeListener(handle);
+ }
+
+ @Override
+ public void setMasterSyncAutomatically(boolean sync) {
+ ContentResolver.setMasterSyncAutomatically(sync);
+ }
+
+ @Override
+ public boolean getMasterSyncAutomatically() {
+ return ContentResolver.getMasterSyncAutomatically();
+ }
+
+ @Override
+ public boolean getSyncAutomatically(Account account, String authority) {
+ return ContentResolver.getSyncAutomatically(account, authority);
+ }
+
+ @Override
+ public void setSyncAutomatically(Account account, String authority, boolean sync) {
+ ContentResolver.setSyncAutomatically(account, authority, sync);
+ }
+
+ @Override
+ public void setIsSyncable(Account account, String authority, int syncable) {
+ ContentResolver.setIsSyncable(account, authority, syncable);
+ }
+
+ @Override
+ public int getIsSyncable(Account account, String authority) {
+ return ContentResolver.getIsSyncable(account, authority);
+ }
+}
diff --git a/sync/android/java/src/org/chromium/sync/signin/AccountManagerDelegate.java b/sync/android/java/src/org/chromium/sync/signin/AccountManagerDelegate.java
new file mode 100644
index 0000000..88ec210
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/signin/AccountManagerDelegate.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.signin;
+
+import android.accounts.Account;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+
+import java.io.IOException;
+
+/**
+ * Wrapper around the Android account manager, to facilitate dependency injection during testing.
+ */
+public interface AccountManagerDelegate {
+ Account[] getAccountsByType(String type);
+
+ AccountManagerFuture<Bundle> getAuthToken(Account account, String authTokenType,
+ boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler);
+
+ AccountManagerFuture<Bundle> getAuthToken(Account account, String authTokenType, Bundle options,
+ Activity activity, AccountManagerCallback<Bundle> callback, Handler handler);
+
+ void invalidateAuthToken(String accountType, String authToken);
+
+ String blockingGetAuthToken(Account account, String authTokenType, boolean notifyAuthFailure)
+ throws OperationCanceledException, IOException, AuthenticatorException;
+
+ Account[] getAccounts();
+
+ boolean addAccountExplicitly(Account account, String password, Bundle userdata);
+
+ AccountManagerFuture<Boolean> removeAccount(Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler);
+
+ String getPassword(Account account);
+
+ void setPassword(Account account, String password);
+
+ void clearPassword(Account account);
+
+ AccountManagerFuture<Bundle> confirmCredentials(Account account, Bundle bundle,
+ Activity activity, AccountManagerCallback<Bundle> callback, Handler handler);
+
+ String peekAuthToken(Account account, String authTokenType);
+}
diff --git a/sync/android/java/src/org/chromium/sync/signin/AccountManagerHelper.java b/sync/android/java/src/org/chromium/sync/signin/AccountManagerHelper.java
new file mode 100644
index 0000000..889b194
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/signin/AccountManagerHelper.java
@@ -0,0 +1,225 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.signin;
+
+
+import com.google.common.annotations.VisibleForTesting;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * AccountManagerHelper wraps our access of AccountManager in Android.
+ *
+ * Use the AccountManagerHelper.get(someContext) to instantiate it
+ */
+public class AccountManagerHelper {
+
+ private static final String TAG = "AccountManagerHelper";
+
+ private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
+
+ private static final Object lock = new Object();
+
+ private static AccountManagerHelper sAccountManagerHelper;
+
+ private final AccountManagerDelegate mAccountManager;
+
+ private Context mApplicationContext;
+
+ public interface GetAuthTokenCallback {
+ /**
+ * Invoked on the UI thread once a token has been provided by the AccountManager.
+ * @param token Auth token, or null if no token is available (bad credentials,
+ * permission denied, etc).
+ */
+ void tokenAvailable(String token);
+ }
+
+ /**
+ * @param context the Android context
+ * @param accountManager the account manager to use as a backend service
+ */
+ private AccountManagerHelper(Context context,
+ AccountManagerDelegate accountManager) {
+ mApplicationContext = context.getApplicationContext();
+ mAccountManager = accountManager;
+ }
+
+ /**
+ * A factory method for the AccountManagerHelper.
+ *
+ * It is possible to override the AccountManager to use in tests for the instance of the
+ * AccountManagerHelper by calling overrideAccountManagerHelperForTests(...) with
+ * your MockAccountManager.
+ *
+ * @param context the applicationContext is retrieved from the context used as an argument.
+ * @return a singleton instance of the AccountManagerHelper
+ */
+ public static AccountManagerHelper get(Context context) {
+ synchronized (lock) {
+ if (sAccountManagerHelper == null) {
+ sAccountManagerHelper = new AccountManagerHelper(context,
+ new SystemAccountManagerDelegate(context));
+ }
+ }
+ return sAccountManagerHelper;
+ }
+
+ @VisibleForTesting
+ public static void overrideAccountManagerHelperForTests(Context context,
+ AccountManagerDelegate accountManager) {
+ synchronized (lock) {
+ sAccountManagerHelper = new AccountManagerHelper(context, accountManager);
+ }
+ }
+
+ /**
+ * Creates an Account object for the given name.
+ */
+ public static Account createAccountFromName(String name) {
+ return new Account(name, GOOGLE_ACCOUNT_TYPE);
+ }
+
+ public List<String> getGoogleAccountNames() {
+ List<String> accountNames = new ArrayList<String>();
+ Account[] accounts = mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
+ for (Account account : accounts) {
+ accountNames.add(account.name);
+ }
+ return accountNames;
+ }
+
+ public Account[] getGoogleAccounts() {
+ return mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
+ }
+
+ public boolean hasGoogleAccounts() {
+ return getGoogleAccounts().length > 0;
+ }
+
+ /**
+ * Returns the account if it exists, null otherwise.
+ */
+ public Account getAccountFromName(String accountName) {
+ Account[] accounts = mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
+ for (Account account : accounts) {
+ if (account.name.equals(accountName)) {
+ return account;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the auth token synchronously.
+ *
+ * - Assumes that the account is a valid account.
+ * - Should not be called on the main thread.
+ */
+ public String getAuthTokenFromBackground(Account account, String authTokenType) {
+ try {
+ AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account,
+ authTokenType, false, null, null);
+ Bundle result = future.getResult();
+ if (result == null) {
+ Log.w(TAG, "Auth token - getAuthToken returned null");
+ return null;
+ }
+ if (result.containsKey(AccountManager.KEY_INTENT)) {
+ Log.d(TAG, "Starting intent to get auth credentials");
+ // Need to start intent to get credentials
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ int flags = intent.getFlags();
+ flags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ intent.setFlags(flags);
+ mApplicationContext.startActivity(intent);
+ return null;
+ }
+ return result.getString(AccountManager.KEY_AUTHTOKEN);
+ } catch (OperationCanceledException e) {
+ Log.w(TAG, "Auth token - operation cancelled", e);
+ } catch (AuthenticatorException e) {
+ Log.w(TAG, "Auth token - authenticator exception", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Auth token - IO exception", e);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the auth token and returns the response asynchronously on the UI thread.
+ * This should be called when we have a foreground activity that needs an auth token.
+ *
+ * - Assumes that the account is a valid account.
+ */
+ public void getAuthTokenFromForeground(Activity activity, Account account,
+ String authTokenType, final GetAuthTokenCallback callback) {
+ final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account,
+ authTokenType, null, activity, null, null);
+ new AsyncTask<Void, Void, String>() {
+ @Override
+ public String doInBackground(Void... params) {
+ try {
+ Bundle result = future.getResult();
+ if (result != null) {
+ return result.getString(AccountManager.KEY_AUTHTOKEN);
+ } else {
+ Log.w(TAG, "Auth token - getAuthToken returned null");
+ }
+ } catch (OperationCanceledException e) {
+ Log.w(TAG, "Auth token - operation cancelled", e);
+ } catch (AuthenticatorException e) {
+ Log.w(TAG, "Auth token - authenticator exception", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Auth token - IO exception", e);
+ }
+ return null;
+ }
+ @Override
+ public void onPostExecute(String authToken) {
+ callback.tokenAvailable(authToken);
+ }
+ }.execute();
+ }
+
+ /**
+ * Invalidates the old token (if non-null/non-empty) and synchronously generates a new one.
+ * Also notifies the user (via status bar) if any user action is required. The method will
+ * return null if any user action is required to generate the new token.
+ *
+ * - Assumes that the account is a valid account.
+ * - Should not be called on the main thread.
+ */
+ public String getNewAuthToken(Account account, String authToken, String authTokenType) {
+ if (authToken != null && !authToken.isEmpty()) {
+ mAccountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken);
+ }
+
+ try {
+ return mAccountManager.blockingGetAuthToken(account, authTokenType, true);
+ } catch (OperationCanceledException e) {
+ Log.w(TAG, "Auth token - operation cancelled", e);
+ } catch (AuthenticatorException e) {
+ Log.w(TAG, "Auth token - authenticator exception", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Auth token - IO exception", e);
+ }
+ return null;
+ }
+}
diff --git a/sync/android/java/src/org/chromium/sync/signin/SystemAccountManagerDelegate.java b/sync/android/java/src/org/chromium/sync/signin/SystemAccountManagerDelegate.java
new file mode 100644
index 0000000..13e3962
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/signin/SystemAccountManagerDelegate.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.signin;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import java.io.IOException;
+
+/**
+ * Default implementation of {@link AccountManagerDelegate} which delegates all calls to the
+ * Android account manager.
+ */
+public class SystemAccountManagerDelegate implements AccountManagerDelegate {
+
+ private final AccountManager mAccountManager;
+
+ public SystemAccountManagerDelegate(Context context) {
+ mAccountManager = AccountManager.get(context);
+ }
+
+ @Override
+ public Account[] getAccountsByType(String type) {
+ return mAccountManager.getAccountsByType(type);
+ }
+
+ @Override
+ public AccountManagerFuture<Bundle> getAuthToken(Account account, String authTokenType,
+ boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler) {
+ return mAccountManager.getAuthToken(account, authTokenType, notifyAuthFailure, callback,
+ handler);
+ }
+
+ @Override
+ public AccountManagerFuture<Bundle> getAuthToken(Account account, String authTokenType,
+ Bundle options, Activity activity, AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ return mAccountManager.getAuthToken(account, authTokenType, options, activity, callback,
+ handler);
+ }
+
+ @Override
+ public void invalidateAuthToken(String accountType, String authToken) {
+ mAccountManager.invalidateAuthToken(accountType, authToken);
+ }
+
+ @Override
+ public String blockingGetAuthToken(Account account, String authTokenType,
+ boolean notifyAuthFailure)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return mAccountManager.blockingGetAuthToken(account, authTokenType, notifyAuthFailure);
+ }
+
+ @Override
+ public Account[] getAccounts() {
+ return mAccountManager.getAccounts();
+ }
+
+ @Override
+ public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
+ return mAccountManager.addAccountExplicitly(account, password, userdata);
+ }
+
+ @Override
+ public AccountManagerFuture<Boolean> removeAccount(Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return mAccountManager.removeAccount(account, callback, handler);
+ }
+
+ @Override
+ public String getPassword(Account account) {
+ return mAccountManager.getPassword(account);
+ }
+
+ @Override
+ public void setPassword(Account account, String password) {
+ mAccountManager.setPassword(account, password);
+ }
+
+ @Override
+ public void clearPassword(Account account) {
+ mAccountManager.clearPassword(account);
+ }
+
+ @Override
+ public AccountManagerFuture<Bundle> confirmCredentials(Account account, Bundle bundle,
+ Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ return mAccountManager.confirmCredentials(account, bundle, activity, callback, handler);
+ }
+
+ @Override
+ public String peekAuthToken(Account account, String authTokenType) {
+ return mAccountManager.peekAuthToken(account, authTokenType);
+ }
+}
diff --git a/sync/android/javatests/src/org/chromium/sync/notifier/signin/SyncStatusHelperTest.java b/sync/android/javatests/src/org/chromium/sync/notifier/signin/SyncStatusHelperTest.java
new file mode 100644
index 0000000..69f3528
--- /dev/null
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/signin/SyncStatusHelperTest.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sync.notifier.signin;
+
+import android.content.ContentResolver;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.sync.notifier.SyncStatusHelper;
+
+public class SyncStatusHelperTest extends InstrumentationTestCase {
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testStatusChangeForSettingsShouldCauseCall() {
+ TestSyncSettingsChangedObserver observer = new TestSyncSettingsChangedObserver();
+ observer.onStatusChanged(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ assertTrue("Should have called the observer", observer.gotCalled);
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testStatusChangeForAnythingElseShouldNoCauseCall() {
+ TestSyncSettingsChangedObserver observer = new TestSyncSettingsChangedObserver();
+ observer.onStatusChanged(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+ observer.onStatusChanged(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
+ assertFalse("Should not have called the observer", observer.gotCalled);
+ }
+
+ private static class TestSyncSettingsChangedObserver
+ extends SyncStatusHelper.SyncSettingsChangedObserver {
+
+ public boolean gotCalled;
+
+ @Override
+ protected void syncSettingsChanged() {
+ gotCalled = true;
+ }
+ }
+
+}