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