OSDN Git Service

original
[gb-231r1-is01/GB_2.3_IS01.git] / cts / tests / tests / accounts / src / android / accounts / cts / AccountManagerTest.java
diff --git a/cts/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java b/cts/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
new file mode 100644 (file)
index 0000000..e72e4db
--- /dev/null
@@ -0,0 +1,1329 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accounts.cts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.AuthenticatorException;
+import android.accounts.OnAccountsUpdateListener;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * You can run those unit tests with the following command line:
+ *
+ *  adb shell am instrument
+ *   -e debug false -w
+ *   -e class android.accounts.cts.AccountManagerTest
+ * android.accounts.cts/android.test.InstrumentationTestRunner
+ */
+public class AccountManagerTest extends AndroidTestCase {
+
+    public static final String ACCOUNT_NAME = "android.accounts.cts.account.name";
+    public static final String ACCOUNT_NAME_OTHER = "android.accounts.cts.account.name.other";
+
+    public static final String ACCOUNT_TYPE = "android.accounts.cts.account.type";
+    public static final String ACCOUNT_TYPE_OTHER = "android.accounts.cts.account.type.other";
+
+    public static final String ACCOUNT_PASSWORD = "android.accounts.cts.account.password";
+
+    public static final String AUTH_TOKEN = "mockAuthToken";
+    public static final String AUTH_TOKEN_TYPE = "mockAuthTokenType";
+    public static final String AUTH_TOKEN_LABEL = "mockAuthTokenLabel";
+
+    public static final String FEATURE_1 = "feature.1";
+    public static final String FEATURE_2 = "feature.2";
+    public static final String NON_EXISTING_FEATURE = "feature.3";
+
+    public static final String OPTION_NAME_1 = "option.name.1";
+    public static final String OPTION_VALUE_1 = "option.value.1";
+
+    public static final String OPTION_NAME_2 = "option.name.2";
+    public static final String OPTION_VALUE_2 = "option.value.2";
+
+    public static final String[] REQUIRED_FEATURES = new String[] { FEATURE_1, FEATURE_2 };
+
+    public static final Activity ACTIVITY = new Activity();
+    public static final Bundle OPTIONS_BUNDLE = new Bundle();
+
+    public static final Bundle USERDATA_BUNDLE = new Bundle();
+
+    public static final String USERDATA_NAME_1 = "user.data.name.1";
+    public static final String USERDATA_NAME_2 = "user.data.name.2";
+    public static final String USERDATA_VALUE_1 = "user.data.value.1";
+    public static final String USERDATA_VALUE_2 = "user.data.value.2";
+
+    public static final Account ACCOUNT = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    public static final Account ACCOUNT_SAME_TYPE = new Account(ACCOUNT_NAME_OTHER, ACCOUNT_TYPE);
+
+    private static MockAccountAuthenticator mockAuthenticator;
+    private static final int LATCH_TIMEOUT_MS = 500;
+    private static AccountManager am;
+
+    public synchronized static MockAccountAuthenticator getMockAuthenticator(Context context) {
+        if (null == mockAuthenticator) {
+            mockAuthenticator = new MockAccountAuthenticator(context);
+        }
+        return mockAuthenticator;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        OPTIONS_BUNDLE.putString(OPTION_NAME_1, OPTION_VALUE_1);
+        OPTIONS_BUNDLE.putString(OPTION_NAME_2, OPTION_VALUE_2);
+
+        USERDATA_BUNDLE.putString(USERDATA_NAME_1, USERDATA_VALUE_1);
+
+        getMockAuthenticator(getContext());
+
+        am = AccountManager.get(getContext());
+    }
+
+    @Override
+    public void tearDown() throws Exception, AuthenticatorException, OperationCanceledException {
+        mockAuthenticator.clearData();
+
+        // Need to clean up created account
+        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+        assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, null /* callback */));
+
+        // need to clean up the authenticator cached data
+        mockAuthenticator.clearData();
+
+        super.tearDown();
+    }
+
+    private void validateAccountAndAuthTokenResult(Bundle result) {
+        assertEquals(ACCOUNT_NAME, result.get(AccountManager.KEY_ACCOUNT_NAME));
+        assertEquals(ACCOUNT_TYPE, result.get(AccountManager.KEY_ACCOUNT_TYPE));
+        assertEquals(AUTH_TOKEN, result.get(AccountManager.KEY_AUTHTOKEN));
+    }
+
+    private void validateAccountAndNoAuthTokenResult(Bundle result) {
+        assertEquals(ACCOUNT_NAME, result.get(AccountManager.KEY_ACCOUNT_NAME));
+        assertEquals(ACCOUNT_TYPE, result.get(AccountManager.KEY_ACCOUNT_TYPE));
+        assertNull(result.get(AccountManager.KEY_AUTHTOKEN));
+    }
+
+    private void validateNullResult(Bundle resultBundle) {
+        assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
+        assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
+        assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
+    }
+
+    private void validateAccountAndAuthTokenType() {
+        assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
+        assertEquals(AUTH_TOKEN_TYPE, mockAuthenticator.getAuthTokenType());
+    }
+
+    private void validateFeatures() {
+        assertEquals(REQUIRED_FEATURES[0], mockAuthenticator.getRequiredFeatures()[0]);
+        assertEquals(REQUIRED_FEATURES[1], mockAuthenticator.getRequiredFeatures()[1]);
+    }
+
+    private void validateOptions(Bundle expectedOptions, Bundle actualOptions) {
+        if (expectedOptions == null) {
+            assertNull(actualOptions);
+        } else {
+            assertNotNull(actualOptions);
+            assertEquals(expectedOptions.get(OPTION_NAME_1), actualOptions.get(OPTION_NAME_1));
+            assertEquals(expectedOptions.get(OPTION_NAME_2), actualOptions.get(OPTION_NAME_2));
+        }
+    }
+
+    private void validateCredentials() {
+        assertEquals(ACCOUNT, mockAuthenticator.getAccount());
+    }
+
+    private int getAccountsCount() {
+        Account[] accounts = am.getAccounts();
+        assertNotNull(accounts);
+        return accounts.length;
+    }
+
+    private Bundle addAccount(AccountManager am, String accountType, String authTokenType,
+            String[] requiredFeatures, Bundle options, Activity activity,
+            AccountManagerCallback<Bundle> callback, Handler handler) throws
+                IOException, AuthenticatorException, OperationCanceledException {
+
+        AccountManagerFuture<Bundle> futureBundle = am.addAccount(
+                accountType,
+                authTokenType,
+                requiredFeatures,
+                options,
+                activity,
+                callback,
+                handler);
+
+        Bundle resultBundle = futureBundle.getResult();
+        assertTrue(futureBundle.isDone());
+        assertNotNull(resultBundle);
+
+        return resultBundle;
+    }
+
+    private boolean removeAccount(AccountManager am, Account account,
+            AccountManagerCallback<Boolean> callback) throws IOException, AuthenticatorException,
+                OperationCanceledException {
+
+        AccountManagerFuture<Boolean> futureBoolean = am.removeAccount(account,
+                callback,
+                null /* handler */);
+        Boolean resultBoolean = futureBoolean.getResult();
+        assertTrue(futureBoolean.isDone());
+
+        return resultBoolean;
+    }
+
+    private void addAccountExplicitly(Account account, String password, Bundle userdata) {
+        assertTrue(am.addAccountExplicitly(account, password, userdata));
+    }
+
+    private Bundle getAuthTokenByFeature(String[] features, Activity activity)
+            throws IOException, AuthenticatorException, OperationCanceledException {
+
+        AccountManagerFuture<Bundle> futureBundle = am.getAuthTokenByFeatures(ACCOUNT_TYPE,
+                AUTH_TOKEN_TYPE,
+                features,
+                activity,
+                OPTIONS_BUNDLE,
+                OPTIONS_BUNDLE,
+                null /* no callback */,
+                null /* no handler */
+        );
+
+        Bundle resultBundle = futureBundle.getResult();
+
+        assertTrue(futureBundle.isDone());
+        assertNotNull(resultBundle);
+
+        return resultBundle;
+    }
+
+    private boolean isAccountPresent(Account[] accounts, Account accountToCheck) {
+        if (null == accounts || null == accountToCheck) {
+            return false;
+        }
+        boolean result = false;
+        int length = accounts.length;
+        for (int n=0; n<length; n++) {
+            if(accountToCheck.equals(accounts[n])) {
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Test singleton
+     */
+    public void testGet() {
+        assertNotNull(AccountManager.get(getContext()));
+    }
+
+    /**
+     * Test a basic addAccount()
+     */
+    public void testAddAccount() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        Bundle resultBundle = addAccount(am,
+                ACCOUNT_TYPE,
+                AUTH_TOKEN_TYPE,
+                REQUIRED_FEATURES,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                null /* callback */,
+                null /* handler */);
+
+        // Assert parameters has been passed correctly
+        validateAccountAndAuthTokenType();
+        validateFeatures();
+        validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
+        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
+
+        // Assert returned result
+        validateAccountAndNoAuthTokenResult(resultBundle);
+    }
+
+    /**
+     * Test addAccount() with callback and handler
+     */
+    public void testAddAccountWithCallbackAndHandler() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        testAddAccountWithCallbackAndHandler(null /* handler */);
+        testAddAccountWithCallbackAndHandler(new Handler());
+    }
+
+    private void testAddAccountWithCallbackAndHandler(Handler handler) throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+            public void run(AccountManagerFuture<Bundle> bundleFuture) {
+                Bundle resultBundle = null;
+                try {
+                    resultBundle = bundleFuture.getResult();
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                }
+
+                // Assert parameters has been passed correctly
+                validateAccountAndAuthTokenType();
+                validateFeatures();
+                validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
+                validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+                validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+                validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
+
+                // Assert return result
+                validateAccountAndNoAuthTokenResult(resultBundle);
+
+                latch.countDown();
+            }
+        };
+
+        addAccount(am,
+                ACCOUNT_TYPE,
+                AUTH_TOKEN_TYPE,
+                REQUIRED_FEATURES,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                callback,
+                handler);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test addAccountExplicitly() and removeAccount()
+     */
+    public void testAddAccountExplicitlyAndRemoveAccount() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final int accountsCount = getAccountsCount();
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Assert that we have one more account
+        Account[] accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(1 + accountsCount, accounts.length);
+        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
+
+        // Need to clean up
+        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+
+        // and verify that we go back to the initial state
+        accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(accountsCount, accounts.length);
+    }
+
+    /**
+     * Test getAccounts() and getAccountsByType()
+     */
+    public void testGetAccountsAndGetAccountsByType() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
+        assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT_SAME_TYPE));
+
+        final int accountsCount = getAccountsCount();
+
+        // Add a first account
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Check that we have the new account
+        Account[] accounts = am.getAccounts();
+        assertEquals(1 + accountsCount, accounts.length);
+        assertEquals(true, isAccountPresent(accounts, ACCOUNT));
+
+        // Add another account
+        addAccountExplicitly(ACCOUNT_SAME_TYPE, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Check that we have one more account again
+        accounts = am.getAccounts();
+        assertEquals(2 + accountsCount, accounts.length);
+        assertEquals(true, isAccountPresent(accounts, ACCOUNT_SAME_TYPE));
+
+        // Check if we have one from first type
+        accounts = am.getAccountsByType(ACCOUNT_TYPE);
+        assertEquals(2, accounts.length);
+
+        // Check if we dont have any account from the other type
+        accounts = am.getAccountsByType(ACCOUNT_TYPE_OTHER);
+        assertEquals(0, accounts.length);
+    }
+
+    /**
+     * Test getAuthenticatorTypes()
+     */
+    public void testGetAuthenticatorTypes() {
+        AuthenticatorDescription[] types = am.getAuthenticatorTypes();
+        for(AuthenticatorDescription description: types) {
+            if (description.type.equals(ACCOUNT_TYPE)) {
+                return;
+            }
+        }
+        fail("should have found Authenticator type: " + ACCOUNT_TYPE);
+    }
+
+    /**
+     * Test setPassword() and getPassword()
+     */
+    public void testSetAndGetAndClearPassword() {
+        // Add a first account
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Check that the password is the one we defined
+        assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
+
+        // Clear the password and check that it is cleared
+        am.clearPassword(ACCOUNT);
+        assertNull(am.getPassword(ACCOUNT));
+
+        // Reset the password
+        am.setPassword(ACCOUNT, ACCOUNT_PASSWORD);
+
+        // Check that the password is the one we defined
+        assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
+    }
+
+    /**
+     * Test setUserData() and getUserData()
+     */
+    public void testSetAndGetUserData() {
+        // Add a first account
+        boolean result = am.addAccountExplicitly(ACCOUNT,
+                                ACCOUNT_PASSWORD,
+                                USERDATA_BUNDLE);
+
+        assertTrue(result);
+
+        // Check that the UserData is the one we defined
+        assertEquals(USERDATA_VALUE_1, am.getUserData(ACCOUNT, USERDATA_NAME_1));
+
+        am.setUserData(ACCOUNT, USERDATA_NAME_2, USERDATA_VALUE_2);
+
+        // Check that the UserData is the one we defined
+        assertEquals(USERDATA_VALUE_2, am.getUserData(ACCOUNT, USERDATA_NAME_2));
+    }
+
+    /**
+     * Test getAccountsByTypeAndFeatures()
+     */
+    public void testGetAccountsByTypeAndFeatures() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
+                ACCOUNT_TYPE, REQUIRED_FEATURES, null, null);
+
+        Account[] accounts = futureAccounts.getResult();
+
+        assertNotNull(accounts);
+        assertEquals(1, accounts.length);
+        assertEquals(true, isAccountPresent(accounts, ACCOUNT));
+
+        futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
+                new String[] { NON_EXISTING_FEATURE },
+                null /* callback*/,
+                null /* handler */);
+        accounts = futureAccounts.getResult();
+
+        assertNotNull(accounts);
+        assertEquals(0, accounts.length);
+    }
+
+    /**
+     * Test getAccountsByTypeAndFeatures() with callback and handler
+     */
+    public void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(null /* handler */);
+        testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(new Handler());
+    }
+
+    private void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(Handler handler) throws
+            IOException, AuthenticatorException, OperationCanceledException {
+
+        final CountDownLatch latch1 = new CountDownLatch(1);
+
+        AccountManagerCallback<Account[]> callback1 = new AccountManagerCallback<Account[]>() {
+            public void run(AccountManagerFuture<Account[]> accountsFuture) {
+                try {
+                    Account[] accounts = accountsFuture.getResult();
+                    assertNotNull(accounts);
+                    assertEquals(1, accounts.length);
+                    assertEquals(true, isAccountPresent(accounts, ACCOUNT));
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                } finally {
+                  latch1.countDown();
+                }
+            }
+        };
+
+        AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
+                ACCOUNT_TYPE,
+                REQUIRED_FEATURES,
+                callback1,
+                handler);
+
+        Account[] accounts = futureAccounts.getResult();
+
+        assertNotNull(accounts);
+        assertEquals(1, accounts.length);
+        assertEquals(true, isAccountPresent(accounts, ACCOUNT));
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch1.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+
+        final CountDownLatch latch2 = new CountDownLatch(1);
+
+        AccountManagerCallback<Account[]> callback2 = new AccountManagerCallback<Account[]>() {
+            public void run(AccountManagerFuture<Account[]> accountsFuture) {
+                try {
+                    Account[] accounts = accountsFuture.getResult();
+                    assertNotNull(accounts);
+                    assertEquals(0, accounts.length);
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                } finally {
+                  latch2.countDown();
+                }
+            }
+        };
+
+        accounts = null;
+
+        futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
+                new String[] { NON_EXISTING_FEATURE },
+                callback2,
+                handler);
+
+        accounts = futureAccounts.getResult();
+        assertNotNull(accounts);
+        assertEquals(0, accounts.length);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch2.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test setAuthToken() and peekAuthToken()
+     */
+    public void testSetAndPeekAndInvalidateAuthToken() {
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        am.setAuthToken(ACCOUNT, AUTH_TOKEN_TYPE, AUTH_TOKEN);
+
+        // Ask for the AuthToken
+        String token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
+        assertNotNull(token);
+        assertEquals(AUTH_TOKEN, token);
+
+        am.invalidateAuthToken(ACCOUNT_TYPE, AUTH_TOKEN);
+        token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
+        assertNull(token);
+    }
+
+    /**
+     * Test blockingGetAuthToken()
+     */
+    public void testBlockingGetAuthToken() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
+
+        String token = am.blockingGetAuthToken(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                false /* no failure notification */);
+
+        // Ask for the AuthToken
+        assertNotNull(token);
+        assertEquals(AUTH_TOKEN, token);
+    }
+
+    /**
+     * Test getAuthToken()
+     */
+    public void testGetAuthToken() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                false /* no failure notification */,
+                null /* no callback */,
+                null /* no handler */
+        );
+
+        Bundle resultBundle = futureBundle.getResult();
+
+        assertTrue(futureBundle.isDone());
+        assertNotNull(resultBundle);
+
+        // Assert returned result
+        validateAccountAndAuthTokenResult(resultBundle);
+    }
+
+    /**
+     * Test getAuthToken() with callback and handler
+     */
+    public void testGetAuthTokenWithCallbackAndHandler() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        testGetAuthTokenWithCallbackAndHandler(null /* handler */);
+        testGetAuthTokenWithCallbackAndHandler(new Handler());
+    }
+
+    private void testGetAuthTokenWithCallbackAndHandler(Handler handler) throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+            public void run(AccountManagerFuture<Bundle> bundleFuture) {
+
+                Bundle resultBundle = null;
+                try {
+                    resultBundle = bundleFuture.getResult();
+
+                    // Assert returned result
+                    validateAccountAndAuthTokenResult(resultBundle);
+
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                }
+                finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                false /* no failure notification */,
+                callback,
+                handler
+        );
+
+        Bundle resultBundle = futureBundle.getResult();
+
+        assertTrue(futureBundle.isDone());
+        assertNotNull(resultBundle);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * test getAuthToken() with options
+     */
+    public void testGetAuthTokenWithOptions() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                null /* no callback */,
+                null /* no handler */
+        );
+
+        Bundle resultBundle = futureBundle.getResult();
+
+        assertTrue(futureBundle.isDone());
+        assertNotNull(resultBundle);
+
+        // Assert returned result
+        validateAccountAndAuthTokenResult(resultBundle);
+
+        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
+        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+        validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsGetAuthToken);
+
+    }
+
+    /**
+     * test getAuthToken() with options and callback and handler
+     */
+    public void testGetAuthTokenWithOptionsAndCallback() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        testGetAuthTokenWithOptionsAndCallbackAndHandler(null /* handler */);
+        testGetAuthTokenWithOptionsAndCallbackAndHandler(new Handler());
+    }
+
+    private void testGetAuthTokenWithOptionsAndCallbackAndHandler(Handler handler) throws
+            IOException, AuthenticatorException, OperationCanceledException {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+            public void run(AccountManagerFuture<Bundle> bundleFuture) {
+
+                Bundle resultBundle = null;
+                try {
+                    resultBundle = bundleFuture.getResult();
+
+                    // Assert returned result
+                    validateAccountAndAuthTokenResult(resultBundle);
+
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                }
+                finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                callback,
+                handler
+        );
+
+        Bundle resultBundle = futureBundle.getResult();
+
+        assertTrue(futureBundle.isDone());
+        assertNotNull(resultBundle);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test getAuthTokenByFeatures()
+     */
+    public void testGetAuthTokenByFeatures() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        Bundle resultBundle = getAuthTokenByFeature(
+                new String[] { NON_EXISTING_FEATURE },
+                null /* activity */
+        );
+
+        // Assert returned result
+        validateNullResult(resultBundle);
+
+        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
+        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
+
+        mockAuthenticator.clearData();
+
+        // Now test with existing features and an activity
+        resultBundle = getAuthTokenByFeature(
+                new String[] { NON_EXISTING_FEATURE },
+                ACTIVITY
+        );
+
+        // Assert returned result
+        validateAccountAndAuthTokenResult(resultBundle);
+
+        validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
+        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
+
+        mockAuthenticator.clearData();
+
+        // Now test with existing features and no activity
+        resultBundle = getAuthTokenByFeature(
+                REQUIRED_FEATURES,
+                null /* activity */
+        );
+
+        // Assert returned result
+        validateAccountAndAuthTokenResult(resultBundle);
+
+        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
+        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
+
+        mockAuthenticator.clearData();
+
+        // Now test with existing features and an activity
+        resultBundle = getAuthTokenByFeature(
+                REQUIRED_FEATURES,
+                ACTIVITY
+        );
+
+        // Assert returned result
+        validateAccountAndAuthTokenResult(resultBundle);
+
+        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
+        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
+
+    }
+
+    /**
+     * Test confirmCredentials()
+     */
+    public void testConfirmCredentials() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                null /* callback*/,
+                null /* handler */);
+
+        futureBundle.getResult();
+
+        // Assert returned result
+        validateCredentials();
+    }
+
+    /**
+     * Test confirmCredentials() with callback
+     */
+    public void testConfirmCredentialsWithCallbackAndHandler() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        testConfirmCredentialsWithCallbackAndHandler(null /* handler */);
+        testConfirmCredentialsWithCallbackAndHandler(new Handler());
+    }
+
+    private void testConfirmCredentialsWithCallbackAndHandler(Handler handler) throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+            public void run(AccountManagerFuture<Bundle> bundleFuture) {
+
+                Bundle resultBundle = null;
+                try {
+                    resultBundle = bundleFuture.getResult();
+
+                    // Assert returned result
+                    validateCredentials();
+
+                    assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                }
+                finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                callback,
+                handler);
+
+        futureBundle.getResult();
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test updateCredentials()
+     */
+    public void testUpdateCredentials() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                null /* callback*/,
+                null /* handler */);
+
+        Bundle result = futureBundle.getResult();
+
+        validateAccountAndNoAuthTokenResult(result);
+
+        // Assert returned result
+        validateCredentials();
+    }
+
+    /**
+     * Test updateCredentials() with callback and handler
+     */
+    public void testUpdateCredentialsWithCallbackAndHandler() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        testUpdateCredentialsWithCallbackAndHandler(null /* handler */);
+        testUpdateCredentialsWithCallbackAndHandler(new Handler());
+    }
+
+    private void testUpdateCredentialsWithCallbackAndHandler(Handler handler) throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+            public void run(AccountManagerFuture<Bundle> bundleFuture) {
+
+                Bundle resultBundle = null;
+                try {
+                    resultBundle = bundleFuture.getResult();
+
+                    // Assert returned result
+                    validateCredentials();
+                    assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
+
+                } catch (OperationCanceledException e) {
+                    fail("should not throw an OperationCanceledException");
+                } catch (IOException e) {
+                    fail("should not throw an IOException");
+                } catch (AuthenticatorException e) {
+                    fail("should not throw an AuthenticatorException");
+                }
+                finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
+                AUTH_TOKEN_TYPE,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                callback,
+                handler);
+
+        futureBundle.getResult();
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test editProperties()
+     */
+    public void testEditProperties() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
+                ACTIVITY,
+                null /* callback */,
+                null /* handler*/);
+
+        Bundle result = futureBundle.getResult();
+
+        validateAccountAndNoAuthTokenResult(result);
+
+        // Assert returned result
+        assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
+    }
+
+    /**
+     * Test editProperties() with callback and handler
+     */
+    public void testEditPropertiesWithCallbackAndHandler() {
+        testEditPropertiesWithCallbackAndHandler(null /* handler */);
+        testEditPropertiesWithCallbackAndHandler(new Handler());
+    }
+
+    private void testEditPropertiesWithCallbackAndHandler(Handler handler) {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+            public void run(AccountManagerFuture<Bundle> bundleFuture) {
+                try {
+                    // Assert returned result
+                    assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
+                }
+                finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
+                ACTIVITY,
+                callback,
+                handler);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test addOnAccountsUpdatedListener() with handler
+     */
+    public void testAddOnAccountsUpdatedListenerWithHandler() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
+                false /* updateImmediately */);
+
+        // Need to cleanup intermediate state
+        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+
+        testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
+                true /* updateImmediately */);
+
+        // Need to cleanup intermediate state
+        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+
+        testAddOnAccountsUpdatedListenerWithHandler(new Handler(),
+                false /* updateImmediately */);
+
+        // Need to cleanup intermediate state
+        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+
+        testAddOnAccountsUpdatedListenerWithHandler(new Handler(),
+                true /* updateImmediately */);
+    }
+
+    private void testAddOnAccountsUpdatedListenerWithHandler(Handler handler,
+            boolean updateImmediately) {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        OnAccountsUpdateListener listener = new OnAccountsUpdateListener() {
+            public void onAccountsUpdated(Account[] accounts) {
+                latch.countDown();
+            }
+        };
+
+        // Add a listener
+        am.addOnAccountsUpdatedListener(listener,
+                handler,
+                updateImmediately);
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+
+        // Cleanup
+        am.removeOnAccountsUpdatedListener(listener);
+    }
+
+    /**
+     * Test removeOnAccountsUpdatedListener() with handler
+     */
+    public void testRemoveOnAccountsUpdatedListener() throws IOException, AuthenticatorException,
+            OperationCanceledException {
+
+        testRemoveOnAccountsUpdatedListenerWithHandler(null /* handler */);
+
+        // Need to cleanup intermediate state
+        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+
+        testRemoveOnAccountsUpdatedListenerWithHandler(new Handler());
+    }
+
+    private void testRemoveOnAccountsUpdatedListenerWithHandler(Handler handler) {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        OnAccountsUpdateListener listener = new OnAccountsUpdateListener() {
+            public void onAccountsUpdated(Account[] accounts) {
+                fail("should not be called");
+            }
+        };
+
+        // First add a listener
+        am.addOnAccountsUpdatedListener(listener,
+                handler,
+                false /* updateImmediately */);
+
+        // Then remove the listener
+        am.removeOnAccountsUpdatedListener(listener);
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    /**
+     * Test hasFeature
+     */
+    public void testHasFeature()
+            throws IOException, AuthenticatorException, OperationCanceledException {
+
+        assertHasFeature(null /* handler */);
+        assertHasFeature(new Handler());
+
+        assertHasFeatureWithCallback(null /* handler */);
+        assertHasFeatureWithCallback(new Handler());
+    }
+
+    private void assertHasFeature(Handler handler)
+            throws IOException, AuthenticatorException, OperationCanceledException {
+        Bundle resultBundle = addAccount(am,
+                ACCOUNT_TYPE,
+                AUTH_TOKEN_TYPE,
+                REQUIRED_FEATURES,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                null /* callback */,
+                null /* handler */);
+
+        // Assert parameters has been passed correctly
+        validateAccountAndAuthTokenType();
+        validateFeatures();
+
+        AccountManagerFuture<Boolean> booleanFuture = am.hasFeatures(ACCOUNT,
+                new String[]{FEATURE_1},
+                null /* callback */,
+                handler);
+        assertTrue(booleanFuture.getResult());
+
+        booleanFuture = am.hasFeatures(ACCOUNT,
+                new String[]{FEATURE_2},
+                null /* callback */,
+                handler);
+        assertTrue(booleanFuture.getResult());
+
+        booleanFuture = am.hasFeatures(ACCOUNT,
+                new String[]{FEATURE_1, FEATURE_2},
+                null /* callback */,
+                handler);
+        assertTrue(booleanFuture.getResult());
+
+        booleanFuture = am.hasFeatures(ACCOUNT,
+                new String[]{NON_EXISTING_FEATURE},
+                null /* callback */,
+                handler);
+        assertFalse(booleanFuture.getResult());
+
+        booleanFuture = am.hasFeatures(ACCOUNT,
+                new String[]{NON_EXISTING_FEATURE, FEATURE_1},
+                null /* callback */,
+                handler);
+        assertFalse(booleanFuture.getResult());
+
+        booleanFuture = am.hasFeatures(ACCOUNT,
+                new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
+                null /* callback */,
+                handler);
+        assertFalse(booleanFuture.getResult());
+    }
+
+    private AccountManagerCallback<Boolean> getAssertTrueCallback(final CountDownLatch latch) {
+        return new AccountManagerCallback<Boolean>() {
+            public void run(AccountManagerFuture<Boolean> booleanFuture) {
+                try {
+                    // Assert returned result should be TRUE
+                    assertTrue(booleanFuture.getResult());
+                } catch (Exception e) {
+                    fail("Exception: " + e);
+                } finally {
+                    latch.countDown();
+                }
+            }
+        };
+    }
+
+    private AccountManagerCallback<Boolean> getAssertFalseCallback(final CountDownLatch latch) {
+        return new AccountManagerCallback<Boolean>() {
+            public void run(AccountManagerFuture<Boolean> booleanFuture) {
+                try {
+                    // Assert returned result should be FALSE
+                    assertFalse(booleanFuture.getResult());
+                } catch (Exception e) {
+                    fail("Exception: " + e);
+                } finally {
+                    latch.countDown();
+                }
+            }
+        };
+    }
+
+    private void waitForLatch(final CountDownLatch latch) {
+        // Wait with timeout for the callback to do its work
+        try {
+            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("should not throw an InterruptedException");
+        }
+    }
+
+    private void assertHasFeatureWithCallback(Handler handler)
+            throws IOException, AuthenticatorException, OperationCanceledException {
+        Bundle resultBundle = addAccount(am,
+                ACCOUNT_TYPE,
+                AUTH_TOKEN_TYPE,
+                REQUIRED_FEATURES,
+                OPTIONS_BUNDLE,
+                ACTIVITY,
+                null /* callback */,
+                null /* handler */);
+
+        // Assert parameters has been passed correctly
+        validateAccountAndAuthTokenType();
+        validateFeatures();
+
+        CountDownLatch latch = new CountDownLatch(1);
+        am.hasFeatures(ACCOUNT,
+                new String[]{FEATURE_1},
+                getAssertTrueCallback(latch),
+                handler);
+        waitForLatch(latch);
+
+        latch = new CountDownLatch(1);
+        am.hasFeatures(ACCOUNT,
+                new String[]{FEATURE_2},
+                getAssertTrueCallback(latch),
+                handler);
+        waitForLatch(latch);
+
+        latch = new CountDownLatch(1);
+        am.hasFeatures(ACCOUNT,
+                new String[]{FEATURE_1, FEATURE_2},
+                getAssertTrueCallback(latch),
+                handler);
+        waitForLatch(latch);
+
+        latch = new CountDownLatch(1);
+        am.hasFeatures(ACCOUNT,
+                new String[]{NON_EXISTING_FEATURE},
+                getAssertFalseCallback(latch),
+                handler);
+        waitForLatch(latch);
+
+        latch = new CountDownLatch(1);
+        am.hasFeatures(ACCOUNT,
+                new String[]{NON_EXISTING_FEATURE, FEATURE_1},
+                getAssertFalseCallback(latch),
+                handler);
+        waitForLatch(latch);
+
+        latch = new CountDownLatch(1);
+        am.hasFeatures(ACCOUNT,
+                new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
+                getAssertFalseCallback(latch),
+                handler);
+        waitForLatch(latch);
+    }
+}