Implement Account Discovery API.

Inludes temporary flow for notifications filtered by accountType
Bug: 33046496
Test: cts tests, manual tests.
Change-Id: I2d767030e851579a0666efd7e243a1239af740c7
This commit is contained in:
Dmitry Dementyev
2017-01-19 16:03:39 -08:00
parent ca0034296b
commit 01985ff6d2
8 changed files with 763 additions and 312 deletions

View File

@@ -53,9 +53,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.SuppressWarnings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -864,11 +867,17 @@ public class AccountManager {
*
* @param account The account for which visibility data should be returned.
*
* @return Map from uid to visibility for given account
* @return Map from uid to visibility for given account.
*/
public Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account) {
// TODO implement.
return null;
try {
@SuppressWarnings("unchecked")
Map<Integer, Integer> result = (Map<Integer, Integer>) mService
.getUidsAndVisibilityForAccount(account);
return result;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
@@ -2110,10 +2119,23 @@ public class AccountManager {
synchronized (mAccountsUpdatedListeners) {
try {
if (mAccountsUpdatedListeners.containsKey(listener)) {
listener.onAccountsUpdated(accountsCopy);
Set<String> types = mAccountsUpdatedListenersTypes.get(listener);
if (types != null) {
// filter by account type;
ArrayList<Account> filtered = new ArrayList<>();
for (Account account : accountsCopy) {
if (types.contains(account.type)) {
filtered.add(account);
}
}
listener.onAccountsUpdated(
filtered.toArray(new Account[filtered.size()]));
} else {
listener.onAccountsUpdated(accountsCopy);
}
}
} catch (SQLException e) {
// Better luck next time. If the problem was disk-full,
// Better luck next time. If the problem was disk-full,
// the STORAGE_OK intent will re-trigger the update.
Log.e(TAG, "Can't update accounts", e);
}
@@ -2759,6 +2781,9 @@ public class AccountManager {
private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
Maps.newHashMap();
private final HashMap<OnAccountsUpdateListener, Set<String> > mAccountsUpdatedListenersTypes =
Maps.newHashMap();
/**
* BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
* so that it can read the updated list of accounts and send them to the listener
@@ -2784,7 +2809,7 @@ public class AccountManager {
* accounts of any type related to the caller. This method is equivalent to
* addOnAccountsUpdatedListener(listener, handler, updateImmediately, null)
*
* @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean, Handler,
* @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean,
* String[])
*/
public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
@@ -2828,7 +2853,10 @@ public class AccountManager {
final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
mAccountsUpdatedListeners.put(listener, handler);
if (accountTypes != null) {
mAccountsUpdatedListenersTypes.put(listener,
new HashSet<String>(Arrays.asList(accountTypes)));
}
if (wasEmpty) {
// Register a broadcast receiver to monitor account changes
@@ -2870,6 +2898,7 @@ public class AccountManager {
return;
}
mAccountsUpdatedListeners.remove(listener);
mAccountsUpdatedListenersTypes.remove(listener);
if (mAccountsUpdatedListeners.isEmpty()) {
mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
}

View File

@@ -52,7 +52,9 @@ public class ChooseAccountActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO This activity is only used by getAuthTokenByFeatures and can not see
// VISIBILITY_USER_MANAGED_NOT_VISIBLE accounts. It should be moved to account managed
// service.
mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
mAccountManagerResponse =
getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);

View File

@@ -40,7 +40,9 @@ import com.android.internal.R;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
@@ -110,7 +112,7 @@ public class ChooseTypeAndAccountActivity extends Activity
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList";
private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountAndVisibilityList";
private static final int SELECTED_ITEM_NONE = -1;
@@ -120,7 +122,11 @@ public class ChooseTypeAndAccountActivity extends Activity
private boolean mSelectedAddNewAccount = false;
private String mDescriptionOverride;
private ArrayList<Account> mAccounts;
private Map<Account, Integer> mAccounts;
// TODO Redesign flow to show NOT_VISIBLE accounts
// and display a warning if they are selected.
// Currently NOT_VISBILE accounts are not shown at all.
private ArrayList<Account> mPossiblyVisibleAccounts;
private int mPendingRequest = REQUEST_NULL;
private Parcelable[] mExistingAccounts = null;
private int mSelectedItemIndex;
@@ -164,12 +170,12 @@ public class ChooseTypeAndAccountActivity extends Activity
savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
// Makes sure that any user selection is preserved across orientation changes.
mSelectedAccountName = savedInstanceState.getString(
KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
mSelectedAddNewAccount = savedInstanceState.getBoolean(
KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST);
mSelectedAccountName =
savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
mSelectedAddNewAccount =
savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
mAccounts = (Map<Account, Integer>) savedInstanceState
.getSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST);
} else {
mPendingRequest = REQUEST_NULL;
mExistingAccounts = null;
@@ -220,9 +226,15 @@ public class ChooseTypeAndAccountActivity extends Activity
}
}
String[] listItems = getListOfDisplayableOptions(mAccounts);
mSelectedItemIndex = getItemIndexToSelect(
mAccounts, mSelectedAccountName, mSelectedAddNewAccount);
mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
mPossiblyVisibleAccounts.add(entry.getKey());
}
}
String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
mSelectedAddNewAccount);
super.onCreate(savedInstanceState);
setContentView(R.layout.choose_type_and_account);
@@ -250,15 +262,18 @@ public class ChooseTypeAndAccountActivity extends Activity
outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
}
if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
if (mSelectedItemIndex == mAccounts.size()) {
if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
} else {
outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
mAccounts.get(mSelectedItemIndex).name);
mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
}
}
outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts);
// should be HashMap by default.
HashMap<Account, Integer> accountsHashMap = (mAccounts instanceof HashMap)
? (HashMap) mAccounts : new HashMap<Account, Integer>(mAccounts);
outState.putSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST, accountsHashMap);
}
public void onCancelButtonClicked(View view) {
@@ -266,11 +281,11 @@ public class ChooseTypeAndAccountActivity extends Activity
}
public void onOkButtonClicked(View view) {
if (mSelectedItemIndex == mAccounts.size()) {
if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
// Selected "Add New Account" option
startChooseAccountTypeActivity();
} else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
onAccountSelected(mAccounts.get(mSelectedItemIndex));
onAccountSelected(mPossiblyVisibleAccounts.get(mSelectedItemIndex));
}
}
@@ -321,6 +336,7 @@ public class ChooseTypeAndAccountActivity extends Activity
}
if (accountName == null || accountType == null) {
// new account was added.
Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
mCallingPackage, mCallingUid);
Set<Account> preExistingAccounts = new HashSet<Account>();
@@ -328,6 +344,7 @@ public class ChooseTypeAndAccountActivity extends Activity
preExistingAccounts.add((Account) accountParcel);
}
for (Account account : currentAccounts) {
// New account is visible to the app - return it.
if (!preExistingAccounts.contains(account)) {
accountName = account.name;
accountType = account.type;
@@ -409,13 +426,27 @@ public class ChooseTypeAndAccountActivity extends Activity
}
private void setResultAndFinish(final String accountName, final String accountType) {
// Mark account as visible since user chose it.
Account account = new Account(accountName, accountType);
Integer oldVisibility = mAccounts.get(account);
// oldVisibility is null if new account was added
if (oldVisibility == null) {
Map<Account, Integer> accountsAndVisibility = AccountManager.get(this)
.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
oldVisibility = accountsAndVisibility.get(account);
}
if (oldVisibility != null
&& oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
AccountManager.get(this).setAccountVisibility(account, mCallingUid,
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: "
+ "selected account " + accountName + ", " + accountType);
Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ accountName + ", " + accountType);
}
finish();
}
@@ -474,25 +505,28 @@ public class ChooseTypeAndAccountActivity extends Activity
}
/**
* Create a list of Account objects for each account that is acceptable. Filter out
* accounts that don't match the allowable types, if provided, or that don't match the
* allowable accounts, if provided.
* Create a list of Account objects for each account that is acceptable. Filter out accounts
* that don't match the allowable types, if provided, or that don't match the allowable
* accounts, if provided.
*/
private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) {
final Account[] accounts = accountManager.getAccountsForPackage(mCallingPackage,
mCallingUid);
ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length);
for (Account account : accounts) {
if (mSetOfAllowableAccounts != null && !mSetOfAllowableAccounts.contains(account)) {
continue;
}
if (mSetOfRelevantAccountTypes != null
&& !mSetOfRelevantAccountTypes.contains(account.type)) {
continue;
}
accountsToPopulate.add(account);
}
return accountsToPopulate;
private Map<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
Map<Account, Integer> accountsAndVisibility =
accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
Map<Account, Integer> accountsToPopulate =
new HashMap<Account, Integer>(accountsAndVisibility.size());
for (Map.Entry<Account, Integer> entry : accountsAndVisibility.entrySet()) {
if (mSetOfAllowableAccounts != null
&& !mSetOfAllowableAccounts.contains(entry.getKey())) {
continue;
}
if (mSetOfRelevantAccountTypes != null
&& !mSetOfRelevantAccountTypes.contains(entry.getKey().type)) {
continue;
}
accountsToPopulate.put(entry.getKey(), entry.getValue());
}
return accountsToPopulate;
}
/**

View File

@@ -108,8 +108,8 @@ interface IAccountManager {
void isCredentialsUpdateSuggested(in IAccountManagerResponse response, in Account account,
String statusToken);
/* Allows Authenticator to get UIDs of packages which registered to receive updates about given account type.*/
int[] getRequestingUidsForType(String accountType);
/* Returns Map<Integer, Integer> from UID to visibility with all values stored for given account*/
Map getUidsAndVisibilityForAccount(in Account account);
boolean addAccountExplicitlyWithVisibility(in Account account, String password, in Bundle extras,
in Map visibility);
@@ -117,7 +117,7 @@ interface IAccountManager {
boolean setAccountVisibility(in Account a, int uid, int newVisibility);
int getAccountVisibility(in Account a, int uid);
/* Type may be null returns Map <Account, Integer>*/
/* Type may be null returns Map <Account, Integer>*/
Map getAccountsAndVisibilityForPackage(in String packageName, in String accountType);
/* Check if the package in a user can access an account */

View File

@@ -355,7 +355,7 @@ class AccountsDb implements AutoCloseable {
boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
return db.delete(CE_TABLE_AUTHTOKENS,
AUTHTOKENS_ACCOUNTS_ID + "=?" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?",
new String[]{String.valueOf(accountId), authtokenType}) > 0;
}
@@ -946,12 +946,13 @@ class AccountsDb implements AutoCloseable {
/**
* Returns a map from uid to visibility value.
*/
Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
Map<Integer, Integer> findAllVisibilityValuesForAccount(Account account) {
SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Map<Integer, Integer> result = new HashMap<>();
final Cursor cursor = db.query(TABLE_VISIBILITY,
new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
new String[] {String.valueOf(accountId)}, null, null, null);
final Cursor cursor =
db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_UID, VISIBILITY_VALUE},
SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
new String[] {account.name, account.type}, null, null, null);
try {
while (cursor.moveToNext()) {
result.put(cursor.getInt(0), cursor.getInt(1));
@@ -1306,4 +1307,4 @@ class AccountsDb implements AutoCloseable {
return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
}
}
}

View File

@@ -1262,25 +1262,6 @@ public class AccountManagerServiceTest extends AndroidTestCase {
}
}
@SmallTest
public void testHasFeaturesReadAccountsNotPermitted() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_DENIED);
when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
.thenReturn(PackageManager.SIGNATURE_NO_MATCH);
try {
mAms.hasFeatures(
mMockAccountManagerResponse, // response
AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
new String[] {"feature1", "feature2"}, // features
"testPackage"); // opPackageName
fail("SecurityException expected. But no exception was thrown.");
} catch (SecurityException e) {
// SecurityException is expected.
}
}
@SmallTest
public void testHasFeaturesReturnNullResult() throws Exception {
unlockSystemUser();

View File

@@ -374,7 +374,7 @@ public class AccountsDbTest {
mAccountsDb.setAccountVisibility(accId, uid2, 3);
assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(3));
Map<Integer, Integer> vis = mAccountsDb.findAccountVisibilityForAccountId(accId);
Map<Integer, Integer> vis = mAccountsDb.findAllVisibilityValuesForAccount(account);
assertEquals(vis.size(), 2);
assertEquals(vis.get(uid1), Integer.valueOf(1));
assertEquals(vis.get(uid2), Integer.valueOf(3));