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