1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.accounts.cts;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.AccountManagerCallback;
22import android.accounts.AccountManagerFuture;
23import android.accounts.AuthenticatorDescription;
24import android.accounts.AuthenticatorException;
25import android.accounts.OnAccountsUpdateListener;
26import android.accounts.OperationCanceledException;
27import android.app.Activity;
28import android.content.Context;
29import android.content.Intent;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.HandlerThread;
33import android.os.Looper;
34import android.os.StrictMode;
35import android.platform.test.annotations.Presubmit;
36import android.test.ActivityInstrumentationTestCase2;
37
38import java.io.IOException;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.concurrent.CountDownLatch;
42import java.util.concurrent.TimeUnit;
43import java.util.concurrent.atomic.AtomicReference;
44
45/**
46 * You can run those unit tests with the following command line:
47 *
48 *  adb shell am instrument
49 *   -e debug false -w
50 *   -e class android.accounts.cts.AccountManagerTest
51 * android.accounts.cts/android.support.test.runner.AndroidJUnitRunner
52 */
53public class AccountManagerTest extends ActivityInstrumentationTestCase2<AccountDummyActivity> {
54
55    public static final String ACCOUNT_NAME = "android.accounts.cts.account.name";
56    public static final String ACCOUNT_NEW_NAME = "android.accounts.cts.account.name.rename";
57    public static final String ACCOUNT_NAME_OTHER = "android.accounts.cts.account.name.other";
58
59    public static final String ACCOUNT_TYPE = "android.accounts.cts.account.type";
60    public static final String ACCOUNT_TYPE_CUSTOM = "android.accounts.cts.custom.account.type";
61    public static final String ACCOUNT_TYPE_ABSENT = "android.accounts.cts.account.type.absent";
62
63    public static final String ACCOUNT_PASSWORD = "android.accounts.cts.account.password";
64
65    public static final String AUTH_TOKEN_TYPE = "mockAuthTokenType";
66    public static final String AUTH_EXPIRING_TOKEN_TYPE = "mockAuthExpiringTokenType";
67    public static final String AUTH_TOKEN_LABEL = "mockAuthTokenLabel";
68    public static final long AUTH_TOKEN_DURATION_MILLIS = 10000L; // Ten seconds.
69
70    public static final String FEATURE_1 = "feature.1";
71    public static final String FEATURE_2 = "feature.2";
72    public static final String NON_EXISTING_FEATURE = "feature.3";
73
74    public static final String OPTION_NAME_1 = "option.name.1";
75    public static final String OPTION_VALUE_1 = "option.value.1";
76
77    public static final String OPTION_NAME_2 = "option.name.2";
78    public static final String OPTION_VALUE_2 = "option.value.2";
79
80    public static final String[] REQUIRED_FEATURES = new String[] { FEATURE_1, FEATURE_2 };
81
82    public static final Bundle OPTIONS_BUNDLE = new Bundle();
83
84    public static final Bundle USERDATA_BUNDLE = new Bundle();
85
86    public static final String USERDATA_NAME_1 = "user.data.name.1";
87    public static final String USERDATA_NAME_2 = "user.data.name.2";
88    public static final String USERDATA_VALUE_1 = "user.data.value.1";
89    public static final String USERDATA_VALUE_2 = "user.data.value.2";
90
91    public static final Account ACCOUNT = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
92    public static final Account ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API = new Account(
93            MockAccountAuthenticator.ACCOUNT_NAME_FOR_NEW_REMOVE_API, ACCOUNT_TYPE);
94    public static final Account ACCOUNT_FOR_DEFAULT_IMPL = new Account(
95            MockAccountAuthenticator.ACCOUNT_NAME_FOR_DEFAULT_IMPL, ACCOUNT_TYPE);
96    public static final Account ACCOUNT_SAME_TYPE = new Account(ACCOUNT_NAME_OTHER, ACCOUNT_TYPE);
97
98    public static final Account CUSTOM_TOKEN_ACCOUNT =
99            new Account(ACCOUNT_NAME,ACCOUNT_TYPE_CUSTOM);
100
101    private static MockAccountAuthenticator mockAuthenticator;
102    private static final int LATCH_TIMEOUT_MS = 500;
103    private static AccountManager am;
104
105    public synchronized static MockAccountAuthenticator getMockAuthenticator(Context context) {
106        if (null == mockAuthenticator) {
107            mockAuthenticator = new MockAccountAuthenticator(context);
108        }
109        return mockAuthenticator;
110    }
111
112    private Activity mActivity;
113    private Context mContext;
114
115    public AccountManagerTest() {
116        super(AccountDummyActivity.class);
117    }
118
119    @Override
120    public void setUp() throws Exception {
121        super.setUp();
122        mActivity = getActivity();
123        mContext = getInstrumentation().getTargetContext();
124
125        OPTIONS_BUNDLE.putString(OPTION_NAME_1, OPTION_VALUE_1);
126        OPTIONS_BUNDLE.putString(OPTION_NAME_2, OPTION_VALUE_2);
127
128        USERDATA_BUNDLE.putString(USERDATA_NAME_1, USERDATA_VALUE_1);
129
130        getMockAuthenticator(mContext);
131
132        am = AccountManager.get(mContext);
133    }
134
135    @Override
136    public void tearDown() throws Exception, AuthenticatorException, OperationCanceledException {
137        mockAuthenticator.clearData();
138
139        // Need to clean up created account
140        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
141                AccountManager.KEY_BOOLEAN_RESULT));
142        assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, mActivity, null /* callback */).getBoolean(
143                AccountManager.KEY_BOOLEAN_RESULT));
144
145        // Clean out any other accounts added during the tests.
146        Account[] ctsAccounts = am.getAccountsByType(ACCOUNT_TYPE);
147        Account[] ctsCustomAccounts = am.getAccountsByType(ACCOUNT_TYPE_CUSTOM);
148        ArrayList<Account> accounts = new ArrayList<>(Arrays.asList(ctsAccounts));
149        accounts.addAll(Arrays.asList(ctsCustomAccounts));
150        for (Account ctsAccount : accounts) {
151            removeAccount(am, ctsAccount, mActivity, null /* callback */);
152        }
153
154        // need to clean up the authenticator cached data
155        mockAuthenticator.clearData();
156
157        super.tearDown();
158    }
159
160    interface TokenFetcher {
161        public Bundle fetch(String tokenType)
162                throws OperationCanceledException, AuthenticatorException, IOException;
163        public Account getAccount();
164    }
165
166    private void validateSuccessfulTokenFetchingLifecycle(TokenFetcher fetcher, String tokenType)
167            throws OperationCanceledException, AuthenticatorException, IOException {
168        Account account = fetcher.getAccount();
169        Bundle expected = new Bundle();
170        expected.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
171        expected.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
172
173        // First fetch.
174        Bundle actual = fetcher.fetch(tokenType);
175        assertTrue(mockAuthenticator.isRecentlyCalled());
176        validateAccountAndAuthTokenResult(expected, actual);
177
178        /*
179         * On the second fetch the cache will be populated if we are using a authenticator with
180         * customTokens=false or we are using a scope that will cause the authenticator to set an
181         * expiration time (and that expiration time hasn't been reached).
182         */
183        actual = fetcher.fetch(tokenType);
184
185        boolean isCachingExpected =
186                ACCOUNT_TYPE.equals(account.type) || AUTH_EXPIRING_TOKEN_TYPE.equals(tokenType);
187        assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
188        validateAccountAndAuthTokenResult(expected, actual);
189
190        try {
191            // Delay further execution until expiring tokens can actually expire.
192            Thread.sleep(mockAuthenticator.getTokenDurationMillis() + 1L);
193        } catch (InterruptedException e) {
194            throw new RuntimeException(e);
195        }
196
197        /*
198         * With the time shift above, the third request will result in cache hits only from
199         * customToken=false authenticators.
200         */
201        actual = fetcher.fetch(tokenType);
202        isCachingExpected = ACCOUNT_TYPE.equals(account.type);
203        assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
204        validateAccountAndAuthTokenResult(expected, actual);
205
206        // invalidate token
207        String token = actual.getString(AccountManager.KEY_AUTHTOKEN);
208        am.invalidateAuthToken(account.type, token);
209
210        /*
211         * Upon invalidating the token, the cache should be clear regardless of authenticator.
212         */
213        actual = fetcher.fetch(tokenType);
214        assertTrue(mockAuthenticator.isRecentlyCalled());
215        validateAccountAndAuthTokenResult(expected, actual);
216    }
217
218    private void validateAccountAndAuthTokenResult(Bundle actual) {
219        assertEquals(
220                ACCOUNT.name,
221                actual.get(AccountManager.KEY_ACCOUNT_NAME));
222        assertEquals(
223                ACCOUNT.type,
224                actual.get(AccountManager.KEY_ACCOUNT_TYPE));
225        assertEquals(
226                mockAuthenticator.getLastTokenServed(),
227                actual.get(AccountManager.KEY_AUTHTOKEN));
228    }
229
230    private void validateAccountAndAuthTokenResult(Bundle expected, Bundle actual) {
231        assertEquals(
232                expected.get(AccountManager.KEY_ACCOUNT_NAME),
233                actual.get(AccountManager.KEY_ACCOUNT_NAME));
234        assertEquals(
235                expected.get(AccountManager.KEY_ACCOUNT_TYPE),
236                actual.get(AccountManager.KEY_ACCOUNT_TYPE));
237        assertEquals(
238                mockAuthenticator.getLastTokenServed(),
239                actual.get(AccountManager.KEY_AUTHTOKEN));
240    }
241
242    private void validateAccountAndNoAuthTokenResult(Bundle result) {
243        assertEquals(ACCOUNT_NAME, result.get(AccountManager.KEY_ACCOUNT_NAME));
244        assertEquals(ACCOUNT_TYPE, result.get(AccountManager.KEY_ACCOUNT_TYPE));
245        assertNull(result.get(AccountManager.KEY_AUTHTOKEN));
246    }
247
248    private void validateNullResult(Bundle resultBundle) {
249        assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
250        assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
251        assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
252    }
253
254    private void validateAccountAndAuthTokenType() {
255        assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
256        assertEquals(AUTH_TOKEN_TYPE, mockAuthenticator.getAuthTokenType());
257    }
258
259    private void validateFeatures() {
260        assertEquals(REQUIRED_FEATURES[0], mockAuthenticator.getRequiredFeatures()[0]);
261        assertEquals(REQUIRED_FEATURES[1], mockAuthenticator.getRequiredFeatures()[1]);
262    }
263
264    private void validateOptions(Bundle expectedOptions, Bundle actualOptions) {
265        // In ICS AccountManager may add options to indicate the caller id.
266        // We only validate that the passed in options are present in the actual ones
267        if (expectedOptions != null) {
268            assertNotNull(actualOptions);
269            assertEquals(expectedOptions.get(OPTION_NAME_1), actualOptions.get(OPTION_NAME_1));
270            assertEquals(expectedOptions.get(OPTION_NAME_2), actualOptions.get(OPTION_NAME_2));
271        }
272    }
273
274    private void validateSystemOptions(Bundle options) {
275        assertNotNull(options.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME));
276        assertTrue(options.containsKey(AccountManager.KEY_CALLER_UID));
277        assertTrue(options.containsKey(AccountManager.KEY_CALLER_PID));
278    }
279
280    private void validateCredentials() {
281        assertEquals(ACCOUNT, mockAuthenticator.getAccount());
282    }
283
284    private int getAccountsCount() {
285        Account[] accounts = am.getAccounts();
286        assertNotNull(accounts);
287        return accounts.length;
288    }
289
290    private Bundle addAccount(AccountManager am, String accountType, String authTokenType,
291            String[] requiredFeatures, Bundle options, Activity activity,
292            AccountManagerCallback<Bundle> callback, Handler handler) throws
293                IOException, AuthenticatorException, OperationCanceledException {
294
295        AccountManagerFuture<Bundle> futureBundle = am.addAccount(
296                accountType,
297                authTokenType,
298                requiredFeatures,
299                options,
300                activity,
301                callback,
302                handler);
303
304        Bundle resultBundle = futureBundle.getResult();
305        assertTrue(futureBundle.isDone());
306        assertNotNull(resultBundle);
307
308        return resultBundle;
309    }
310
311    private Account renameAccount(AccountManager am, Account account, String newName)
312            throws OperationCanceledException, AuthenticatorException, IOException {
313        AccountManagerFuture<Account> futureAccount = am.renameAccount(
314                account, newName, null /* callback */, null /* handler */);
315        Account renamedAccount = futureAccount.getResult();
316        assertTrue(futureAccount.isDone());
317        assertNotNull(renamedAccount);
318        return renamedAccount;
319    }
320
321    private boolean removeAccount(AccountManager am, Account account,
322            AccountManagerCallback<Boolean> callback) throws IOException, AuthenticatorException,
323                OperationCanceledException {
324        AccountManagerFuture<Boolean> futureBoolean = am.removeAccount(account,
325                callback,
326                null /* handler */);
327        Boolean resultBoolean = futureBoolean.getResult();
328        assertTrue(futureBoolean.isDone());
329
330        return resultBoolean;
331    }
332
333    private Bundle removeAccountWithIntentLaunch(AccountManager am, Account account,
334            Activity activity, AccountManagerCallback<Bundle> callback) throws IOException,
335            AuthenticatorException, OperationCanceledException {
336
337        AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
338                activity,
339                callback,
340                null /* handler */);
341        Bundle resultBundle = futureBundle.getResult();
342        assertTrue(futureBundle.isDone());
343
344        return resultBundle;
345    }
346
347    private Bundle removeAccount(AccountManager am, Account account, Activity activity,
348            AccountManagerCallback<Bundle> callback) throws IOException, AuthenticatorException,
349                OperationCanceledException {
350
351        AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
352                activity,
353                callback,
354                null /* handler */);
355        Bundle resultBundle = futureBundle.getResult();
356        assertTrue(futureBundle.isDone());
357
358        return resultBundle;
359    }
360
361    private boolean removeAccountExplicitly(AccountManager am, Account account) {
362        return am.removeAccountExplicitly(account);
363    }
364
365    private void addAccountExplicitly(Account account, String password, Bundle userdata) {
366        assertTrue(am.addAccountExplicitly(account, password, userdata));
367    }
368
369    private Bundle getAuthTokenByFeature(String[] features, Activity activity)
370            throws IOException, AuthenticatorException, OperationCanceledException {
371
372        AccountManagerFuture<Bundle> futureBundle = am.getAuthTokenByFeatures(ACCOUNT_TYPE,
373                AUTH_TOKEN_TYPE,
374                features,
375                activity,
376                OPTIONS_BUNDLE,
377                OPTIONS_BUNDLE,
378                null /* no callback */,
379                null /* no handler */
380        );
381
382        Bundle resultBundle = futureBundle.getResult();
383
384        assertTrue(futureBundle.isDone());
385        assertNotNull(resultBundle);
386
387        return resultBundle;
388    }
389
390    private boolean isAccountPresent(Account[] accounts, Account accountToCheck) {
391        if (null == accounts || null == accountToCheck) {
392            return false;
393        }
394        boolean result = false;
395        int length = accounts.length;
396        for (int n=0; n<length; n++) {
397            if(accountToCheck.equals(accounts[n])) {
398                result = true;
399                break;
400            }
401        }
402        return result;
403    }
404
405    /**
406     * Test singleton
407     */
408    public void testGet() {
409        assertNotNull(AccountManager.get(mContext));
410    }
411
412    /**
413     * Test creation of intent
414     */
415    public void testNewChooseAccountIntent() {
416        Intent intent = AccountManager.newChooseAccountIntent(null, null, null,
417                null, null,
418                null, null);
419        assertNotNull(intent);
420    }
421
422    /**
423     * Test creation of intent
424     */
425    public void testNewChooseAccountIntentDepracated() {
426        Intent intent = AccountManager.newChooseAccountIntent(null, null, null, false,
427                null, null,
428                null, null);
429        assertNotNull(intent);
430    }
431
432    /**
433     * Test a basic addAccount()
434     */
435    public void testAddAccount() throws IOException, AuthenticatorException,
436            OperationCanceledException {
437
438        Bundle resultBundle = addAccount(am,
439                ACCOUNT_TYPE,
440                AUTH_TOKEN_TYPE,
441                REQUIRED_FEATURES,
442                OPTIONS_BUNDLE,
443                mActivity,
444                null /* callback */,
445                null /* handler */);
446
447        // Assert parameters has been passed correctly
448        validateAccountAndAuthTokenType();
449        validateFeatures();
450        validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
451        validateSystemOptions(mockAuthenticator.mOptionsAddAccount);
452        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
453        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
454        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
455
456        // Assert returned result
457        validateAccountAndNoAuthTokenResult(resultBundle);
458    }
459
460    /**
461     * Test addAccount() with callback and handler
462     */
463    public void testAddAccountWithCallbackAndHandler() throws IOException,
464            AuthenticatorException, OperationCanceledException {
465
466        testAddAccountWithCallbackAndHandler(null /* handler */);
467        testAddAccountWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
468    }
469
470    /**
471     * Test addAccount() with no associated account authenticator
472     */
473    public void testAddAccountWithNoAuthenticator() throws IOException,
474            AuthenticatorException, OperationCanceledException {
475
476        try {
477            AccountManagerFuture<Bundle> futureBundle = am.addAccount(
478                    "nonExistingAccountType",
479                    null,
480                    null,
481                    null,
482                    null,
483                    null,
484                    null);
485
486            futureBundle.getResult();
487            fail();
488        } catch (AuthenticatorException expectedException) {
489            return;
490        }
491    }
492
493    private void testAddAccountWithCallbackAndHandler(Handler handler) throws IOException,
494            AuthenticatorException, OperationCanceledException {
495
496        final CountDownLatch latch = new CountDownLatch(1);
497
498        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
499            @Override
500            public void run(AccountManagerFuture<Bundle> bundleFuture) {
501                Bundle resultBundle = null;
502                try {
503                    resultBundle = bundleFuture.getResult();
504                } catch (OperationCanceledException e) {
505                    fail("should not throw an OperationCanceledException");
506                } catch (IOException e) {
507                    fail("should not throw an IOException");
508                } catch (AuthenticatorException e) {
509                    fail("should not throw an AuthenticatorException");
510                }
511
512                // Assert parameters has been passed correctly
513                validateAccountAndAuthTokenType();
514                validateFeatures();
515                validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
516                validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
517                validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
518                validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
519
520                // Assert return result
521                validateAccountAndNoAuthTokenResult(resultBundle);
522
523                latch.countDown();
524            }
525        };
526
527        addAccount(am,
528                ACCOUNT_TYPE,
529                AUTH_TOKEN_TYPE,
530                REQUIRED_FEATURES,
531                OPTIONS_BUNDLE,
532                mActivity,
533                callback,
534                handler);
535
536        // Wait with timeout for the callback to do its work
537        try {
538            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
539        } catch (InterruptedException e) {
540            fail("should not throw an InterruptedException");
541        }
542    }
543
544    /**
545     * Test addAccountExplicitly(), renameAccount() and removeAccount().
546     */
547    public void testAddAccountExplicitlyAndRemoveAccount() throws IOException,
548            AuthenticatorException, OperationCanceledException {
549
550        final int expectedAccountsCount = getAccountsCount();
551
552        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
553
554        // Assert that we have one more account
555        Account[] accounts = am.getAccounts();
556        assertNotNull(accounts);
557        assertEquals(1 + expectedAccountsCount, accounts.length);
558        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
559        // Need to clean up
560        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
561                AccountManager.KEY_BOOLEAN_RESULT));
562
563        // and verify that we go back to the initial state
564        accounts = am.getAccounts();
565        assertNotNull(accounts);
566        assertEquals(expectedAccountsCount, accounts.length);
567    }
568
569    /**
570     * Test addAccountExplicitly(), renameAccount() and removeAccount().
571     */
572    public void testAddAccountExplicitlyAndRemoveAccountWithNewApi() throws IOException,
573            AuthenticatorException, OperationCanceledException {
574
575        final int expectedAccountsCount = getAccountsCount();
576
577        addAccountExplicitly(ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, ACCOUNT_PASSWORD, null /* userData */);
578
579        // Assert that we have one more account
580        Account[] accounts = am.getAccounts();
581        assertNotNull(accounts);
582        assertEquals(1 + expectedAccountsCount, accounts.length);
583        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
584        // Deprecated API should not work
585        assertFalse(removeAccount(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, null /* callback */));
586        accounts = am.getAccounts();
587        assertNotNull(accounts);
588        assertEquals(1 + expectedAccountsCount, accounts.length);
589        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
590        // Check removal of account
591        assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, mActivity, null /* callback */)
592                .getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
593        // and verify that we go back to the initial state
594        accounts = am.getAccounts();
595        assertNotNull(accounts);
596        assertEquals(expectedAccountsCount, accounts.length);
597    }
598
599    /**
600     * Test addAccountExplicitly(), renameAccount() and removeAccount() calling
601     * into default implementations.
602     */
603    public void testAddAccountExplicitlyAndRemoveAccountWithDefaultImpl() throws IOException,
604            AuthenticatorException, OperationCanceledException {
605
606        final int expectedAccountsCount = getAccountsCount();
607
608        addAccountExplicitly(ACCOUNT_FOR_DEFAULT_IMPL, ACCOUNT_PASSWORD, null /* userData */);
609
610        // Assert that we have one more account
611        Account[] accounts = am.getAccounts();
612        assertNotNull(accounts);
613        assertEquals(1 + expectedAccountsCount, accounts.length);
614        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_DEFAULT_IMPL));
615        // Check removal of account
616        assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_DEFAULT_IMPL, mActivity, null /* callback */)
617                .getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
618        // and verify that we go back to the initial state
619        accounts = am.getAccounts();
620        assertNotNull(accounts);
621        assertEquals(expectedAccountsCount, accounts.length);
622    }
623
624    /**
625     * Test addAccountExplicitly(), renameAccount() and removeAccount().
626     */
627    public void testAddAccountExplicitlyAndRemoveAccountWithDeprecatedApi() throws IOException,
628            AuthenticatorException, OperationCanceledException {
629
630        final int expectedAccountsCount = getAccountsCount();
631
632        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
633
634        // Assert that we have one more account
635        Account[] accounts = am.getAccounts();
636        assertNotNull(accounts);
637        assertEquals(1 + expectedAccountsCount, accounts.length);
638        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
639        // Need to clean up
640        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
641
642        // and verify that we go back to the initial state
643        accounts = am.getAccounts();
644        assertNotNull(accounts);
645        assertEquals(expectedAccountsCount, accounts.length);
646    }
647
648    /**
649     * Test addAccountExplicitly() and removeAccountExplictly().
650     */
651    public void testAddAccountExplicitlyAndRemoveAccountExplicitly() {
652        final int expectedAccountsCount = getAccountsCount();
653
654        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
655
656        // Assert that we have one more account
657        Account[] accounts = am.getAccounts();
658        assertNotNull(accounts);
659        assertEquals(1 + expectedAccountsCount, accounts.length);
660        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
661        // Need to clean up
662        assertTrue(removeAccountExplicitly(am, ACCOUNT));
663
664        // and verify that we go back to the initial state
665        accounts = am.getAccounts();
666        assertNotNull(accounts);
667        assertEquals(expectedAccountsCount, accounts.length);
668    }
669
670    /**
671     * Test setUserData() and getUserData().
672     */
673    public void testAccountRenameAndGetPreviousName()
674            throws OperationCanceledException, AuthenticatorException, IOException {
675        // Add a first account
676        boolean result = am.addAccountExplicitly(ACCOUNT,
677                                ACCOUNT_PASSWORD,
678                                USERDATA_BUNDLE);
679        assertTrue(result);
680
681        // Prior to a renmae, the previous name should be null.
682        String nullName = am.getPreviousName(ACCOUNT);
683        assertNull(nullName);
684
685        final int expectedAccountsCount = getAccountsCount();
686
687        Account renamedAccount = renameAccount(am, ACCOUNT, ACCOUNT_NEW_NAME);
688
689        /*
690         *  Make sure that the resultant renamed account has the correct name
691         *  and is associated with the correct account type.
692         */
693        assertEquals(ACCOUNT_NEW_NAME, renamedAccount.name);
694        assertEquals(ACCOUNT.type, renamedAccount.type);
695
696        // Make sure the total number of accounts is the same.
697        Account[] accounts = am.getAccounts();
698        assertEquals(expectedAccountsCount, accounts.length);
699
700        // Make sure the old account isn't present.
701        assertFalse(isAccountPresent(am.getAccounts(), ACCOUNT));
702
703        // But that the new one is.
704        assertTrue(isAccountPresent(am.getAccounts(), renamedAccount));
705
706        // Check that the UserData is still present.
707        assertEquals(USERDATA_VALUE_1, am.getUserData(renamedAccount, USERDATA_NAME_1));
708
709        assertEquals(ACCOUNT.name, am.getPreviousName(renamedAccount));
710
711       // Need to clean up
712        assertTrue(removeAccount(am, renamedAccount, mActivity, null /* callback */).getBoolean(
713                AccountManager.KEY_BOOLEAN_RESULT));
714    }
715
716    /**
717     * Test getAccounts() and getAccountsByType()
718     */
719    public void testGetAccountsAndGetAccountsByType() {
720
721        assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
722        assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT_SAME_TYPE));
723
724        final int accountsCount = getAccountsCount();
725
726        // Add a first account
727        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
728
729        // Check that we have the new account
730        Account[] accounts = am.getAccounts();
731        assertEquals(1 + accountsCount, accounts.length);
732        assertEquals(true, isAccountPresent(accounts, ACCOUNT));
733
734        // Add another account
735        addAccountExplicitly(ACCOUNT_SAME_TYPE, ACCOUNT_PASSWORD, null /* userData */);
736
737        // Check that we have one more account again
738        accounts = am.getAccounts();
739        assertEquals(2 + accountsCount, accounts.length);
740        assertEquals(true, isAccountPresent(accounts, ACCOUNT_SAME_TYPE));
741
742        // Check if we have one from first type
743        accounts = am.getAccountsByType(ACCOUNT_TYPE);
744        assertEquals(2, accounts.length);
745
746        // Check if we dont have any account from the other type
747        accounts = am.getAccountsByType(ACCOUNT_TYPE_ABSENT);
748        assertEquals(0, accounts.length);
749    }
750
751    /**
752     * Test getAuthenticatorTypes()
753     */
754    public void testGetAuthenticatorTypes() {
755        AuthenticatorDescription[] types = am.getAuthenticatorTypes();
756        for(AuthenticatorDescription description: types) {
757            if (description.type.equals(ACCOUNT_TYPE)) {
758                return;
759            }
760        }
761        fail("should have found Authenticator type: " + ACCOUNT_TYPE);
762    }
763
764    /**
765     * Test setPassword() and getPassword()
766     */
767    public void testSetAndGetAndClearPassword() {
768        // Add a first account
769        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
770
771        // Check that the password is the one we defined
772        assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
773
774        // Clear the password and check that it is cleared
775        am.clearPassword(ACCOUNT);
776        assertNull(am.getPassword(ACCOUNT));
777
778        // Reset the password
779        am.setPassword(ACCOUNT, ACCOUNT_PASSWORD);
780
781        // Check that the password is the one we defined
782        assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
783    }
784
785    /**
786     * Test setUserData() and getUserData()
787     */
788    public void testSetAndGetUserData() {
789        // Add a first account
790        boolean result = am.addAccountExplicitly(ACCOUNT,
791                                ACCOUNT_PASSWORD,
792                                USERDATA_BUNDLE);
793
794        assertTrue(result);
795
796        // Check that the UserData is the one we defined
797        assertEquals(USERDATA_VALUE_1, am.getUserData(ACCOUNT, USERDATA_NAME_1));
798
799        am.setUserData(ACCOUNT, USERDATA_NAME_2, USERDATA_VALUE_2);
800
801        // Check that the UserData is the one we defined
802        assertEquals(USERDATA_VALUE_2, am.getUserData(ACCOUNT, USERDATA_NAME_2));
803    }
804
805    /**
806     * Test getAccountsByTypeAndFeatures()
807     */
808    public void testGetAccountsByTypeAndFeatures() throws IOException,
809            AuthenticatorException, OperationCanceledException {
810
811        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
812
813        AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
814                ACCOUNT_TYPE, REQUIRED_FEATURES, null, null);
815
816        Account[] accounts = futureAccounts.getResult();
817
818        assertNotNull(accounts);
819        assertEquals(1, accounts.length);
820        assertEquals(true, isAccountPresent(accounts, ACCOUNT));
821
822        futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
823                new String[] { NON_EXISTING_FEATURE },
824                null /* callback*/,
825                null /* handler */);
826        accounts = futureAccounts.getResult();
827
828        assertNotNull(accounts);
829        assertEquals(0, accounts.length);
830    }
831
832    /**
833     * Test getAccountsByTypeAndFeatures() with callback and handler
834     */
835    public void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler() throws IOException,
836            AuthenticatorException, OperationCanceledException {
837
838        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
839
840        testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(null /* handler */);
841        testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
842    }
843
844    private void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(Handler handler) throws
845            IOException, AuthenticatorException, OperationCanceledException {
846
847        final CountDownLatch latch1 = new CountDownLatch(1);
848
849        AccountManagerCallback<Account[]> callback1 = new AccountManagerCallback<Account[]>() {
850            @Override
851            public void run(AccountManagerFuture<Account[]> accountsFuture) {
852                try {
853                    Account[] accounts = accountsFuture.getResult();
854                    assertNotNull(accounts);
855                    assertEquals(1, accounts.length);
856                    assertEquals(true, isAccountPresent(accounts, ACCOUNT));
857                } catch (OperationCanceledException e) {
858                    fail("should not throw an OperationCanceledException");
859                } catch (IOException e) {
860                    fail("should not throw an IOException");
861                } catch (AuthenticatorException e) {
862                    fail("should not throw an AuthenticatorException");
863                } finally {
864                  latch1.countDown();
865                }
866            }
867        };
868
869        AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
870                ACCOUNT_TYPE,
871                REQUIRED_FEATURES,
872                callback1,
873                handler);
874
875        Account[] accounts = futureAccounts.getResult();
876
877        assertNotNull(accounts);
878        assertEquals(1, accounts.length);
879        assertEquals(true, isAccountPresent(accounts, ACCOUNT));
880
881        // Wait with timeout for the callback to do its work
882        try {
883            latch1.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
884        } catch (InterruptedException e) {
885            fail("should not throw an InterruptedException");
886        }
887
888        final CountDownLatch latch2 = new CountDownLatch(1);
889
890        AccountManagerCallback<Account[]> callback2 = new AccountManagerCallback<Account[]>() {
891            @Override
892            public void run(AccountManagerFuture<Account[]> accountsFuture) {
893                try {
894                    Account[] accounts = accountsFuture.getResult();
895                    assertNotNull(accounts);
896                    assertEquals(0, accounts.length);
897                } catch (OperationCanceledException e) {
898                    fail("should not throw an OperationCanceledException");
899                } catch (IOException e) {
900                    fail("should not throw an IOException");
901                } catch (AuthenticatorException e) {
902                    fail("should not throw an AuthenticatorException");
903                } finally {
904                  latch2.countDown();
905                }
906            }
907        };
908
909        accounts = null;
910
911        futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
912                new String[] { NON_EXISTING_FEATURE },
913                callback2,
914                handler);
915
916        accounts = futureAccounts.getResult();
917        assertNotNull(accounts);
918        assertEquals(0, accounts.length);
919
920        // Wait with timeout for the callback to do its work
921        try {
922            latch2.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
923        } catch (InterruptedException e) {
924            fail("should not throw an InterruptedException");
925        }
926    }
927
928    /**
929     * Test setAuthToken() and peekAuthToken()
930     */
931    public void testSetAndPeekAndInvalidateAuthToken() {
932        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
933        String expected = "x";
934        am.setAuthToken(ACCOUNT, AUTH_TOKEN_TYPE, expected);
935
936        // Ask for the AuthToken
937        String token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
938        assertNotNull(token);
939        assertEquals(expected, token);
940
941        am.invalidateAuthToken(ACCOUNT_TYPE, token);
942        token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
943        assertNull(token);
944    }
945
946    /**
947     * Test successful blockingGetAuthToken() with customTokens=false authenticator.
948     */
949    public void testBlockingGetAuthToken_DefaultToken_Success()
950            throws IOException, AuthenticatorException, OperationCanceledException {
951        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
952
953        String token = am.blockingGetAuthToken(ACCOUNT,
954                AUTH_TOKEN_TYPE,
955                false /* no failure notification */);
956
957        // Ask for the AuthToken
958        assertNotNull(token);
959        assertEquals(mockAuthenticator.getLastTokenServed(), token);
960    }
961
962    private static class BlockingGetAuthTokenFetcher implements TokenFetcher {
963        private final Account mAccount;
964
965        BlockingGetAuthTokenFetcher(Account account) {
966            mAccount = account;
967        }
968
969        @Override
970        public Bundle fetch(String tokenType)
971                throws OperationCanceledException, AuthenticatorException, IOException {
972            String token = am.blockingGetAuthToken(
973                    getAccount(),
974                    tokenType,
975                    false /* no failure notification */);
976            Bundle result = new Bundle();
977            result.putString(AccountManager.KEY_AUTHTOKEN, token);
978            result.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
979            result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
980            return result;
981        }
982        @Override
983        public Account getAccount() {
984            return CUSTOM_TOKEN_ACCOUNT;
985        }
986    }
987
988    /**
989     * Test successful blockingGetAuthToken() with customTokens=true authenticator.
990     */
991    public void testBlockingGetAuthToken_CustomToken_NoCaching_Success()
992            throws IOException, AuthenticatorException, OperationCanceledException {
993        addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
994        TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
995        validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
996    }
997
998    /**
999     * Test successful blockingGetAuthToken() with customTokens=true authenticator.
1000     */
1001    public void testBlockingGetAuthToken_CustomToken_ExpiringCache_Success()
1002            throws IOException, AuthenticatorException, OperationCanceledException {
1003        addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1004        TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
1005        validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
1006    }
1007
1008    /**
1009     * Test successful getAuthToken() using a future with customTokens=false authenticator.
1010     */
1011    public void testDeprecatedGetAuthTokenWithFuture_NoOptions_DefaultToken_Success()
1012            throws IOException, AuthenticatorException, OperationCanceledException {
1013        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1014        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1015                AUTH_TOKEN_TYPE,
1016                false /* no failure notification */,
1017                null /* no callback */,
1018                null /* no handler */
1019        );
1020
1021        Bundle resultBundle = futureBundle.getResult();
1022
1023        assertTrue(futureBundle.isDone());
1024        assertNotNull(resultBundle);
1025
1026        // Assert returned result
1027        validateAccountAndAuthTokenResult(resultBundle);
1028    }
1029
1030    /**
1031     * Test successful getAuthToken() using a future with customTokens=false without
1032     * expiring tokens.
1033     */
1034    public void testDeprecatedGetAuthTokenWithFuture_NoOptions_CustomToken_Success()
1035            throws IOException, AuthenticatorException, OperationCanceledException {
1036        addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1037        // validateSuccessfulTokenFetchingLifecycle(AccountManager am, TokenFetcher fetcher, String tokenType)
1038        TokenFetcher f = new TokenFetcher() {
1039            @Override
1040            public Bundle fetch(String tokenType)
1041                    throws OperationCanceledException, AuthenticatorException, IOException {
1042                AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
1043                        getAccount(),
1044                        tokenType,
1045                        false /* no failure notification */,
1046                        null /* no callback */,
1047                        null /* no handler */
1048                );
1049                Bundle actual = futureBundle.getResult();
1050                assertTrue(futureBundle.isDone());
1051                assertNotNull(actual);
1052                return actual;
1053            }
1054
1055            @Override
1056            public Account getAccount() {
1057                return CUSTOM_TOKEN_ACCOUNT;
1058            }
1059        };
1060        validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
1061        validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
1062    }
1063
1064    /**
1065     * Test successful getAuthToken() using a future with customTokens=false without
1066     * expiring tokens.
1067     */
1068    public void testGetAuthTokenWithFuture_Options_DefaultToken_Success()
1069            throws IOException, AuthenticatorException, OperationCanceledException {
1070        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1071
1072        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1073                AUTH_TOKEN_TYPE,
1074                OPTIONS_BUNDLE,
1075                mActivity,
1076                null /* no callback */,
1077                null /* no handler */
1078        );
1079
1080        Bundle resultBundle = futureBundle.getResult();
1081
1082        assertTrue(futureBundle.isDone());
1083        assertNotNull(resultBundle);
1084
1085        // Assert returned result
1086        validateAccountAndAuthTokenResult(resultBundle);
1087
1088        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1089        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1090        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1091        validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsGetAuthToken);
1092        validateSystemOptions(mockAuthenticator.mOptionsGetAuthToken);
1093    }
1094
1095    /**
1096     * Test successful getAuthToken() using a future with customTokens=false without
1097     * expiring tokens.
1098     */
1099    public void testGetAuthTokenWithFuture_Options_CustomToken_Success()
1100            throws IOException, AuthenticatorException, OperationCanceledException {
1101        addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1102        TokenFetcher fetcherWithOptions = new TokenFetcher() {
1103            @Override
1104            public Bundle fetch(String tokenType)
1105                    throws OperationCanceledException, AuthenticatorException, IOException {
1106                AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
1107                        getAccount(),
1108                        tokenType,
1109                        OPTIONS_BUNDLE,
1110                        false /* no failure notification */,
1111                        null /* no callback */,
1112                        null /* no handler */
1113                );
1114                Bundle actual = futureBundle.getResult();
1115                assertTrue(futureBundle.isDone());
1116                assertNotNull(actual);
1117                return actual;
1118            }
1119
1120            @Override
1121            public Account getAccount() {
1122                return CUSTOM_TOKEN_ACCOUNT;
1123            }
1124        };
1125        validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
1126        validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
1127    }
1128
1129
1130    /**
1131     * Test successful getAuthToken() using a future with customTokens=false without
1132     * expiring tokens.
1133     */
1134    public void testGetAuthTokenWithCallback_Options_Handler_DefaultToken_Success()
1135            throws IOException, AuthenticatorException, OperationCanceledException {
1136        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
1137        final HandlerThread handlerThread = new HandlerThread("accounts.test");
1138        handlerThread.start();
1139        TokenFetcher fetcherWithOptions = new TokenFetcher() {
1140            @Override
1141            public Bundle fetch(String tokenType)
1142                    throws OperationCanceledException, AuthenticatorException, IOException {
1143                final AtomicReference<Bundle> actualRef = new AtomicReference<>();
1144                final CountDownLatch latch = new CountDownLatch(1);
1145
1146                AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1147                    @Override
1148                    public void run(AccountManagerFuture<Bundle> bundleFuture) {
1149                        Bundle resultBundle = null;
1150                        try {
1151                            resultBundle = bundleFuture.getResult();
1152                            actualRef.set(resultBundle);
1153                        } catch (OperationCanceledException e) {
1154                            fail("should not throw an OperationCanceledException");
1155                        } catch (IOException e) {
1156                            fail("should not throw an IOException");
1157                        } catch (AuthenticatorException e) {
1158                            fail("should not throw an AuthenticatorException");
1159                        } finally {
1160                            latch.countDown();
1161                        }
1162                    }
1163                };
1164
1165                am.getAuthToken(getAccount(),
1166                        tokenType,
1167                        OPTIONS_BUNDLE,
1168                        false /* no failure notification */,
1169                        callback,
1170                        new Handler(handlerThread.getLooper()));
1171
1172                // Wait with timeout for the callback to do its work
1173                try {
1174                    latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1175                } catch (InterruptedException e) {
1176                    fail("should not throw an InterruptedException");
1177                }
1178                return actualRef.get();
1179            }
1180
1181            @Override
1182            public Account getAccount() {
1183                return ACCOUNT;
1184            }
1185        };
1186        validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
1187        validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
1188    }
1189
1190    /**
1191     * Test successful getAuthToken() using a future with customTokens=false without
1192     * expiring tokens.
1193     */
1194    public void testGetAuthTokenWithCallback_Options_Handler_CustomToken_Success()
1195            throws IOException, AuthenticatorException, OperationCanceledException {
1196        addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1197        final HandlerThread handlerThread = new HandlerThread("accounts.test");
1198        handlerThread.start();
1199        TokenFetcher fetcherWithOptions = new TokenFetcher() {
1200            @Override
1201            public Bundle fetch(String tokenType)
1202                    throws OperationCanceledException, AuthenticatorException, IOException {
1203                final AtomicReference<Bundle> actualRef = new AtomicReference<>();
1204                final CountDownLatch latch = new CountDownLatch(1);
1205
1206                AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1207                    @Override
1208                    public void run(AccountManagerFuture<Bundle> bundleFuture) {
1209                        Bundle resultBundle = null;
1210                        try {
1211                            resultBundle = bundleFuture.getResult();
1212                            actualRef.set(resultBundle);
1213                        } catch (OperationCanceledException e) {
1214                            fail("should not throw an OperationCanceledException");
1215                        } catch (IOException e) {
1216                            fail("should not throw an IOException");
1217                        } catch (AuthenticatorException e) {
1218                            fail("should not throw an AuthenticatorException");
1219                        } finally {
1220                            latch.countDown();
1221                        }
1222                    }
1223                };
1224
1225                am.getAuthToken(getAccount(),
1226                        tokenType,
1227                        OPTIONS_BUNDLE,
1228                        false /* no failure notification */,
1229                        callback,
1230                        new Handler(handlerThread.getLooper()));
1231
1232                // Wait with timeout for the callback to do its work
1233                try {
1234                    latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1235                } catch (InterruptedException e) {
1236                    fail("should not throw an InterruptedException");
1237                }
1238                return actualRef.get();
1239            }
1240
1241            @Override
1242            public Account getAccount() {
1243                return CUSTOM_TOKEN_ACCOUNT;
1244            }
1245        };
1246        validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
1247        validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
1248    }
1249
1250    /**
1251     * Test getAuthToken() with callback and handler
1252     */
1253    public void testGetAuthTokenWithCallbackAndHandler() throws IOException, AuthenticatorException,
1254            OperationCanceledException {
1255
1256        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1257
1258        testGetAuthTokenWithCallbackAndHandler(null /* handler */);
1259        testGetAuthTokenWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1260    }
1261
1262    private void testGetAuthTokenWithCallbackAndHandler(Handler handler) throws IOException,
1263            AuthenticatorException, OperationCanceledException {
1264
1265        final CountDownLatch latch = new CountDownLatch(1);
1266
1267        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1268            @Override
1269            public void run(AccountManagerFuture<Bundle> bundleFuture) {
1270
1271                Bundle resultBundle = null;
1272                try {
1273                    resultBundle = bundleFuture.getResult();
1274
1275                    // Assert returned result
1276                    validateAccountAndAuthTokenResult(resultBundle);
1277
1278                } catch (OperationCanceledException e) {
1279                    fail("should not throw an OperationCanceledException");
1280                } catch (IOException e) {
1281                    fail("should not throw an IOException");
1282                } catch (AuthenticatorException e) {
1283                    fail("should not throw an AuthenticatorException");
1284                }
1285                finally {
1286                    latch.countDown();
1287                }
1288            }
1289        };
1290
1291        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1292                AUTH_TOKEN_TYPE,
1293                false /* no failure notification */,
1294                callback,
1295                handler
1296        );
1297
1298        Bundle resultBundle = futureBundle.getResult();
1299
1300        assertTrue(futureBundle.isDone());
1301        assertNotNull(resultBundle);
1302
1303        // Wait with timeout for the callback to do its work
1304        try {
1305            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1306        } catch (InterruptedException e) {
1307            fail("should not throw an InterruptedException");
1308        }
1309    }
1310
1311    /**
1312     * test getAuthToken() with options and callback and handler
1313     */
1314    public void testGetAuthTokenWithOptionsAndCallback() throws IOException,
1315            AuthenticatorException, OperationCanceledException {
1316
1317        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1318
1319        testGetAuthTokenWithOptionsAndCallbackAndHandler(null /* handler */);
1320        testGetAuthTokenWithOptionsAndCallbackAndHandler(new Handler(Looper.getMainLooper()));
1321    }
1322
1323    private void testGetAuthTokenWithOptionsAndCallbackAndHandler(Handler handler) throws
1324            IOException, AuthenticatorException, OperationCanceledException {
1325
1326        final CountDownLatch latch = new CountDownLatch(1);
1327
1328        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1329            @Override
1330            public void run(AccountManagerFuture<Bundle> bundleFuture) {
1331
1332                Bundle resultBundle = null;
1333                try {
1334                    resultBundle = bundleFuture.getResult();
1335                    // Assert returned result
1336                    validateAccountAndAuthTokenResult(resultBundle);
1337                } catch (OperationCanceledException e) {
1338                    fail("should not throw an OperationCanceledException");
1339                } catch (IOException e) {
1340                    fail("should not throw an IOException");
1341                } catch (AuthenticatorException e) {
1342                    fail("should not throw an AuthenticatorException");
1343                }
1344                finally {
1345                    latch.countDown();
1346                }
1347            }
1348        };
1349
1350        AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1351                AUTH_TOKEN_TYPE,
1352                OPTIONS_BUNDLE,
1353                mActivity,
1354                callback,
1355                handler
1356        );
1357
1358        Bundle resultBundle = futureBundle.getResult();
1359
1360        assertTrue(futureBundle.isDone());
1361        assertNotNull(resultBundle);
1362
1363        // Wait with timeout for the callback to do its work
1364        try {
1365            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1366        } catch (InterruptedException e) {
1367            fail("should not throw an InterruptedException");
1368        }
1369    }
1370
1371    /**
1372     * Test getAuthTokenByFeatures()
1373     */
1374    public void testGetAuthTokenByFeatures() throws IOException, AuthenticatorException,
1375            OperationCanceledException {
1376
1377        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1378
1379        Bundle resultBundle = getAuthTokenByFeature(
1380                new String[] { NON_EXISTING_FEATURE },
1381                null /* activity */
1382        );
1383
1384        // Assert returned result
1385        validateNullResult(resultBundle);
1386
1387        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1388        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1389        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1390        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1391
1392        mockAuthenticator.clearData();
1393
1394        // Now test with existing features and an activity
1395        resultBundle = getAuthTokenByFeature(
1396                new String[] { NON_EXISTING_FEATURE },
1397                mActivity
1398        );
1399
1400        // Assert returned result
1401        validateAccountAndAuthTokenResult(resultBundle);
1402
1403        validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
1404        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1405        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1406        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1407
1408        mockAuthenticator.clearData();
1409
1410        // Now test with existing features and no activity
1411        resultBundle = getAuthTokenByFeature(
1412                REQUIRED_FEATURES,
1413                null /* activity */
1414        );
1415
1416        // Assert returned result
1417        validateAccountAndAuthTokenResult(resultBundle);
1418
1419        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1420        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1421        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1422        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1423
1424        mockAuthenticator.clearData();
1425
1426        // Now test with existing features and an activity
1427        resultBundle = getAuthTokenByFeature(
1428                REQUIRED_FEATURES,
1429                mActivity
1430        );
1431
1432        // Assert returned result
1433        validateAccountAndAuthTokenResult(resultBundle);
1434
1435        validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1436        validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1437        validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1438        validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1439    }
1440
1441    /**
1442     * Test confirmCredentials()
1443     */
1444    @Presubmit
1445    public void testConfirmCredentials() throws IOException, AuthenticatorException,
1446            OperationCanceledException {
1447
1448        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1449
1450        AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
1451                OPTIONS_BUNDLE,
1452                mActivity,
1453                null /* callback*/,
1454                null /* handler */);
1455
1456        futureBundle.getResult();
1457
1458        // Assert returned result
1459        validateCredentials();
1460    }
1461
1462    /**
1463     * Tests the setting of lastAuthenticatedTime on adding account
1464     */
1465    public void testLastAuthenticatedTimeAfterAddAccount() throws IOException,
1466            AuthenticatorException, OperationCanceledException {
1467        assertTrue(addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD) > 0);
1468    }
1469
1470    /**
1471     * Test confirmCredentials() for account not on device. Just that no error
1472     * should be thrown.
1473     */
1474    public void testConfirmCredentialsAccountNotOnDevice() throws IOException,
1475            AuthenticatorException, OperationCanceledException {
1476
1477        Account account = new Account("AccountNotOnThisDevice", ACCOUNT_TYPE);
1478        AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(account,
1479                OPTIONS_BUNDLE,
1480                mActivity,
1481                null /* callback */,
1482                null /* handler */);
1483
1484        futureBundle.getResult();
1485    }
1486
1487    /**
1488     * Tests the setting of lastAuthenticatedTime on confirmCredentials being
1489     * successful.
1490     */
1491    public void testLastAuthenticatedTimeAfterConfirmCredentialsSuccess() throws IOException,
1492            AuthenticatorException, OperationCanceledException {
1493
1494        long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
1495
1496        // Now this confirm credentials call returns true, which in turn
1497        // should update the last authenticated timestamp.
1498        Bundle result = am.confirmCredentials(ACCOUNT,
1499                OPTIONS_BUNDLE, /* options */
1500                null, /* activity */
1501                null /* callback */,
1502                null /* handler */).getResult();
1503        long confirmedCredTime = result.getLong(
1504                AccountManager.KEY_LAST_AUTHENTICATED_TIME, -1);
1505        assertTrue(confirmedCredTime > accountAddTime);
1506    }
1507
1508    /**
1509     * Tests the setting of lastAuthenticatedTime on updateCredentials being
1510     * successful.
1511     */
1512    public void testLastAuthenticatedTimeAfterUpdateCredentialsSuccess() throws IOException,
1513            AuthenticatorException, OperationCanceledException {
1514
1515        long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
1516
1517        am.updateCredentials(ACCOUNT,
1518                AUTH_TOKEN_TYPE,
1519                OPTIONS_BUNDLE,
1520                mActivity,
1521                null /* callback */,
1522                null /* handler */).getResult();
1523        long updateCredTime = getLastAuthenticatedTime(ACCOUNT);
1524        assertTrue(updateCredTime > accountAddTime);
1525    }
1526
1527    /**
1528     * LastAuthenticatedTime on setPassword should not be disturbed.
1529     */
1530    public void testLastAuthenticatedTimeAfterSetPassword() throws IOException,
1531            AuthenticatorException, OperationCanceledException {
1532        long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
1533        mockAuthenticator.callSetPassword();
1534        long setPasswordTime = getLastAuthenticatedTime(ACCOUNT);
1535        assertTrue(setPasswordTime == accountAddTime);
1536    }
1537
1538    /**
1539     * Test confirmCredentials() with callback
1540     */
1541    public void testConfirmCredentialsWithCallbackAndHandler() {
1542
1543        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1544
1545        testConfirmCredentialsWithCallbackAndHandler(null /* handler */);
1546        testConfirmCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1547    }
1548
1549    private void testConfirmCredentialsWithCallbackAndHandler(Handler handler) {
1550        final CountDownLatch latch = new CountDownLatch(1);
1551        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1552            @Override
1553            public void run(AccountManagerFuture<Bundle> bundleFuture) {
1554
1555                Bundle resultBundle = null;
1556                try {
1557                    resultBundle = bundleFuture.getResult();
1558
1559                    // Assert returned result
1560                    validateCredentials();
1561
1562                    assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
1563                } catch (OperationCanceledException e) {
1564                    fail("should not throw an OperationCanceledException");
1565                } catch (IOException e) {
1566                    fail("should not throw an IOException");
1567                } catch (AuthenticatorException e) {
1568                    fail("should not throw an AuthenticatorException");
1569                }
1570                finally {
1571                    latch.countDown();
1572                }
1573            }
1574        };
1575        AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
1576                OPTIONS_BUNDLE,
1577                mActivity,
1578                callback,
1579                handler);
1580        // Wait with timeout for the callback to do its work
1581        try {
1582            latch.await(3 * LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1583        } catch (InterruptedException e) {
1584            fail("should not throw an InterruptedException");
1585        }
1586    }
1587
1588    /**
1589     * Test updateCredentials()
1590     */
1591    public void testUpdateCredentials() throws IOException, AuthenticatorException,
1592            OperationCanceledException {
1593
1594        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1595
1596        AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
1597                AUTH_TOKEN_TYPE,
1598                OPTIONS_BUNDLE,
1599                mActivity,
1600                null /* callback*/,
1601                null /* handler */);
1602
1603        Bundle result = futureBundle.getResult();
1604
1605        validateAccountAndNoAuthTokenResult(result);
1606
1607        // Assert returned result
1608        validateCredentials();
1609    }
1610
1611    /**
1612     * Test updateCredentials() with callback and handler
1613     */
1614    public void testUpdateCredentialsWithCallbackAndHandler() throws IOException,
1615            AuthenticatorException, OperationCanceledException {
1616
1617        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1618
1619        testUpdateCredentialsWithCallbackAndHandler(null /* handler */);
1620        testUpdateCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1621    }
1622
1623    private void testUpdateCredentialsWithCallbackAndHandler(Handler handler) throws IOException,
1624            AuthenticatorException, OperationCanceledException {
1625
1626        final CountDownLatch latch = new CountDownLatch(1);
1627
1628        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1629            @Override
1630            public void run(AccountManagerFuture<Bundle> bundleFuture) {
1631
1632                Bundle resultBundle = null;
1633                try {
1634                    resultBundle = bundleFuture.getResult();
1635
1636                    // Assert returned result
1637                    validateCredentials();
1638                    assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
1639
1640                } catch (OperationCanceledException e) {
1641                    fail("should not throw an OperationCanceledException");
1642                } catch (IOException e) {
1643                    fail("should not throw an IOException");
1644                } catch (AuthenticatorException e) {
1645                    fail("should not throw an AuthenticatorException");
1646                }
1647                finally {
1648                    latch.countDown();
1649                }
1650            }
1651        };
1652
1653        AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
1654                AUTH_TOKEN_TYPE,
1655                OPTIONS_BUNDLE,
1656                mActivity,
1657                callback,
1658                handler);
1659
1660        futureBundle.getResult();
1661
1662        // Wait with timeout for the callback to do its work
1663        try {
1664            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1665        } catch (InterruptedException e) {
1666            fail("should not throw an InterruptedException");
1667        }
1668    }
1669
1670    /**
1671     * Test editProperties()
1672     */
1673    public void testEditProperties() throws IOException, AuthenticatorException,
1674            OperationCanceledException {
1675
1676        AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
1677                mActivity,
1678                null /* callback */,
1679                null /* handler*/);
1680
1681        Bundle result = futureBundle.getResult();
1682
1683        validateAccountAndNoAuthTokenResult(result);
1684
1685        // Assert returned result
1686        assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
1687    }
1688
1689    /**
1690     * Test editProperties() with callback and handler
1691     */
1692    public void testEditPropertiesWithCallbackAndHandler() {
1693        testEditPropertiesWithCallbackAndHandler(null /* handler */);
1694        testEditPropertiesWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1695    }
1696
1697    private void testEditPropertiesWithCallbackAndHandler(Handler handler) {
1698        final CountDownLatch latch = new CountDownLatch(1);
1699
1700        AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1701            @Override
1702            public void run(AccountManagerFuture<Bundle> bundleFuture) {
1703                try {
1704                    // Assert returned result
1705                    assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
1706                }
1707                finally {
1708                    latch.countDown();
1709                }
1710            }
1711        };
1712
1713        AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
1714                mActivity,
1715                callback,
1716                handler);
1717
1718        // Wait with timeout for the callback to do its work
1719        try {
1720            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1721        } catch (InterruptedException e) {
1722            fail("should not throw an InterruptedException");
1723        }
1724    }
1725
1726    /**
1727     * Test addOnAccountsUpdatedListener() with handler
1728     */
1729    public void testAddOnAccountsUpdatedListenerWithHandler() throws IOException,
1730            AuthenticatorException, OperationCanceledException {
1731
1732        testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
1733                false /* updateImmediately */);
1734
1735        // Need to cleanup intermediate state
1736        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1737                AccountManager.KEY_BOOLEAN_RESULT));
1738
1739        testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
1740                true /* updateImmediately */);
1741
1742        // Need to cleanup intermediate state
1743        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1744                AccountManager.KEY_BOOLEAN_RESULT));
1745
1746        testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
1747                false /* updateImmediately */);
1748
1749        // Need to cleanup intermediate state
1750        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1751                AccountManager.KEY_BOOLEAN_RESULT));
1752
1753        testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
1754                true /* updateImmediately */);
1755    }
1756
1757    private void testAddOnAccountsUpdatedListenerWithHandler(Handler handler,
1758            boolean updateImmediately) {
1759
1760        final CountDownLatch latch = new CountDownLatch(1);
1761
1762        OnAccountsUpdateListener listener = new OnAccountsUpdateListener() {
1763            @Override
1764            public void onAccountsUpdated(Account[] accounts) {
1765                latch.countDown();
1766            }
1767        };
1768
1769        // Add a listener
1770        am.addOnAccountsUpdatedListener(listener,
1771                handler,
1772                updateImmediately);
1773
1774        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1775
1776        // Wait with timeout for the callback to do its work
1777        try {
1778            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1779        } catch (InterruptedException e) {
1780            fail("should not throw an InterruptedException");
1781        }
1782
1783        // Cleanup
1784        am.removeOnAccountsUpdatedListener(listener);
1785    }
1786
1787    /**
1788     * Test removeOnAccountsUpdatedListener() with handler
1789     */
1790    public void testRemoveOnAccountsUpdatedListener() throws IOException, AuthenticatorException,
1791            OperationCanceledException {
1792
1793        testRemoveOnAccountsUpdatedListenerWithHandler(null /* handler */);
1794
1795        // Need to cleanup intermediate state
1796        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1797                AccountManager.KEY_BOOLEAN_RESULT));
1798
1799        testRemoveOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()));
1800    }
1801
1802    private void testRemoveOnAccountsUpdatedListenerWithHandler(Handler handler) {
1803        final CountDownLatch latch = new CountDownLatch(1);
1804
1805        OnAccountsUpdateListener listener = new OnAccountsUpdateListener() {
1806            @Override
1807            public void onAccountsUpdated(Account[] accounts) {
1808                fail("should not be called");
1809            }
1810        };
1811
1812        // First add a listener
1813        am.addOnAccountsUpdatedListener(listener,
1814                handler,
1815                false /* updateImmediately */);
1816
1817        // Then remove the listener
1818        am.removeOnAccountsUpdatedListener(listener);
1819
1820        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1821
1822        // Wait with timeout for the callback to do its work
1823        try {
1824            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1825        } catch (InterruptedException e) {
1826            fail("should not throw an InterruptedException");
1827        }
1828    }
1829
1830    /**
1831     * Test hasFeature
1832     */
1833    public void testHasFeature()
1834            throws IOException, AuthenticatorException, OperationCanceledException {
1835
1836        assertHasFeature(null /* handler */);
1837        assertHasFeature(new Handler(Looper.getMainLooper()));
1838
1839        assertHasFeatureWithCallback(null /* handler */);
1840        assertHasFeatureWithCallback(new Handler(Looper.getMainLooper()));
1841    }
1842
1843    private void assertHasFeature(Handler handler)
1844            throws IOException, AuthenticatorException, OperationCanceledException {
1845        Bundle resultBundle = addAccount(am,
1846                ACCOUNT_TYPE,
1847                AUTH_TOKEN_TYPE,
1848                REQUIRED_FEATURES,
1849                OPTIONS_BUNDLE,
1850                mActivity,
1851                null /* callback */,
1852                null /* handler */);
1853
1854        // Assert parameters has been passed correctly
1855        validateAccountAndAuthTokenType();
1856        validateFeatures();
1857
1858        AccountManagerFuture<Boolean> booleanFuture = am.hasFeatures(ACCOUNT,
1859                new String[]{FEATURE_1},
1860                null /* callback */,
1861                handler);
1862        assertTrue(booleanFuture.getResult());
1863
1864        booleanFuture = am.hasFeatures(ACCOUNT,
1865                new String[]{FEATURE_2},
1866                null /* callback */,
1867                handler);
1868        assertTrue(booleanFuture.getResult());
1869
1870        booleanFuture = am.hasFeatures(ACCOUNT,
1871                new String[]{FEATURE_1, FEATURE_2},
1872                null /* callback */,
1873                handler);
1874        assertTrue(booleanFuture.getResult());
1875
1876        booleanFuture = am.hasFeatures(ACCOUNT,
1877                new String[]{NON_EXISTING_FEATURE},
1878                null /* callback */,
1879                handler);
1880        assertFalse(booleanFuture.getResult());
1881
1882        booleanFuture = am.hasFeatures(ACCOUNT,
1883                new String[]{NON_EXISTING_FEATURE, FEATURE_1},
1884                null /* callback */,
1885                handler);
1886        assertFalse(booleanFuture.getResult());
1887
1888        booleanFuture = am.hasFeatures(ACCOUNT,
1889                new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
1890                null /* callback */,
1891                handler);
1892        assertFalse(booleanFuture.getResult());
1893    }
1894
1895    private AccountManagerCallback<Boolean> getAssertTrueCallback(final CountDownLatch latch) {
1896        return new AccountManagerCallback<Boolean>() {
1897            @Override
1898            public void run(AccountManagerFuture<Boolean> booleanFuture) {
1899                try {
1900                    // Assert returned result should be TRUE
1901                    assertTrue(booleanFuture.getResult());
1902                } catch (Exception e) {
1903                    fail("Exception: " + e);
1904                } finally {
1905                    latch.countDown();
1906                }
1907            }
1908        };
1909    }
1910
1911    private AccountManagerCallback<Boolean> getAssertFalseCallback(final CountDownLatch latch) {
1912        return new AccountManagerCallback<Boolean>() {
1913            @Override
1914            public void run(AccountManagerFuture<Boolean> booleanFuture) {
1915                try {
1916                    // Assert returned result should be FALSE
1917                    assertFalse(booleanFuture.getResult());
1918                } catch (Exception e) {
1919                    fail("Exception: " + e);
1920                } finally {
1921                    latch.countDown();
1922                }
1923            }
1924        };
1925    }
1926
1927    private void waitForLatch(final CountDownLatch latch) {
1928        // Wait with timeout for the callback to do its work
1929        try {
1930            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1931        } catch (InterruptedException e) {
1932            fail("should not throw an InterruptedException");
1933        }
1934    }
1935
1936    private void assertHasFeatureWithCallback(Handler handler)
1937            throws IOException, AuthenticatorException, OperationCanceledException {
1938        Bundle resultBundle = addAccount(am,
1939                ACCOUNT_TYPE,
1940                AUTH_TOKEN_TYPE,
1941                REQUIRED_FEATURES,
1942                OPTIONS_BUNDLE,
1943                mActivity,
1944                null /* callback */,
1945                null /* handler */);
1946
1947        // Assert parameters has been passed correctly
1948        validateAccountAndAuthTokenType();
1949        validateFeatures();
1950
1951        CountDownLatch latch = new CountDownLatch(1);
1952        am.hasFeatures(ACCOUNT,
1953                new String[]{FEATURE_1},
1954                getAssertTrueCallback(latch),
1955                handler);
1956        waitForLatch(latch);
1957
1958        latch = new CountDownLatch(1);
1959        am.hasFeatures(ACCOUNT,
1960                new String[]{FEATURE_2},
1961                getAssertTrueCallback(latch),
1962                handler);
1963        waitForLatch(latch);
1964
1965        latch = new CountDownLatch(1);
1966        am.hasFeatures(ACCOUNT,
1967                new String[]{FEATURE_1, FEATURE_2},
1968                getAssertTrueCallback(latch),
1969                handler);
1970        waitForLatch(latch);
1971
1972        latch = new CountDownLatch(1);
1973        am.hasFeatures(ACCOUNT,
1974                new String[]{NON_EXISTING_FEATURE},
1975                getAssertFalseCallback(latch),
1976                handler);
1977        waitForLatch(latch);
1978
1979        latch = new CountDownLatch(1);
1980        am.hasFeatures(ACCOUNT,
1981                new String[]{NON_EXISTING_FEATURE, FEATURE_1},
1982                getAssertFalseCallback(latch),
1983                handler);
1984        waitForLatch(latch);
1985
1986        latch = new CountDownLatch(1);
1987        am.hasFeatures(ACCOUNT,
1988                new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
1989                getAssertFalseCallback(latch),
1990                handler);
1991        waitForLatch(latch);
1992    }
1993
1994    private long getLastAuthenticatedTime(Account account) throws OperationCanceledException,
1995            AuthenticatorException, IOException {
1996        Bundle options = new Bundle();
1997        options.putBoolean(MockAccountAuthenticator.KEY_RETURN_INTENT, true);
1998        // Not really confirming, but a way to get last authenticated timestamp
1999        Bundle result = am.confirmCredentials(account,
2000                options,// OPTIONS_BUNDLE,
2001                null, /* activity */
2002                null /* callback */,
2003                null /* handler */).getResult();
2004        return result.getLong(
2005                AccountManager.KEY_LAST_AUTHENTICATED_TIME, -1);
2006    }
2007
2008    private long addAccountAndReturnAccountAddedTime(Account account, String password)
2009            throws OperationCanceledException, AuthenticatorException, IOException {
2010        addAccount(am,
2011                ACCOUNT_TYPE,
2012                AUTH_TOKEN_TYPE,
2013                REQUIRED_FEATURES,
2014                OPTIONS_BUNDLE,
2015                mActivity,
2016                null /* callback */,
2017                null /* handler */);
2018        return getLastAuthenticatedTime(account);
2019    }
2020
2021    /**
2022     * Tests that AccountManagerService is properly caching data.
2023     */
2024    public void testGetsAreCached() {
2025
2026        // Add an account,
2027        assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
2028        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
2029
2030        // Then verify that we don't hit disk retrieving it,
2031        StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
2032        try {
2033            StrictMode.setThreadPolicy(
2034                new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build());
2035            Account[] accounts = am.getAccounts();
2036            assertNotNull(accounts);
2037            assertTrue(accounts.length > 0);
2038        } finally {
2039            StrictMode.setThreadPolicy(oldPolicy);
2040        }
2041    }
2042
2043}
2044