blob: 795fcd6faa40ad68589678f0a5526cd1739d0dc2 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.accounts.cts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorDescription;
import android.accounts.AuthenticatorException;
import android.accounts.OnAccountsUpdateListener;
import android.accounts.OperationCanceledException;
import android.accounts.cts.common.Fixtures;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.StrictMode;
import android.platform.test.annotations.Presubmit;
import android.test.ActivityInstrumentationTestCase2;
import java.io.IOException;
import java.lang.Math;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* You can run those unit tests with the following command line:
*
* adb shell am instrument
* -e debug false -w
* -e class android.accounts.cts.AccountManagerTest
* android.accounts.cts/android.support.test.runner.AndroidJUnitRunner
*/
public class AccountManagerTest extends ActivityInstrumentationTestCase2<AccountDummyActivity> {
public static final String ACCOUNT_NAME = "android.accounts.cts.account.name";
public static final String ACCOUNT_NEW_NAME = "android.accounts.cts.account.name.rename";
public static final String ACCOUNT_NAME_OTHER = "android.accounts.cts.account.name.other";
public static final String ACCOUNT_TYPE = "android.accounts.cts.account.type";
public static final String ACCOUNT_TYPE_CUSTOM = "android.accounts.cts.custom.account.type";
public static final String ACCOUNT_TYPE_ABSENT = "android.accounts.cts.account.type.absent";
public static final String ACCOUNT_PASSWORD = "android.accounts.cts.account.password";
public static final String ACCOUNT_STATUS_TOKEN = "android.accounts.cts.account.status.token";
public static final String AUTH_TOKEN_TYPE = "mockAuthTokenType";
public static final String AUTH_EXPIRING_TOKEN_TYPE = "mockAuthExpiringTokenType";
public static final String AUTH_TOKEN_LABEL = "mockAuthTokenLabel";
public static final long AUTH_TOKEN_DURATION_MILLIS = 10000L; // Ten seconds.
public static final String FEATURE_1 = "feature.1";
public static final String FEATURE_2 = "feature.2";
public static final String NON_EXISTING_FEATURE = "feature.3";
public static final String OPTION_NAME_1 = "option.name.1";
public static final String OPTION_VALUE_1 = "option.value.1";
public static final String OPTION_NAME_2 = "option.name.2";
public static final String OPTION_VALUE_2 = "option.value.2";
public static final String[] REQUIRED_FEATURES = new String[] { FEATURE_1, FEATURE_2 };
public static final Bundle OPTIONS_BUNDLE = new Bundle();
public static final Bundle USERDATA_BUNDLE = new Bundle();
public static final String USERDATA_NAME_1 = "user.data.name.1";
public static final String USERDATA_NAME_2 = "user.data.name.2";
public static final String USERDATA_VALUE_1 = "user.data.value.1";
public static final String USERDATA_VALUE_2 = "user.data.value.2";
public static final Account ACCOUNT = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
public static final Account ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API = new Account(
MockAccountAuthenticator.ACCOUNT_NAME_FOR_NEW_REMOVE_API, ACCOUNT_TYPE);
public static final Account ACCOUNT_FOR_DEFAULT_IMPL = new Account(
MockAccountAuthenticator.ACCOUNT_NAME_FOR_DEFAULT_IMPL, ACCOUNT_TYPE);
public static final Account ACCOUNT_SAME_TYPE = new Account(ACCOUNT_NAME_OTHER, ACCOUNT_TYPE);
public static final Account CUSTOM_TOKEN_ACCOUNT =
new Account(ACCOUNT_NAME,ACCOUNT_TYPE_CUSTOM);
// Installed packages to test visibility API.
public static final String PACKAGE_NAME_1 = "android.accounts.cts.unaffiliated";
public static final String PACKAGE_NAME_PRIVILEGED = "android.accounts.cts"; // authenticator
public static final Bundle SESSION_BUNDLE = new Bundle();
public static final String SESSION_DATA_NAME_1 = "session.data.name.1";
public static final String SESSION_DATA_VALUE_1 = "session.data.value.1";
public static final String ERROR_MESSAGE = "android.accounts.cts.account.error.message";
public static final String KEY_CIPHER = "cipher";
public static final String KEY_MAC = "mac";
private static MockAccountAuthenticator mockAuthenticator;
private static final int LATCH_TIMEOUT_MS = 1000;
private static AccountManager am;
public synchronized static MockAccountAuthenticator getMockAuthenticator(Context context) {
if (null == mockAuthenticator) {
mockAuthenticator = new MockAccountAuthenticator(context);
}
return mockAuthenticator;
}
private Activity mActivity;
private Context mContext;
public AccountManagerTest() {
super(AccountDummyActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
mContext = getInstrumentation().getTargetContext();
OPTIONS_BUNDLE.putString(OPTION_NAME_1, OPTION_VALUE_1);
OPTIONS_BUNDLE.putString(OPTION_NAME_2, OPTION_VALUE_2);
USERDATA_BUNDLE.putString(USERDATA_NAME_1, USERDATA_VALUE_1);
SESSION_BUNDLE.putString(SESSION_DATA_NAME_1, SESSION_DATA_VALUE_1);
SESSION_BUNDLE.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
getMockAuthenticator(mContext);
am = AccountManager.get(mContext);
}
@Override
public void tearDown() throws Exception, AuthenticatorException, OperationCanceledException {
mockAuthenticator.clearData();
// Need to clean up created account
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
// Clean out any other accounts added during the tests.
Account[] ctsAccounts = am.getAccountsByType(ACCOUNT_TYPE);
Account[] ctsCustomAccounts = am.getAccountsByType(ACCOUNT_TYPE_CUSTOM);
ArrayList<Account> accounts = new ArrayList<>(Arrays.asList(ctsAccounts));
accounts.addAll(Arrays.asList(ctsCustomAccounts));
for (Account ctsAccount : accounts) {
removeAccount(am, ctsAccount, mActivity, null /* callback */);
}
// need to clean up the authenticator cached data
mockAuthenticator.clearData();
super.tearDown();
}
interface TokenFetcher {
public Bundle fetch(String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException;
public Account getAccount();
}
private void validateSuccessfulTokenFetchingLifecycle(TokenFetcher fetcher, String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException {
Account account = fetcher.getAccount();
Bundle expected = new Bundle();
expected.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
expected.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
// First fetch.
Bundle actual = fetcher.fetch(tokenType);
assertTrue(mockAuthenticator.isRecentlyCalled());
validateAccountAndAuthTokenResult(expected, actual);
/*
* On the second fetch the cache will be populated if we are using a authenticator with
* customTokens=false or we are using a scope that will cause the authenticator to set an
* expiration time (and that expiration time hasn't been reached).
*/
actual = fetcher.fetch(tokenType);
boolean isCachingExpected =
ACCOUNT_TYPE.equals(account.type) || AUTH_EXPIRING_TOKEN_TYPE.equals(tokenType);
assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
validateAccountAndAuthTokenResult(expected, actual);
try {
// Delay further execution until expiring tokens can actually expire.
Thread.sleep(mockAuthenticator.getTokenDurationMillis() + 50L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
/*
* With the time shift above, the third request will result in cache hits only from
* customToken=false authenticators.
*/
actual = fetcher.fetch(tokenType);
isCachingExpected = ACCOUNT_TYPE.equals(account.type);
assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
validateAccountAndAuthTokenResult(expected, actual);
// invalidate token
String token = actual.getString(AccountManager.KEY_AUTHTOKEN);
am.invalidateAuthToken(account.type, token);
/*
* Upon invalidating the token, the cache should be clear regardless of authenticator.
*/
actual = fetcher.fetch(tokenType);
assertTrue(mockAuthenticator.isRecentlyCalled());
validateAccountAndAuthTokenResult(expected, actual);
}
private void validateAccountAndAuthTokenResult(Bundle actual) {
assertEquals(
ACCOUNT.name,
actual.get(AccountManager.KEY_ACCOUNT_NAME));
assertEquals(
ACCOUNT.type,
actual.get(AccountManager.KEY_ACCOUNT_TYPE));
assertEquals(
mockAuthenticator.getLastTokenServed(),
actual.get(AccountManager.KEY_AUTHTOKEN));
}
private void validateAccountAndAuthTokenResult(Bundle expected, Bundle actual) {
assertEquals(
expected.get(AccountManager.KEY_ACCOUNT_NAME),
actual.get(AccountManager.KEY_ACCOUNT_NAME));
assertEquals(
expected.get(AccountManager.KEY_ACCOUNT_TYPE),
actual.get(AccountManager.KEY_ACCOUNT_TYPE));
assertEquals(
mockAuthenticator.getLastTokenServed(),
actual.get(AccountManager.KEY_AUTHTOKEN));
}
private void validateAccountAndNoAuthTokenResult(Bundle result) {
assertEquals(ACCOUNT_NAME, result.get(AccountManager.KEY_ACCOUNT_NAME));
assertEquals(ACCOUNT_TYPE, result.get(AccountManager.KEY_ACCOUNT_TYPE));
assertNull(result.get(AccountManager.KEY_AUTHTOKEN));
}
private void validateNullResult(Bundle resultBundle) {
assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
}
private void validateAccountAndAuthTokenType() {
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
assertEquals(AUTH_TOKEN_TYPE, mockAuthenticator.getAuthTokenType());
}
private void validateFeatures() {
assertEquals(REQUIRED_FEATURES[0], mockAuthenticator.getRequiredFeatures()[0]);
assertEquals(REQUIRED_FEATURES[1], mockAuthenticator.getRequiredFeatures()[1]);
}
private void validateOptions(Bundle expectedOptions, Bundle actualOptions) {
// In ICS AccountManager may add options to indicate the caller id.
// We only validate that the passed in options are present in the actual ones
if (expectedOptions != null) {
assertNotNull(actualOptions);
assertEquals(expectedOptions.get(OPTION_NAME_1), actualOptions.get(OPTION_NAME_1));
assertEquals(expectedOptions.get(OPTION_NAME_2), actualOptions.get(OPTION_NAME_2));
}
}
private void validateSystemOptions(Bundle options) {
assertNotNull(options.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME));
assertTrue(options.containsKey(AccountManager.KEY_CALLER_UID));
assertTrue(options.containsKey(AccountManager.KEY_CALLER_PID));
}
private void validateCredentials() {
assertEquals(ACCOUNT, mockAuthenticator.getAccount());
}
private int getAccountsCount() {
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
return accounts.length;
}
private Bundle addAccount(AccountManager am, String accountType, String authTokenType,
String[] requiredFeatures, Bundle options, Activity activity,
AccountManagerCallback<Bundle> callback, Handler handler) throws
IOException, AuthenticatorException, OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.addAccount(
accountType,
authTokenType,
requiredFeatures,
options,
activity,
callback,
handler);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
return resultBundle;
}
private Account renameAccount(AccountManager am, Account account, String newName)
throws OperationCanceledException, AuthenticatorException, IOException {
AccountManagerFuture<Account> futureAccount = am.renameAccount(
account, newName, null /* callback */, null /* handler */);
Account renamedAccount = futureAccount.getResult();
assertTrue(futureAccount.isDone());
assertNotNull(renamedAccount);
return renamedAccount;
}
private boolean removeAccount(AccountManager am, Account account,
AccountManagerCallback<Boolean> callback) throws IOException, AuthenticatorException,
OperationCanceledException {
AccountManagerFuture<Boolean> futureBoolean = am.removeAccount(account,
callback,
null /* handler */);
Boolean resultBoolean = futureBoolean.getResult();
assertTrue(futureBoolean.isDone());
return resultBoolean;
}
private Bundle removeAccountWithIntentLaunch(AccountManager am, Account account,
Activity activity, AccountManagerCallback<Bundle> callback) throws IOException,
AuthenticatorException, OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
activity,
callback,
null /* handler */);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
return resultBundle;
}
private Bundle removeAccount(AccountManager am, Account account, Activity activity,
AccountManagerCallback<Bundle> callback) throws IOException, AuthenticatorException,
OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
activity,
callback,
null /* handler */);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
return resultBundle;
}
private boolean removeAccountExplicitly(AccountManager am, Account account) {
return am.removeAccountExplicitly(account);
}
private void addAccountExplicitly(Account account, String password, Bundle userdata) {
assertTrue(am.addAccountExplicitly(account, password, userdata));
}
private Bundle getAuthTokenByFeature(String[] features, Activity activity)
throws IOException, AuthenticatorException, OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.getAuthTokenByFeatures(ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
features,
activity,
OPTIONS_BUNDLE,
OPTIONS_BUNDLE,
null /* no callback */,
null /* no handler */
);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
return resultBundle;
}
private boolean isAccountPresent(Account[] accounts, Account accountToCheck) {
if (null == accounts || null == accountToCheck) {
return false;
}
boolean result = false;
int length = accounts.length;
for (int n=0; n<length; n++) {
if(accountToCheck.equals(accounts[n])) {
result = true;
break;
}
}
return result;
}
/**
* Test singleton
*/
public void testGet() {
assertNotNull(AccountManager.get(mContext));
}
/**
* Test creation of intent
*/
public void testNewChooseAccountIntent() {
Intent intent = AccountManager.newChooseAccountIntent(null, null, null,
null, null,
null, null);
assertNotNull(intent);
}
/**
* Test creation of intent
*/
public void testNewChooseAccountIntentDepracated() {
Intent intent = AccountManager.newChooseAccountIntent(null, null, null, false,
null, null,
null, null);
assertNotNull(intent);
}
/**
* Test a basic addAccount()
*/
public void testAddAccount() throws IOException, AuthenticatorException,
OperationCanceledException {
Bundle resultBundle = addAccount(am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
OPTIONS_BUNDLE,
mActivity,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
validateAccountAndAuthTokenType();
validateFeatures();
validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
validateSystemOptions(mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
// Assert returned result
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Test addAccount() with callback and handler
*/
public void testAddAccountWithCallbackAndHandler() throws IOException,
AuthenticatorException, OperationCanceledException {
testAddAccountWithCallbackAndHandler(null /* handler */);
testAddAccountWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
/**
* Test addAccount() with no associated account authenticator
*/
public void testAddAccountWithNoAuthenticator() throws IOException,
AuthenticatorException, OperationCanceledException {
try {
AccountManagerFuture<Bundle> futureBundle = am.addAccount(
"nonExistingAccountType",
null,
null,
null,
null,
null,
null);
futureBundle.getResult();
fail();
} catch (AuthenticatorException expectedException) {
return;
}
}
private void testAddAccountWithCallbackAndHandler(Handler handler) throws IOException,
AuthenticatorException, OperationCanceledException {
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
// Assert parameters has been passed correctly
validateAccountAndAuthTokenType();
validateFeatures();
validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
// Assert return result
validateAccountAndNoAuthTokenResult(resultBundle);
latch.countDown();
}
};
addAccount(am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
OPTIONS_BUNDLE,
mActivity,
callback,
handler);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test addAccountExplicitly(), renameAccount() and removeAccount().
*/
public void testAddAccountExplicitlyAndRemoveAccount() throws IOException,
AuthenticatorException, OperationCanceledException {
final int expectedAccountsCount = getAccountsCount();
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Assert that we have one more account
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
// Need to clean up
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
// and verify that we go back to the initial state
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(expectedAccountsCount, accounts.length);
}
/**
* Test addAccountExplicitly(), renameAccount() and removeAccount().
*/
public void testAddAccountExplicitlyAndRemoveAccountWithNewApi() throws IOException,
AuthenticatorException, OperationCanceledException {
final int expectedAccountsCount = getAccountsCount();
addAccountExplicitly(ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, ACCOUNT_PASSWORD, null /* userData */);
// Assert that we have one more account
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
// Deprecated API should not work
assertFalse(removeAccount(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, null /* callback */));
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
// Check removal of account
assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, mActivity, null /* callback */)
.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
// and verify that we go back to the initial state
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(expectedAccountsCount, accounts.length);
}
/**
* Test addAccountExplicitly(), renameAccount() and removeAccount() calling
* into default implementations.
*/
public void testAddAccountExplicitlyAndRemoveAccountWithDefaultImpl() throws IOException,
AuthenticatorException, OperationCanceledException {
final int expectedAccountsCount = getAccountsCount();
addAccountExplicitly(ACCOUNT_FOR_DEFAULT_IMPL, ACCOUNT_PASSWORD, null /* userData */);
// Assert that we have one more account
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_DEFAULT_IMPL));
// Check removal of account
assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_DEFAULT_IMPL, mActivity, null /* callback */)
.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
// and verify that we go back to the initial state
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(expectedAccountsCount, accounts.length);
}
/**
* Test addAccountExplicitly(), renameAccount() and removeAccount().
*/
public void testAddAccountExplicitlyAndRemoveAccountWithDeprecatedApi() throws IOException,
AuthenticatorException, OperationCanceledException {
final int expectedAccountsCount = getAccountsCount();
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Assert that we have one more account
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
// Need to clean up
assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
// and verify that we go back to the initial state
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(expectedAccountsCount, accounts.length);
}
/**
* Test addAccountExplicitly() and removeAccountExplictly().
*/
public void testAddAccountExplicitlyAndRemoveAccountExplicitly() {
final int expectedAccountsCount = getAccountsCount();
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Assert that we have one more account
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
// Need to clean up
assertTrue(removeAccountExplicitly(am, ACCOUNT));
// and verify that we go back to the initial state
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(expectedAccountsCount, accounts.length);
}
/**
* Test updates to account visibility.
*/
public void testSetAccountVisibility()
throws IOException, AuthenticatorException, OperationCanceledException {
am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_VISIBLE);
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_VISIBLE);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_NOT_VISIBLE);
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_NOT_VISIBLE);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED,
AccountManager.VISIBILITY_VISIBLE);
// No changes to PACKAGE_NAME_1
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_NOT_VISIBLE);
}
/**
* Test updates to account visibility for authenticator package.
*/
public void testSetAccountVisibilityForPrivilegedPackage()
throws IOException, AuthenticatorException, OperationCanceledException {
am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED),
AccountManager.VISIBILITY_VISIBLE);
Map<String, Integer> visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
assertNull(visibilities.get(PACKAGE_NAME_PRIVILEGED)); // no entry in database
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED,
AccountManager.VISIBILITY_NOT_VISIBLE);
visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
// database is updated
assertEquals((int) visibilities.get(PACKAGE_NAME_PRIVILEGED),
AccountManager.VISIBILITY_NOT_VISIBLE);
// VISIBILITY_VISIBLE is used for Authenticator despite database entry.
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED),
AccountManager.VISIBILITY_VISIBLE);
}
/**
* Test getPackagesAndVisibilityForAccount() method.
*/
public void testGetPackagesAndVisibilityForAccount()
throws IOException, AuthenticatorException, OperationCanceledException {
am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
Map<String, Integer> visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
assertNull(visibilities.get(PACKAGE_NAME_1)); // no entry in database
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_VISIBLE);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_PRIVILEGED,
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
visibilities = am.getPackagesAndVisibilityForAccount(ACCOUNT);
assertEquals(visibilities.size(), 2);
assertEquals((int) visibilities.get(PACKAGE_NAME_1), AccountManager.VISIBILITY_VISIBLE);
assertEquals((int) visibilities.get(PACKAGE_NAME_PRIVILEGED),
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
/**
* Test addAccountExplicitly(), setAccountVisibility() , getAccountVisibility(), and
* removeAccount().
*/
public void testAddAccountExplicitlyWithVisibility()
throws IOException, AuthenticatorException, OperationCanceledException {
Map<String, Integer> visibility = new HashMap<>();
visibility.put(PACKAGE_NAME_1, AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
final int expectedAccountsCount = getAccountsCount();
am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */, visibility);
// Assert that we have one more account
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(1 + expectedAccountsCount, accounts.length);
assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
// Visibility values were stored.
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */)
.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
// Visibility values were removed
Map<Account, Integer> visibilities =
am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
assertNull(visibilities.get(ACCOUNT));
// and verify that we go back to the initial state
accounts = am.getAccounts();
assertNotNull(accounts);
assertEquals(expectedAccountsCount, accounts.length);
}
/**
* Test testGetAccountsAndVisibilityForPackage(), getAccountsByTypeForPackage() methods.
*/
public void testGetAccountsAndVisibilityForPackage() {
am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */, null);
am.addAccountExplicitly(ACCOUNT_SAME_TYPE, ACCOUNT_PASSWORD, null /* userData */, null);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_NOT_VISIBLE);
am.setAccountVisibility(ACCOUNT_SAME_TYPE, PACKAGE_NAME_1,
AccountManager.VISIBILITY_VISIBLE);
assertEquals(am.getAccountVisibility(ACCOUNT_SAME_TYPE, PACKAGE_NAME_1),
AccountManager.VISIBILITY_VISIBLE);
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_NOT_VISIBLE);
Account[] accounts = am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1);
assertEquals(accounts.length, 1); // VISIBILITY_NOT_VISIBLE accounts are not returned.
assertEquals(accounts[0], ACCOUNT_SAME_TYPE);
Map<Account, Integer> visibilities =
am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
assertEquals((int) visibilities.get(ACCOUNT), AccountManager.VISIBILITY_NOT_VISIBLE);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1,
AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
// VISIBILITY_USER_MANAGED_NOT_VISIBLE accounts are returned by getAccountsByTypeForPackage
assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
visibilities = am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
assertEquals((int) visibilities.get(ACCOUNT),
AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1,
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
assertEquals(am.getAccountVisibility(ACCOUNT, PACKAGE_NAME_1),
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
visibilities = am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
assertEquals((int) visibilities.get(ACCOUNT),
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
am.setAccountVisibility(ACCOUNT, PACKAGE_NAME_1, AccountManager.VISIBILITY_VISIBLE);
assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
visibilities = am.getAccountsAndVisibilityForPackage(PACKAGE_NAME_1, ACCOUNT_TYPE);
assertEquals((int) visibilities.get(ACCOUNT), AccountManager.VISIBILITY_VISIBLE);
assertEquals(am.getAccountsByTypeForPackage(ACCOUNT_TYPE, PACKAGE_NAME_1).length, 2);
// VISIBILITY_USER MANAGED_NOT_VISIBLE accounts are not returned when type is null.
// It should be equivalent to callling am.getAccounts() which doesn't return
// VISIBILITY_USER MANAGED_NOT_VISIBLE accounts.
assertEquals(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1).length,
am.getAccounts().length);
}
/**
* Test checks order of accounts returned by getAccounts...().
* Accounts should be grouped by type.
*/
public void testGetAccountsReturnedOrder() {
Account account_1_1 = new Account("account_z", ACCOUNT_TYPE);
Account account_1_2 = new Account("account_c", ACCOUNT_TYPE);
Account account_1_3 = new Account("account_a", ACCOUNT_TYPE);
Account account_2_1 = new Account("account_b", ACCOUNT_TYPE_CUSTOM);
Account account_2_2 = new Account("account_f", ACCOUNT_TYPE_CUSTOM);
Account account_2_3 = new Account("account_a", ACCOUNT_TYPE_CUSTOM);
am.addAccountExplicitly(account_1_1, ACCOUNT_PASSWORD, null /* userData */, null);
am.addAccountExplicitly(account_1_2, ACCOUNT_PASSWORD, null /* userData */, null);
am.addAccountExplicitly(account_2_1, ACCOUNT_PASSWORD, null /* userData */, null);
verifyAccountsGroupedByType(am.getAccounts());
verifyAccountsGroupedByType(am.getAccountsByType(null));
verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
am.addAccountExplicitly(account_2_2, ACCOUNT_PASSWORD, null /* userData */, null);
verifyAccountsGroupedByType(am.getAccounts());
verifyAccountsGroupedByType(am.getAccountsByType(null));
verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
am.addAccountExplicitly(account_1_3, ACCOUNT_PASSWORD, null /* userData */, null);
verifyAccountsGroupedByType(am.getAccounts());
verifyAccountsGroupedByType(am.getAccountsByType(null));
verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
am.addAccountExplicitly(account_2_3, ACCOUNT_PASSWORD, null /* userData */, null);
verifyAccountsGroupedByType(am.getAccounts());
verifyAccountsGroupedByType(am.getAccountsByType(null));
verifyAccountsGroupedByType(am.getAccountsByTypeForPackage(null, PACKAGE_NAME_1));
}
/**
* Test setUserData() and getUserData().
*/
public void testAccountRenameAndGetPreviousName()
throws OperationCanceledException, AuthenticatorException, IOException {
// Add a first account
boolean result = am.addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, USERDATA_BUNDLE);
assertTrue(result);
// Prior to a rename, the previous name should be null.
String nullName = am.getPreviousName(ACCOUNT);
assertNull(nullName);
final int expectedAccountsCount = getAccountsCount();
Account renamedAccount = renameAccount(am, ACCOUNT, ACCOUNT_NEW_NAME);
/*
* Make sure that the resultant renamed account has the correct name
* and is associated with the correct account type.
*/
assertEquals(ACCOUNT_NEW_NAME, renamedAccount.name);
assertEquals(ACCOUNT.type, renamedAccount.type);
// Make sure the total number of accounts is the same.
Account[] accounts = am.getAccounts();
assertEquals(expectedAccountsCount, accounts.length);
// Make sure the old account isn't present.
assertFalse(isAccountPresent(am.getAccounts(), ACCOUNT));
// But that the new one is.
assertTrue(isAccountPresent(am.getAccounts(), renamedAccount));
// Check that the UserData is still present.
assertEquals(USERDATA_VALUE_1, am.getUserData(renamedAccount, USERDATA_NAME_1));
assertEquals(ACCOUNT.name, am.getPreviousName(renamedAccount));
// Need to clean up
assertTrue(removeAccount(am, renamedAccount, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
}
/**
* Test getAccounts() and getAccountsByType()
*/
public void testGetAccountsAndGetAccountsByType() {
assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT_SAME_TYPE));
final int accountsCount = getAccountsCount();
// Add a first account
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Check that we have the new account
Account[] accounts = am.getAccounts();
assertEquals(1 + accountsCount, accounts.length);
assertEquals(true, isAccountPresent(accounts, ACCOUNT));
// Add another account
addAccountExplicitly(ACCOUNT_SAME_TYPE, ACCOUNT_PASSWORD, null /* userData */);
// Check that we have one more account again
accounts = am.getAccounts();
assertEquals(2 + accountsCount, accounts.length);
assertEquals(true, isAccountPresent(accounts, ACCOUNT_SAME_TYPE));
// Check if we have one from first type
accounts = am.getAccountsByType(ACCOUNT_TYPE);
assertEquals(2, accounts.length);
// Check if we dont have any account from the other type
accounts = am.getAccountsByType(ACCOUNT_TYPE_ABSENT);
assertEquals(0, accounts.length);
}
/**
* Test getAuthenticatorTypes()
*/
public void testGetAuthenticatorTypes() {
AuthenticatorDescription[] types = am.getAuthenticatorTypes();
for(AuthenticatorDescription description: types) {
if (description.type.equals(ACCOUNT_TYPE)) {
return;
}
}
fail("should have found Authenticator type: " + ACCOUNT_TYPE);
}
/**
* Test setPassword() and getPassword()
*/
public void testSetAndGetAndClearPassword() {
// Add a first account
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Check that the password is the one we defined
assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
// Clear the password and check that it is cleared
am.clearPassword(ACCOUNT);
assertNull(am.getPassword(ACCOUNT));
// Reset the password
am.setPassword(ACCOUNT, ACCOUNT_PASSWORD);
// Check that the password is the one we defined
assertEquals(ACCOUNT_PASSWORD, am.getPassword(ACCOUNT));
}
/**
* Test setUserData() and getUserData()
*/
public void testSetAndGetUserData() {
// Add a first account
boolean result = am.addAccountExplicitly(ACCOUNT,
ACCOUNT_PASSWORD,
USERDATA_BUNDLE);
assertTrue(result);
// Check that the UserData is the one we defined
assertEquals(USERDATA_VALUE_1, am.getUserData(ACCOUNT, USERDATA_NAME_1));
am.setUserData(ACCOUNT, USERDATA_NAME_2, USERDATA_VALUE_2);
// Check that the UserData is the one we defined
assertEquals(USERDATA_VALUE_2, am.getUserData(ACCOUNT, USERDATA_NAME_2));
}
/**
* Test getAccountsByTypeAndFeatures()
*/
public void testGetAccountsByTypeAndFeatures() throws IOException,
AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
ACCOUNT_TYPE, REQUIRED_FEATURES, null, null);
Account[] accounts = futureAccounts.getResult();
assertNotNull(accounts);
assertEquals(1, accounts.length);
assertEquals(true, isAccountPresent(accounts, ACCOUNT));
futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
new String[] { NON_EXISTING_FEATURE },
null /* callback*/,
null /* handler */);
accounts = futureAccounts.getResult();
assertNotNull(accounts);
assertEquals(0, accounts.length);
}
/**
* Test getAccountsByTypeAndFeatures() with callback and handler
*/
public void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler() throws IOException,
AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(null /* handler */);
testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testGetAccountsByTypeAndFeaturesWithCallbackAndHandler(Handler handler) throws
IOException, AuthenticatorException, OperationCanceledException {
final CountDownLatch latch1 = new CountDownLatch(1);
AccountManagerCallback<Account[]> callback1 = new AccountManagerCallback<Account[]>() {
@Override
public void run(AccountManagerFuture<Account[]> accountsFuture) {
try {
Account[] accounts = accountsFuture.getResult();
assertNotNull(accounts);
assertEquals(1, accounts.length);
assertEquals(true, isAccountPresent(accounts, ACCOUNT));
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
} finally {
latch1.countDown();
}
}
};
AccountManagerFuture<Account[]> futureAccounts = am.getAccountsByTypeAndFeatures(
ACCOUNT_TYPE,
REQUIRED_FEATURES,
callback1,
handler);
Account[] accounts = futureAccounts.getResult();
assertNotNull(accounts);
assertEquals(1, accounts.length);
assertEquals(true, isAccountPresent(accounts, ACCOUNT));
// Wait with timeout for the callback to do its work
try {
latch1.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
final CountDownLatch latch2 = new CountDownLatch(1);
AccountManagerCallback<Account[]> callback2 = new AccountManagerCallback<Account[]>() {
@Override
public void run(AccountManagerFuture<Account[]> accountsFuture) {
try {
Account[] accounts = accountsFuture.getResult();
assertNotNull(accounts);
assertEquals(0, accounts.length);
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
} finally {
latch2.countDown();
}
}
};
accounts = null;
futureAccounts = am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
new String[] { NON_EXISTING_FEATURE },
callback2,
handler);
accounts = futureAccounts.getResult();
assertNotNull(accounts);
assertEquals(0, accounts.length);
// Wait with timeout for the callback to do its work
try {
latch2.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test setAuthToken() and peekAuthToken()
*/
public void testSetAndPeekAndInvalidateAuthToken() {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
String expected = "x";
am.setAuthToken(ACCOUNT, AUTH_TOKEN_TYPE, expected);
// Ask for the AuthToken
String token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
assertNotNull(token);
assertEquals(expected, token);
am.invalidateAuthToken(ACCOUNT_TYPE, token);
token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
assertNull(token);
}
/**
* Test successful blockingGetAuthToken() with customTokens=false authenticator.
*/
public void testBlockingGetAuthToken_DefaultToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
String token = am.blockingGetAuthToken(ACCOUNT,
AUTH_TOKEN_TYPE,
false /* no failure notification */);
// Ask for the AuthToken
assertNotNull(token);
assertEquals(mockAuthenticator.getLastTokenServed(), token);
}
private static class BlockingGetAuthTokenFetcher implements TokenFetcher {
private final Account mAccount;
BlockingGetAuthTokenFetcher(Account account) {
mAccount = account;
}
@Override
public Bundle fetch(String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException {
String token = am.blockingGetAuthToken(
getAccount(),
tokenType,
false /* no failure notification */);
Bundle result = new Bundle();
result.putString(AccountManager.KEY_AUTHTOKEN, token);
result.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
return result;
}
@Override
public Account getAccount() {
return CUSTOM_TOKEN_ACCOUNT;
}
}
/**
* Test successful blockingGetAuthToken() with customTokens=true authenticator.
*/
public void testBlockingGetAuthToken_CustomToken_NoCaching_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
}
/**
* Test successful blockingGetAuthToken() with customTokens=true authenticator.
*/
public void testBlockingGetAuthToken_CustomToken_ExpiringCache_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
}
/**
* Test successful getAuthToken() using a future with customTokens=false authenticator.
*/
public void testDeprecatedGetAuthTokenWithFuture_NoOptions_DefaultToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
AUTH_TOKEN_TYPE,
false /* no failure notification */,
null /* no callback */,
null /* no handler */
);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
}
/**
* Test successful getAuthToken() using a future with customTokens=false without
* expiring tokens.
*/
public void testDeprecatedGetAuthTokenWithFuture_NoOptions_CustomToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
// validateSuccessfulTokenFetchingLifecycle(AccountManager am, TokenFetcher fetcher, String tokenType)
TokenFetcher f = new TokenFetcher() {
@Override
public Bundle fetch(String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException {
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
getAccount(),
tokenType,
false /* no failure notification */,
null /* no callback */,
null /* no handler */
);
Bundle actual = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(actual);
return actual;
}
@Override
public Account getAccount() {
return CUSTOM_TOKEN_ACCOUNT;
}
};
validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
}
/**
* Test successful getAuthToken() using a future with customTokens=false without
* expiring tokens.
*/
public void testGetAuthTokenWithFuture_Options_DefaultToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
AUTH_TOKEN_TYPE,
OPTIONS_BUNDLE,
mActivity,
null /* no callback */,
null /* no handler */
);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsGetAuthToken);
validateSystemOptions(mockAuthenticator.mOptionsGetAuthToken);
}
/**
* Test successful getAuthToken() using a future with customTokens=false without
* expiring tokens.
*/
public void testGetAuthTokenWithFuture_Options_CustomToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
TokenFetcher fetcherWithOptions = new TokenFetcher() {
@Override
public Bundle fetch(String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException {
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
getAccount(),
tokenType,
OPTIONS_BUNDLE,
false /* no failure notification */,
null /* no callback */,
null /* no handler */
);
Bundle actual = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(actual);
return actual;
}
@Override
public Account getAccount() {
return CUSTOM_TOKEN_ACCOUNT;
}
};
validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
}
/**
* Test successful getAuthToken() using a future with customTokens=false without
* expiring tokens.
*/
public void testGetAuthTokenWithCallback_Options_Handler_DefaultToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
final HandlerThread handlerThread = new HandlerThread("accounts.test");
handlerThread.start();
TokenFetcher fetcherWithOptions = new TokenFetcher() {
@Override
public Bundle fetch(String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException {
final AtomicReference<Bundle> actualRef = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
actualRef.set(resultBundle);
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
} finally {
latch.countDown();
}
}
};
am.getAuthToken(getAccount(),
tokenType,
OPTIONS_BUNDLE,
false /* no failure notification */,
callback,
new Handler(handlerThread.getLooper()));
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
return actualRef.get();
}
@Override
public Account getAccount() {
return ACCOUNT;
}
};
validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
}
/**
* Test successful getAuthToken() using a future with customTokens=false without
* expiring tokens.
*/
public void testGetAuthTokenWithCallback_Options_Handler_CustomToken_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
final HandlerThread handlerThread = new HandlerThread("accounts.test");
handlerThread.start();
TokenFetcher fetcherWithOptions = new TokenFetcher() {
@Override
public Bundle fetch(String tokenType)
throws OperationCanceledException, AuthenticatorException, IOException {
final AtomicReference<Bundle> actualRef = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
actualRef.set(resultBundle);
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
} finally {
latch.countDown();
}
}
};
am.getAuthToken(getAccount(),
tokenType,
OPTIONS_BUNDLE,
false /* no failure notification */,
callback,
new Handler(handlerThread.getLooper()));
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
return actualRef.get();
}
@Override
public Account getAccount() {
return CUSTOM_TOKEN_ACCOUNT;
}
};
validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
}
/**
* Test getAuthToken() with callback and handler
*/
public void testGetAuthTokenWithCallbackAndHandler() throws IOException, AuthenticatorException,
OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
testGetAuthTokenWithCallbackAndHandler(null /* handler */);
testGetAuthTokenWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testGetAuthTokenWithCallbackAndHandler(Handler handler) throws IOException,
AuthenticatorException, OperationCanceledException {
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
finally {
latch.countDown();
}
}
};
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
AUTH_TOKEN_TYPE,
false /* no failure notification */,
callback,
handler
);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* test getAuthToken() with options and callback and handler
*/
public void testGetAuthTokenWithOptionsAndCallback() throws IOException,
AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
testGetAuthTokenWithOptionsAndCallbackAndHandler(null /* handler */);
testGetAuthTokenWithOptionsAndCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testGetAuthTokenWithOptionsAndCallbackAndHandler(Handler handler) throws
IOException, AuthenticatorException, OperationCanceledException {
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
finally {
latch.countDown();
}
}
};
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
AUTH_TOKEN_TYPE,
OPTIONS_BUNDLE,
mActivity,
callback,
handler
);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test getAuthTokenByFeatures()
*/
public void testGetAuthTokenByFeatures() throws IOException, AuthenticatorException,
OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
Bundle resultBundle = getAuthTokenByFeature(
new String[] { NON_EXISTING_FEATURE },
null /* activity */
);
// Assert returned result
validateNullResult(resultBundle);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
mockAuthenticator.clearData();
// Now test with existing features and an activity
resultBundle = getAuthTokenByFeature(
new String[] { NON_EXISTING_FEATURE },
mActivity
);
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
mockAuthenticator.clearData();
// Now test with existing features and no activity
resultBundle = getAuthTokenByFeature(
REQUIRED_FEATURES,
null /* activity */
);
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
mockAuthenticator.clearData();
// Now test with existing features and an activity
resultBundle = getAuthTokenByFeature(
REQUIRED_FEATURES,
mActivity
);
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
}
/**
* Test confirmCredentials()
*/
@Presubmit
public void testConfirmCredentials() throws IOException, AuthenticatorException,
OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
OPTIONS_BUNDLE,
mActivity,
null /* callback*/,
null /* handler */);
futureBundle.getResult();
// Assert returned result
validateCredentials();
}
/**
* Tests the setting of lastAuthenticatedTime on adding account
*/
public void testLastAuthenticatedTimeAfterAddAccount() throws IOException,
AuthenticatorException, OperationCanceledException {
assertTrue(addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD) > 0);
}
/**
* Test confirmCredentials() for account not on device. Just that no error
* should be thrown.
*/
public void testConfirmCredentialsAccountNotOnDevice() throws IOException,
AuthenticatorException, OperationCanceledException {
Account account = new Account("AccountNotOnThisDevice", ACCOUNT_TYPE);
AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(account,
OPTIONS_BUNDLE,
mActivity,
null /* callback */,
null /* handler */);
futureBundle.getResult();
}
/**
* Tests the setting of lastAuthenticatedTime on confirmCredentials being
* successful.
*/
public void testLastAuthenticatedTimeAfterConfirmCredentialsSuccess() throws IOException,
AuthenticatorException, OperationCanceledException {
long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
// Now this confirm credentials call returns true, which in turn
// should update the last authenticated timestamp.
Bundle result = am.confirmCredentials(ACCOUNT,
OPTIONS_BUNDLE, /* options */
null, /* activity */
null /* callback */,
null /* handler */).getResult();
long confirmedCredTime = result.getLong(
AccountManager.KEY_LAST_AUTHENTICATED_TIME, -1);
assertTrue(confirmedCredTime > accountAddTime);
}
/**
* Tests the setting of lastAuthenticatedTime on updateCredentials being
* successful.
*/
public void testLastAuthenticatedTimeAfterUpdateCredentialsSuccess() throws IOException,
AuthenticatorException, OperationCanceledException {
long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
am.updateCredentials(ACCOUNT,
AUTH_TOKEN_TYPE,
OPTIONS_BUNDLE,
mActivity,
null /* callback */,
null /* handler */).getResult();
long updateCredTime = getLastAuthenticatedTime(ACCOUNT);
assertTrue(updateCredTime > accountAddTime);
}
/**
* LastAuthenticatedTime on setPassword should not be disturbed.
*/
public void testLastAuthenticatedTimeAfterSetPassword() throws IOException,
AuthenticatorException, OperationCanceledException {
long accountAddTime = addAccountAndReturnAccountAddedTime(ACCOUNT, ACCOUNT_PASSWORD);
mockAuthenticator.callSetPassword();
long setPasswordTime = getLastAuthenticatedTime(ACCOUNT);
assertTrue(setPasswordTime == accountAddTime);
}
/**
* Test confirmCredentials() with callback
*/
public void testConfirmCredentialsWithCallbackAndHandler() {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
testConfirmCredentialsWithCallbackAndHandler(null /* handler */);
testConfirmCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testConfirmCredentialsWithCallbackAndHandler(Handler handler) {
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
// Assert returned result
validateCredentials();
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
finally {
latch.countDown();
}
}
};
AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
OPTIONS_BUNDLE,
mActivity,
callback,
handler);
// Wait with timeout for the callback to do its work
try {
latch.await(3 * LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test updateCredentials()
*/
public void testUpdateCredentials() throws IOException, AuthenticatorException,
OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
AUTH_TOKEN_TYPE,
OPTIONS_BUNDLE,
mActivity,
null /* callback*/,
null /* handler */);
Bundle result = futureBundle.getResult();
validateAccountAndNoAuthTokenResult(result);
// Assert returned result
validateCredentials();
}
/**
* Test updateCredentials() with callback and handler
*/
public void testUpdateCredentialsWithCallbackAndHandler() throws IOException,
AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
testUpdateCredentialsWithCallbackAndHandler(null /* handler */);
testUpdateCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testUpdateCredentialsWithCallbackAndHandler(Handler handler) throws IOException,
AuthenticatorException, OperationCanceledException {
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
// Assert returned result
validateCredentials();
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
finally {
latch.countDown();
}
}
};
AccountManagerFuture<Bundle> futureBundle = am.updateCredentials(ACCOUNT,
AUTH_TOKEN_TYPE,
OPTIONS_BUNDLE,
mActivity,
callback,
handler);
futureBundle.getResult();
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test editProperties()
*/
public void testEditProperties() throws IOException, AuthenticatorException,
OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
mActivity,
null /* callback */,
null /* handler*/);
Bundle result = futureBundle.getResult();
validateAccountAndNoAuthTokenResult(result);
// Assert returned result
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
}
/**
* Test editProperties() with callback and handler
*/
public void testEditPropertiesWithCallbackAndHandler() {
testEditPropertiesWithCallbackAndHandler(null /* handler */);
testEditPropertiesWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testEditPropertiesWithCallbackAndHandler(Handler handler) {
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
try {
// Assert returned result
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
}
finally {
latch.countDown();
}
}
};
AccountManagerFuture<Bundle> futureBundle = am.editProperties(ACCOUNT_TYPE,
mActivity,
callback,
handler);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test addOnAccountsUpdatedListener() with handler
*/
public void testAddOnAccountsUpdatedListenerWithHandler() throws IOException,
AuthenticatorException, OperationCanceledException {
testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
false /* updateImmediately */);
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
true /* updateImmediately */);
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
false /* updateImmediately */);
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
true /* updateImmediately */);
}
private void testAddOnAccountsUpdatedListenerWithHandler(Handler handler,
boolean updateImmediately) {
final CountDownLatch latch = new CountDownLatch(1);
OnAccountsUpdateListener listener = accounts -> latch.countDown();
// Add a listener
am.addOnAccountsUpdatedListener(listener,
handler,
updateImmediately);
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
// Cleanup
am.removeOnAccountsUpdatedListener(listener);
}
/**
* Test addOnAccountsUpdatedListener() with visibility
*/
public void testAddOnAccountsUpdatedListenerWithVisibility() throws IOException,
AuthenticatorException, OperationCanceledException {
testAddOnAccountsUpdatedListenerWithVisibility(null /* handler */,
false /* updateImmediately */, new String[] {ACCOUNT_TYPE, ACCOUNT_TYPE_ABSENT});
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testAddOnAccountsUpdatedListenerWithVisibility(null /* handler */,
true /* updateImmediately */, null /* types */);
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testAddOnAccountsUpdatedListenerWithVisibility(new Handler(Looper.getMainLooper()),
false /* updateImmediately */, new String[] {ACCOUNT_TYPE});
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testAddOnAccountsUpdatedListenerWithVisibility(new Handler(Looper.getMainLooper()),
true /* updateImmediately */, new String[] {ACCOUNT_TYPE});
}
private void testAddOnAccountsUpdatedListenerWithVisibility(Handler handler,
boolean updateImmediately, String[] accountTypes) {
final CountDownLatch latch = new CountDownLatch(1);
OnAccountsUpdateListener listener = accounts -> latch.countDown();
// Add a listener
am.addOnAccountsUpdatedListener(listener,
handler,
updateImmediately,
accountTypes);
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
// Cleanup
am.removeOnAccountsUpdatedListener(listener);
}
/**
* Test removeOnAccountsUpdatedListener() with handler
*/
public void testRemoveOnAccountsUpdatedListener() throws IOException, AuthenticatorException,
OperationCanceledException {
testRemoveOnAccountsUpdatedListenerWithHandler(null /* handler */);
// Need to cleanup intermediate state
assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
testRemoveOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()));
}
private void testRemoveOnAccountsUpdatedListenerWithHandler(Handler handler) {
final CountDownLatch latch = new CountDownLatch(1);
OnAccountsUpdateListener listener = accounts -> fail("should not be called");
// First add a listener
am.addOnAccountsUpdatedListener(listener,
handler,
false /* updateImmediately */);
// Then remove the listener
am.removeOnAccountsUpdatedListener(listener);
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
/**
* Test hasFeature
*/
public void testHasFeature()
throws IOException, AuthenticatorException, OperationCanceledException {
assertHasFeature(null /* handler */);
assertHasFeature(new Handler(Looper.getMainLooper()));
assertHasFeatureWithCallback(null /* handler */);
assertHasFeatureWithCallback(new Handler(Looper.getMainLooper()));
}
private void assertHasFeature(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
Bundle resultBundle = addAccount(am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
OPTIONS_BUNDLE,
mActivity,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
validateAccountAndAuthTokenType();
validateFeatures();
AccountManagerFuture<Boolean> booleanFuture = am.hasFeatures(ACCOUNT,
new String[]{FEATURE_1},
null /* callback */,
handler);
assertTrue(booleanFuture.getResult());
booleanFuture = am.hasFeatures(ACCOUNT,
new String[]{FEATURE_2},
null /* callback */,
handler);
assertTrue(booleanFuture.getResult());
booleanFuture = am.hasFeatures(ACCOUNT,
new String[]{FEATURE_1, FEATURE_2},
null /* callback */,
handler);
assertTrue(booleanFuture.getResult());
booleanFuture = am.hasFeatures(ACCOUNT,
new String[]{NON_EXISTING_FEATURE},
null /* callback */,
handler);
assertFalse(booleanFuture.getResult());
booleanFuture = am.hasFeatures(ACCOUNT,
new String[]{NON_EXISTING_FEATURE, FEATURE_1},
null /* callback */,
handler);
assertFalse(booleanFuture.getResult());
booleanFuture = am.hasFeatures(ACCOUNT,
new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
null /* callback */,
handler);
assertFalse(booleanFuture.getResult());
}
private AccountManagerCallback<Boolean> getAssertTrueCallback(final CountDownLatch latch) {
return new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> booleanFuture) {
try {
// Assert returned result should be TRUE
assertTrue(booleanFuture.getResult());
} catch (Exception e) {
fail("Exception: " + e);
} finally {
latch.countDown();
}
}
};
}
private AccountManagerCallback<Boolean> getAssertFalseCallback(final CountDownLatch latch) {
return new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> booleanFuture) {
try {
// Assert returned result should be FALSE
assertFalse(booleanFuture.getResult());
} catch (Exception e) {
fail("Exception: " + e);
} finally {
latch.countDown();
}
}
};
}
private void waitForLatch(final CountDownLatch latch) {
// Wait with timeout for the callback to do its work
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("should not throw an InterruptedException");
}
}
private void assertHasFeatureWithCallback(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
Bundle resultBundle = addAccount(am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
OPTIONS_BUNDLE,
mActivity,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
validateAccountAndAuthTokenType();
validateFeatures();
CountDownLatch latch = new CountDownLatch(1);
am.hasFeatures(ACCOUNT,
new String[]{FEATURE_1},
getAssertTrueCallback(latch),
handler);
waitForLatch(latch);
latch = new CountDownLatch(1);
am.hasFeatures(ACCOUNT,
new String[]{FEATURE_2},
getAssertTrueCallback(latch),
handler);
waitForLatch(latch);
latch = new CountDownLatch(1);
am.hasFeatures(ACCOUNT,
new String[]{FEATURE_1, FEATURE_2},
getAssertTrueCallback(latch),
handler);
waitForLatch(latch);
latch = new CountDownLatch(1);
am.hasFeatures(ACCOUNT,
new String[]{NON_EXISTING_FEATURE},
getAssertFalseCallback(latch),
handler);
waitForLatch(latch);
latch = new CountDownLatch(1);
am.hasFeatures(ACCOUNT,
new String[]{NON_EXISTING_FEATURE, FEATURE_1},
getAssertFalseCallback(latch),
handler);
waitForLatch(latch);
latch = new CountDownLatch(1);
am.hasFeatures(ACCOUNT,
new String[]{NON_EXISTING_FEATURE, FEATURE_1, FEATURE_2},
getAssertFalseCallback(latch),
handler);
waitForLatch(latch);
}
private long getLastAuthenticatedTime(Account account) throws OperationCanceledException,
AuthenticatorException, IOException {
Bundle options = new Bundle();
options.putBoolean(MockAccountAuthenticator.KEY_RETURN_INTENT, true);
// Not really confirming, but a way to get last authenticated timestamp
Bundle result = am.confirmCredentials(account,
options,// OPTIONS_BUNDLE,
null, /* activity */
null /* callback */,
null /* handler */).getResult();
return result.getLong(
AccountManager.KEY_LAST_AUTHENTICATED_TIME, -1);
}
private long addAccountAndReturnAccountAddedTime(Account account, String password)
throws OperationCanceledException, AuthenticatorException, IOException {
addAccount(am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
OPTIONS_BUNDLE,
mActivity,
null /* callback */,
null /* handler */);
return getLastAuthenticatedTime(account);
}
/**
* Tests that AccountManagerService is properly caching data.
*/
public void testGetsAreCached() {
// Add an account,
assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
// Then verify that we don't hit disk retrieving it,
StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
StrictMode.setThreadPolicy(
new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build());
Account[] accounts = am.getAccounts();
assertNotNull(accounts);
assertTrue(accounts.length > 0);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
/**
* Tests a basic startAddAccountSession() which returns a bundle containing
* encrypted session bundle, account password and status token.
*/
public void testStartAddAccountSession()
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
}
/**
* Tests startAddAccountSession() with null session bundle. Only account
* password and status token should be included in the result as session
* bundle is not inspected.
*/
public void testStartAddAccountSessionWithNullSessionBundle()
throws IOException, AuthenticatorException, OperationCanceledException {
final Bundle options = new Bundle();
final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putAll(OPTIONS_BUNDLE);
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
assertNull(resultBundle.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertEquals(ACCOUNT_STATUS_TOKEN,
resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
}
/**
* Tests startAddAccountSession() with empty session bundle. An encrypted
* session bundle, account password and status token should be included in
* the result as session bundle is not inspected.
*/
public void testStartAddAccountSessionWithEmptySessionBundle()
throws IOException, AuthenticatorException, OperationCanceledException {
final Bundle options = new Bundle();
final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, new Bundle());
options.putAll(OPTIONS_BUNDLE);
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
}
/**
* Tests startAddAccountSession with authenticator activity started. When
* Activity is provided, AccountManager would start the resolution Intent
* and return the final result which contains an encrypted session bundle,
* account password and status token.
*/
public void testStartAddAccountSessionIntervene()
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
null /* callback */,
null /* handler */);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
}
/**
* Tests startAddAccountSession with KEY_INTENT returned but not started
* automatically. When no Activity is provided and authenticator requires
* additional data from user, KEY_INTENT will be returned by AccountManager.
*/
public void testStartAddAccountSessionWithReturnIntent()
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
// Assert that KEY_INTENT is returned.
assertNotNull(returnIntent);
assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
// Assert that no other data is returned.
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
}
/**
* Tests startAddAccountSession error case. AuthenticatorException is
* expected when authenticator return
* {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
*/
public void testStartAddAccountSessionError() throws IOException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_ERROR + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountNameAndError(accountName);
try {
startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
fail("startAddAccountSession should throw AuthenticatorException in error case.");
} catch (AuthenticatorException e) {
}
}
/**
* Tests startAddAccountSession() with callback and handler. An encrypted
* session bundle, account password and status token should be included in
* the result. Callback should be triggered with the result regardless of a
* handler is provided or not.
*/
public void testStartAddAccountSessionWithCallbackAndHandler()
throws IOException, AuthenticatorException, OperationCanceledException {
testStartAddAccountSessionWithCallbackAndHandler(null /* handler */);
testStartAddAccountSessionWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
/**
* Tests startAddAccountSession() with callback and handler and activity
* started. When Activity is provided, AccountManager would start the
* resolution Intent and return the final result which contains an encrypted
* session bundle, account password and status token. Callback should be
* triggered with the result regardless of a handled is provided or not.
*/
public void testStartAddAccountSessionWithCallbackAndHandlerWithIntervene()
throws IOException, AuthenticatorException, OperationCanceledException {
testStartAddAccountSessionWithCallbackAndHandlerWithIntervene(null /* handler */);
testStartAddAccountSessionWithCallbackAndHandlerWithIntervene(
new Handler(Looper.getMainLooper()));
}
/**
* Tests startAddAccountSession() with callback and handler with KEY_INTENT
* returned. When no Activity is provided and authenticator requires
* additional data from user, KEY_INTENT will be returned by AccountManager
* in callback regardless of a handler is provided or not.
*/
public void testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent()
throws IOException, AuthenticatorException, OperationCanceledException {
testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent(null /* handler */);
testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent(
new Handler(Looper.getMainLooper()));
}
/**
* Tests startAddAccountSession() error case with callback and handler.
* AuthenticatorException is expected when authenticator return
* {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
*/
public void testStartAddAccountSessionErrorWithCallbackAndHandler()
throws IOException, OperationCanceledException {
testStartAddAccountSessionErrorWithCallbackAndHandler(null /* handler */);
testStartAddAccountSessionErrorWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testStartAddAccountSessionWithCallbackAndHandler(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
// Wait with timeout for the callback to do its work
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
latch.countDown();
}
};
startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
callback,
handler);
waitForLatch(latch);
}
private void testStartAddAccountSessionWithCallbackAndHandlerWithIntervene(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
// Wait with timeout for the callback to do its work
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
latch.countDown();
}
};
startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
callback,
handler);
waitForLatch(latch);
}
private void testStartAddAccountSessionWithCallbackAndHandlerWithReturnIntent(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
// Wait with timeout for the callback to do its work
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
validateStartAddAccountSessionParametersAndOptions(accountName, options);
// Assert returned result
Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
// Assert KEY_INTENT is returned.
assertNotNull(returnIntent);
assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
// Assert that no other data is returned.
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
latch.countDown();
}
};
startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null, // activity
callback,
handler);
waitForLatch(latch);
}
private void testStartAddAccountSessionErrorWithCallbackAndHandler(Handler handler)
throws IOException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountNameAndError(accountName);
// Wait with timeout for the callback to do its work
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
try {
bundleFuture.getResult();
fail("should have thrown an AuthenticatorException");
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
latch.countDown();
}
}
};
try {
startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
callback,
handler);
// AuthenticatorException should be thrown when authenticator
// returns AccountManager.ERROR_CODE_INVALID_RESPONSE.
fail("should have thrown an AuthenticatorException");
} catch (AuthenticatorException e1) {
}
waitForLatch(latch);
}
/**
* Test a basic startUpdateCredentialsSession() which returns a bundle containing
* encrypted session bundle, account password and status token.
*/
public void testStartUpdateCredentialsSession()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
}
/**
* Tests startUpdateCredentialsSession() with null session bundle. Only account
* password and status token should be included in the result as session
* bundle is not inspected.
*/
public void testStartUpdateCredentialsSessionWithNullSessionBundle()
throws IOException, AuthenticatorException, OperationCanceledException {
Bundle options = new Bundle();
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putAll(OPTIONS_BUNDLE);
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
assertNull(resultBundle.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertEquals(ACCOUNT_STATUS_TOKEN,
resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
}
/**
* Tests startUpdateCredentialsSession() with empty session bundle. An encrypted
* session bundle, account password and status token should be included in
* the result as session bundle is not inspected.
*/
public void testStartUpdateCredentialsSessionWithEmptySessionBundle()
throws IOException, AuthenticatorException, OperationCanceledException {
Bundle options = new Bundle();
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, new Bundle());
options.putAll(OPTIONS_BUNDLE);
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
}
/**
* Tests startUpdateCredentialsSession with authenticator activity started. When
* Activity is provided, AccountManager would start the resolution Intent
* and return the final result which contains an encrypted session bundle,
* account password and status token.
*/
public void testStartUpdateCredentialsSessionIntervene()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
mActivity,
null /* callback */,
null /* handler */);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
}
/**
* Tests startUpdateCredentialsSession with KEY_INTENT returned but not
* started automatically. When no Activity is provided and authenticator requires
* additional data from user, KEY_INTENT will be returned by AccountManager.
*/
public void testStartUpdateCredentialsSessionWithReturnIntent()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
// Assert that KEY_INTENT is returned.
assertNotNull(returnIntent);
assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
// Assert that no other data is returned.
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
}
/**
* Tests startUpdateCredentialsSession error case. AuthenticatorException is
* expected when authenticator return
* {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
*/
public void testStartUpdateCredentialsSessionError()
throws IOException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountNameAndError(accountName);
try {
startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
fail("startUpdateCredentialsSession should throw AuthenticatorException in error.");
} catch (AuthenticatorException e) {
}
}
/**
* Tests startUpdateCredentialsSession() with callback and handler. An encrypted
* session bundle, account password and status token should be included in
* the result. Callback should be triggered with the result regardless of a
* handler is provided or not.
*/
public void testStartUpdateCredentialsSessionWithCallbackAndHandler()
throws IOException, AuthenticatorException, OperationCanceledException {
testStartUpdateCredentialsSessionWithCallbackAndHandler(null /* handler */);
testStartUpdateCredentialsSessionWithCallbackAndHandler(
new Handler(Looper.getMainLooper()));
}
/**
* Tests startUpdateCredentialsSession() with callback and handler and
* activity started. When Activity is provided, AccountManager would start the
* resolution Intent and return the final result which contains an encrypted
* session bundle, account password and status token. Callback should be
* triggered with the result regardless of a handler is provided or not.
*/
public void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene()
throws IOException, AuthenticatorException, OperationCanceledException {
testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene(null /* handler */);
testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene(
new Handler(Looper.getMainLooper()));
}
/**
* Tests startUpdateCredentialsSession() with callback and handler with
* KEY_INTENT returned. When no Activity is provided and authenticator requires
* additional data from user, KEY_INTENT will be returned by AccountManager
* in callback regardless of a handler is provided or not.
*/
public void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent()
throws IOException, AuthenticatorException, OperationCanceledException {
testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent(null /* handler */);
testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent(
new Handler(Looper.getMainLooper()));
}
/**
* Tests startUpdateCredentialsSession() error case with callback and
* handler. AuthenticatorException is expected when authenticator return
* {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
*/
public void testStartUpdateCredentialsSessionErrorWithCallbackAndHandler()
throws IOException, OperationCanceledException {
testStartUpdateCredentialsSessionErrorWithCallbackAndHandler(null /* handler */);
testStartUpdateCredentialsSessionErrorWithCallbackAndHandler(
new Handler(Looper.getMainLooper()));
}
private void testStartUpdateCredentialsSessionWithCallbackAndHandler(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
latch.countDown();
}
};
startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
mActivity,
callback,
handler);
waitForLatch(latch);
}
private void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithIntervene(
Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
latch.countDown();
}
};
startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
mActivity,
callback,
handler);
waitForLatch(latch);
}
private void testStartUpdateCredentialsSessionWithCallbackAndHandlerWithReturnIntent(
Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountName(accountName);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
validateStartUpdateCredentialsSessionParametersAndOptions(accountName, options);
// Assert returned result
Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
// Assert KEY_INTENT is returned.
assertNotNull(returnIntent);
assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
// Assert that no other data is returned.
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertNull(resultBundle.getString(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
latch.countDown();
}
};
startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null,
callback,
handler);
waitForLatch(latch);
}
private void testStartUpdateCredentialsSessionErrorWithCallbackAndHandler(Handler handler)
throws IOException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
final Bundle options = createOptionsWithAccountNameAndError(accountName);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
try {
bundleFuture.getResult();
fail("should have thrown an AuthenticatorException");
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
latch.countDown();
}
}
};
try {
startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
mActivity,
callback,
handler);
// AuthenticatorException should be thrown when authenticator
// returns AccountManager.ERROR_CODE_INVALID_RESPONSE.
fail("should have thrown an AuthenticatorException");
} catch (AuthenticatorException e1) {
}
waitForLatch(latch);
}
private Bundle startUpdateCredentialsSession(AccountManager am,
Account account,
String authTokenType,
Bundle options,
Activity activity,
AccountManagerCallback<Bundle> callback,
Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.startUpdateCredentialsSession(
account,
authTokenType,
options,
activity,
callback,
handler);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
return resultBundle;
}
private Bundle startAddAccountSession(AccountManager am,
String accountType,
String authTokenType,
String[] requiredFeatures,
Bundle options,
Activity activity,
AccountManagerCallback<Bundle> callback,
Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
AccountManagerFuture<Bundle> futureBundle = am.startAddAccountSession(
accountType,
authTokenType,
requiredFeatures,
options,
activity,
callback,
handler);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
return resultBundle;
}
private Bundle createOptionsWithAccountName(final String accountName) {
SESSION_BUNDLE.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
Bundle options = new Bundle();
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, SESSION_BUNDLE);
options.putAll(OPTIONS_BUNDLE);
return options;
}
private Bundle createOptionsWithAccountNameAndError(final String accountName) {
Bundle options = createOptionsWithAccountName(accountName);
options.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_INVALID_RESPONSE);
options.putString(AccountManager.KEY_ERROR_MESSAGE, ERROR_MESSAGE);
return options;
}
private void validateStartAddAccountSessionParametersAndOptions(
String accountName, Bundle options) {
// Assert parameters has been passed correctly
validateAccountAndAuthTokenType();
validateFeatures();
// Validate options
validateOptions(options, mockAuthenticator.mOptionsStartAddAccountSession);
assertNotNull(mockAuthenticator.mOptionsStartAddAccountSession);
assertEquals(accountName, mockAuthenticator.mOptionsStartAddAccountSession
.getString(Fixtures.KEY_ACCOUNT_NAME));
validateSystemOptions(mockAuthenticator.mOptionsStartAddAccountSession);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
validateOptions(null, mockAuthenticator.mOptionsFinishSession);
}
private void validateStartUpdateCredentialsSessionParametersAndOptions(
String accountName, Bundle options) {
// Assert parameters has been passed correctly
assertEquals(AUTH_TOKEN_TYPE, mockAuthenticator.getAuthTokenType());
assertEquals(ACCOUNT, mockAuthenticator.mAccount);
// Validate options
validateOptions(options, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
assertNotNull(mockAuthenticator.mOptionsStartUpdateCredentialsSession);
assertEquals(accountName, mockAuthenticator.mOptionsStartUpdateCredentialsSession
.getString(Fixtures.KEY_ACCOUNT_NAME));
// Validate system options
assertNotNull(mockAuthenticator.mOptionsStartUpdateCredentialsSession
.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME));
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsStartAddAccountSession);
validateOptions(null, mockAuthenticator.mOptionsFinishSession);
}
private void validateIsCredentialsUpdateSuggestedParametersAndOptions(Account account) {
assertEquals(account, mockAuthenticator.getAccount());
assertEquals(ACCOUNT_STATUS_TOKEN, mockAuthenticator.getStatusToken());
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
validateOptions(null, mockAuthenticator.mOptionsStartAddAccountSession);
}
private void validateSessionBundleAndPasswordAndStatusTokenResult(Bundle resultBundle) {
Bundle sessionBundle = resultBundle.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
assertNotNull(sessionBundle);
// Assert that session bundle is encrypted and hence data not visible.
assertNull(sessionBundle.getString(SESSION_DATA_NAME_1));
// Assert password is not returned since cts test is not signed with system key
assertNull(resultBundle.getString(AccountManager.KEY_PASSWORD));
assertEquals(ACCOUNT_STATUS_TOKEN,
resultBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
}
private Bundle getResultExpectNoException(AccountManagerFuture<Bundle> bundleFuture) {
try {
return bundleFuture.getResult();
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
return null;
}
/**
* Tests a basic finishSession() with session bundle created by
* startAddAccountSession(...). A bundle containing account name and account
* type is expected.
*/
public void testFinishSessionWithStartAddAccountSession()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests a basic finishSession() with session bundle created by
* startUpdateCredentialsSession(...). A bundle containing account name and account
* type is expected.
*/
public void testFinishSessionWithStartUpdateCredentialsSession()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startUpdateCredentialsSession(...)
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests finishSession() with null session bundle. IllegalArgumentException
* is expected as session bundle cannot be null.
*/
public void testFinishSessionWithNullSessionBundle()
throws IOException, AuthenticatorException, OperationCanceledException {
try {
finishSession(
am,
null /* sessionBundle */,
null /* activity */,
null /* callback */,
null /* handler */);
fail("Should have thrown IllegalArgumentException when sessionBundle is null");
} catch (IllegalArgumentException e) {
}
}
/**
* Tests finishSession() with empty session bundle. IllegalArgumentException
* is expected as session bundle would always contain something if it was
* processed properly by AccountManagerService.
*/
public void testFinishSessionWithEmptySessionBundle()
throws IOException, AuthenticatorException, OperationCanceledException {
try {
finishSession(am,
new Bundle(),
null /* activity */,
null /* callback */,
null /* handler */);
fail("Should have thrown IllegalArgumentException when sessionBundle is empty");
} catch (IllegalArgumentException e) {
}
}
/**
* Tests finishSession() with sessionBundle not encrypted by the right key.
* AuthenticatorException is expected if AccountManagerService failed to
* decrypt the session bundle because of wrong key or crypto data was
* tampered.
*/
public void testFinishSessionWithDecryptionError()
throws IOException, OperationCanceledException {
byte[] mac = new byte[] {
1, 1, 0, 0
};
byte[] cipher = new byte[] {
1, 0, 0, 1, 1
};
Bundle sessionBundle = new Bundle();
sessionBundle.putByteArray(KEY_MAC, mac);
sessionBundle.putByteArray(KEY_CIPHER, cipher);
try {
finishSession(am,
sessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
fail("Should have thrown AuthenticatorException when failed to decrypt sessionBundle");
} catch (AuthenticatorException e) {
}
}
/**
* Tests finishSession() with sessionBundle invalid contents.
* AuthenticatorException is expected if AccountManagerService failed to
* decrypt the session bundle because of wrong key or crypto data was
* tampered.
*/
public void testFinishSessionWithInvalidEncryptedContent()
throws IOException, OperationCanceledException {
byte[] mac = new byte[] {};
Bundle sessionBundle = new Bundle();
sessionBundle.putByteArray(KEY_MAC, mac);
try {
finishSession(am,
sessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
fail("Should have thrown AuthenticatorException when failed to decrypt sessionBundle");
} catch (AuthenticatorException e) {
}
}
/**
* Tests a finishSession() when account type is not added to session bundle
* by startAddAccount(...) of authenticator. A bundle containing account
* name and account type should still be returned as AccountManagerSerivce
* will always add account type to the session bundle before encrypting it.
*/
public void testFinishSessionFromStartAddAccountWithoutAccountType()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
// Create a session bundle without account type for MockAccountAuthenticator to return
SESSION_BUNDLE.remove(AccountManager.KEY_ACCOUNT_TYPE);
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests a finishSession() when account type is not added to session bundle
* by startUpdateCredentialsSession(...) of authenticator. A bundle
* containing account name and account type should still be returned as
* AccountManagerSerivce will always add account type to the session bundle
* before encrypting it.
*/
public void testFinishSessionFromStartUpdateCredentialsSessionWithoutAccountType()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
// Create a session bundle without account type for MockAccountAuthenticator to return
SESSION_BUNDLE.remove(AccountManager.KEY_ACCOUNT_TYPE);
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests a finishSession() when a different account type is added to session bundle
* by startAddAccount(...) of authenticator. A bundle containing account
* name and the correct account type should be returned as AccountManagerSerivce
* will always overrides account type to the session bundle before encrypting it.
*/
public void testFinishSessionFromStartAddAccountAccountTypeOverriden()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
SESSION_BUNDLE.putString(AccountManager.KEY_ACCOUNT_TYPE, "randomAccountType");
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, correct type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests a finishSession() when a different account type is added to session bundle
* by startUpdateCredentialsSession(...) of authenticator. A bundle
* containing account name and the correct account type should be returned as
* AccountManagerSerivce will always override account type to the session bundle
* before encrypting it.
*/
public void testFinishSessionFromStartUpdateCredentialsSessionAccountTypeOverriden()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
// MockAccountAuthenticator to return
SESSION_BUNDLE.putString(AccountManager.KEY_ACCOUNT_TYPE, "randomAccountType");
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startUpdateCredentialsSession(
am,
ACCOUNT,
AUTH_TOKEN_TYPE,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, correct type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests finishSession with authenticator activity started. When additional
* info is needed from user for finishing the session and an Activity was
* provided by caller, the resolution intent will be started automatically.
* A bundle containing account name and type will be returned.
*/
public void testFinishSessionIntervene()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
null /* callback */,
null /* handler */);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
mActivity,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
}
/**
* Tests finishSession with KEY_INTENT returned but not started
* automatically. When additional info is needed from user for finishing the
* session and no Activity was provided by caller, the resolution intent
* will not be started automatically. A bundle containing KEY_INTENT will be
* returned instead.
*/
public void testFinishSessionWithReturnIntent()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
null /* callback */,
null /* handler */);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
resultBundle = finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result
Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
assertNotNull(returnIntent);
assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
}
/**
* Tests finishSession error case. AuthenticatorException is expected when
* AccountManager.ERROR_CODE_INVALID_RESPONSE is returned by authenticator.
*/
public void testFinishSessionError()
throws IOException, AuthenticatorException, OperationCanceledException {
Bundle sessionBundle = new Bundle();
String accountNameForFinish = Fixtures.PREFIX_NAME_ERROR + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountNameForFinish);
sessionBundle.putInt(AccountManager.KEY_ERROR_CODE,
AccountManager.ERROR_CODE_INVALID_RESPONSE);
sessionBundle.putString(AccountManager.KEY_ERROR_MESSAGE, ERROR_MESSAGE);
Bundle options = new Bundle();
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
options.putAll(OPTIONS_BUNDLE);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
try {
finishSession(
am,
encryptedSessionBundle,
null /* activity */,
null /* callback */,
null /* handler */);
fail("finishSession should throw AuthenticatorException in error case.");
} catch (AuthenticatorException e) {
}
}
/**
* Tests finishSession() with callback and handler. A bundle containing
* account name and type should be returned via the callback regardless of
* whether a handler is provided.
*/
public void testFinishSessionWithCallbackAndHandler()
throws IOException, AuthenticatorException, OperationCanceledException {
testFinishSessionWithCallbackAndHandler(null /* handler */);
testFinishSessionWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
/**
* Tests finishSession() with callback and handler and activity started.
* When additional info is needed from user for finishing the session and an
* Activity was provided by caller, the resolution intent will be started
* automatically. A bundle containing account name and type will be returned
* via the callback regardless of if handler is provided or now.
*/
public void testFinishSessionWithCallbackAndHandlerWithIntervene()
throws IOException, AuthenticatorException, OperationCanceledException {
testFinishSessionWithCallbackAndHandlerWithIntervene(null /* handler */);
testFinishSessionWithCallbackAndHandlerWithIntervene(
new Handler(Looper.getMainLooper()));
}
/**
* Tests finishSession() with callback and handler with KEY_INTENT
* returned. When additional info is needed from user for finishing the
* session and no Activity was provided by caller, the resolution intent
* will not be started automatically. A bundle containing KEY_INTENT will be
* returned instead via callback regardless of if handler is provided or not.
*/
public void testFinishSessionWithCallbackAndHandlerWithReturnIntent()
throws IOException, AuthenticatorException, OperationCanceledException {
testFinishSessionWithCallbackAndHandlerWithReturnIntent(null /* handler */);
testFinishSessionWithCallbackAndHandlerWithReturnIntent(
new Handler(Looper.getMainLooper()));
}
/**
* Tests finishSession() error case with callback and handler.
* AuthenticatorException is expected when
* AccountManager.ERROR_CODE_INVALID_RESPONSE is returned by authenticator.
*/
public void testFinishSessionErrorWithCallbackAndHandler()
throws IOException, OperationCanceledException, AuthenticatorException {
testFinishSessionErrorWithCallbackAndHandler(null /* handler */);
testFinishSessionErrorWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
private void testFinishSessionWithCallbackAndHandler(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
latch.countDown();
}
};
finishSession(am, encryptedSessionBundle, mActivity, callback, handler);
// Wait with timeout for the callback to do its work
waitForLatch(latch);
}
private void testFinishSessionWithCallbackAndHandlerWithIntervene(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
null /* callback */,
null /* handler */);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert returned result containing account name, type but not auth token type.
validateAccountAndNoAuthTokenResult(resultBundle);
latch.countDown();
}
};
finishSession(am, encryptedSessionBundle, mActivity, callback, handler);
// Wait with timeout for the callback to do its work
waitForLatch(latch);
}
private void testFinishSessionWithCallbackAndHandlerWithReturnIntent(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
final String accountName = Fixtures.PREFIX_NAME_INTERVENE + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
Bundle options = createOptionsWithAccountName(accountName);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
mActivity,
null /* callback */,
null /* handler */);
// Assert returned result
assertNull(resultBundle.getParcelable(AccountManager.KEY_INTENT));
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = getResultExpectNoException(bundleFuture);
// Assert parameters has been passed correctly
assertEquals(ACCOUNT_TYPE, mockAuthenticator.getAccountType());
validateFinishSessionOptions(accountName, SESSION_BUNDLE);
// Assert returned result
Intent returnIntent = resultBundle.getParcelable(AccountManager.KEY_INTENT);
assertNotNull(returnIntent);
assertNotNull(returnIntent.getParcelableExtra(Fixtures.KEY_RESULT));
assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_NAME));
assertNull(resultBundle.get(AccountManager.KEY_ACCOUNT_TYPE));
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
latch.countDown();
}
};
finishSession(am, encryptedSessionBundle, null, callback, handler);
// Wait with timeout for the callback to do its work
waitForLatch(latch);
}
private void testFinishSessionErrorWithCallbackAndHandler(Handler handler)
throws IOException, OperationCanceledException, AuthenticatorException {
Bundle sessionBundle = new Bundle();
String accountNameForFinish = Fixtures.PREFIX_NAME_ERROR + "@"
+ Fixtures.SUFFIX_NAME_FIXTURE;
sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountNameForFinish);
sessionBundle.putInt(AccountManager.KEY_ERROR_CODE,
AccountManager.ERROR_CODE_INVALID_RESPONSE);
sessionBundle.putString(AccountManager.KEY_ERROR_MESSAGE, ERROR_MESSAGE);
Bundle options = new Bundle();
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
options.putAll(OPTIONS_BUNDLE);
// First get an encrypted session bundle from startAddAccountSession(...)
Bundle resultBundle = startAddAccountSession(
am,
ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
REQUIRED_FEATURES,
options,
null /* activity */,
null /* callback */,
null /* handler */);
// Assert returned result
// Assert that auth token was stripped.
assertNull(resultBundle.get(AccountManager.KEY_AUTHTOKEN));
validateSessionBundleAndPasswordAndStatusTokenResult(resultBundle);
Bundle encryptedSessionBundle = resultBundle
.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
try {
bundleFuture.getResult();
fail("should have thrown an AuthenticatorException");
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
latch.countDown();
}
}
};
try {
finishSession(am, encryptedSessionBundle, mActivity, callback, handler);
fail("should have thrown an AuthenticatorException");
} catch (AuthenticatorException e1) {
}
// Wait with timeout for the callback to do its work
waitForLatch(latch);
}
private Bundle finishSession(AccountManager am, Bundle sessionBundle, Activity activity,
AccountManagerCallback<Bundle> callback, Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
// Cleanup before calling finishSession(...) with the encrypted session bundle.
mockAuthenticator.clearData();
AccountManagerFuture<Bundle> futureBundle = am.finishSession(
sessionBundle,
activity,
callback,
handler);
Bundle resultBundle = futureBundle.getResult();
assertTrue(futureBundle.isDone());
assertNotNull(resultBundle);
return resultBundle;
}
private void validateFinishSessionOptions(String accountName, Bundle options) {
validateOptions(options, mockAuthenticator.mOptionsFinishSession);
assertNotNull(mockAuthenticator.mOptionsFinishSession);
assertEquals(ACCOUNT_TYPE, mockAuthenticator.mOptionsFinishSession
.getString(AccountManager.KEY_ACCOUNT_TYPE));
assertEquals(accountName,
mockAuthenticator.mOptionsFinishSession.getString(Fixtures.KEY_ACCOUNT_NAME));
validateSystemOptions(mockAuthenticator.mOptionsFinishSession);
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
validateOptions(null, mockAuthenticator.mOptionsAddAccount);
validateOptions(null, mockAuthenticator.mOptionsStartAddAccountSession);
validateOptions(null, mockAuthenticator.mOptionsStartUpdateCredentialsSession);
}
/**
* Tests a basic isCredentialsUpdateSuggested() which returns a bundle containing boolean true.
*/
public void testIsCredentialsUpdateSuggested_Success()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Account account = new Account(accountName, ACCOUNT_TYPE);
Boolean result = isCredentialsUpdateSuggested(
am,
account,
ACCOUNT_STATUS_TOKEN,
null /* callback */,
null /* handler */);
// Assert parameters has been passed correctly
validateIsCredentialsUpdateSuggestedParametersAndOptions(account);
// Assert returned result
assertTrue(result);
}
/**
* Tests isCredentialsUpdateSuggested() when account is null.
* It should throw IllegalArgumentationException.
*/
public void testIsCredentialsUpdateSuggestedNullAccount_IllegalArgumentationException()
throws IOException, AuthenticatorException, OperationCanceledException {
try {
isCredentialsUpdateSuggested(
am,
null /* account */,
ACCOUNT_STATUS_TOKEN,
null /* callback */,
null /* handler */);
fail("Should have thrown IllegalArgumentation when calling with null account!");
} catch (IllegalArgumentException e) {
}
}
/**
* Tests isCredentialsUpdateSuggested() when statusToken is empty.
* It should throw IllegalArgumentationException.
*/
public void testIsCredentialsUpdateSuggestedEmptyToken_IllegalArgumentationException()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Account account = new Account(accountName, ACCOUNT_TYPE);
try {
isCredentialsUpdateSuggested(
am,
account,
"" /* statusToken */,
null /* callback */,
null /* handler */);
fail("Should have thrown IllegalArgumentation when calling with empty statusToken!");
} catch (IllegalArgumentException e) {
}
}
/**
* Tests isCredentialsUpdateSuggested() error case. AuthenticatorException is expected when
* authenticator return {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
*/
public void testIsCredentialsUpdateSuggested_Error()
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
Account account = new Account(accountName, ACCOUNT_TYPE);
try {
isCredentialsUpdateSuggested(
am,
account,
ACCOUNT_STATUS_TOKEN,
null /* callback */,
null /* handler */);
fail("Should have thrown AuthenticatorException in error case.");
} catch (AuthenticatorException e) {
}
}
/**
* Tests isCredentialsUpdateSuggested() with callback and handler. A boolean should be included
* in the result. Callback should be triggered with the result regardless of a handler is
* provided or not.
*/
public void testIsCredentialsUpdateSuggestedWithCallbackAndHandler()
throws IOException, AuthenticatorException, OperationCanceledException {
testIsCredentialsUpdateSuggestedWithCallbackAndHandler(null /* handler */);
testIsCredentialsUpdateSuggestedWithCallbackAndHandler(
new Handler(Looper.getMainLooper()));
}
/**
* Tests isCredentialsUpdateSuggested() error case with callback and handler.
* AuthenticatorException is expected when authenticator return
* {@link AccountManager#ERROR_CODE_INVALID_RESPONSE} error code.
*/
public void testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler()
throws IOException, OperationCanceledException, AuthenticatorException {
testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler(null /* handler */);
testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler(
new Handler(Looper.getMainLooper()));
}
private void testIsCredentialsUpdateSuggestedWithCallbackAndHandler(Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
final Account account = new Account(accountName, ACCOUNT_TYPE);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> booleanFuture) {
Boolean result = false;
try {
result = booleanFuture.getResult();
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
fail("should not throw an AuthenticatorException");
}
// Assert parameters has been passed correctly
validateIsCredentialsUpdateSuggestedParametersAndOptions(account);
// Assert returned result
assertTrue(result);
latch.countDown();
}
};
isCredentialsUpdateSuggested(
am,
account,
ACCOUNT_STATUS_TOKEN,
callback,
handler);
// Wait with timeout for the callback to do its work
waitForLatch(latch);
}
private void testIsCredentialsUpdateSuggestedErrorWithCallbackAndHandler(Handler handler)
throws IOException, OperationCanceledException, AuthenticatorException {
String accountName = Fixtures.PREFIX_NAME_ERROR + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
final Account account = new Account(accountName, ACCOUNT_TYPE);
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> booleanFuture) {
try {
booleanFuture.getResult();
// AuthenticatorException should be thrown when authenticator
// returns AccountManager.ERROR_CODE_INVALID_RESPONSE.
fail("should have thrown an AuthenticatorException");
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
fail("should not throw an IOException");
} catch (AuthenticatorException e) {
// Test passed as AuthenticatorException is expected.
} finally {
latch.countDown();
}
}
};
isCredentialsUpdateSuggested(
am,
account,
ACCOUNT_STATUS_TOKEN,
callback,
handler);
// Wait with timeout for the callback to do its work
waitForLatch(latch);
}
private Boolean isCredentialsUpdateSuggested(
AccountManager am,
Account account,
String statusToken,
AccountManagerCallback<Boolean> callback,
Handler handler)
throws IOException, AuthenticatorException, OperationCanceledException {
AccountManagerFuture<Boolean> booleanFuture = am.isCredentialsUpdateSuggested(
account,
statusToken,
callback,
handler);
Boolean result = false;
if (callback == null) {
result = booleanFuture.getResult();
assertTrue(booleanFuture.isDone());
}
return result;
}
private void verifyAccountsGroupedByType(Account[] accounts) {
Map<String, Integer> firstPositionForType = new HashMap<>();
Map<String, Integer> lastPositionForType = new HashMap<>();
Map<String, Integer> counterForType = new HashMap<>();
for (int i = 0; i < accounts.length; i++) {
String type = accounts[i].type;
Integer first = firstPositionForType.get(type);
first = first != null ? first : Integer.MAX_VALUE;
firstPositionForType.put(type, Math.min(first, i));
Integer last = lastPositionForType.get(type);
last = last != null ? last : Integer.MIN_VALUE;
lastPositionForType.put(type, Math.max(last, i));
Integer counter = counterForType.get(type);
counter = counter != null ? counter : 0;
counterForType.put(type, counter + 1);
}
for (String type : counterForType.keySet()) {
assertEquals((int)lastPositionForType.get(type),
firstPositionForType.get(type) + counterForType.get(type) - 1);
}
}
}