• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.accounts.cts;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.accounts.AccountManagerCallback;
22 import android.accounts.AccountManagerFuture;
23 import android.accounts.AuthenticatorDescription;
24 import android.accounts.AuthenticatorException;
25 import android.accounts.OnAccountsUpdateListener;
26 import android.accounts.OperationCanceledException;
27 import android.accounts.cts.common.Fixtures;
28 import android.app.Activity;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.os.Looper;
35 import android.os.StrictMode;
36 import android.platform.test.annotations.AppModeFull;
37 import android.platform.test.annotations.Presubmit;
38 import android.test.ActivityInstrumentationTestCase2;
39 
40 import java.io.IOException;
41 import java.lang.Math;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.HashMap;
45 import java.util.Map;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.atomic.AtomicReference;
49 
50 /**
51  * You can run those unit tests with the following command line:
52  *
53  *  adb shell am instrument
54  *   -e debug false -w
55  *   -e class android.accounts.cts.AccountManagerTest
56  * android.accounts.cts/androidx.test.runner.AndroidJUnitRunner
57  */
58 public class AccountManagerTest extends ActivityInstrumentationTestCase2<AccountDummyActivity> {
59 
60     public static final String ACCOUNT_NAME = "android.accounts.cts.account.name";
61     public static final String ACCOUNT_NEW_NAME = "android.accounts.cts.account.name.rename";
62     public static final String ACCOUNT_NAME_OTHER = "android.accounts.cts.account.name.other";
63 
64     public static final String ACCOUNT_TYPE = "android.accounts.cts.account.type";
65     public static final String ACCOUNT_TYPE_CUSTOM = "android.accounts.cts.custom.account.type";
66     public static final String ACCOUNT_TYPE_ABSENT = "android.accounts.cts.account.type.absent";
67 
68     public static final String ACCOUNT_PASSWORD = "android.accounts.cts.account.password";
69 
70     public static final String ACCOUNT_STATUS_TOKEN = "android.accounts.cts.account.status.token";
71 
72     public static final String AUTH_TOKEN_TYPE = "mockAuthTokenType";
73     public static final String AUTH_EXPIRING_TOKEN_TYPE = "mockAuthExpiringTokenType";
74     public static final String AUTH_TOKEN_LABEL = "mockAuthTokenLabel";
75     public static final long AUTH_TOKEN_DURATION_MILLIS = 10000L; // Ten seconds.
76 
77     public static final String FEATURE_1 = "feature.1";
78     public static final String FEATURE_2 = "feature.2";
79     public static final String NON_EXISTING_FEATURE = "feature.3";
80 
81     public static final String OPTION_NAME_1 = "option.name.1";
82     public static final String OPTION_VALUE_1 = "option.value.1";
83 
84     public static final String OPTION_NAME_2 = "option.name.2";
85     public static final String OPTION_VALUE_2 = "option.value.2";
86 
87     public static final String[] REQUIRED_FEATURES = new String[] { FEATURE_1, FEATURE_2 };
88 
89     public static final Bundle OPTIONS_BUNDLE = new Bundle();
90 
91     public static final Bundle USERDATA_BUNDLE = new Bundle();
92 
93     public static final String USERDATA_NAME_1 = "user.data.name.1";
94     public static final String USERDATA_NAME_2 = "user.data.name.2";
95     public static final String USERDATA_VALUE_1 = "user.data.value.1";
96     public static final String USERDATA_VALUE_2 = "user.data.value.2";
97 
98     public static final Account ACCOUNT = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
99     public static final Account ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API = new Account(
100             MockAccountAuthenticator.ACCOUNT_NAME_FOR_NEW_REMOVE_API, ACCOUNT_TYPE);
101     public static final Account ACCOUNT_FOR_DEFAULT_IMPL = new Account(
102             MockAccountAuthenticator.ACCOUNT_NAME_FOR_DEFAULT_IMPL, ACCOUNT_TYPE);
103     public static final Account ACCOUNT_SAME_TYPE = new Account(ACCOUNT_NAME_OTHER, ACCOUNT_TYPE);
104 
105     public static final Account CUSTOM_TOKEN_ACCOUNT =
106             new Account(ACCOUNT_NAME,ACCOUNT_TYPE_CUSTOM);
107 
108     // Installed packages to test visibility API.
109     public static final String PACKAGE_NAME_1 = "android.accounts.cts.unaffiliated";
110     public static final String PACKAGE_NAME_PRIVILEGED = "android.accounts.cts"; // authenticator
111 
112     public static final Bundle SESSION_BUNDLE = new Bundle();
113     public static final String SESSION_DATA_NAME_1 = "session.data.name.1";
114     public static final String SESSION_DATA_VALUE_1 = "session.data.value.1";
115 
116     public static final String ERROR_MESSAGE = "android.accounts.cts.account.error.message";
117 
118     public static final String KEY_CIPHER = "cipher";
119     public static final String KEY_MAC = "mac";
120 
121     private static MockAccountAuthenticator mockAuthenticator;
122     private static final int LATCH_TIMEOUT_MS = 1000;
123     private static AccountManager am;
124 
getMockAuthenticator(Context context)125     public synchronized static MockAccountAuthenticator getMockAuthenticator(Context context) {
126         if (null == mockAuthenticator) {
127             mockAuthenticator = new MockAccountAuthenticator(context);
128         }
129         return mockAuthenticator;
130     }
131 
132     private Activity mActivity;
133     private Context mContext;
134 
AccountManagerTest()135     public AccountManagerTest() {
136         super(AccountDummyActivity.class);
137     }
138 
139     @Override
setUp()140     public void setUp() throws Exception {
141         super.setUp();
142         mActivity = getActivity();
143         mContext = getInstrumentation().getTargetContext();
144 
145         OPTIONS_BUNDLE.putString(OPTION_NAME_1, OPTION_VALUE_1);
146         OPTIONS_BUNDLE.putString(OPTION_NAME_2, OPTION_VALUE_2);
147 
148         USERDATA_BUNDLE.putString(USERDATA_NAME_1, USERDATA_VALUE_1);
149 
150         SESSION_BUNDLE.putString(SESSION_DATA_NAME_1, SESSION_DATA_VALUE_1);
151         SESSION_BUNDLE.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
152 
153         getMockAuthenticator(mContext);
154 
155         am = AccountManager.get(mContext);
156     }
157 
158     @Override
tearDown()159     public void tearDown() throws Exception, AuthenticatorException, OperationCanceledException {
160         mockAuthenticator.clearData();
161 
162         // Need to clean up created account
163         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
164                 AccountManager.KEY_BOOLEAN_RESULT));
165         assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, mActivity, null /* callback */).getBoolean(
166                 AccountManager.KEY_BOOLEAN_RESULT));
167 
168         // Clean out any other accounts added during the tests.
169         Account[] ctsAccounts = am.getAccountsByType(ACCOUNT_TYPE);
170         Account[] ctsCustomAccounts = am.getAccountsByType(ACCOUNT_TYPE_CUSTOM);
171         ArrayList<Account> accounts = new ArrayList<>(Arrays.asList(ctsAccounts));
172         accounts.addAll(Arrays.asList(ctsCustomAccounts));
173         for (Account ctsAccount : accounts) {
174             removeAccount(am, ctsAccount, mActivity, null /* callback */);
175         }
176 
177         // need to clean up the authenticator cached data
178         mockAuthenticator.clearData();
179 
180         super.tearDown();
181     }
182 
183     interface TokenFetcher {
fetch(String tokenType)184         public Bundle fetch(String tokenType)
185                 throws OperationCanceledException, AuthenticatorException, IOException;
getAccount()186         public Account getAccount();
187     }
188 
validateSuccessfulTokenFetchingLifecycle(TokenFetcher fetcher, String tokenType)189     private void validateSuccessfulTokenFetchingLifecycle(TokenFetcher fetcher, String tokenType)
190             throws OperationCanceledException, AuthenticatorException, IOException {
191         Account account = fetcher.getAccount();
192         Bundle expected = new Bundle();
193         expected.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
194         expected.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
195 
196         // First fetch.
197         Bundle actual = fetcher.fetch(tokenType);
198         assertTrue(mockAuthenticator.isRecentlyCalled());
199         validateAccountAndAuthTokenResult(expected, actual);
200 
201         /*
202          * On the second fetch the cache will be populated if we are using a authenticator with
203          * customTokens=false or we are using a scope that will cause the authenticator to set an
204          * expiration time (and that expiration time hasn't been reached).
205          */
206         actual = fetcher.fetch(tokenType);
207 
208         boolean isCachingExpected =
209                 ACCOUNT_TYPE.equals(account.type) || AUTH_EXPIRING_TOKEN_TYPE.equals(tokenType);
210         assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
211         validateAccountAndAuthTokenResult(expected, actual);
212 
213         try {
214             // Delay further execution until expiring tokens can actually expire.
215             Thread.sleep(mockAuthenticator.getTokenDurationMillis() + 50L);
216         } catch (InterruptedException e) {
217             throw new RuntimeException(e);
218         }
219 
220         /*
221          * With the time shift above, the third request will result in cache hits only from
222          * customToken=false authenticators.
223          */
224         actual = fetcher.fetch(tokenType);
225         isCachingExpected = ACCOUNT_TYPE.equals(account.type);
226         assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
227         validateAccountAndAuthTokenResult(expected, actual);
228 
229         // invalidate token
230         String token = actual.getString(AccountManager.KEY_AUTHTOKEN);
231         am.invalidateAuthToken(account.type, token);
232 
233         /*
234          * Upon invalidating the token, the cache should be clear regardless of authenticator.
235          */
236         actual = fetcher.fetch(tokenType);
237         assertTrue(mockAuthenticator.isRecentlyCalled());
238         validateAccountAndAuthTokenResult(expected, actual);
239     }
240 
validateAccountAndAuthTokenResult(Bundle actual)241     private void validateAccountAndAuthTokenResult(Bundle actual) {
242         assertEquals(
243                 ACCOUNT.name,
244                 actual.get(AccountManager.KEY_ACCOUNT_NAME));
245         assertEquals(
246                 ACCOUNT.type,
247                 actual.get(AccountManager.KEY_ACCOUNT_TYPE));
248         assertEquals(
249                 mockAuthenticator.getLastTokenServed(),
250                 actual.get(AccountManager.KEY_AUTHTOKEN));
251     }
252 
validateAccountAndAuthTokenResult(Bundle expected, Bundle actual)253     private void validateAccountAndAuthTokenResult(Bundle expected, Bundle actual) {
254         assertEquals(
255                 expected.get(AccountManager.KEY_ACCOUNT_NAME),
256                 actual.get(AccountManager.KEY_ACCOUNT_NAME));
257         assertEquals(
258                 expected.get(AccountManager.KEY_ACCOUNT_TYPE),
259                 actual.get(AccountManager.KEY_ACCOUNT_TYPE));
260         assertEquals(
261                 mockAuthenticator.getLastTokenServed(),
262                 actual.get(AccountManager.KEY_AUTHTOKEN));
263     }
264 
validateAccountAndNoAuthTokenResult(Bundle result)265     private void validateAccountAndNoAuthTokenResult(Bundle result) {
266         assertEquals(ACCOUNT_NAME, result.get(AccountManager.KEY_ACCOUNT_NAME));
267         assertEquals(ACCOUNT_TYPE, result.get(AccountManager.KEY_ACCOUNT_TYPE));
268         assertNull(result.get(AccountManager.KEY_AUTHTOKEN));
269     }
270 
validateNullResult(Bundle resultBundle)271     private void validateNullResult(Bundle resultBundle) {
272         assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
273         assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
274         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
275     }
276 
validateAccountAndAuthTokenType()277     private void validateAccountAndAuthTokenType() {
278         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
279         assertEquals(AUTH_TOKEN_TYPE, mockAuthenticator.getAuthTokenType());
280     }
281 
validateFeatures()282     private void validateFeatures() {
283         assertEquals(REQUIRED_FEATURES[0], mockAuthenticator.getRequiredFeatures()[0]);
284         assertEquals(REQUIRED_FEATURES[1], mockAuthenticator.getRequiredFeatures()[1]);
285     }
286 
validateOptions(Bundle expectedOptions, Bundle actualOptions)287     private void validateOptions(Bundle expectedOptions, Bundle actualOptions) {
288         // In ICS AccountManager may add options to indicate the caller id.
289         // We only validate that the passed in options are present in the actual ones
290         if (expectedOptions != null) {
291             assertNotNull(actualOptions);
292             assertEquals(expectedOptions.get(OPTION_NAME_1), actualOptions.get(OPTION_NAME_1));
293             assertEquals(expectedOptions.get(OPTION_NAME_2), actualOptions.get(OPTION_NAME_2));
294         }
295     }
296 
validateSystemOptions(Bundle options)297     private void validateSystemOptions(Bundle options) {
298         assertNotNull(options.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME));
299         assertTrue(options.containsKey(AccountManager.KEY_CALLER_UID));
300         assertTrue(options.containsKey(AccountManager.KEY_CALLER_PID));
301     }
302 
validateCredentials()303     private void validateCredentials() {
304         assertEquals(ACCOUNT, mockAuthenticator.getAccount());
305     }
306 
getAccountsCount()307     private int getAccountsCount() {
308         Account[] accounts = am.getAccounts();
309         assertNotNull(accounts);
310         return accounts.length;
311     }
312 
addAccount(AccountManager am, String accountType, String authTokenType, String[] requiredFeatures, Bundle options, Activity activity, AccountManagerCallback<Bundle> callback, Handler handler)313     private Bundle addAccount(AccountManager am, String accountType, String authTokenType,
314             String[] requiredFeatures, Bundle options, Activity activity,
315             AccountManagerCallback<Bundle> callback, Handler handler) throws
316                 IOException, AuthenticatorException, OperationCanceledException {
317 
318         AccountManagerFuture<Bundle> futureBundle = am.addAccount(
319                 accountType,
320                 authTokenType,
321                 requiredFeatures,
322                 options,
323                 activity,
324                 callback,
325                 handler);
326 
327         Bundle resultBundle = futureBundle.getResult();
328         assertTrue(futureBundle.isDone());
329         assertNotNull(resultBundle);
330 
331         return resultBundle;
332     }
333 
renameAccount(AccountManager am, Account account, String newName)334     private Account renameAccount(AccountManager am, Account account, String newName)
335             throws OperationCanceledException, AuthenticatorException, IOException {
336         AccountManagerFuture<Account> futureAccount = am.renameAccount(
337                 account, newName, null /* callback */, null /* handler */);
338         Account renamedAccount = futureAccount.getResult();
339         assertTrue(futureAccount.isDone());
340         assertNotNull(renamedAccount);
341         return renamedAccount;
342     }
343 
removeAccount(AccountManager am, Account account, AccountManagerCallback<Boolean> callback)344     private boolean removeAccount(AccountManager am, Account account,
345             AccountManagerCallback<Boolean> callback) throws IOException, AuthenticatorException,
346                 OperationCanceledException {
347         AccountManagerFuture<Boolean> futureBoolean = am.removeAccount(account,
348                 callback,
349                 null /* handler */);
350         Boolean resultBoolean = futureBoolean.getResult();
351         assertTrue(futureBoolean.isDone());
352 
353         return resultBoolean;
354     }
355 
removeAccountWithIntentLaunch(AccountManager am, Account account, Activity activity, AccountManagerCallback<Bundle> callback)356     private Bundle removeAccountWithIntentLaunch(AccountManager am, Account account,
357             Activity activity, AccountManagerCallback<Bundle> callback) throws IOException,
358             AuthenticatorException, OperationCanceledException {
359 
360         AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
361                 activity,
362                 callback,
363                 null /* handler */);
364         Bundle resultBundle = futureBundle.getResult();
365         assertTrue(futureBundle.isDone());
366 
367         return resultBundle;
368     }
369 
removeAccount(AccountManager am, Account account, Activity activity, AccountManagerCallback<Bundle> callback)370     private Bundle removeAccount(AccountManager am, Account account, Activity activity,
371             AccountManagerCallback<Bundle> callback) throws IOException, AuthenticatorException,
372                 OperationCanceledException {
373 
374         AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
375                 activity,
376                 callback,
377                 null /* handler */);
378         Bundle resultBundle = futureBundle.getResult();
379         assertTrue(futureBundle.isDone());
380 
381         return resultBundle;
382     }
383 
removeAccountExplicitly(AccountManager am, Account account)384     private boolean removeAccountExplicitly(AccountManager am, Account account) {
385         return am.removeAccountExplicitly(account);
386     }
387 
addAccountExplicitly(Account account, String password, Bundle userdata)388     private void addAccountExplicitly(Account account, String password, Bundle userdata) {
389         assertTrue(am.addAccountExplicitly(account, password, userdata));
390     }
391 
getAuthTokenByFeature(String[] features, Activity activity)392     private Bundle getAuthTokenByFeature(String[] features, Activity activity)
393             throws IOException, AuthenticatorException, OperationCanceledException {
394 
395         AccountManagerFuture<Bundle> futureBundle = am.getAuthTokenByFeatures(ACCOUNT_TYPE,
396                 AUTH_TOKEN_TYPE,
397                 features,
398                 activity,
399                 OPTIONS_BUNDLE,
400                 OPTIONS_BUNDLE,
401                 null /* no callback */,
402                 null /* no handler */
403         );
404 
405         Bundle resultBundle = futureBundle.getResult();
406 
407         assertTrue(futureBundle.isDone());
408         assertNotNull(resultBundle);
409 
410         return resultBundle;
411     }
412 
isAccountPresent(Account[] accounts, Account accountToCheck)413     private boolean isAccountPresent(Account[] accounts, Account accountToCheck) {
414         if (null == accounts || null == accountToCheck) {
415             return false;
416         }
417         boolean result = false;
418         int length = accounts.length;
419         for (int n=0; n<length; n++) {
420             if(accountToCheck.equals(accounts[n])) {
421                 result = true;
422                 break;
423             }
424         }
425         return result;
426     }
427 
428     /**
429      * Test singleton
430      */
testGet()431     public void testGet() {
432         assertNotNull(AccountManager.get(mContext));
433     }
434 
435     /**
436      * Test creation of intent
437      */
testNewChooseAccountIntent()438     public void testNewChooseAccountIntent() {
439         Intent intent = AccountManager.newChooseAccountIntent(null, null, null,
440                 null, null,
441                 null, null);
442         assertNotNull(intent);
443     }
444 
445     /**
446      * Test creation of intent
447      */
testNewChooseAccountIntentDepracated()448     public void testNewChooseAccountIntentDepracated() {
449         Intent intent = AccountManager.newChooseAccountIntent(null, null, null, false,
450                 null, null,
451                 null, null);
452         assertNotNull(intent);
453     }
454 
455     /**
456      * Test a basic addAccount()
457      */
testAddAccount()458     public void testAddAccount() throws IOException, AuthenticatorException,
459             OperationCanceledException {
460 
461         Bundle resultBundle = addAccount(am,
462                 ACCOUNT_TYPE,
463                 AUTH_TOKEN_TYPE,
464                 REQUIRED_FEATURES,
465                 OPTIONS_BUNDLE,
466                 mActivity,
467                 null /* callback */,
468                 null /* handler */);
469 
470         // Assert parameters has been passed correctly
471         validateAccountAndAuthTokenType();
472         validateFeatures();
473         validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
474         validateSystemOptions(mockAuthenticator.mOptionsAddAccount);
475         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
476         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
477         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
478 
479         // Assert returned result
480         validateAccountAndNoAuthTokenResult(resultBundle);
481     }
482 
483     /**
484      * Test addAccount() with callback and handler
485      */
testAddAccountWithCallbackAndHandler()486     public void testAddAccountWithCallbackAndHandler() throws IOException,
487             AuthenticatorException, OperationCanceledException {
488 
489         testAddAccountWithCallbackAndHandler(null /* handler */);
490         testAddAccountWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
491     }
492 
493     /**
494      * Test addAccount() with no associated account authenticator
495      */
testAddAccountWithNoAuthenticator()496     public void testAddAccountWithNoAuthenticator() throws IOException,
497             AuthenticatorException, OperationCanceledException {
498 
499         try {
500             AccountManagerFuture<Bundle> futureBundle = am.addAccount(
501                     "nonExistingAccountType",
502                     null,
503                     null,
504                     null,
505                     null,
506                     null,
507                     null);
508 
509             futureBundle.getResult();
510             fail();
511         } catch (AuthenticatorException expectedException) {
512             return;
513         }
514     }
515 
testAddAccountWithCallbackAndHandler(Handler handler)516     private void testAddAccountWithCallbackAndHandler(Handler handler) throws IOException,
517             AuthenticatorException, OperationCanceledException {
518 
519         final CountDownLatch latch = new CountDownLatch(1);
520 
521         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
522             @Override
523             public void run(AccountManagerFuture<Bundle> bundleFuture) {
524                 Bundle resultBundle = null;
525                 try {
526                     resultBundle = bundleFuture.getResult();
527                 } catch (OperationCanceledException e) {
528                     fail("should not throw an OperationCanceledException");
529                 } catch (IOException e) {
530                     fail("should not throw an IOException");
531                 } catch (AuthenticatorException e) {
532                     fail("should not throw an AuthenticatorException");
533                 }
534 
535                 // Assert parameters has been passed correctly
536                 validateAccountAndAuthTokenType();
537                 validateFeatures();
538                 validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
539                 validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
540                 validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
541                 validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
542 
543                 // Assert return result
544                 validateAccountAndNoAuthTokenResult(resultBundle);
545 
546                 latch.countDown();
547             }
548         };
549 
550         addAccount(am,
551                 ACCOUNT_TYPE,
552                 AUTH_TOKEN_TYPE,
553                 REQUIRED_FEATURES,
554                 OPTIONS_BUNDLE,
555                 mActivity,
556                 callback,
557                 handler);
558 
559         // Wait with timeout for the callback to do its work
560         try {
561             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
562         } catch (InterruptedException e) {
563             fail("should not throw an InterruptedException");
564         }
565     }
566 
567     /**
568      * Test addAccountExplicitly(), renameAccount() and removeAccount().
569      */
570     @AppModeFull(reason = "The methods are for sign-up wizards associated with authenticators.")
testAddAccountExplicitlyAndRemoveAccount()571     public void testAddAccountExplicitlyAndRemoveAccount() throws IOException,
572             AuthenticatorException, OperationCanceledException {
573 
574         final int expectedAccountsCount = getAccountsCount();
575 
576         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
577 
578         // Assert that we have one more account
579         Account[] accounts = am.getAccounts();
580         assertNotNull(accounts);
581         assertEquals(1 + expectedAccountsCount, accounts.length);
582         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
583         // Need to clean up
584         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
585                 AccountManager.KEY_BOOLEAN_RESULT));
586 
587         // and verify that we go back to the initial state
588         accounts = am.getAccounts();
589         assertNotNull(accounts);
590         assertEquals(expectedAccountsCount, accounts.length);
591     }
592 
593     /**
594      * Test addAccountExplicitly(), renameAccount() and removeAccount().
595      */
596     @AppModeFull(reason = "The methods are for sign-up wizards associated with authenticators.")
testAddAccountExplicitlyAndRemoveAccountWithNewApi()597     public void testAddAccountExplicitlyAndRemoveAccountWithNewApi() throws IOException,
598             AuthenticatorException, OperationCanceledException {
599 
600         final int expectedAccountsCount = getAccountsCount();
601 
602         addAccountExplicitly(ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, ACCOUNT_PASSWORD, null /* userData */);
603 
604         // Assert that we have one more account
605         Account[] accounts = am.getAccounts();
606         assertNotNull(accounts);
607         assertEquals(1 + expectedAccountsCount, accounts.length);
608         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
609         // Deprecated API should not work
610         assertFalse(removeAccount(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, null /* callback */));
611         accounts = am.getAccounts();
612         assertNotNull(accounts);
613         assertEquals(1 + expectedAccountsCount, accounts.length);
614         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
615         // Check removal of account
616         assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, 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() calling
626      * into default implementations.
627      */
628     @AppModeFull(reason = "The methods are for sign-up wizards associated with authenticators.")
testAddAccountExplicitlyAndRemoveAccountWithDefaultImpl()629     public void testAddAccountExplicitlyAndRemoveAccountWithDefaultImpl() throws IOException,
630             AuthenticatorException, OperationCanceledException {
631 
632         final int expectedAccountsCount = getAccountsCount();
633 
634         addAccountExplicitly(ACCOUNT_FOR_DEFAULT_IMPL, ACCOUNT_PASSWORD, null /* userData */);
635 
636         // Assert that we have one more account
637         Account[] accounts = am.getAccounts();
638         assertNotNull(accounts);
639         assertEquals(1 + expectedAccountsCount, accounts.length);
640         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_DEFAULT_IMPL));
641         // Check removal of account
642         assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_DEFAULT_IMPL, mActivity, null /* callback */)
643                 .getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
644         // and verify that we go back to the initial state
645         accounts = am.getAccounts();
646         assertNotNull(accounts);
647         assertEquals(expectedAccountsCount, accounts.length);
648     }
649 
650     /**
651      * Test addAccountExplicitly(), renameAccount() and removeAccount().
652      */
653     @AppModeFull(reason = "The methods are for sign-up wizards associated with authenticators.")
testAddAccountExplicitlyAndRemoveAccountWithDeprecatedApi()654     public void testAddAccountExplicitlyAndRemoveAccountWithDeprecatedApi() throws IOException,
655             AuthenticatorException, OperationCanceledException {
656 
657         final int expectedAccountsCount = getAccountsCount();
658 
659         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
660 
661         // Assert that we have one more account
662         Account[] accounts = am.getAccounts();
663         assertNotNull(accounts);
664         assertEquals(1 + expectedAccountsCount, accounts.length);
665         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
666         // Need to clean up
667         assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
668 
669         // and verify that we go back to the initial state
670         accounts = am.getAccounts();
671         assertNotNull(accounts);
672         assertEquals(expectedAccountsCount, accounts.length);
673     }
674 
675     /**
676      * Test addAccountExplicitly() and removeAccountExplictly().
677      */
678     @AppModeFull(reason = "The methods are for sign-up wizards associated with authenticators.")
testAddAccountExplicitlyAndRemoveAccountExplicitly()679     public void testAddAccountExplicitlyAndRemoveAccountExplicitly() {
680         final int expectedAccountsCount = getAccountsCount();
681 
682         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
683 
684         // Assert that we have one more account
685         Account[] accounts = am.getAccounts();
686         assertNotNull(accounts);
687         assertEquals(1 + expectedAccountsCount, accounts.length);
688         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
689         // Need to clean up
690         assertTrue(removeAccountExplicitly(am, ACCOUNT));
691 
692         // and verify that we go back to the initial state
693         accounts = am.getAccounts();
694         assertNotNull(accounts);
695         assertEquals(expectedAccountsCount, accounts.length);
696     }
697 
698     /**
699      * Test updates to account visibility.
700      */
701     @AppModeFull(reason = "The methods requires the caller to match signature with authenticator.")
testSetAccountVisibility()702     public void testSetAccountVisibility()
703             throws IOException, AuthenticatorException, OperationCanceledException {
704         am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
705 
706         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_VISIBLE);
707         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
708                 AccountManager.VISIBILITY_VISIBLE);
709 
710         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_NOT_VISIBLE);
711         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
712                 AccountManager.VISIBILITY_NOT_VISIBLE);
713 
714         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED,
715                 AccountManager.VISIBILITY_VISIBLE);
716         // No changes to PACKAGE_NAME_1
717         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
718                 AccountManager.VISIBILITY_NOT_VISIBLE);
719     }
720 
721     /**
722      * Test updates to account visibility for authenticator package.
723      */
724     @AppModeFull(reason = "The methods requires the caller to match signature with authenticator.")
testSetAccountVisibilityForPrivilegedPackage()725     public void testSetAccountVisibilityForPrivilegedPackage()
726             throws IOException, AuthenticatorException, OperationCanceledException {
727         am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
728 
729         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED),
730                 AccountManager.VISIBILITY_VISIBLE);
731         Map<String, Integer> visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
732         assertNull(visibilities.get(PACKAGE_NAME_PRIVILEGED)); // no entry in database
733 
734         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED,
735                 AccountManager.VISIBILITY_NOT_VISIBLE);
736         visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
737         // database is updated
738         assertEquals((int) visibilities.get(PACKAGE_NAME_PRIVILEGED),
739                 AccountManager.VISIBILITY_NOT_VISIBLE);
740         // VISIBILITY_VISIBLE is used for Authenticator despite database entry.
741         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED),
742                 AccountManager.VISIBILITY_VISIBLE);
743     }
744 
745     /**
746      * Test getPackagesAndVisibilityForAccount() method.
747      */
748     @AppModeFull(reason = "The methods requires the caller to match signature with authenticator.")
testGetPackagesAndVisibilityForAccount()749     public void testGetPackagesAndVisibilityForAccount()
750             throws IOException, AuthenticatorException, OperationCanceledException {
751         am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
752 
753         Map<String, Integer> visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
754         assertNull(visibilities.get(PACKAGE_NAME_1)); // no entry in database
755 
756         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_VISIBLE);
757         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED,
758                 AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
759         visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
760         assertEquals(visibilities.size(), 2);
761         assertEquals((int) visibilities.get(PACKAGE_NAME_1), AccountManager.VISIBILITY_VISIBLE);
762         assertEquals((int) visibilities.get(PACKAGE_NAME_PRIVILEGED),
763                 AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
764     }
765 
766     /**
767      * Test addAccountExplicitly(), setAccountVisibility() , getAccountVisibility(), and
768      * removeAccount().
769      */
770     @AppModeFull(reason = "The methods are for sign-up wizards associated with authenticators.")
testAddAccountExplicitlyWithVisibility()771     public void testAddAccountExplicitlyWithVisibility()
772             throws IOException, AuthenticatorException, OperationCanceledException {
773         Map<String, Integer> visibility = new HashMap<>();
774         visibility.put(PACKAGE_NAME_1, AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
775 
776         final int expectedAccountsCount = getAccountsCount();
777 
778         am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */, visibility);
779 
780         // Assert that we have one more account
781         Account[] accounts = am.getAccounts();
782         assertNotNull(accounts);
783         assertEquals(1 + expectedAccountsCount, accounts.length);
784         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
785 
786         // Visibility values were stored.
787         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
788                 AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
789         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */)
790                 .getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
791 
792         // Visibility values were removed
793         Map<Account, Integer> visibilities =
794                 am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
795         assertNull(visibilities.get(ACCOUNT));
796 
797         // and verify that we go back to the initial state
798         accounts = am.getAccounts();
799         assertNotNull(accounts);
800         assertEquals(expectedAccountsCount, accounts.length);
801     }
802 
803     /**
804      * Test testGetAccountsAndVisibilityForPackage(), getAccountsByTypeForPackage() methods.
805      */
806     @AppModeFull(reason = "The methods requires the caller to match signature with authenticator.")
testGetAccountsAndVisibilityForPackage()807     public void testGetAccountsAndVisibilityForPackage() {
808         am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */, null);
809         am.addAccountExplicitly(ACCOUNT_SAME_TYPE, ACCOUNT_PASSWORD, null /* userData */, null);
810 
811         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_NOT_VISIBLE);
812         am.setAccountVisibility(ACCOUNT_SAME_TYPE, PACKAGE_NAME_1,
813                 AccountManager.VISIBILITY_VISIBLE);
814         assertEquals(am.getAccountVisibility(ACCOUNT_SAME_TYPE, PACKAGE_NAME_1),
815                 AccountManager.VISIBILITY_VISIBLE);
816         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
817                 AccountManager.VISIBILITY_NOT_VISIBLE);
818         Account[] accounts = am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1);
819         assertEquals(accounts.length, 1); // VISIBILITY_NOT_VISIBLE accounts are not returned.
820         assertEquals(accounts[0], ACCOUNT_SAME_TYPE);
821         Map<Account, Integer> visibilities =
822                 am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
823         assertEquals((int) visibilities.get(ACCOUNT), AccountManager.VISIBILITY_NOT_VISIBLE);
824 
825         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1,
826                 AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
827 
828         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
829                 AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
830         // VISIBILITY_USER_MANAGED_NOT_VISIBLE accounts are returned by getAccountsByTypeForPackage
831         assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
832         visibilities = am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
833         assertEquals((int) visibilities.get(ACCOUNT),
834                 AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
835 
836         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1,
837                 AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
838 
839         assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
840                 AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
841         assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
842         visibilities = am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
843         assertEquals((int) visibilities.get(ACCOUNT),
844                 AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
845 
846         am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_VISIBLE);
847 
848         assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
849         visibilities = am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
850         assertEquals((int) visibilities.get(ACCOUNT), AccountManager.VISIBILITY_VISIBLE);
851         assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
852 
853         // VISIBILITY_USER MANAGED_NOT_VISIBLE accounts are not returned when type is null.
854         // It should be equivalent to callling am.getAccounts() which doesn't return
855         // VISIBILITY_USER MANAGED_NOT_VISIBLE accounts.
856         assertEquals(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1).length,
857             am.getAccounts().length);
858     }
859 
860     /**
861      * Test checks order of accounts returned by getAccounts...().
862      * Accounts should be grouped by type.
863      */
864     @AppModeFull(reason = "The methods requires the caller to match signature with authenticator.")
testGetAccountsReturnedOrder()865     public void testGetAccountsReturnedOrder() {
866         Account account_1_1 = new Account("account_z", ACCOUNT_TYPE);
867         Account account_1_2 = new Account("account_c", ACCOUNT_TYPE);
868         Account account_1_3 = new Account("account_a", ACCOUNT_TYPE);
869 
870         Account account_2_1 = new Account("account_b", ACCOUNT_TYPE_CUSTOM);
871         Account account_2_2 = new Account("account_f", ACCOUNT_TYPE_CUSTOM);
872         Account account_2_3 = new Account("account_a", ACCOUNT_TYPE_CUSTOM);
873 
874         am.addAccountExplicitly(account_1_1, ACCOUNT_PASSWORD, null /* userData */, null);
875         am.addAccountExplicitly(account_1_2, ACCOUNT_PASSWORD, null /* userData */, null);
876         am.addAccountExplicitly(account_2_1, ACCOUNT_PASSWORD, null /* userData */, null);
877 
878         verifyAccountsGroupedByType(am.getAccounts());
879         verifyAccountsGroupedByType(am.getAccountsByType(null));
880         verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
881 
882         am.addAccountExplicitly(account_2_2, ACCOUNT_PASSWORD, null /* userData */, null);
883 
884         verifyAccountsGroupedByType(am.getAccounts());
885         verifyAccountsGroupedByType(am.getAccountsByType(null));
886         verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
887 
888         am.addAccountExplicitly(account_1_3, ACCOUNT_PASSWORD, null /* userData */, null);
889         verifyAccountsGroupedByType(am.getAccounts());
890         verifyAccountsGroupedByType(am.getAccountsByType(null));
891         verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
892 
893         am.addAccountExplicitly(account_2_3, ACCOUNT_PASSWORD, null /* userData */, null);
894 
895         verifyAccountsGroupedByType(am.getAccounts());
896         verifyAccountsGroupedByType(am.getAccountsByType(null));
897         verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
898     }
899 
900     /**
901      * Test setUserData() and getUserData().
902      */
testAccountRenameAndGetPreviousName()903     public void testAccountRenameAndGetPreviousName()
904             throws OperationCanceledException, AuthenticatorException, IOException {
905         // Add a first account
906 
907         boolean result = am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, USERDATA_BUNDLE);
908 
909         assertTrue(result);
910 
911         // Prior to a rename, the previous name should be null.
912         String nullName = am.getPreviousName(ACCOUNT);
913         assertNull(nullName);
914 
915         final int expectedAccountsCount = getAccountsCount();
916 
917         Account renamedAccount = renameAccount(am, ACCOUNT, ACCOUNT_NEW_NAME);
918         /*
919          *  Make sure that the resultant renamed account has the correct name
920          *  and is associated with the correct account type.
921          */
922         assertEquals(ACCOUNT_NEW_NAME, renamedAccount.name);
923         assertEquals(ACCOUNT.type, renamedAccount.type);
924 
925         // Make sure the total number of accounts is the same.
926         Account[] accounts = am.getAccounts();
927         assertEquals(expectedAccountsCount, accounts.length);
928 
929         // Make sure the old account isn't present.
930         assertFalse(isAccountPresent(am.getAccounts(), ACCOUNT));
931 
932         // But that the new one is.
933         assertTrue(isAccountPresent(am.getAccounts(), renamedAccount));
934 
935         // Check that the UserData is still present.
936         assertEquals(USERDATA_VALUE_1, am.getUserData(renamedAccount, USERDATA_NAME_1));
937 
938         assertEquals(ACCOUNT.name, am.getPreviousName(renamedAccount));
939 
940         // Need to clean up
941         assertTrue(removeAccount(am, renamedAccount, mActivity, null /* callback */).getBoolean(
942                 AccountManager.KEY_BOOLEAN_RESULT));
943 
944     }
945 
946     /**
947      * Test getAccounts() and getAccountsByType()
948      */
testGetAccountsAndGetAccountsByType()949     public void testGetAccountsAndGetAccountsByType() {
950 
951         assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
952         assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT_SAME_TYPE));
953 
954         final int accountsCount = getAccountsCount();
955 
956         // Add a first account
957         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
958 
959         // Check that we have the new account
960         Account[] accounts = am.getAccounts();
961         assertEquals(1 + accountsCount, accounts.length);
962         assertEquals(true, isAccountPresent(accounts, ACCOUNT));
963 
964         // Add another account
965         addAccountExplicitly(ACCOUNT_SAME_TYPE, ACCOUNT_PASSWORD, null /* userData */);
966 
967         // Check that we have one more account again
968         accounts = am.getAccounts();
969         assertEquals(2 + accountsCount, accounts.length);
970         assertEquals(true, isAccountPresent(accounts, ACCOUNT_SAME_TYPE));
971 
972         // Check if we have one from first type
973         accounts = am.getAccountsByType(ACCOUNT_TYPE);
974         assertEquals(2, accounts.length);
975 
976         // Check if we dont have any account from the other type
977         accounts = am.getAccountsByType(ACCOUNT_TYPE_ABSENT);
978         assertEquals(0, accounts.length);
979     }
980 
981     /**
982      * Test getAuthenticatorTypes()
983      */
testGetAuthenticatorTypes()984     public void testGetAuthenticatorTypes() {
985         AuthenticatorDescription[] types = am.getAuthenticatorTypes();
986         for(AuthenticatorDescription description: types) {
987             if (description.type.equals(ACCOUNT_TYPE)) {
988                 return;
989             }
990         }
991         fail("should have found Authenticator type: " + ACCOUNT_TYPE);
992     }
993 
994     /**
995      * Test setPassword() and getPassword()
996      */
testSetAndGetAndClearPassword()997     public void testSetAndGetAndClearPassword() {
998         // Add a first account
999         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1000 
1001         // Check that the password is the one we defined
1002         assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
1003 
1004         // Clear the password and check that it is cleared
1005         am.clearPassword(ACCOUNT);
1006         assertNull(am.getPassword(ACCOUNT));
1007 
1008         // Reset the password
1009         am.setPassword(ACCOUNT, ACCOUNT_PASSWORD);
1010 
1011         // Check that the password is the one we defined
1012         assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
1013     }
1014 
1015     /**
1016      * Test setUserData() and getUserData()
1017      */
testSetAndGetUserData()1018     public void testSetAndGetUserData() {
1019         // Add a first account
1020         boolean result = am.addAccountExplicitly(ACCOUNT,
1021                                 ACCOUNT_PASSWORD,
1022                                 USERDATA_BUNDLE);
1023 
1024         assertTrue(result);
1025 
1026         // Check that the UserData is the one we defined
1027         assertEquals(USERDATA_VALUE_1, am.getUserData(ACCOUNT, USERDATA_NAME_1));
1028 
1029         am.setUserData(ACCOUNT, USERDATA_NAME_2, USERDATA_VALUE_2);
1030 
1031         // Check that the UserData is the one we defined
1032         assertEquals(USERDATA_VALUE_2, am.getUserData(ACCOUNT, USERDATA_NAME_2));
1033     }
1034 
1035     /**
1036      * Test getAccountsByTypeAndFeatures()
1037      */
testGetAccountsByTypeAndFeatures()1038     public void testGetAccountsByTypeAndFeatures() throws IOException,
1039             AuthenticatorException, OperationCanceledException {
1040 
1041         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1042 
1043         AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
1044                 ACCOUNT_TYPE, REQUIRED_FEATURES, null, null);
1045 
1046         Account[] accounts = futureAccounts.getResult();
1047 
1048         assertNotNull(accounts);
1049         assertEquals(1, accounts.length);
1050         assertEquals(true, isAccountPresent(accounts, ACCOUNT));
1051 
1052         futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
1053                 new String[] { NON_EXISTING_FEATURE },
1054                 null /* callback*/,
1055                 null /* handler */);
1056         accounts = futureAccounts.getResult();
1057 
1058         assertNotNull(accounts);
1059         assertEquals(0, accounts.length);
1060     }
1061 
1062     /**
1063      * Test getAccountsByTypeAndFeatures() with callback and handler
1064      */
testGetAccountsByTypeAndFeaturesWithCallbackAndHandler()1065     public void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler() throws IOException,
1066             AuthenticatorException, OperationCanceledException {
1067 
1068         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1069 
1070         testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(null /* handler */);
1071         testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1072     }
1073 
testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(Handler handler)1074     private void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(Handler handler) throws
1075             IOException, AuthenticatorException, OperationCanceledException {
1076 
1077         final CountDownLatch latch1 = new CountDownLatch(1);
1078 
1079         AccountManagerCallback<Account[]> callback1 = new AccountManagerCallback<Account[]>() {
1080             @Override
1081             public void run(AccountManagerFuture<Account[]> accountsFuture) {
1082                 try {
1083                     Account[] accounts = accountsFuture.getResult();
1084                     assertNotNull(accounts);
1085                     assertEquals(1, accounts.length);
1086                     assertEquals(true, isAccountPresent(accounts, ACCOUNT));
1087                 } catch (OperationCanceledException e) {
1088                     fail("should not throw an OperationCanceledException");
1089                 } catch (IOException e) {
1090                     fail("should not throw an IOException");
1091                 } catch (AuthenticatorException e) {
1092                     fail("should not throw an AuthenticatorException");
1093                 } finally {
1094                   latch1.countDown();
1095                 }
1096             }
1097         };
1098 
1099         AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
1100                 ACCOUNT_TYPE,
1101                 REQUIRED_FEATURES,
1102                 callback1,
1103                 handler);
1104 
1105         Account[] accounts = futureAccounts.getResult();
1106 
1107         assertNotNull(accounts);
1108         assertEquals(1, accounts.length);
1109         assertEquals(true, isAccountPresent(accounts, ACCOUNT));
1110 
1111         // Wait with timeout for the callback to do its work
1112         try {
1113             latch1.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1114         } catch (InterruptedException e) {
1115             fail("should not throw an InterruptedException");
1116         }
1117 
1118         final CountDownLatch latch2 = new CountDownLatch(1);
1119 
1120         AccountManagerCallback<Account[]> callback2 = new AccountManagerCallback<Account[]>() {
1121             @Override
1122             public void run(AccountManagerFuture<Account[]> accountsFuture) {
1123                 try {
1124                     Account[] accounts = accountsFuture.getResult();
1125                     assertNotNull(accounts);
1126                     assertEquals(0, accounts.length);
1127                 } catch (OperationCanceledException e) {
1128                     fail("should not throw an OperationCanceledException");
1129                 } catch (IOException e) {
1130                     fail("should not throw an IOException");
1131                 } catch (AuthenticatorException e) {
1132                     fail("should not throw an AuthenticatorException");
1133                 } finally {
1134                   latch2.countDown();
1135                 }
1136             }
1137         };
1138 
1139         accounts = null;
1140 
1141         futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
1142                 new String[] { NON_EXISTING_FEATURE },
1143                 callback2,
1144                 handler);
1145 
1146         accounts = futureAccounts.getResult();
1147         assertNotNull(accounts);
1148         assertEquals(0, accounts.length);
1149 
1150         // Wait with timeout for the callback to do its work
1151         try {
1152             latch2.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1153         } catch (InterruptedException e) {
1154             fail("should not throw an InterruptedException");
1155         }
1156     }
1157 
1158     /**
1159      * Test setAuthToken() and peekAuthToken()
1160      */
testSetAndPeekAndInvalidateAuthToken()1161     public void testSetAndPeekAndInvalidateAuthToken() {
1162         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1163         String expected = "x";
1164         am.setAuthToken(ACCOUNT, AUTH_TOKEN_TYPE, expected);
1165 
1166         // Ask for the AuthToken
1167         String token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
1168         assertNotNull(token);
1169         assertEquals(expected, token);
1170 
1171         am.invalidateAuthToken(ACCOUNT_TYPE, token);
1172         token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
1173         assertNull(token);
1174     }
1175 
1176     /**
1177      * Test successful blockingGetAuthToken() with customTokens=false authenticator.
1178      */
testBlockingGetAuthToken_DefaultToken_Success()1179     public void testBlockingGetAuthToken_DefaultToken_Success()
1180             throws IOException, AuthenticatorException, OperationCanceledException {
1181         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
1182 
1183         String token = am.blockingGetAuthToken(ACCOUNT,
1184                 AUTH_TOKEN_TYPE,
1185                 false /* no failure notification */);
1186 
1187         // Ask for the AuthToken
1188         assertNotNull(token);
1189         assertEquals(mockAuthenticator.getLastTokenServed(), token);
1190     }
1191 
1192     private static class BlockingGetAuthTokenFetcher implements TokenFetcher {
1193         private final Account mAccount;
1194 
BlockingGetAuthTokenFetcher(Account account)1195         BlockingGetAuthTokenFetcher(Account account) {
1196             mAccount = account;
1197         }
1198 
1199         @Override
fetch(String tokenType)1200         public Bundle fetch(String tokenType)
1201                 throws OperationCanceledException, AuthenticatorException, IOException {
1202             String token = am.blockingGetAuthToken(
1203                     getAccount(),
1204                     tokenType,
1205                     false /* no failure notification */);
1206             Bundle result = new Bundle();
1207             result.putString(AccountManager.KEY_AUTHTOKEN, token);
1208             result.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
1209             result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
1210             return result;
1211         }
1212         @Override
getAccount()1213         public Account getAccount() {
1214             return CUSTOM_TOKEN_ACCOUNT;
1215         }
1216     }
1217 
1218     /**
1219      * Test successful blockingGetAuthToken() with customTokens=true authenticator.
1220      */
testBlockingGetAuthToken_CustomToken_NoCaching_Success()1221     public void testBlockingGetAuthToken_CustomToken_NoCaching_Success()
1222             throws IOException, AuthenticatorException, OperationCanceledException {
1223         addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1224         TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
1225         validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
1226     }
1227 
1228     /**
1229      * Test successful blockingGetAuthToken() with customTokens=true authenticator.
1230      */
testBlockingGetAuthToken_CustomToken_ExpiringCache_Success()1231     public void testBlockingGetAuthToken_CustomToken_ExpiringCache_Success()
1232             throws IOException, AuthenticatorException, OperationCanceledException {
1233         addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1234         TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
1235         validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
1236     }
1237 
1238     /**
1239      * Test successful getAuthToken() using a future with customTokens=false authenticator.
1240      */
testDeprecatedGetAuthTokenWithFuture_NoOptions_DefaultToken_Success()1241     public void testDeprecatedGetAuthTokenWithFuture_NoOptions_DefaultToken_Success()
1242             throws IOException, AuthenticatorException, OperationCanceledException {
1243         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1244         AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1245                 AUTH_TOKEN_TYPE,
1246                 false /* no failure notification */,
1247                 null /* no callback */,
1248                 null /* no handler */
1249         );
1250 
1251         Bundle resultBundle = futureBundle.getResult();
1252 
1253         assertTrue(futureBundle.isDone());
1254         assertNotNull(resultBundle);
1255 
1256         // Assert returned result
1257         validateAccountAndAuthTokenResult(resultBundle);
1258     }
1259 
1260     /**
1261      * Test successful getAuthToken() using a future with customTokens=false without
1262      * expiring tokens.
1263      */
testDeprecatedGetAuthTokenWithFuture_NoOptions_CustomToken_Success()1264     public void testDeprecatedGetAuthTokenWithFuture_NoOptions_CustomToken_Success()
1265             throws IOException, AuthenticatorException, OperationCanceledException {
1266         addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1267         // validateSuccessfulTokenFetchingLifecycle(AccountManager am, TokenFetcher fetcher, String tokenType)
1268         TokenFetcher f = new TokenFetcher() {
1269             @Override
1270             public Bundle fetch(String tokenType)
1271                     throws OperationCanceledException, AuthenticatorException, IOException {
1272                 AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
1273                         getAccount(),
1274                         tokenType,
1275                         false /* no failure notification */,
1276                         null /* no callback */,
1277                         null /* no handler */
1278                 );
1279                 Bundle actual = futureBundle.getResult();
1280                 assertTrue(futureBundle.isDone());
1281                 assertNotNull(actual);
1282                 return actual;
1283             }
1284 
1285             @Override
1286             public Account getAccount() {
1287                 return CUSTOM_TOKEN_ACCOUNT;
1288             }
1289         };
1290         validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
1291         validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
1292     }
1293 
1294     /**
1295      * Test successful getAuthToken() using a future with customTokens=false without
1296      * expiring tokens.
1297      */
testGetAuthTokenWithFuture_Options_DefaultToken_Success()1298     public void testGetAuthTokenWithFuture_Options_DefaultToken_Success()
1299             throws IOException, AuthenticatorException, OperationCanceledException {
1300         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1301 
1302         AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1303                 AUTH_TOKEN_TYPE,
1304                 OPTIONS_BUNDLE,
1305                 mActivity,
1306                 null /* no callback */,
1307                 null /* no handler */
1308         );
1309 
1310         Bundle resultBundle = futureBundle.getResult();
1311 
1312         assertTrue(futureBundle.isDone());
1313         assertNotNull(resultBundle);
1314 
1315         // Assert returned result
1316         validateAccountAndAuthTokenResult(resultBundle);
1317 
1318         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1319         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1320         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1321         validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsGetAuthToken);
1322         validateSystemOptions(mockAuthenticator.mOptionsGetAuthToken);
1323     }
1324 
1325     /**
1326      * Test successful getAuthToken() using a future with customTokens=false without
1327      * expiring tokens.
1328      */
testGetAuthTokenWithFuture_Options_CustomToken_Success()1329     public void testGetAuthTokenWithFuture_Options_CustomToken_Success()
1330             throws IOException, AuthenticatorException, OperationCanceledException {
1331         addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1332         TokenFetcher fetcherWithOptions = new TokenFetcher() {
1333             @Override
1334             public Bundle fetch(String tokenType)
1335                     throws OperationCanceledException, AuthenticatorException, IOException {
1336                 AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
1337                         getAccount(),
1338                         tokenType,
1339                         OPTIONS_BUNDLE,
1340                         false /* no failure notification */,
1341                         null /* no callback */,
1342                         null /* no handler */
1343                 );
1344                 Bundle actual = futureBundle.getResult();
1345                 assertTrue(futureBundle.isDone());
1346                 assertNotNull(actual);
1347                 return actual;
1348             }
1349 
1350             @Override
1351             public Account getAccount() {
1352                 return CUSTOM_TOKEN_ACCOUNT;
1353             }
1354         };
1355         validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
1356         validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
1357     }
1358 
1359 
1360     /**
1361      * Test successful getAuthToken() using a future with customTokens=false without
1362      * expiring tokens.
1363      */
testGetAuthTokenWithCallback_Options_Handler_DefaultToken_Success()1364     public void testGetAuthTokenWithCallback_Options_Handler_DefaultToken_Success()
1365             throws IOException, AuthenticatorException, OperationCanceledException {
1366         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
1367         final HandlerThread handlerThread = new HandlerThread("accounts.test");
1368         handlerThread.start();
1369         TokenFetcher fetcherWithOptions = new TokenFetcher() {
1370             @Override
1371             public Bundle fetch(String tokenType)
1372                     throws OperationCanceledException, AuthenticatorException, IOException {
1373                 final AtomicReference<Bundle> actualRef = new AtomicReference<>();
1374                 final CountDownLatch latch = new CountDownLatch(1);
1375 
1376                 AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1377                     @Override
1378                     public void run(AccountManagerFuture<Bundle> bundleFuture) {
1379                         Bundle resultBundle = null;
1380                         try {
1381                             resultBundle = bundleFuture.getResult();
1382                             actualRef.set(resultBundle);
1383                         } catch (OperationCanceledException e) {
1384                             fail("should not throw an OperationCanceledException");
1385                         } catch (IOException e) {
1386                             fail("should not throw an IOException");
1387                         } catch (AuthenticatorException e) {
1388                             fail("should not throw an AuthenticatorException");
1389                         } finally {
1390                             latch.countDown();
1391                         }
1392                     }
1393                 };
1394 
1395                 am.getAuthToken(getAccount(),
1396                         tokenType,
1397                         OPTIONS_BUNDLE,
1398                         false /* no failure notification */,
1399                         callback,
1400                         new Handler(handlerThread.getLooper()));
1401 
1402                 // Wait with timeout for the callback to do its work
1403                 try {
1404                     latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1405                 } catch (InterruptedException e) {
1406                     fail("should not throw an InterruptedException");
1407                 }
1408                 return actualRef.get();
1409             }
1410 
1411             @Override
1412             public Account getAccount() {
1413                 return ACCOUNT;
1414             }
1415         };
1416         validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
1417         validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
1418     }
1419 
1420     /**
1421      * Test successful getAuthToken() using a future with customTokens=false without
1422      * expiring tokens.
1423      */
testGetAuthTokenWithCallback_Options_Handler_CustomToken_Success()1424     public void testGetAuthTokenWithCallback_Options_Handler_CustomToken_Success()
1425             throws IOException, AuthenticatorException, OperationCanceledException {
1426         addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
1427         final HandlerThread handlerThread = new HandlerThread("accounts.test");
1428         handlerThread.start();
1429         TokenFetcher fetcherWithOptions = new TokenFetcher() {
1430             @Override
1431             public Bundle fetch(String tokenType)
1432                     throws OperationCanceledException, AuthenticatorException, IOException {
1433                 final AtomicReference<Bundle> actualRef = new AtomicReference<>();
1434                 final CountDownLatch latch = new CountDownLatch(1);
1435 
1436                 AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1437                     @Override
1438                     public void run(AccountManagerFuture<Bundle> bundleFuture) {
1439                         Bundle resultBundle = null;
1440                         try {
1441                             resultBundle = bundleFuture.getResult();
1442                             actualRef.set(resultBundle);
1443                         } catch (OperationCanceledException e) {
1444                             fail("should not throw an OperationCanceledException");
1445                         } catch (IOException e) {
1446                             fail("should not throw an IOException");
1447                         } catch (AuthenticatorException e) {
1448                             fail("should not throw an AuthenticatorException");
1449                         } finally {
1450                             latch.countDown();
1451                         }
1452                     }
1453                 };
1454 
1455                 am.getAuthToken(getAccount(),
1456                         tokenType,
1457                         OPTIONS_BUNDLE,
1458                         false /* no failure notification */,
1459                         callback,
1460                         new Handler(handlerThread.getLooper()));
1461 
1462                 // Wait with timeout for the callback to do its work
1463                 try {
1464                     latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1465                 } catch (InterruptedException e) {
1466                     fail("should not throw an InterruptedException");
1467                 }
1468                 return actualRef.get();
1469             }
1470 
1471             @Override
1472             public Account getAccount() {
1473                 return CUSTOM_TOKEN_ACCOUNT;
1474             }
1475         };
1476         validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
1477         validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
1478     }
1479 
1480     /**
1481      * Test getAuthToken() with callback and handler
1482      */
testGetAuthTokenWithCallbackAndHandler()1483     public void testGetAuthTokenWithCallbackAndHandler() throws IOException, AuthenticatorException,
1484             OperationCanceledException {
1485 
1486         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1487 
1488         testGetAuthTokenWithCallbackAndHandler(null /* handler */);
1489         testGetAuthTokenWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1490     }
1491 
testGetAuthTokenWithCallbackAndHandler(Handler handler)1492     private void testGetAuthTokenWithCallbackAndHandler(Handler handler) throws IOException,
1493             AuthenticatorException, OperationCanceledException {
1494 
1495         final CountDownLatch latch = new CountDownLatch(1);
1496 
1497         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1498             @Override
1499             public void run(AccountManagerFuture<Bundle> bundleFuture) {
1500 
1501                 Bundle resultBundle = null;
1502                 try {
1503                     resultBundle = bundleFuture.getResult();
1504 
1505                     // Assert returned result
1506                     validateAccountAndAuthTokenResult(resultBundle);
1507 
1508                 } catch (OperationCanceledException e) {
1509                     fail("should not throw an OperationCanceledException");
1510                 } catch (IOException e) {
1511                     fail("should not throw an IOException");
1512                 } catch (AuthenticatorException e) {
1513                     fail("should not throw an AuthenticatorException");
1514                 }
1515                 finally {
1516                     latch.countDown();
1517                 }
1518             }
1519         };
1520 
1521         AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1522                 AUTH_TOKEN_TYPE,
1523                 false /* no failure notification */,
1524                 callback,
1525                 handler
1526         );
1527 
1528         Bundle resultBundle = futureBundle.getResult();
1529 
1530         assertTrue(futureBundle.isDone());
1531         assertNotNull(resultBundle);
1532 
1533         // Wait with timeout for the callback to do its work
1534         try {
1535             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1536         } catch (InterruptedException e) {
1537             fail("should not throw an InterruptedException");
1538         }
1539     }
1540 
1541     /**
1542      * test getAuthToken() with options and callback and handler
1543      */
testGetAuthTokenWithOptionsAndCallback()1544     public void testGetAuthTokenWithOptionsAndCallback() throws IOException,
1545             AuthenticatorException, OperationCanceledException {
1546 
1547         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1548 
1549         testGetAuthTokenWithOptionsAndCallbackAndHandler(null /* handler */);
1550         testGetAuthTokenWithOptionsAndCallbackAndHandler(new Handler(Looper.getMainLooper()));
1551     }
1552 
testGetAuthTokenWithOptionsAndCallbackAndHandler(Handler handler)1553     private void testGetAuthTokenWithOptionsAndCallbackAndHandler(Handler handler) throws
1554             IOException, AuthenticatorException, OperationCanceledException {
1555 
1556         final CountDownLatch latch = new CountDownLatch(1);
1557 
1558         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1559             @Override
1560             public void run(AccountManagerFuture<Bundle> bundleFuture) {
1561 
1562                 Bundle resultBundle = null;
1563                 try {
1564                     resultBundle = bundleFuture.getResult();
1565                     // Assert returned result
1566                     validateAccountAndAuthTokenResult(resultBundle);
1567                 } catch (OperationCanceledException e) {
1568                     fail("should not throw an OperationCanceledException");
1569                 } catch (IOException e) {
1570                     fail("should not throw an IOException");
1571                 } catch (AuthenticatorException e) {
1572                     fail("should not throw an AuthenticatorException");
1573                 }
1574                 finally {
1575                     latch.countDown();
1576                 }
1577             }
1578         };
1579 
1580         AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
1581                 AUTH_TOKEN_TYPE,
1582                 OPTIONS_BUNDLE,
1583                 mActivity,
1584                 callback,
1585                 handler
1586         );
1587 
1588         Bundle resultBundle = futureBundle.getResult();
1589 
1590         assertTrue(futureBundle.isDone());
1591         assertNotNull(resultBundle);
1592 
1593         // Wait with timeout for the callback to do its work
1594         try {
1595             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1596         } catch (InterruptedException e) {
1597             fail("should not throw an InterruptedException");
1598         }
1599     }
1600 
1601     /**
1602      * Test getAuthTokenByFeatures()
1603      */
testGetAuthTokenByFeatures()1604     public void testGetAuthTokenByFeatures() throws IOException, AuthenticatorException,
1605             OperationCanceledException {
1606 
1607         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1608 
1609         Bundle resultBundle = getAuthTokenByFeature(
1610                 new String[] { NON_EXISTING_FEATURE },
1611                 null /* activity */
1612         );
1613 
1614         // Assert returned result
1615         validateNullResult(resultBundle);
1616 
1617         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1618         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1619         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1620         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1621 
1622         mockAuthenticator.clearData();
1623 
1624         // Now test with existing features and an activity
1625         resultBundle = getAuthTokenByFeature(
1626                 new String[] { NON_EXISTING_FEATURE },
1627                 mActivity
1628         );
1629 
1630         // Assert returned result
1631         validateAccountAndAuthTokenResult(resultBundle);
1632 
1633         validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
1634         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1635         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1636         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1637 
1638         mockAuthenticator.clearData();
1639 
1640         // Now test with existing features and no activity
1641         resultBundle = getAuthTokenByFeature(
1642                 REQUIRED_FEATURES,
1643                 null /* activity */
1644         );
1645 
1646         // Assert returned result
1647         validateAccountAndAuthTokenResult(resultBundle);
1648 
1649         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1650         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1651         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1652         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1653 
1654         mockAuthenticator.clearData();
1655 
1656         // Now test with existing features and an activity
1657         resultBundle = getAuthTokenByFeature(
1658                 REQUIRED_FEATURES,
1659                 mActivity
1660         );
1661 
1662         // Assert returned result
1663         validateAccountAndAuthTokenResult(resultBundle);
1664 
1665         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
1666         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
1667         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
1668         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
1669     }
1670 
1671     /**
1672      * Test confirmCredentials()
1673      */
1674     @Presubmit
testConfirmCredentials()1675     public void testConfirmCredentials() throws IOException, AuthenticatorException,
1676             OperationCanceledException {
1677 
1678         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1679 
1680         AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
1681                 OPTIONS_BUNDLE,
1682                 mActivity,
1683                 null /* callback*/,
1684                 null /* handler */);
1685 
1686         futureBundle.getResult();
1687 
1688         // Assert returned result
1689         validateCredentials();
1690     }
1691 
1692     /**
1693      * Tests the setting of lastAuthenticatedTime on adding account
1694      */
1695     // TODO: Either allow the system to see the activity from instant app,
1696     // Or separate the authenticator and test app to allow the instant app mode test.
1697     @AppModeFull
testLastAuthenticatedTimeAfterAddAccount()1698     public void testLastAuthenticatedTimeAfterAddAccount() throws IOException,
1699             AuthenticatorException, OperationCanceledException {
1700         assertTrue(addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD) > 0);
1701     }
1702 
1703     /**
1704      * Test confirmCredentials() for account not on device. Just that no error
1705      * should be thrown.
1706      */
testConfirmCredentialsAccountNotOnDevice()1707     public void testConfirmCredentialsAccountNotOnDevice() throws IOException,
1708             AuthenticatorException, OperationCanceledException {
1709 
1710         Account account = new Account("AccountNotOnThisDevice", ACCOUNT_TYPE);
1711         AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(account,
1712                 OPTIONS_BUNDLE,
1713                 mActivity,
1714                 null /* callback */,
1715                 null /* handler */);
1716 
1717         futureBundle.getResult();
1718     }
1719 
1720     /**
1721      * Tests the setting of lastAuthenticatedTime on confirmCredentials being
1722      * successful.
1723      */
1724     // TODO: Either allow the system to see the activity from instant app,
1725     // Or separate the authenticator and test app to allow the instant app mode test.
1726     @AppModeFull
testLastAuthenticatedTimeAfterConfirmCredentialsSuccess()1727     public void testLastAuthenticatedTimeAfterConfirmCredentialsSuccess() throws IOException,
1728             AuthenticatorException, OperationCanceledException {
1729 
1730         long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
1731 
1732         // Now this confirm credentials call returns true, which in turn
1733         // should update the last authenticated timestamp.
1734         Bundle result = am.confirmCredentials(ACCOUNT,
1735                 OPTIONS_BUNDLE, /* options */
1736                 null, /* activity */
1737                 null /* callback */,
1738                 null /* handler */).getResult();
1739         long confirmedCredTime = result.getLong(
1740                 AccountManager.KEY_LAST_AUTHENTICATED_TIME, -1);
1741         assertTrue(confirmedCredTime > accountAddTime);
1742     }
1743 
1744     /**
1745      * Tests the setting of lastAuthenticatedTime on updateCredentials being
1746      * successful.
1747      */
1748     // TODO: Either allow the system to see the activity from instant app,
1749     // Or separate the authenticator and test app to allow the instant app mode test.
1750     @AppModeFull
testLastAuthenticatedTimeAfterUpdateCredentialsSuccess()1751     public void testLastAuthenticatedTimeAfterUpdateCredentialsSuccess() throws IOException,
1752             AuthenticatorException, OperationCanceledException {
1753 
1754         long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
1755 
1756         am.updateCredentials(ACCOUNT,
1757                 AUTH_TOKEN_TYPE,
1758                 OPTIONS_BUNDLE,
1759                 mActivity,
1760                 null /* callback */,
1761                 null /* handler */).getResult();
1762         long updateCredTime = getLastAuthenticatedTime(ACCOUNT);
1763         assertTrue(updateCredTime > accountAddTime);
1764     }
1765 
1766     /**
1767      * LastAuthenticatedTime on setPassword should not be disturbed.
1768      */
1769     @AppModeFull(reason = "setPassword should be called by authenticator.")
testLastAuthenticatedTimeAfterSetPassword()1770     public void testLastAuthenticatedTimeAfterSetPassword() throws IOException,
1771             AuthenticatorException, OperationCanceledException {
1772         long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
1773         mockAuthenticator.callSetPassword();
1774         long setPasswordTime = getLastAuthenticatedTime(ACCOUNT);
1775         assertTrue(setPasswordTime == accountAddTime);
1776     }
1777 
1778     /**
1779      * Test confirmCredentials() with callback
1780      */
testConfirmCredentialsWithCallbackAndHandler()1781     public void testConfirmCredentialsWithCallbackAndHandler() {
1782 
1783         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1784 
1785         testConfirmCredentialsWithCallbackAndHandler(null /* handler */);
1786         testConfirmCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1787     }
1788 
testConfirmCredentialsWithCallbackAndHandler(Handler handler)1789     private void testConfirmCredentialsWithCallbackAndHandler(Handler handler) {
1790         final CountDownLatch latch = new CountDownLatch(1);
1791         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1792             @Override
1793             public void run(AccountManagerFuture<Bundle> bundleFuture) {
1794 
1795                 Bundle resultBundle = null;
1796                 try {
1797                     resultBundle = bundleFuture.getResult();
1798 
1799                     // Assert returned result
1800                     validateCredentials();
1801 
1802                     assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
1803                 } catch (OperationCanceledException e) {
1804                     fail("should not throw an OperationCanceledException");
1805                 } catch (IOException e) {
1806                     fail("should not throw an IOException");
1807                 } catch (AuthenticatorException e) {
1808                     fail("should not throw an AuthenticatorException");
1809                 }
1810                 finally {
1811                     latch.countDown();
1812                 }
1813             }
1814         };
1815         AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
1816                 OPTIONS_BUNDLE,
1817                 mActivity,
1818                 callback,
1819                 handler);
1820         // Wait with timeout for the callback to do its work
1821         try {
1822             latch.await(3 * LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1823         } catch (InterruptedException e) {
1824             fail("should not throw an InterruptedException");
1825         }
1826     }
1827 
1828     /**
1829      * Test updateCredentials()
1830      */
testUpdateCredentials()1831     public void testUpdateCredentials() throws IOException, AuthenticatorException,
1832             OperationCanceledException {
1833 
1834         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1835 
1836         AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
1837                 AUTH_TOKEN_TYPE,
1838                 OPTIONS_BUNDLE,
1839                 mActivity,
1840                 null /* callback*/,
1841                 null /* handler */);
1842 
1843         Bundle result = futureBundle.getResult();
1844 
1845         validateAccountAndNoAuthTokenResult(result);
1846 
1847         // Assert returned result
1848         validateCredentials();
1849     }
1850 
1851     /**
1852      * Test updateCredentials() with callback and handler
1853      */
testUpdateCredentialsWithCallbackAndHandler()1854     public void testUpdateCredentialsWithCallbackAndHandler() throws IOException,
1855             AuthenticatorException, OperationCanceledException {
1856 
1857         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
1858 
1859         testUpdateCredentialsWithCallbackAndHandler(null /* handler */);
1860         testUpdateCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1861     }
1862 
testUpdateCredentialsWithCallbackAndHandler(Handler handler)1863     private void testUpdateCredentialsWithCallbackAndHandler(Handler handler) throws IOException,
1864             AuthenticatorException, OperationCanceledException {
1865 
1866         final CountDownLatch latch = new CountDownLatch(1);
1867 
1868         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1869             @Override
1870             public void run(AccountManagerFuture<Bundle> bundleFuture) {
1871 
1872                 Bundle resultBundle = null;
1873                 try {
1874                     resultBundle = bundleFuture.getResult();
1875 
1876                     // Assert returned result
1877                     validateCredentials();
1878                     assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
1879 
1880                 } catch (OperationCanceledException e) {
1881                     fail("should not throw an OperationCanceledException");
1882                 } catch (IOException e) {
1883                     fail("should not throw an IOException");
1884                 } catch (AuthenticatorException e) {
1885                     fail("should not throw an AuthenticatorException");
1886                 }
1887                 finally {
1888                     latch.countDown();
1889                 }
1890             }
1891         };
1892 
1893         AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
1894                 AUTH_TOKEN_TYPE,
1895                 OPTIONS_BUNDLE,
1896                 mActivity,
1897                 callback,
1898                 handler);
1899 
1900         futureBundle.getResult();
1901 
1902         // Wait with timeout for the callback to do its work
1903         try {
1904             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1905         } catch (InterruptedException e) {
1906             fail("should not throw an InterruptedException");
1907         }
1908     }
1909 
1910     /**
1911      * Test editProperties()
1912      */
testEditProperties()1913     public void testEditProperties() throws IOException, AuthenticatorException,
1914             OperationCanceledException {
1915 
1916         AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
1917                 mActivity,
1918                 null /* callback */,
1919                 null /* handler*/);
1920 
1921         Bundle result = futureBundle.getResult();
1922 
1923         validateAccountAndNoAuthTokenResult(result);
1924 
1925         // Assert returned result
1926         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
1927     }
1928 
1929     /**
1930      * Test editProperties() with callback and handler
1931      */
testEditPropertiesWithCallbackAndHandler()1932     public void testEditPropertiesWithCallbackAndHandler() {
1933         testEditPropertiesWithCallbackAndHandler(null /* handler */);
1934         testEditPropertiesWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
1935     }
1936 
testEditPropertiesWithCallbackAndHandler(Handler handler)1937     private void testEditPropertiesWithCallbackAndHandler(Handler handler) {
1938         final CountDownLatch latch = new CountDownLatch(1);
1939 
1940         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
1941             @Override
1942             public void run(AccountManagerFuture<Bundle> bundleFuture) {
1943                 try {
1944                     // Assert returned result
1945                     assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
1946                 }
1947                 finally {
1948                     latch.countDown();
1949                 }
1950             }
1951         };
1952 
1953         AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
1954                 mActivity,
1955                 callback,
1956                 handler);
1957 
1958         // Wait with timeout for the callback to do its work
1959         try {
1960             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1961         } catch (InterruptedException e) {
1962             fail("should not throw an InterruptedException");
1963         }
1964     }
1965 
1966     /**
1967      * Test addOnAccountsUpdatedListener() with handler
1968      */
testAddOnAccountsUpdatedListenerWithHandler()1969     public void testAddOnAccountsUpdatedListenerWithHandler() throws IOException,
1970             AuthenticatorException, OperationCanceledException {
1971 
1972         testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
1973                 false /* updateImmediately */);
1974 
1975         // Need to cleanup intermediate state
1976         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1977                 AccountManager.KEY_BOOLEAN_RESULT));
1978 
1979         testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
1980                 true /* updateImmediately */);
1981 
1982         // Need to cleanup intermediate state
1983         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1984                 AccountManager.KEY_BOOLEAN_RESULT));
1985 
1986         testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
1987                 false /* updateImmediately */);
1988 
1989         // Need to cleanup intermediate state
1990         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
1991                 AccountManager.KEY_BOOLEAN_RESULT));
1992 
1993         testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
1994                 true /* updateImmediately */);
1995     }
1996 
testAddOnAccountsUpdatedListenerWithHandler(Handler handler, boolean updateImmediately)1997     private void testAddOnAccountsUpdatedListenerWithHandler(Handler handler,
1998             boolean updateImmediately) {
1999 
2000         final CountDownLatch latch = new CountDownLatch(1);
2001         OnAccountsUpdateListener listener =  accounts -> latch.countDown();
2002 
2003         // Add a listener
2004         am.addOnAccountsUpdatedListener(listener,
2005                 handler,
2006                 updateImmediately);
2007 
2008         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
2009 
2010         // Wait with timeout for the callback to do its work
2011         try {
2012             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
2013         } catch (InterruptedException e) {
2014             fail("should not throw an InterruptedException");
2015         }
2016 
2017         // Cleanup
2018         am.removeOnAccountsUpdatedListener(listener);
2019     }
2020 
2021     /**
2022      * Test addOnAccountsUpdatedListener() with visibility
2023      */
testAddOnAccountsUpdatedListenerWithVisibility()2024     public void testAddOnAccountsUpdatedListenerWithVisibility() throws IOException,
2025             AuthenticatorException, OperationCanceledException {
2026 
2027         testAddOnAccountsUpdatedListenerWithVisibility(null /* handler */,
2028                 false /* updateImmediately */, new String[] {ACCOUNT_TYPE, ACCOUNT_TYPE_ABSENT});
2029 
2030         // Need to cleanup intermediate state
2031         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
2032                 AccountManager.KEY_BOOLEAN_RESULT));
2033 
2034         testAddOnAccountsUpdatedListenerWithVisibility(null /* handler */,
2035                 true /* updateImmediately */, null /* types */);
2036 
2037         // Need to cleanup intermediate state
2038         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
2039                 AccountManager.KEY_BOOLEAN_RESULT));
2040 
2041         testAddOnAccountsUpdatedListenerWithVisibility(new Handler(Looper.getMainLooper()),
2042                 false /* updateImmediately */, new String[] {ACCOUNT_TYPE});
2043 
2044         // Need to cleanup intermediate state
2045         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
2046                 AccountManager.KEY_BOOLEAN_RESULT));
2047 
2048         testAddOnAccountsUpdatedListenerWithVisibility(new Handler(Looper.getMainLooper()),
2049                 true /* updateImmediately */, new String[] {ACCOUNT_TYPE});
2050     }
2051 
testAddOnAccountsUpdatedListenerWithVisibility(Handler handler, boolean updateImmediately, String[] accountTypes)2052     private void testAddOnAccountsUpdatedListenerWithVisibility(Handler handler,
2053             boolean updateImmediately, String[] accountTypes) {
2054 
2055         final CountDownLatch latch = new CountDownLatch(1);
2056         OnAccountsUpdateListener listener =  accounts -> latch.countDown();
2057 
2058         // Add a listener
2059         am.addOnAccountsUpdatedListener(listener,
2060                 handler,
2061                 updateImmediately,
2062                 accountTypes);
2063 
2064         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
2065 
2066         // Wait with timeout for the callback to do its work
2067         try {
2068             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
2069         } catch (InterruptedException e) {
2070             fail("should not throw an InterruptedException");
2071         }
2072 
2073         // Cleanup
2074         am.removeOnAccountsUpdatedListener(listener);
2075     }
2076 
2077     /**
2078      * Test removeOnAccountsUpdatedListener() with handler
2079      */
testRemoveOnAccountsUpdatedListener()2080     public void testRemoveOnAccountsUpdatedListener() throws IOException, AuthenticatorException,
2081             OperationCanceledException {
2082 
2083         testRemoveOnAccountsUpdatedListenerWithHandler(null /* handler */);
2084 
2085         // Need to cleanup intermediate state
2086         assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
2087                 AccountManager.KEY_BOOLEAN_RESULT));
2088 
2089         testRemoveOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()));
2090     }
2091 
testRemoveOnAccountsUpdatedListenerWithHandler(Handler handler)2092     private void testRemoveOnAccountsUpdatedListenerWithHandler(Handler handler) {
2093 
2094         final CountDownLatch latch = new CountDownLatch(1);
2095         OnAccountsUpdateListener listener =  accounts -> fail("should not be called");
2096 
2097         // First add a listener
2098         am.addOnAccountsUpdatedListener(listener,
2099                 handler,
2100                 false /* updateImmediately */);
2101 
2102         // Then remove the listener
2103         am.removeOnAccountsUpdatedListener(listener);
2104 
2105         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
2106 
2107         // Wait with timeout for the callback to do its work
2108         try {
2109             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
2110         } catch (InterruptedException e) {
2111             fail("should not throw an InterruptedException");
2112         }
2113     }
2114 
2115     /**
2116      * Test hasFeature
2117      */
testHasFeature()2118     public void testHasFeature()
2119             throws IOException, AuthenticatorException, OperationCanceledException {
2120 
2121         assertHasFeature(null /* handler */);
2122         assertHasFeature(new Handler(Looper.getMainLooper()));
2123 
2124         assertHasFeatureWithCallback(null /* handler */);
2125         assertHasFeatureWithCallback(new Handler(Looper.getMainLooper()));
2126     }
2127 
assertHasFeature(Handler handler)2128     private void assertHasFeature(Handler handler)
2129             throws IOException, AuthenticatorException, OperationCanceledException {
2130         Bundle resultBundle = addAccount(am,
2131                 ACCOUNT_TYPE,
2132                 AUTH_TOKEN_TYPE,
2133                 REQUIRED_FEATURES,
2134                 OPTIONS_BUNDLE,
2135                 mActivity,
2136                 null /* callback */,
2137                 null /* handler */);
2138 
2139         // Assert parameters has been passed correctly
2140         validateAccountAndAuthTokenType();
2141         validateFeatures();
2142 
2143         AccountManagerFuture<Boolean> booleanFuture = am.hasFeatures(ACCOUNT,
2144                 new String[]{FEATURE_1},
2145                 null /* callback */,
2146                 handler);
2147         assertTrue(booleanFuture.getResult());
2148 
2149         booleanFuture = am.hasFeatures(ACCOUNT,
2150                 new String[]{FEATURE_2},
2151                 null /* callback */,
2152                 handler);
2153         assertTrue(booleanFuture.getResult());
2154 
2155         booleanFuture = am.hasFeatures(ACCOUNT,
2156                 new String[]{FEATURE_1, FEATURE_2},
2157                 null /* callback */,
2158                 handler);
2159         assertTrue(booleanFuture.getResult());
2160 
2161         booleanFuture = am.hasFeatures(ACCOUNT,
2162                 new String[]{NON_EXISTING_FEATURE},
2163                 null /* callback */,
2164                 handler);
2165         assertFalse(booleanFuture.getResult());
2166 
2167         booleanFuture = am.hasFeatures(ACCOUNT,
2168                 new String[]{NON_EXISTING_FEATURE, FEATURE_1},
2169                 null /* callback */,
2170                 handler);
2171         assertFalse(booleanFuture.getResult());
2172 
2173         booleanFuture = am.hasFeatures(ACCOUNT,
2174                 new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
2175                 null /* callback */,
2176                 handler);
2177         assertFalse(booleanFuture.getResult());
2178     }
2179 
getAssertTrueCallback(final CountDownLatch latch)2180     private AccountManagerCallback<Boolean> getAssertTrueCallback(final CountDownLatch latch) {
2181         return new AccountManagerCallback<Boolean>() {
2182             @Override
2183             public void run(AccountManagerFuture<Boolean> booleanFuture) {
2184                 try {
2185                     // Assert returned result should be TRUE
2186                     assertTrue(booleanFuture.getResult());
2187                 } catch (Exception e) {
2188                     fail("Exception: " + e);
2189                 } finally {
2190                     latch.countDown();
2191                 }
2192             }
2193         };
2194     }
2195 
2196     private AccountManagerCallback<Boolean> getAssertFalseCallback(final CountDownLatch latch) {
2197         return new AccountManagerCallback<Boolean>() {
2198             @Override
2199             public void run(AccountManagerFuture<Boolean> booleanFuture) {
2200                 try {
2201                     // Assert returned result should be FALSE
2202                     assertFalse(booleanFuture.getResult());
2203                 } catch (Exception e) {
2204                     fail("Exception: " + e);
2205                 } finally {
2206                     latch.countDown();
2207                 }
2208             }
2209         };
2210     }
2211 
2212     private void waitForLatch(final CountDownLatch latch) {
2213         // Wait with timeout for the callback to do its work
2214         try {
2215             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
2216         } catch (InterruptedException e) {
2217             fail("should not throw an InterruptedException");
2218         }
2219     }
2220 
2221     private void assertHasFeatureWithCallback(Handler handler)
2222             throws IOException, AuthenticatorException, OperationCanceledException {
2223         Bundle resultBundle = addAccount(am,
2224                 ACCOUNT_TYPE,
2225                 AUTH_TOKEN_TYPE,
2226                 REQUIRED_FEATURES,
2227                 OPTIONS_BUNDLE,
2228                 mActivity,
2229                 null /* callback */,
2230                 null /* handler */);
2231 
2232         // Assert parameters has been passed correctly
2233         validateAccountAndAuthTokenType();
2234         validateFeatures();
2235 
2236         CountDownLatch latch = new CountDownLatch(1);
2237         am.hasFeatures(ACCOUNT,
2238                 new String[]{FEATURE_1},
2239                 getAssertTrueCallback(latch),
2240                 handler);
2241         waitForLatch(latch);
2242 
2243         latch = new CountDownLatch(1);
2244         am.hasFeatures(ACCOUNT,
2245                 new String[]{FEATURE_2},
2246                 getAssertTrueCallback(latch),
2247                 handler);
2248         waitForLatch(latch);
2249 
2250         latch = new CountDownLatch(1);
2251         am.hasFeatures(ACCOUNT,
2252                 new String[]{FEATURE_1, FEATURE_2},
2253                 getAssertTrueCallback(latch),
2254                 handler);
2255         waitForLatch(latch);
2256 
2257         latch = new CountDownLatch(1);
2258         am.hasFeatures(ACCOUNT,
2259                 new String[]{NON_EXISTING_FEATURE},
2260                 getAssertFalseCallback(latch),
2261                 handler);
2262         waitForLatch(latch);
2263 
2264         latch = new CountDownLatch(1);
2265         am.hasFeatures(ACCOUNT,
2266                 new String[]{NON_EXISTING_FEATURE, FEATURE_1},
2267                 getAssertFalseCallback(latch),
2268                 handler);
2269         waitForLatch(latch);
2270 
2271         latch = new CountDownLatch(1);
2272         am.hasFeatures(ACCOUNT,
2273                 new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
2274                 getAssertFalseCallback(latch),
2275                 handler);
2276         waitForLatch(latch);
2277     }
2278 
2279     private long getLastAuthenticatedTime(Account account) throws OperationCanceledException,
2280             AuthenticatorException, IOException {
2281         Bundle options = new Bundle();
2282         options.putBoolean(MockAccountAuthenticator.KEY_RETURN_INTENT, true);
2283         // Not really confirming, but a way to get last authenticated timestamp
2284         Bundle result = am.confirmCredentials(account,
2285                 options,// OPTIONS_BUNDLE,
2286                 null, /* activity */
2287                 null /* callback */,
2288                 null /* handler */).getResult();
2289         return result.getLong(
2290                 AccountManager.KEY_LAST_AUTHENTICATED_TIME, -1);
2291     }
2292 
2293     private long addAccountAndReturnAccountAddedTime(Account account, String password)
2294             throws OperationCanceledException, AuthenticatorException, IOException {
2295         addAccount(am,
2296                 ACCOUNT_TYPE,
2297                 AUTH_TOKEN_TYPE,
2298                 REQUIRED_FEATURES,
2299                 OPTIONS_BUNDLE,
2300                 mActivity,
2301                 null /* callback */,
2302                 null /* handler */);
2303         return getLastAuthenticatedTime(account);
2304     }
2305 
2306     /**
2307      * Tests that AccountManagerService is properly caching data.
2308      */
2309     public void testGetsAreCached() {
2310 
2311         // Add an account,
2312         assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
2313         addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
2314 
2315         // Then verify that we don't hit disk retrieving it,
2316         StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
2317         try {
2318             StrictMode.setThreadPolicy(
2319                     new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build());
2320             // getAccounts()
2321             Account[] accounts = am.getAccounts();
2322             assertNotNull(accounts);
2323             assertTrue(accounts.length > 0);
2324 
2325             // getAccountsAndVisibilityForPackage(...)
2326             Map<Account, Integer> accountsAndVisibility =
2327                 am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_PRIVILEGED, ACCOUNT_TYPE);
2328             assertNotNull(accountsAndVisibility);
2329             assertTrue(accountsAndVisibility.size() > 0);
2330 
2331             // getAccountsByType(...)
2332             Account[] accountsByType = am.getAccountsByType(ACCOUNT_TYPE);
2333             assertNotNull(accountsByType);
2334             assertTrue(accountsByType.length > 0);
2335 
2336             // getAccountsByTypeForPackage(...)
2337             Account[] accountsByTypeForPackage =
2338                 am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_PRIVILEGED);
2339             assertNotNull(accountsByTypeForPackage);
2340             assertTrue(accountsByTypeForPackage.length > 0);
2341 
2342             // getAccountsByTypeAndFeatures(...)
2343             am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE, null /* features */, null, null);
2344             am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE, REQUIRED_FEATURES, null, null);
2345 
2346         } finally {
2347             StrictMode.setThreadPolicy(oldPolicy);
2348         }
2349     }
2350 
2351     /**
2352      * Tests a basic startAddAccountSession() which returns a bundle containing
2353      * encrypted session bundle, account password and status token.
2354      */
2355     public void testStartAddAccountSession()
2356             throws IOException, AuthenticatorException, OperationCanceledException {
2357         final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
2358                 + Fixtures.SUFFIX_NAME_FIXTURE;
2359         final Bundle options = createOptionsWithAccountName(accountName);
2360 
2361         Bundle resultBundle = startAddAccountSession(
2362                 am,
2363                 ACCOUNT_TYPE,
2364                 AUTH_TOKEN_TYPE,
2365                 REQUIRED_FEATURES,
2366                 options,
2367                 null /* activity */,
2368                 null /* callback */,
2369                 null /* handler */);
2370 
2371         validateStartAddAccountSessionParametersAndOptions(accountName, options);
2372 
2373         // Assert returned result
2374         // Assert that auth token was stripped.
2375         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2376         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2377     }
2378 
2379     /**
2380      * Tests startAddAccountSession() with null session bundle. Only account
2381      * password and status token should be included in the result as session
2382      * bundle is not inspected.
2383      */
2384     public void testStartAddAccountSessionWithNullSessionBundle()
2385             throws IOException, AuthenticatorException, OperationCanceledException {
2386         final Bundle options = new Bundle();
2387         final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
2388                 + Fixtures.SUFFIX_NAME_FIXTURE;
2389         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
2390         options.putAll(OPTIONS_BUNDLE);
2391 
2392         Bundle resultBundle = startAddAccountSession(
2393                 am,
2394                 ACCOUNT_TYPE,
2395                 AUTH_TOKEN_TYPE,
2396                 REQUIRED_FEATURES,
2397                 options,
2398                 null /* activity */,
2399                 null /* callback */,
2400                 null /* handler */);
2401 
2402         validateStartAddAccountSessionParametersAndOptions(accountName, options);
2403 
2404         // Assert returned result
2405         // Assert that auth token was stripped.
2406         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2407         assertNull(resultBundle.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
2408         assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
2409         assertEquals(ACCOUNT_STATUS_TOKEN,
2410                 resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
2411     }
2412 
2413     /**
2414      * Tests startAddAccountSession() with empty session bundle. An encrypted
2415      * session bundle, account password and status token should be included in
2416      * the result as session bundle is not inspected.
2417      */
2418     public void testStartAddAccountSessionWithEmptySessionBundle()
2419             throws IOException, AuthenticatorException, OperationCanceledException {
2420         final Bundle options = new Bundle();
2421         final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
2422                 + Fixtures.SUFFIX_NAME_FIXTURE;
2423         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
2424         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, new Bundle());
2425         options.putAll(OPTIONS_BUNDLE);
2426 
2427         Bundle resultBundle = startAddAccountSession(
2428                 am,
2429                 ACCOUNT_TYPE,
2430                 AUTH_TOKEN_TYPE,
2431                 REQUIRED_FEATURES,
2432                 options,
2433                 null /* activity */,
2434                 null /* callback */,
2435                 null /* handler */);
2436 
2437         validateStartAddAccountSessionParametersAndOptions(accountName, options);
2438 
2439         // Assert returned result
2440         // Assert that auth token was stripped.
2441         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2442         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2443     }
2444 
2445     /**
2446      * Tests startAddAccountSession with authenticator activity started. When
2447      * Activity is provided, AccountManager would start the resolution Intent
2448      * and return the final result which contains an encrypted session bundle,
2449      * account password and status token.
2450      */
2451     // TODO: Either allow the system to see the activity from instant app,
2452     // Or separate the authenticator and test app to allow the instant app mode test.
2453     @AppModeFull
2454     public void testStartAddAccountSessionIntervene()
2455             throws IOException, AuthenticatorException, OperationCanceledException {
2456         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
2457                 + Fixtures.SUFFIX_NAME_FIXTURE;
2458         final Bundle options = createOptionsWithAccountName(accountName);
2459 
2460         Bundle resultBundle = startAddAccountSession(
2461                 am,
2462                 ACCOUNT_TYPE,
2463                 AUTH_TOKEN_TYPE,
2464                 REQUIRED_FEATURES,
2465                 options,
2466                 mActivity,
2467                 null /* callback */,
2468                 null /* handler */);
2469 
2470         validateStartAddAccountSessionParametersAndOptions(accountName, options);
2471 
2472         // Assert returned result
2473         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
2474         // Assert that auth token was stripped.
2475         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2476         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2477     }
2478 
2479     /**
2480      * Tests startAddAccountSession with KEY_INTENT returned but not started
2481      * automatically. When no Activity is provided and authenticator requires
2482      * additional data from user, KEY_INTENT will be returned by AccountManager.
2483      */
2484     // TODO: Either allow the system to see the activity from instant app,
2485     // Or separate the authenticator and test app to allow the instant app mode test.
2486     @AppModeFull
2487     public void testStartAddAccountSessionWithReturnIntent()
2488             throws IOException, AuthenticatorException, OperationCanceledException {
2489         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
2490                 + Fixtures.SUFFIX_NAME_FIXTURE;
2491         final Bundle options = createOptionsWithAccountName(accountName);
2492 
2493         Bundle resultBundle = startAddAccountSession(
2494                 am,
2495                 ACCOUNT_TYPE,
2496                 AUTH_TOKEN_TYPE,
2497                 REQUIRED_FEATURES,
2498                 options,
2499                 null /* activity */,
2500                 null /* callback */,
2501                 null /* handler */);
2502 
2503         validateStartAddAccountSessionParametersAndOptions(accountName, options);
2504 
2505         // Assert returned result
2506         Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
2507         // Assert that KEY_INTENT is returned.
2508         assertNotNull(returnIntent);
2509         assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
2510         // Assert that no other data is returned.
2511         assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
2512         assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
2513         assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
2514         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2515     }
2516 
2517     /**
2518      * Tests startAddAccountSession error case. AuthenticatorException is
2519      * expected when authenticator return
2520      * {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
2521      */
2522     public void testStartAddAccountSessionError() throws IOException, OperationCanceledException {
2523         final String accountName = Fixtures.PREFIX_NAME_ERROR + "@"
2524                 + Fixtures.SUFFIX_NAME_FIXTURE;
2525         final Bundle options = createOptionsWithAccountNameAndError(accountName);
2526 
2527         try {
2528             startAddAccountSession(
2529                 am,
2530                 ACCOUNT_TYPE,
2531                 AUTH_TOKEN_TYPE,
2532                 REQUIRED_FEATURES,
2533                 options,
2534                 null /* activity */,
2535                 null /* callback */,
2536                 null /* handler */);
2537             fail("startAddAccountSession should throw AuthenticatorException in error case.");
2538         } catch (AuthenticatorException e) {
2539         }
2540     }
2541 
2542     /**
2543      * Tests startAddAccountSession() with callback and handler. An encrypted
2544      * session bundle, account password and status token should be included in
2545      * the result. Callback should be triggered with the result regardless of a
2546      * handler is provided or not.
2547      */
2548     public void testStartAddAccountSessionWithCallbackAndHandler()
2549             throws IOException, AuthenticatorException, OperationCanceledException {
2550         testStartAddAccountSessionWithCallbackAndHandler(null /* handler */);
2551         testStartAddAccountSessionWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
2552     }
2553 
2554     /**
2555      * Tests startAddAccountSession() with callback and handler and activity
2556      * started. When Activity is provided, AccountManager would start the
2557      * resolution Intent and return the final result which contains an encrypted
2558      * session bundle, account password and status token. Callback should be
2559      * triggered with the result regardless of a handled is provided or not.
2560      */
2561     // TODO: Either allow the system to see the activity from instant app,
2562     // Or separate the authenticator and test app to allow the instant app mode test.
2563     @AppModeFull
2564     public void testStartAddAccountSessionWithCallbackAndHandlerWithIntervene()
2565             throws IOException, AuthenticatorException, OperationCanceledException {
2566         testStartAddAccountSessionWithCallbackAndHandlerWithIntervene(null /* handler */);
2567         testStartAddAccountSessionWithCallbackAndHandlerWithIntervene(
2568                 new Handler(Looper.getMainLooper()));
2569     }
2570 
2571     /**
2572      * Tests startAddAccountSession() with callback and handler with KEY_INTENT
2573      * returned. When no Activity is provided and authenticator requires
2574      * additional data from user, KEY_INTENT will be returned by AccountManager
2575      * in callback regardless of a handler is provided or not.
2576      */
2577     // TODO: Either allow the system to see the activity from instant app,
2578     // Or separate the authenticator and test app to allow the instant app mode test.
2579     @AppModeFull
2580     public void testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent()
2581             throws IOException, AuthenticatorException, OperationCanceledException {
2582         testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent(null /* handler */);
2583         testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent(
2584                 new Handler(Looper.getMainLooper()));
2585     }
2586 
2587     /**
2588      * Tests startAddAccountSession() error case with callback and handler.
2589      * AuthenticatorException is expected when authenticator return
2590      * {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
2591      */
2592     public void testStartAddAccountSessionErrorWithCallbackAndHandler()
2593             throws IOException, OperationCanceledException {
2594         testStartAddAccountSessionErrorWithCallbackAndHandler(null /* handler */);
2595         testStartAddAccountSessionErrorWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
2596     }
2597 
2598     private void testStartAddAccountSessionWithCallbackAndHandler(Handler handler)
2599             throws IOException, AuthenticatorException, OperationCanceledException {
2600         final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
2601                 + Fixtures.SUFFIX_NAME_FIXTURE;
2602         final Bundle options = createOptionsWithAccountName(accountName);
2603 
2604         // Wait with timeout for the callback to do its work
2605         final CountDownLatch latch = new CountDownLatch(1);
2606 
2607         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
2608             @Override
2609             public void run(AccountManagerFuture<Bundle> bundleFuture) {
2610                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
2611 
2612                 validateStartAddAccountSessionParametersAndOptions(accountName, options);
2613 
2614                 // Assert returned result
2615                 // Assert that auth token was stripped.
2616                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2617                 validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2618 
2619                 latch.countDown();
2620             }
2621         };
2622 
2623         startAddAccountSession(
2624                 am,
2625                 ACCOUNT_TYPE,
2626                 AUTH_TOKEN_TYPE,
2627                 REQUIRED_FEATURES,
2628                 options,
2629                 mActivity,
2630                 callback,
2631                 handler);
2632         waitForLatch(latch);
2633     }
2634 
2635     private void testStartAddAccountSessionWithCallbackAndHandlerWithIntervene(Handler handler)
2636             throws IOException, AuthenticatorException, OperationCanceledException {
2637         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
2638                 + Fixtures.SUFFIX_NAME_FIXTURE;
2639         final Bundle options = createOptionsWithAccountName(accountName);
2640 
2641         // Wait with timeout for the callback to do its work
2642         final CountDownLatch latch = new CountDownLatch(1);
2643 
2644         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
2645             @Override
2646             public void run(AccountManagerFuture<Bundle> bundleFuture) {
2647                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
2648 
2649                 validateStartAddAccountSessionParametersAndOptions(accountName, options);
2650 
2651                 // Assert returned result
2652                 assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
2653                 // Assert that auth token was stripped.
2654                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2655                 validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2656 
2657                 latch.countDown();
2658             }
2659         };
2660 
2661         startAddAccountSession(
2662                 am,
2663                 ACCOUNT_TYPE,
2664                 AUTH_TOKEN_TYPE,
2665                 REQUIRED_FEATURES,
2666                 options,
2667                 mActivity,
2668                 callback,
2669                 handler);
2670         waitForLatch(latch);
2671     }
2672 
2673     private void testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent(Handler handler)
2674             throws IOException, AuthenticatorException, OperationCanceledException {
2675         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
2676                 + Fixtures.SUFFIX_NAME_FIXTURE;
2677         final Bundle options = createOptionsWithAccountName(accountName);
2678 
2679         // Wait with timeout for the callback to do its work
2680         final CountDownLatch latch = new CountDownLatch(1);
2681 
2682         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
2683             @Override
2684             public void run(AccountManagerFuture<Bundle> bundleFuture) {
2685                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
2686 
2687                 validateStartAddAccountSessionParametersAndOptions(accountName, options);
2688 
2689                 // Assert returned result
2690                 Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
2691                 // Assert KEY_INTENT is returned.
2692                 assertNotNull(returnIntent);
2693                 assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
2694                 // Assert that no other data is returned.
2695                 assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
2696                 assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
2697                 assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
2698                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2699 
2700                 latch.countDown();
2701             }
2702         };
2703 
2704         startAddAccountSession(
2705                 am,
2706                 ACCOUNT_TYPE,
2707                 AUTH_TOKEN_TYPE,
2708                 REQUIRED_FEATURES,
2709                 options,
2710                 null, // activity
2711                 callback,
2712                 handler);
2713         waitForLatch(latch);
2714     }
2715 
2716     private void testStartAddAccountSessionErrorWithCallbackAndHandler(Handler handler)
2717             throws IOException, OperationCanceledException {
2718         final String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2719         final Bundle options = createOptionsWithAccountNameAndError(accountName);
2720 
2721         // Wait with timeout for the callback to do its work
2722         final CountDownLatch latch = new CountDownLatch(1);
2723 
2724         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
2725             @Override
2726             public void run(AccountManagerFuture<Bundle> bundleFuture) {
2727                 try {
2728                     bundleFuture.getResult();
2729                     fail("should have thrown an AuthenticatorException");
2730                 } catch (OperationCanceledException e) {
2731                     fail("should not throw an OperationCanceledException");
2732                 } catch (IOException e) {
2733                     fail("should not throw an IOException");
2734                 } catch (AuthenticatorException e) {
2735                     latch.countDown();
2736                 }
2737             }
2738         };
2739 
2740         try {
2741             startAddAccountSession(
2742                     am,
2743                     ACCOUNT_TYPE,
2744                     AUTH_TOKEN_TYPE,
2745                     REQUIRED_FEATURES,
2746                     options,
2747                     mActivity,
2748                     callback,
2749                     handler);
2750             // AuthenticatorException should be thrown when authenticator
2751             // returns AccountManager.ERROR_CODE_INVALID_RESPONSE.
2752             fail("should have thrown an AuthenticatorException");
2753         } catch (AuthenticatorException e1) {
2754         }
2755 
2756         waitForLatch(latch);
2757     }
2758 
2759     /**
2760      * Test a basic startUpdateCredentialsSession() which returns a bundle containing
2761      * encrypted session bundle, account password and status token.
2762      */
2763     public void testStartUpdateCredentialsSession()
2764             throws IOException, AuthenticatorException, OperationCanceledException {
2765         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2766         Bundle options = createOptionsWithAccountName(accountName);
2767 
2768         Bundle resultBundle = startUpdateCredentialsSession(
2769                 am,
2770                 ACCOUNT,
2771                 AUTH_TOKEN_TYPE,
2772                 options,
2773                 null /* activity */,
2774                 null /* callback */,
2775                 null /* handler */);
2776 
2777         validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
2778 
2779         // Assert returned result
2780         // Assert that auth token was stripped.
2781         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2782         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2783     }
2784 
2785     /**
2786      * Tests startUpdateCredentialsSession() with null session bundle. Only account
2787      * password and status token should be included in the result as session
2788      * bundle is not inspected.
2789      */
2790     public void testStartUpdateCredentialsSessionWithNullSessionBundle()
2791             throws IOException, AuthenticatorException, OperationCanceledException {
2792         Bundle options = new Bundle();
2793         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2794         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
2795         options.putAll(OPTIONS_BUNDLE);
2796 
2797         Bundle resultBundle = startUpdateCredentialsSession(
2798                 am,
2799                 ACCOUNT,
2800                 AUTH_TOKEN_TYPE,
2801                 options,
2802                 null /* activity */,
2803                 null /* callback */,
2804                 null /* handler */);
2805 
2806         validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
2807 
2808         // Assert returned result
2809         // Assert that auth token was stripped.
2810         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2811         assertNull(resultBundle.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
2812         assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
2813         assertEquals(ACCOUNT_STATUS_TOKEN,
2814                 resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
2815     }
2816 
2817     /**
2818      * Tests startUpdateCredentialsSession() with empty session bundle. An encrypted
2819      * session bundle, account password and status token should be included in
2820      * the result as session bundle is not inspected.
2821      */
2822     public void testStartUpdateCredentialsSessionWithEmptySessionBundle()
2823             throws IOException, AuthenticatorException, OperationCanceledException {
2824         Bundle options = new Bundle();
2825         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2826         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
2827         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, new Bundle());
2828         options.putAll(OPTIONS_BUNDLE);
2829 
2830         Bundle resultBundle = startUpdateCredentialsSession(
2831                 am,
2832                 ACCOUNT,
2833                 AUTH_TOKEN_TYPE,
2834                 options,
2835                 null /* activity */,
2836                 null /* callback */,
2837                 null /* handler */);
2838 
2839         validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
2840 
2841         // Assert returned result
2842         // Assert that auth token was stripped.
2843         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2844         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2845     }
2846 
2847     /**
2848      * Tests startUpdateCredentialsSession with authenticator activity started. When
2849      * Activity is provided, AccountManager would start the resolution Intent
2850      * and return the final result which contains an encrypted session bundle,
2851      * account password and status token.
2852      */
2853     // TODO: Either allow the system to see the activity from instant app,
2854     // Or separate the authenticator and test app to allow the instant app mode test.
2855     @AppModeFull
2856     public void testStartUpdateCredentialsSessionIntervene()
2857             throws IOException, AuthenticatorException, OperationCanceledException {
2858         String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2859         Bundle options = createOptionsWithAccountName(accountName);
2860 
2861         Bundle resultBundle = startUpdateCredentialsSession(
2862                 am,
2863                 ACCOUNT,
2864                 AUTH_TOKEN_TYPE,
2865                 options,
2866                 mActivity,
2867                 null /* callback */,
2868                 null /* handler */);
2869 
2870         validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
2871 
2872         // Assert returned result
2873         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
2874         // Assert that auth token was stripped.
2875         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2876         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
2877     }
2878 
2879     /**
2880      * Tests startUpdateCredentialsSession with KEY_INTENT returned but not
2881      * started automatically. When no Activity is provided and authenticator requires
2882      * additional data from user, KEY_INTENT will be returned by AccountManager.
2883      */
2884     // TODO: Either allow the system to see the activity from instant app,
2885     // Or separate the authenticator and test app to allow the instant app mode test.
2886     @AppModeFull
2887     public void testStartUpdateCredentialsSessionWithReturnIntent()
2888             throws IOException, AuthenticatorException, OperationCanceledException {
2889         String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2890         Bundle options = createOptionsWithAccountName(accountName);
2891 
2892         Bundle resultBundle = startUpdateCredentialsSession(
2893                 am,
2894                 ACCOUNT,
2895                 AUTH_TOKEN_TYPE,
2896                 options,
2897                 null /* activity */,
2898                 null /* callback */,
2899                 null /* handler */);
2900 
2901         validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
2902 
2903         // Assert returned result
2904         Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
2905         // Assert that KEY_INTENT is returned.
2906         assertNotNull(returnIntent);
2907         assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
2908         // Assert that no other data is returned.
2909         assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
2910         assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
2911         assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
2912         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
2913     }
2914 
2915     /**
2916      * Tests startUpdateCredentialsSession error case. AuthenticatorException is
2917      * expected when authenticator return
2918      * {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
2919      */
2920     public void testStartUpdateCredentialsSessionError()
2921             throws IOException, OperationCanceledException {
2922         String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
2923         Bundle options = createOptionsWithAccountNameAndError(accountName);
2924 
2925         try {
2926             startUpdateCredentialsSession(
2927                     am,
2928                     ACCOUNT,
2929                     AUTH_TOKEN_TYPE,
2930                     options,
2931                     null /* activity */,
2932                     null /* callback */,
2933                     null /* handler */);
2934             fail("startUpdateCredentialsSession should throw AuthenticatorException in error.");
2935         } catch (AuthenticatorException e) {
2936         }
2937     }
2938 
2939     /**
2940      * Tests startUpdateCredentialsSession() with callback and handler. An encrypted
2941      * session bundle, account password and status token should be included in
2942      * the result. Callback should be triggered with the result regardless of a
2943      * handler is provided or not.
2944      */
2945     public void testStartUpdateCredentialsSessionWithCallbackAndHandler()
2946             throws IOException, AuthenticatorException, OperationCanceledException {
2947         testStartUpdateCredentialsSessionWithCallbackAndHandler(null /* handler */);
2948         testStartUpdateCredentialsSessionWithCallbackAndHandler(
2949                 new Handler(Looper.getMainLooper()));
2950     }
2951 
2952     /**
2953      * Tests startUpdateCredentialsSession() with callback and handler and
2954      * activity started. When Activity is provided, AccountManager would start the
2955      * resolution Intent and return the final result which contains an encrypted
2956      * session bundle, account password and status token. Callback should be
2957      * triggered with the result regardless of a handler is provided or not.
2958      */
2959     // TODO: Either allow the system to see the activity from instant app,
2960     // Or separate the authenticator and test app to allow the instant app mode test.
2961     @AppModeFull
2962     public void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene()
2963             throws IOException, AuthenticatorException, OperationCanceledException {
2964         testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene(null /* handler */);
2965         testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene(
2966                 new Handler(Looper.getMainLooper()));
2967     }
2968 
2969     /**
2970      * Tests startUpdateCredentialsSession() with callback and handler with
2971      * KEY_INTENT returned. When no Activity is provided and authenticator requires
2972      * additional data from user, KEY_INTENT will be returned by AccountManager
2973      * in callback regardless of a handler is provided or not.
2974      */
2975     // TODO: Either allow the system to see the activity from instant app,
2976     // Or separate the authenticator and test app to allow the instant app mode test.
2977     @AppModeFull
2978     public void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent()
2979             throws IOException, AuthenticatorException, OperationCanceledException {
2980         testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent(null /* handler */);
2981         testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent(
2982                 new Handler(Looper.getMainLooper()));
2983     }
2984 
2985     /**
2986      * Tests startUpdateCredentialsSession() error case with callback and
2987      * handler. AuthenticatorException is expected when authenticator return
2988      * {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
2989      */
2990     public void testStartUpdateCredentialsSessionErrorWithCallbackAndHandler()
2991             throws IOException, OperationCanceledException {
2992         testStartUpdateCredentialsSessionErrorWithCallbackAndHandler(null /* handler */);
2993         testStartUpdateCredentialsSessionErrorWithCallbackAndHandler(
2994                 new Handler(Looper.getMainLooper()));
2995     }
2996 
2997     private void testStartUpdateCredentialsSessionWithCallbackAndHandler(Handler handler)
2998             throws IOException, AuthenticatorException, OperationCanceledException {
2999         final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
3000                 + Fixtures.SUFFIX_NAME_FIXTURE;
3001         final Bundle options = createOptionsWithAccountName(accountName);
3002 
3003         final CountDownLatch latch = new CountDownLatch(1);
3004 
3005         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
3006             @Override
3007             public void run(AccountManagerFuture<Bundle> bundleFuture) {
3008                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
3009 
3010                 validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
3011 
3012                 // Assert returned result
3013                 // Assert that auth token was stripped.
3014                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3015                 validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3016 
3017                 latch.countDown();
3018             }
3019         };
3020 
3021         startUpdateCredentialsSession(
3022                 am,
3023                 ACCOUNT,
3024                 AUTH_TOKEN_TYPE,
3025                 options,
3026                 mActivity,
3027                 callback,
3028                 handler);
3029 
3030         waitForLatch(latch);
3031     }
3032 
3033     private void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene(
3034             Handler handler)
3035                     throws IOException, AuthenticatorException, OperationCanceledException {
3036         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
3037                 + Fixtures.SUFFIX_NAME_FIXTURE;
3038         final Bundle options = createOptionsWithAccountName(accountName);
3039 
3040         final CountDownLatch latch = new CountDownLatch(1);
3041 
3042         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
3043             @Override
3044             public void run(AccountManagerFuture<Bundle> bundleFuture) {
3045                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
3046 
3047                 validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
3048 
3049                 // Assert returned result
3050                 assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
3051                 // Assert that auth token was stripped.
3052                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3053                 validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3054 
3055                 latch.countDown();
3056             }
3057         };
3058 
3059         startUpdateCredentialsSession(
3060                 am,
3061                 ACCOUNT,
3062                 AUTH_TOKEN_TYPE,
3063                 options,
3064                 mActivity,
3065                 callback,
3066                 handler);
3067 
3068         waitForLatch(latch);
3069     }
3070 
3071     private void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent(
3072             Handler handler)
3073                     throws IOException, AuthenticatorException, OperationCanceledException {
3074         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
3075                 + Fixtures.SUFFIX_NAME_FIXTURE;
3076         final Bundle options = createOptionsWithAccountName(accountName);
3077 
3078         final CountDownLatch latch = new CountDownLatch(1);
3079 
3080         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
3081             @Override
3082             public void run(AccountManagerFuture<Bundle> bundleFuture) {
3083                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
3084 
3085                 validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
3086 
3087                 // Assert returned result
3088                 Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
3089                 // Assert KEY_INTENT is returned.
3090                 assertNotNull(returnIntent);
3091                 assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
3092                 // Assert that no other data is returned.
3093                 assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
3094                 assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
3095                 assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
3096                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3097 
3098                 latch.countDown();
3099             }
3100         };
3101 
3102         startUpdateCredentialsSession(
3103                 am,
3104                 ACCOUNT,
3105                 AUTH_TOKEN_TYPE,
3106                 options,
3107                 null,
3108                 callback,
3109                 handler);
3110 
3111         waitForLatch(latch);
3112     }
3113 
3114     private void testStartUpdateCredentialsSessionErrorWithCallbackAndHandler(Handler handler)
3115             throws IOException, OperationCanceledException {
3116         final String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3117         final Bundle options = createOptionsWithAccountNameAndError(accountName);
3118 
3119         final CountDownLatch latch = new CountDownLatch(1);
3120 
3121         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
3122             @Override
3123             public void run(AccountManagerFuture<Bundle> bundleFuture) {
3124                 try {
3125                     bundleFuture.getResult();
3126                     fail("should have thrown an AuthenticatorException");
3127                 } catch (OperationCanceledException e) {
3128                     fail("should not throw an OperationCanceledException");
3129                 } catch (IOException e) {
3130                     fail("should not throw an IOException");
3131                 } catch (AuthenticatorException e) {
3132                     latch.countDown();
3133                 }
3134             }
3135         };
3136 
3137         try {
3138             startUpdateCredentialsSession(
3139                     am,
3140                     ACCOUNT,
3141                     AUTH_TOKEN_TYPE,
3142                     options,
3143                     mActivity,
3144                     callback,
3145                     handler);
3146             // AuthenticatorException should be thrown when authenticator
3147             // returns AccountManager.ERROR_CODE_INVALID_RESPONSE.
3148             fail("should have thrown an AuthenticatorException");
3149         } catch (AuthenticatorException e1) {
3150         }
3151 
3152         waitForLatch(latch);
3153     }
3154 
3155     private Bundle startUpdateCredentialsSession(AccountManager am,
3156             Account account,
3157             String authTokenType,
3158             Bundle options,
3159             Activity activity,
3160             AccountManagerCallback<Bundle> callback,
3161             Handler handler)
3162                     throws IOException, AuthenticatorException, OperationCanceledException {
3163 
3164         AccountManagerFuture<Bundle> futureBundle = am.startUpdateCredentialsSession(
3165                 account,
3166                 authTokenType,
3167                 options,
3168                 activity,
3169                 callback,
3170                 handler);
3171 
3172         Bundle resultBundle = futureBundle.getResult();
3173         assertTrue(futureBundle.isDone());
3174         assertNotNull(resultBundle);
3175 
3176         return resultBundle;
3177     }
3178 
3179     private Bundle startAddAccountSession(AccountManager am,
3180             String accountType,
3181             String authTokenType,
3182             String[] requiredFeatures,
3183             Bundle options,
3184             Activity activity,
3185             AccountManagerCallback<Bundle> callback,
3186             Handler handler)
3187                     throws IOException, AuthenticatorException, OperationCanceledException {
3188 
3189         AccountManagerFuture<Bundle> futureBundle = am.startAddAccountSession(
3190                 accountType,
3191                 authTokenType,
3192                 requiredFeatures,
3193                 options,
3194                 activity,
3195                 callback,
3196                 handler);
3197 
3198         Bundle resultBundle = futureBundle.getResult();
3199         assertTrue(futureBundle.isDone());
3200         assertNotNull(resultBundle);
3201 
3202         return resultBundle;
3203     }
3204 
3205     private Bundle createOptionsWithAccountName(final String accountName) {
3206         SESSION_BUNDLE.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
3207         Bundle options = new Bundle();
3208         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
3209         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, SESSION_BUNDLE);
3210         options.putAll(OPTIONS_BUNDLE);
3211         return options;
3212     }
3213 
3214     private Bundle createOptionsWithAccountNameAndError(final String accountName) {
3215         Bundle options = createOptionsWithAccountName(accountName);
3216         options.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_INVALID_RESPONSE);
3217         options.putString(AccountManager.KEY_ERROR_MESSAGE, ERROR_MESSAGE);
3218         return options;
3219     }
3220 
3221 
3222     private void validateStartAddAccountSessionParametersAndOptions(
3223             String accountName, Bundle options) {
3224         // Assert parameters has been passed correctly
3225         validateAccountAndAuthTokenType();
3226         validateFeatures();
3227 
3228         // Validate options
3229         validateOptions(options, mockAuthenticator.mOptionsStartAddAccountSession);
3230         assertNotNull(mockAuthenticator.mOptionsStartAddAccountSession);
3231         assertEquals(accountName, mockAuthenticator.mOptionsStartAddAccountSession
3232                 .getString(Fixtures.KEY_ACCOUNT_NAME));
3233 
3234         validateSystemOptions(mockAuthenticator.mOptionsStartAddAccountSession);
3235         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
3236         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
3237         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
3238         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
3239         validateOptions(null, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
3240         validateOptions(null, mockAuthenticator.mOptionsFinishSession);
3241     }
3242 
3243     private void validateStartUpdateCredentialsSessionParametersAndOptions(
3244             String accountName, Bundle options) {
3245         // Assert parameters has been passed correctly
3246         assertEquals(AUTH_TOKEN_TYPE, mockAuthenticator.getAuthTokenType());
3247         assertEquals(ACCOUNT, mockAuthenticator.mAccount);
3248 
3249         // Validate options
3250         validateOptions(options, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
3251         assertNotNull(mockAuthenticator.mOptionsStartUpdateCredentialsSession);
3252         assertEquals(accountName, mockAuthenticator.mOptionsStartUpdateCredentialsSession
3253                 .getString(Fixtures.KEY_ACCOUNT_NAME));
3254 
3255         // Validate system options
3256         assertNotNull(mockAuthenticator.mOptionsStartUpdateCredentialsSession
3257                 .getString(AccountManager.KEY_ANDROID_PACKAGE_NAME));
3258 
3259         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
3260         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
3261         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
3262         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
3263         validateOptions(null, mockAuthenticator.mOptionsStartAddAccountSession);
3264         validateOptions(null, mockAuthenticator.mOptionsFinishSession);
3265     }
3266 
3267     private void validateIsCredentialsUpdateSuggestedParametersAndOptions(Account account) {
3268         assertEquals(account, mockAuthenticator.getAccount());
3269         assertEquals(ACCOUNT_STATUS_TOKEN, mockAuthenticator.getStatusToken());
3270 
3271         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
3272         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
3273         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
3274         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
3275         validateOptions(null, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
3276         validateOptions(null, mockAuthenticator.mOptionsStartAddAccountSession);
3277     }
3278 
3279     private void validateSessionBundleAndPasswordAndStatusTokenResult(Bundle resultBundle) {
3280         Bundle sessionBundle = resultBundle.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3281         assertNotNull(sessionBundle);
3282         // Assert that session bundle is encrypted and hence data not visible.
3283         assertNull(sessionBundle.getString(SESSION_DATA_NAME_1));
3284         // Assert password is not returned since cts test is not signed with system key
3285         assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
3286         assertEquals(ACCOUNT_STATUS_TOKEN,
3287                 resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
3288     }
3289 
3290     private Bundle getResultExpectNoException(AccountManagerFuture<Bundle> bundleFuture) {
3291         try {
3292             return bundleFuture.getResult();
3293         } catch (OperationCanceledException e) {
3294             fail("should not throw an OperationCanceledException");
3295         } catch (IOException e) {
3296             fail("should not throw an IOException");
3297         } catch (AuthenticatorException e) {
3298             fail("should not throw an AuthenticatorException");
3299         }
3300         return null;
3301     }
3302 
3303     /**
3304      * Tests a basic finishSession() with session bundle created by
3305      * startAddAccountSession(...). A bundle containing account name and account
3306      * type is expected.
3307      */
3308     public void testFinishSessionWithStartAddAccountSession()
3309             throws IOException, AuthenticatorException, OperationCanceledException {
3310         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3311         Bundle options = createOptionsWithAccountName(accountName);
3312 
3313         // First get an encrypted session bundle from startAddAccountSession(...)
3314         Bundle resultBundle = startAddAccountSession(
3315                 am,
3316                 ACCOUNT_TYPE,
3317                 AUTH_TOKEN_TYPE,
3318                 REQUIRED_FEATURES,
3319                 options,
3320                 null /* activity */,
3321                 null /* callback */,
3322                 null /* handler */);
3323 
3324         // Assert returned result
3325         // Assert that auth token was stripped.
3326         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3327         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3328         Bundle encryptedSessionBundle = resultBundle
3329                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3330 
3331         resultBundle = finishSession(
3332                 am,
3333                 encryptedSessionBundle,
3334                 null /* activity */,
3335                 null /* callback */,
3336                 null /* handler */);
3337 
3338         // Assert parameters has been passed correctly
3339         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3340         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3341 
3342         // Assert returned result containing account name, type but not auth token type.
3343         validateAccountAndNoAuthTokenResult(resultBundle);
3344     }
3345 
3346     /**
3347      * Tests a basic finishSession() with session bundle created by
3348      * startUpdateCredentialsSession(...). A bundle containing account name and account
3349      * type is expected.
3350      */
3351     public void testFinishSessionWithStartUpdateCredentialsSession()
3352             throws IOException, AuthenticatorException, OperationCanceledException {
3353         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3354         Bundle options = createOptionsWithAccountName(accountName);
3355 
3356         // First get an encrypted session bundle from startUpdateCredentialsSession(...)
3357         Bundle resultBundle = startUpdateCredentialsSession(
3358                 am,
3359                 ACCOUNT,
3360                 AUTH_TOKEN_TYPE,
3361                 options,
3362                 null /* activity */,
3363                 null /* callback */,
3364                 null /* handler */);
3365 
3366         // Assert returned result
3367         // Assert that auth token was stripped.
3368         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3369         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3370         Bundle encryptedSessionBundle = resultBundle
3371                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3372 
3373         resultBundle = finishSession(
3374                 am,
3375                 encryptedSessionBundle,
3376                 null /* activity */,
3377                 null /* callback */,
3378                 null /* handler */);
3379 
3380         // Assert parameters has been passed correctly
3381         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3382         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3383 
3384         // Assert returned result containing account name, type but not auth token type.
3385         validateAccountAndNoAuthTokenResult(resultBundle);
3386     }
3387 
3388     /**
3389      * Tests finishSession() with null session bundle. IllegalArgumentException
3390      * is expected as session bundle cannot be null.
3391      */
3392     public void testFinishSessionWithNullSessionBundle()
3393             throws IOException, AuthenticatorException, OperationCanceledException {
3394         try {
3395             finishSession(
3396                     am,
3397                     null /* sessionBundle */,
3398                     null /* activity */,
3399                     null /* callback */,
3400                     null /* handler */);
3401             fail("Should have thrown IllegalArgumentException when sessionBundle is null");
3402         } catch (IllegalArgumentException e) {
3403 
3404         }
3405     }
3406 
3407     /**
3408      * Tests finishSession() with empty session bundle. IllegalArgumentException
3409      * is expected as session bundle would always contain something if it was
3410      * processed properly by AccountManagerService.
3411      */
3412     public void testFinishSessionWithEmptySessionBundle()
3413             throws IOException, AuthenticatorException, OperationCanceledException {
3414 
3415         try {
3416             finishSession(am,
3417                     new Bundle(),
3418                     null /* activity */,
3419                     null /* callback */,
3420                     null /* handler */);
3421             fail("Should have thrown IllegalArgumentException when sessionBundle is empty");
3422         } catch (IllegalArgumentException e) {
3423 
3424         }
3425     }
3426 
3427     /**
3428      * Tests finishSession() with sessionBundle not encrypted by the right key.
3429      * AuthenticatorException is expected if AccountManagerService failed to
3430      * decrypt the session bundle because of wrong key or crypto data was
3431      * tampered.
3432      */
3433     public void testFinishSessionWithDecryptionError()
3434             throws IOException, OperationCanceledException {
3435         byte[] mac = new byte[] {
3436                 1, 1, 0, 0
3437         };
3438         byte[] cipher = new byte[] {
3439                 1, 0, 0, 1, 1
3440         };
3441         Bundle sessionBundle = new Bundle();
3442         sessionBundle.putByteArray(KEY_MAC, mac);
3443         sessionBundle.putByteArray(KEY_CIPHER, cipher);
3444 
3445         try {
3446             finishSession(am,
3447                     sessionBundle,
3448                     null /* activity */,
3449                     null /* callback */,
3450                     null /* handler */);
3451             fail("Should have thrown AuthenticatorException when failed to decrypt sessionBundle");
3452         } catch (AuthenticatorException e) {
3453 
3454         }
3455     }
3456 
3457     /**
3458      * Tests finishSession() with sessionBundle invalid contents.
3459      * AuthenticatorException is expected if AccountManagerService failed to
3460      * decrypt the session bundle because of wrong key or crypto data was
3461      * tampered.
3462      */
3463     public void testFinishSessionWithInvalidEncryptedContent()
3464             throws IOException, OperationCanceledException {
3465         byte[] mac = new byte[] {};
3466         Bundle sessionBundle = new Bundle();
3467         sessionBundle.putByteArray(KEY_MAC, mac);
3468 
3469         try {
3470             finishSession(am,
3471                     sessionBundle,
3472                     null /* activity */,
3473                     null /* callback */,
3474                     null /* handler */);
3475             fail("Should have thrown AuthenticatorException when failed to decrypt sessionBundle");
3476         } catch (AuthenticatorException e) {
3477         }
3478     }
3479 
3480     /**
3481      * Tests a finishSession() when account type is not added to session bundle
3482      * by startAddAccount(...) of authenticator. A bundle containing account
3483      * name and account type should still be returned as AccountManagerSerivce
3484      * will always add account type to the session bundle before encrypting it.
3485      */
3486     public void testFinishSessionFromStartAddAccountWithoutAccountType()
3487             throws IOException, AuthenticatorException, OperationCanceledException {
3488         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3489         // Create a session bundle without account type for MockAccountAuthenticator to return
3490         SESSION_BUNDLE.remove(AccountManager.KEY_ACCOUNT_TYPE);
3491         Bundle options = createOptionsWithAccountName(accountName);
3492 
3493         // First get an encrypted session bundle from startAddAccountSession(...)
3494         Bundle resultBundle = startAddAccountSession(
3495                 am,
3496                 ACCOUNT_TYPE,
3497                 AUTH_TOKEN_TYPE,
3498                 REQUIRED_FEATURES,
3499                 options,
3500                 null /* activity */,
3501                 null /* callback */,
3502                 null /* handler */);
3503 
3504         // Assert returned result
3505         // Assert that auth token was stripped.
3506         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3507         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3508         Bundle encryptedSessionBundle = resultBundle
3509                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3510 
3511         resultBundle = finishSession(
3512                 am,
3513                 encryptedSessionBundle,
3514                 null /* activity */,
3515                 null /* callback */,
3516                 null /* handler */);
3517 
3518         // Assert parameters has been passed correctly
3519         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3520 
3521         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3522 
3523         // Assert returned result containing account name, type but not auth token type.
3524         validateAccountAndNoAuthTokenResult(resultBundle);
3525     }
3526 
3527     /**
3528      * Tests a finishSession() when account type is not added to session bundle
3529      * by startUpdateCredentialsSession(...) of authenticator. A bundle
3530      * containing account name and account type should still be returned as
3531      * AccountManagerSerivce will always add account type to the session bundle
3532      * before encrypting it.
3533      */
3534     public void testFinishSessionFromStartUpdateCredentialsSessionWithoutAccountType()
3535             throws IOException, AuthenticatorException, OperationCanceledException {
3536         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3537         // Create a session bundle without account type for MockAccountAuthenticator to return
3538         SESSION_BUNDLE.remove(AccountManager.KEY_ACCOUNT_TYPE);
3539         Bundle options = createOptionsWithAccountName(accountName);
3540 
3541         // First get an encrypted session bundle from startAddAccountSession(...)
3542         Bundle resultBundle = startUpdateCredentialsSession(
3543                 am,
3544                 ACCOUNT,
3545                 AUTH_TOKEN_TYPE,
3546                 options,
3547                 null /* activity */,
3548                 null /* callback */,
3549                 null /* handler */);
3550 
3551         // Assert returned result
3552         // Assert that auth token was stripped.
3553         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3554         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3555         Bundle encryptedSessionBundle = resultBundle
3556                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3557 
3558         resultBundle = finishSession(
3559                 am,
3560                 encryptedSessionBundle,
3561                 null /* activity */,
3562                 null /* callback */,
3563                 null /* handler */);
3564 
3565         // Assert parameters has been passed correctly
3566         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3567 
3568         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3569 
3570         // Assert returned result containing account name, type but not auth token type.
3571         validateAccountAndNoAuthTokenResult(resultBundle);
3572     }
3573 
3574     /**
3575      * Tests a finishSession() when a different account type is added to session bundle
3576      * by startAddAccount(...) of authenticator. A bundle containing account
3577      * name and the correct account type should be returned as AccountManagerSerivce
3578      * will always overrides account type to the session bundle before encrypting it.
3579      */
3580     public void testFinishSessionFromStartAddAccountAccountTypeOverriden()
3581             throws IOException, AuthenticatorException, OperationCanceledException {
3582         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3583         SESSION_BUNDLE.putString(AccountManager.KEY_ACCOUNT_TYPE, "randomAccountType");
3584         Bundle options = createOptionsWithAccountName(accountName);
3585 
3586         // First get an encrypted session bundle from startAddAccountSession(...)
3587         Bundle resultBundle = startAddAccountSession(
3588                 am,
3589                 ACCOUNT_TYPE,
3590                 AUTH_TOKEN_TYPE,
3591                 REQUIRED_FEATURES,
3592                 options,
3593                 null /* activity */,
3594                 null /* callback */,
3595                 null /* handler */);
3596 
3597         // Assert returned result
3598         // Assert that auth token was stripped.
3599         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3600         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3601         Bundle encryptedSessionBundle = resultBundle
3602                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3603 
3604         resultBundle = finishSession(
3605                 am,
3606                 encryptedSessionBundle,
3607                 null /* activity */,
3608                 null /* callback */,
3609                 null /* handler */);
3610 
3611         // Assert parameters has been passed correctly
3612         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3613 
3614         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3615 
3616         // Assert returned result containing account name, correct type but not auth token type.
3617         validateAccountAndNoAuthTokenResult(resultBundle);
3618     }
3619 
3620     /**
3621      * Tests a finishSession() when a different account type is added to session bundle
3622      * by startUpdateCredentialsSession(...) of authenticator. A bundle
3623      * containing account name and the correct account type should be returned as
3624      * AccountManagerSerivce will always override account type to the session bundle
3625      * before encrypting it.
3626      */
3627     public void testFinishSessionFromStartUpdateCredentialsSessionAccountTypeOverriden()
3628             throws IOException, AuthenticatorException, OperationCanceledException {
3629         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3630         // MockAccountAuthenticator to return
3631         SESSION_BUNDLE.putString(AccountManager.KEY_ACCOUNT_TYPE, "randomAccountType");
3632         Bundle options = createOptionsWithAccountName(accountName);
3633 
3634         // First get an encrypted session bundle from startAddAccountSession(...)
3635         Bundle resultBundle = startUpdateCredentialsSession(
3636                 am,
3637                 ACCOUNT,
3638                 AUTH_TOKEN_TYPE,
3639                 options,
3640                 null /* activity */,
3641                 null /* callback */,
3642                 null /* handler */);
3643 
3644         // Assert returned result
3645         // Assert that auth token was stripped.
3646         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3647         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3648         Bundle encryptedSessionBundle = resultBundle
3649                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3650 
3651         resultBundle = finishSession(
3652                 am,
3653                 encryptedSessionBundle,
3654                 null /* activity */,
3655                 null /* callback */,
3656                 null /* handler */);
3657 
3658         // Assert parameters has been passed correctly
3659         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3660 
3661         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3662 
3663         // Assert returned result containing account name, correct type but not auth token type.
3664         validateAccountAndNoAuthTokenResult(resultBundle);
3665     }
3666 
3667     /**
3668      * Tests finishSession with authenticator activity started. When additional
3669      * info is needed from user for finishing the session and an Activity was
3670      * provided by caller, the resolution intent will be started automatically.
3671      * A bundle containing account name and type will be returned.
3672      */
3673     // TODO: Either allow the system to see the activity from instant app,
3674     // Or separate the authenticator and test app to allow the instant app mode test.
3675     @AppModeFull
3676     public void testFinishSessionIntervene()
3677             throws IOException, AuthenticatorException, OperationCanceledException {
3678         String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3679         Bundle options = createOptionsWithAccountName(accountName);
3680 
3681         // First get an encrypted session bundle from startAddAccountSession(...)
3682         Bundle resultBundle = startAddAccountSession(
3683                 am,
3684                 ACCOUNT_TYPE,
3685                 AUTH_TOKEN_TYPE,
3686                 REQUIRED_FEATURES,
3687                 options,
3688                 mActivity,
3689                 null /* callback */,
3690                 null /* handler */);
3691 
3692         // Assert returned result
3693         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
3694         // Assert that auth token was stripped.
3695         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3696         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3697         Bundle encryptedSessionBundle = resultBundle
3698                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3699 
3700         resultBundle = finishSession(
3701                 am,
3702                 encryptedSessionBundle,
3703                 mActivity,
3704                 null /* callback */,
3705                 null /* handler */);
3706 
3707         // Assert parameters has been passed correctly
3708         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3709 
3710         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3711 
3712         // Assert returned result
3713         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
3714         // Assert returned result containing account name, type but not auth token type.
3715         validateAccountAndNoAuthTokenResult(resultBundle);
3716     }
3717 
3718     /**
3719      * Tests finishSession with KEY_INTENT returned but not started
3720      * automatically. When additional info is needed from user for finishing the
3721      * session and no Activity was provided by caller, the resolution intent
3722      * will not be started automatically. A bundle containing KEY_INTENT will be
3723      * returned instead.
3724      */
3725     // TODO: Either allow the system to see the activity from instant app,
3726     // Or separate the authenticator and test app to allow the instant app mode test.
3727     @AppModeFull
3728     public void testFinishSessionWithReturnIntent()
3729             throws IOException, AuthenticatorException, OperationCanceledException {
3730         String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3731         Bundle options = createOptionsWithAccountName(accountName);
3732 
3733         // First get an encrypted session bundle from startAddAccountSession(...)
3734         Bundle resultBundle = startAddAccountSession(
3735                 am,
3736                 ACCOUNT_TYPE,
3737                 AUTH_TOKEN_TYPE,
3738                 REQUIRED_FEATURES,
3739                 options,
3740                 mActivity,
3741                 null /* callback */,
3742                 null /* handler */);
3743 
3744         // Assert returned result
3745         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
3746         // Assert that auth token was stripped.
3747         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3748         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3749         Bundle encryptedSessionBundle = resultBundle
3750                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3751 
3752         resultBundle = finishSession(
3753                 am,
3754                 encryptedSessionBundle,
3755                 null /* activity */,
3756                 null /* callback */,
3757                 null /* handler */);
3758 
3759         // Assert parameters has been passed correctly
3760         assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3761 
3762         validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3763 
3764         // Assert returned result
3765         Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
3766         assertNotNull(returnIntent);
3767         assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
3768 
3769         assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
3770         assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
3771         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3772     }
3773 
3774     /**
3775      * Tests finishSession error case. AuthenticatorException is expected when
3776      * AccountManager.ERROR_CODE_INVALID_RESPONSE is returned by authenticator.
3777      */
3778     public void testFinishSessionError()
3779             throws IOException, AuthenticatorException, OperationCanceledException {
3780         Bundle sessionBundle = new Bundle();
3781         String accountNameForFinish = Fixtures.PREFIX_NAME_ERROR + "@"
3782                 + Fixtures.SUFFIX_NAME_FIXTURE;
3783         sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountNameForFinish);
3784         sessionBundle.putInt(AccountManager.KEY_ERROR_CODE,
3785                 AccountManager.ERROR_CODE_INVALID_RESPONSE);
3786         sessionBundle.putString(AccountManager.KEY_ERROR_MESSAGE, ERROR_MESSAGE);
3787 
3788         Bundle options = new Bundle();
3789         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
3790         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
3791         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
3792         options.putAll(OPTIONS_BUNDLE);
3793 
3794         // First get an encrypted session bundle from startAddAccountSession(...)
3795         Bundle resultBundle = startAddAccountSession(
3796                 am,
3797                 ACCOUNT_TYPE,
3798                 AUTH_TOKEN_TYPE,
3799                 REQUIRED_FEATURES,
3800                 options,
3801                 null /* activity */,
3802                 null /* callback */,
3803                 null /* handler */);
3804 
3805         // Assert returned result
3806         // Assert that auth token was stripped.
3807         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3808         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3809         Bundle encryptedSessionBundle = resultBundle
3810                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3811 
3812         try {
3813             finishSession(
3814                     am,
3815                     encryptedSessionBundle,
3816                     null /* activity */,
3817                     null /* callback */,
3818                     null /* handler */);
3819             fail("finishSession should throw AuthenticatorException in error case.");
3820         } catch (AuthenticatorException e) {
3821         }
3822     }
3823 
3824     /**
3825      * Tests finishSession() with callback and handler. A bundle containing
3826      * account name and type should be returned via the callback regardless of
3827      * whether a handler is provided.
3828      */
3829     public void testFinishSessionWithCallbackAndHandler()
3830             throws IOException, AuthenticatorException, OperationCanceledException {
3831         testFinishSessionWithCallbackAndHandler(null /* handler */);
3832         testFinishSessionWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
3833     }
3834 
3835     /**
3836      * Tests finishSession() with callback and handler and activity started.
3837      * When additional info is needed from user for finishing the session and an
3838      * Activity was provided by caller, the resolution intent will be started
3839      * automatically. A bundle containing account name and type will be returned
3840      * via the callback regardless of if handler is provided or now.
3841      */
3842     // TODO: Either allow the system to see the activity from instant app,
3843     // Or separate the authenticator and test app to allow the instant app mode test.
3844     @AppModeFull
3845     public void testFinishSessionWithCallbackAndHandlerWithIntervene()
3846             throws IOException, AuthenticatorException, OperationCanceledException {
3847         testFinishSessionWithCallbackAndHandlerWithIntervene(null /* handler */);
3848         testFinishSessionWithCallbackAndHandlerWithIntervene(
3849                 new Handler(Looper.getMainLooper()));
3850     }
3851 
3852     /**
3853      * Tests finishSession() with callback and handler with KEY_INTENT
3854      * returned. When additional info is needed from user for finishing the
3855      * session and no Activity was provided by caller, the resolution intent
3856      * will not be started automatically. A bundle containing KEY_INTENT will be
3857      * returned instead via callback regardless of if handler is provided or not.
3858      */
3859     // TODO: Either allow the system to see the activity from instant app,
3860     // Or separate the authenticator and test app to allow the instant app mode test.
3861     @AppModeFull
3862     public void testFinishSessionWithCallbackAndHandlerWithReturnIntent()
3863             throws IOException, AuthenticatorException, OperationCanceledException {
3864         testFinishSessionWithCallbackAndHandlerWithReturnIntent(null /* handler */);
3865         testFinishSessionWithCallbackAndHandlerWithReturnIntent(
3866                 new Handler(Looper.getMainLooper()));
3867     }
3868 
3869     /**
3870      * Tests finishSession() error case with callback and handler.
3871      * AuthenticatorException is expected when
3872      * AccountManager.ERROR_CODE_INVALID_RESPONSE is returned by authenticator.
3873      */
3874     public void testFinishSessionErrorWithCallbackAndHandler()
3875             throws IOException, OperationCanceledException, AuthenticatorException {
3876         testFinishSessionErrorWithCallbackAndHandler(null /* handler */);
3877         testFinishSessionErrorWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
3878     }
3879 
3880     private void testFinishSessionWithCallbackAndHandler(Handler handler)
3881             throws IOException, AuthenticatorException, OperationCanceledException {
3882         final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
3883                 + Fixtures.SUFFIX_NAME_FIXTURE;
3884         Bundle options = createOptionsWithAccountName(accountName);
3885 
3886         // First get an encrypted session bundle from startAddAccountSession(...)
3887         Bundle resultBundle = startAddAccountSession(
3888                 am,
3889                 ACCOUNT_TYPE,
3890                 AUTH_TOKEN_TYPE,
3891                 REQUIRED_FEATURES,
3892                 options,
3893                 null /* activity */,
3894                 null /* callback */,
3895                 null /* handler */);
3896 
3897         // Assert returned result
3898         // Assert that auth token was stripped.
3899         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3900         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3901         Bundle encryptedSessionBundle = resultBundle
3902                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3903 
3904         final CountDownLatch latch = new CountDownLatch(1);
3905 
3906         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
3907             @Override
3908             public void run(AccountManagerFuture<Bundle> bundleFuture) {
3909                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
3910 
3911                 // Assert parameters has been passed correctly
3912                 assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3913 
3914                 validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3915 
3916                 // Assert returned result containing account name, type but not auth token type.
3917                 validateAccountAndNoAuthTokenResult(resultBundle);
3918 
3919                 latch.countDown();
3920             }
3921         };
3922 
3923         finishSession(am, encryptedSessionBundle, mActivity, callback, handler);
3924 
3925         // Wait with timeout for the callback to do its work
3926         waitForLatch(latch);
3927     }
3928 
3929     private void testFinishSessionWithCallbackAndHandlerWithIntervene(Handler handler)
3930             throws IOException, AuthenticatorException, OperationCanceledException {
3931         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
3932                 + Fixtures.SUFFIX_NAME_FIXTURE;
3933         Bundle options = createOptionsWithAccountName(accountName);
3934 
3935         // First get an encrypted session bundle from startAddAccountSession(...)
3936         Bundle resultBundle = startAddAccountSession(
3937                 am,
3938                 ACCOUNT_TYPE,
3939                 AUTH_TOKEN_TYPE,
3940                 REQUIRED_FEATURES,
3941                 options,
3942                 mActivity,
3943                 null /* callback */,
3944                 null /* handler */);
3945 
3946         // Assert returned result
3947         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
3948         // Assert that auth token was stripped.
3949         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
3950         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
3951         Bundle encryptedSessionBundle = resultBundle
3952                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
3953 
3954         final CountDownLatch latch = new CountDownLatch(1);
3955 
3956         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
3957             @Override
3958             public void run(AccountManagerFuture<Bundle> bundleFuture) {
3959                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
3960 
3961                 // Assert parameters has been passed correctly
3962                 assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
3963 
3964                 validateFinishSessionOptions(accountName, SESSION_BUNDLE);
3965 
3966                 // Assert returned result
3967                 assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
3968                 // Assert returned result containing account name, type but not auth token type.
3969                 validateAccountAndNoAuthTokenResult(resultBundle);
3970 
3971                 latch.countDown();
3972             }
3973         };
3974 
3975         finishSession(am, encryptedSessionBundle, mActivity, callback, handler);
3976 
3977         // Wait with timeout for the callback to do its work
3978         waitForLatch(latch);
3979     }
3980 
3981     private void testFinishSessionWithCallbackAndHandlerWithReturnIntent(Handler handler)
3982             throws IOException, AuthenticatorException, OperationCanceledException {
3983         final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
3984                 + Fixtures.SUFFIX_NAME_FIXTURE;
3985         Bundle options = createOptionsWithAccountName(accountName);
3986 
3987         // First get an encrypted session bundle from startAddAccountSession(...)
3988         Bundle resultBundle = startAddAccountSession(
3989                 am,
3990                 ACCOUNT_TYPE,
3991                 AUTH_TOKEN_TYPE,
3992                 REQUIRED_FEATURES,
3993                 options,
3994                 mActivity,
3995                 null /* callback */,
3996                 null /* handler */);
3997 
3998         // Assert returned result
3999         assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
4000         // Assert that auth token was stripped.
4001         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
4002         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
4003         Bundle encryptedSessionBundle = resultBundle
4004                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
4005 
4006         final CountDownLatch latch = new CountDownLatch(1);
4007 
4008         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
4009             @Override
4010             public void run(AccountManagerFuture<Bundle> bundleFuture) {
4011                 Bundle resultBundle = getResultExpectNoException(bundleFuture);
4012 
4013                 // Assert parameters has been passed correctly
4014                 assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
4015 
4016                 validateFinishSessionOptions(accountName, SESSION_BUNDLE);
4017 
4018                 // Assert returned result
4019                 Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
4020                 assertNotNull(returnIntent);
4021                 assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
4022 
4023                 assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
4024                 assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
4025                 assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
4026 
4027                 latch.countDown();
4028             }
4029         };
4030 
4031         finishSession(am, encryptedSessionBundle, null, callback, handler);
4032 
4033         // Wait with timeout for the callback to do its work
4034         waitForLatch(latch);
4035     }
4036 
4037     private void testFinishSessionErrorWithCallbackAndHandler(Handler handler)
4038             throws IOException, OperationCanceledException, AuthenticatorException {
4039         Bundle sessionBundle = new Bundle();
4040         String accountNameForFinish = Fixtures.PREFIX_NAME_ERROR + "@"
4041                 + Fixtures.SUFFIX_NAME_FIXTURE;
4042         sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountNameForFinish);
4043         sessionBundle.putInt(AccountManager.KEY_ERROR_CODE,
4044                 AccountManager.ERROR_CODE_INVALID_RESPONSE);
4045         sessionBundle.putString(AccountManager.KEY_ERROR_MESSAGE, ERROR_MESSAGE);
4046 
4047         Bundle options = new Bundle();
4048         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
4049         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
4050         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
4051         options.putAll(OPTIONS_BUNDLE);
4052 
4053         // First get an encrypted session bundle from startAddAccountSession(...)
4054         Bundle resultBundle = startAddAccountSession(
4055                 am,
4056                 ACCOUNT_TYPE,
4057                 AUTH_TOKEN_TYPE,
4058                 REQUIRED_FEATURES,
4059                 options,
4060                 null /* activity */,
4061                 null /* callback */,
4062                 null /* handler */);
4063 
4064         // Assert returned result
4065         // Assert that auth token was stripped.
4066         assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
4067         validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
4068         Bundle encryptedSessionBundle = resultBundle
4069                 .getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
4070 
4071         final CountDownLatch latch = new CountDownLatch(1);
4072 
4073         AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
4074             @Override
4075             public void run(AccountManagerFuture<Bundle> bundleFuture) {
4076                 try {
4077                     bundleFuture.getResult();
4078                     fail("should have thrown an AuthenticatorException");
4079                 } catch (OperationCanceledException e) {
4080                     fail("should not throw an OperationCanceledException");
4081                 } catch (IOException e) {
4082                     fail("should not throw an IOException");
4083                 } catch (AuthenticatorException e) {
4084                     latch.countDown();
4085                 }
4086             }
4087         };
4088 
4089         try {
4090             finishSession(am, encryptedSessionBundle, mActivity, callback, handler);
4091             fail("should have thrown an AuthenticatorException");
4092         } catch (AuthenticatorException e1) {
4093         }
4094 
4095         // Wait with timeout for the callback to do its work
4096         waitForLatch(latch);
4097     }
4098 
4099     private Bundle finishSession(AccountManager am, Bundle sessionBundle, Activity activity,
4100             AccountManagerCallback<Bundle> callback, Handler handler)
4101                     throws IOException, AuthenticatorException, OperationCanceledException {
4102         // Cleanup before calling finishSession(...) with the encrypted session bundle.
4103         mockAuthenticator.clearData();
4104 
4105         AccountManagerFuture<Bundle> futureBundle = am.finishSession(
4106                 sessionBundle,
4107                 activity,
4108                 callback,
4109                 handler);
4110 
4111         Bundle resultBundle = futureBundle.getResult();
4112         assertTrue(futureBundle.isDone());
4113         assertNotNull(resultBundle);
4114 
4115         return resultBundle;
4116     }
4117 
4118     private void validateFinishSessionOptions(String accountName, Bundle options) {
4119         validateOptions(options, mockAuthenticator.mOptionsFinishSession);
4120         assertNotNull(mockAuthenticator.mOptionsFinishSession);
4121         assertEquals(ACCOUNT_TYPE, mockAuthenticator.mOptionsFinishSession
4122                 .getString(AccountManager.KEY_ACCOUNT_TYPE));
4123         assertEquals(accountName,
4124                 mockAuthenticator.mOptionsFinishSession.getString(Fixtures.KEY_ACCOUNT_NAME));
4125 
4126         validateSystemOptions(mockAuthenticator.mOptionsFinishSession);
4127         validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
4128         validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
4129         validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
4130         validateOptions(null, mockAuthenticator.mOptionsAddAccount);
4131         validateOptions(null, mockAuthenticator.mOptionsStartAddAccountSession);
4132         validateOptions(null, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
4133     }
4134 
4135     /**
4136      * Tests a basic isCredentialsUpdateSuggested() which returns a bundle containing boolean true.
4137      */
4138     public void testIsCredentialsUpdateSuggested_Success()
4139             throws IOException, AuthenticatorException, OperationCanceledException {
4140         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
4141         Account account = new Account(accountName, ACCOUNT_TYPE);
4142 
4143         Boolean result = isCredentialsUpdateSuggested(
4144                 am,
4145                 account,
4146                 ACCOUNT_STATUS_TOKEN,
4147                 null /* callback */,
4148                 null /* handler */);
4149 
4150         // Assert parameters has been passed correctly
4151         validateIsCredentialsUpdateSuggestedParametersAndOptions(account);
4152 
4153         // Assert returned result
4154         assertTrue(result);
4155     }
4156 
4157     /**
4158      * Tests isCredentialsUpdateSuggested() when account is null.
4159      * It should throw IllegalArgumentationException.
4160      */
4161     public void testIsCredentialsUpdateSuggestedNullAccount_IllegalArgumentationException()
4162             throws IOException, AuthenticatorException, OperationCanceledException {
4163 
4164         try {
4165             isCredentialsUpdateSuggested(
4166                     am,
4167                     null /* account */,
4168                     ACCOUNT_STATUS_TOKEN,
4169                     null /* callback */,
4170                     null /* handler */);
4171             fail("Should have thrown IllegalArgumentation when calling with null account!");
4172         } catch (IllegalArgumentException e) {
4173         }
4174     }
4175 
4176     /**
4177      * Tests isCredentialsUpdateSuggested() when statusToken is empty.
4178      * It should throw IllegalArgumentationException.
4179      */
4180     public void testIsCredentialsUpdateSuggestedEmptyToken_IllegalArgumentationException()
4181             throws IOException, AuthenticatorException, OperationCanceledException {
4182 
4183         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
4184         Account account = new Account(accountName, ACCOUNT_TYPE);
4185         try {
4186             isCredentialsUpdateSuggested(
4187                     am,
4188                     account,
4189                     "" /* statusToken */,
4190                     null /* callback */,
4191                     null /* handler */);
4192             fail("Should have thrown IllegalArgumentation when calling with empty statusToken!");
4193         } catch (IllegalArgumentException e) {
4194         }
4195     }
4196 
4197     /**
4198      * Tests isCredentialsUpdateSuggested() error case. AuthenticatorException is expected when
4199      * authenticator return {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
4200      */
4201     public void testIsCredentialsUpdateSuggested_Error()
4202             throws IOException, AuthenticatorException, OperationCanceledException {
4203         String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
4204         Account account = new Account(accountName, ACCOUNT_TYPE);
4205 
4206         try {
4207             isCredentialsUpdateSuggested(
4208                     am,
4209                     account,
4210                     ACCOUNT_STATUS_TOKEN,
4211                     null /* callback */,
4212                     null /* handler */);
4213             fail("Should have thrown AuthenticatorException in error case.");
4214         } catch (AuthenticatorException e) {
4215         }
4216     }
4217 
4218     /**
4219      * Tests isCredentialsUpdateSuggested() with callback and handler. A boolean should be included
4220      * in the result. Callback should be triggered with the result regardless of a handler is
4221      * provided or not.
4222      */
4223     public void testIsCredentialsUpdateSuggestedWithCallbackAndHandler()
4224             throws IOException, AuthenticatorException, OperationCanceledException {
4225         testIsCredentialsUpdateSuggestedWithCallbackAndHandler(null /* handler */);
4226         testIsCredentialsUpdateSuggestedWithCallbackAndHandler(
4227                 new Handler(Looper.getMainLooper()));
4228     }
4229 
4230     /**
4231      * Tests isCredentialsUpdateSuggested() error case with callback and handler.
4232      * AuthenticatorException is expected when authenticator return
4233      * {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
4234      */
4235     public void testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler()
4236             throws IOException, OperationCanceledException, AuthenticatorException {
4237         testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler(null /* handler */);
4238         testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler(
4239                 new Handler(Looper.getMainLooper()));
4240     }
4241 
4242     private void testIsCredentialsUpdateSuggestedWithCallbackAndHandler(Handler handler)
4243             throws IOException, AuthenticatorException, OperationCanceledException {
4244         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
4245         final Account account = new Account(accountName, ACCOUNT_TYPE);
4246         final CountDownLatch latch = new CountDownLatch(1);
4247 
4248         AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
4249             @Override
4250             public void run(AccountManagerFuture<Boolean> booleanFuture) {
4251                 Boolean result = false;
4252                 try {
4253                     result = booleanFuture.getResult();
4254                 } catch (OperationCanceledException e) {
4255                     fail("should not throw an OperationCanceledException");
4256                 } catch (IOException e) {
4257                     fail("should not throw an IOException");
4258                 } catch (AuthenticatorException e) {
4259                     fail("should not throw an AuthenticatorException");
4260                 }
4261 
4262                 // Assert parameters has been passed correctly
4263                 validateIsCredentialsUpdateSuggestedParametersAndOptions(account);
4264 
4265                 // Assert returned result
4266                 assertTrue(result);
4267 
4268                 latch.countDown();
4269             }
4270         };
4271 
4272         isCredentialsUpdateSuggested(
4273                 am,
4274                 account,
4275                 ACCOUNT_STATUS_TOKEN,
4276                 callback,
4277                 handler);
4278 
4279         // Wait with timeout for the callback to do its work
4280         waitForLatch(latch);
4281     }
4282 
4283     private void testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler(Handler handler)
4284             throws IOException, OperationCanceledException, AuthenticatorException {
4285         String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
4286         final Account account = new Account(accountName, ACCOUNT_TYPE);
4287 
4288         final CountDownLatch latch = new CountDownLatch(1);
4289 
4290         AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
4291             @Override
4292             public void run(AccountManagerFuture<Boolean> booleanFuture) {
4293                 try {
4294                     booleanFuture.getResult();
4295                     // AuthenticatorException should be thrown when authenticator
4296                     // returns AccountManager.ERROR_CODE_INVALID_RESPONSE.
4297                     fail("should have thrown an AuthenticatorException");
4298                 } catch (OperationCanceledException e) {
4299                     fail("should not throw an OperationCanceledException");
4300                 } catch (IOException e) {
4301                     fail("should not throw an IOException");
4302                 } catch (AuthenticatorException e) {
4303                     // Test passed as AuthenticatorException is expected.
4304                 } finally {
4305                     latch.countDown();
4306                 }
4307             }
4308         };
4309 
4310         isCredentialsUpdateSuggested(
4311                 am,
4312                 account,
4313                 ACCOUNT_STATUS_TOKEN,
4314                 callback,
4315                 handler);
4316 
4317         // Wait with timeout for the callback to do its work
4318         waitForLatch(latch);
4319     }
4320 
4321     private Boolean isCredentialsUpdateSuggested(
4322             AccountManager am,
4323             Account account,
4324             String statusToken,
4325             AccountManagerCallback<Boolean> callback,
4326             Handler handler)
4327                     throws IOException, AuthenticatorException, OperationCanceledException {
4328 
4329         AccountManagerFuture<Boolean> booleanFuture = am.isCredentialsUpdateSuggested(
4330                 account,
4331                 statusToken,
4332                 callback,
4333                 handler);
4334 
4335         Boolean result = false;
4336         if (callback == null) {
4337             result = booleanFuture.getResult();
4338             assertTrue(booleanFuture.isDone());
4339         }
4340 
4341         return result;
4342     }
4343 
4344     private void verifyAccountsGroupedByType(Account[] accounts) {
4345 
4346         Map<String, Integer> firstPositionForType = new HashMap<>();
4347         Map<String, Integer> lastPositionForType = new HashMap<>();
4348         Map<String, Integer> counterForType = new HashMap<>();
4349         for (int i = 0; i < accounts.length; i++) {
4350             String type = accounts[i].type;
4351 
4352             Integer first = firstPositionForType.get(type);
4353             first = first != null ? first : Integer.MAX_VALUE;
4354             firstPositionForType.put(type, Math.min(first, i));
4355 
4356             Integer last = lastPositionForType.get(type);
4357             last = last != null ? last : Integer.MIN_VALUE;
4358             lastPositionForType.put(type, Math.max(last, i));
4359 
4360             Integer counter = counterForType.get(type);
4361             counter = counter != null ? counter  : 0;
4362             counterForType.put(type, counter + 1);
4363         }
4364         for (String type : counterForType.keySet()) {
4365             assertEquals((int)lastPositionForType.get(type),
4366                 firstPositionForType.get(type) + counterForType.get(type) - 1);
4367         }
4368     }
4369 }
4370