Implement Account Discovery API.
Inludes temporary flow for notifications filtered by accountType
Bug: 33046496
Test: cts tests, manual tests.
Change-Id: I2d767030e851579a0666efd7e243a1239af740c7
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index b27fa24..def0ff9 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -53,9 +53,12 @@
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 @@
*
* @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 @@
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 @@
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 @@
* 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 @@
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 @@
return;
}
mAccountsUpdatedListeners.remove(listener);
+ mAccountsUpdatedListenersTypes.remove(listener);
if (mAccountsUpdatedListeners.isEmpty()) {
mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
}
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index 242b3ea..16a45ba 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -52,7 +52,9 @@
@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);
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 8c71f50..95fdfef 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -40,7 +40,9 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 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 @@
}
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 @@
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 @@
}
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 @@
}
/**
- * 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;
}
/**
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 66c3ca3..63a0919 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -108,8 +108,8 @@
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 @@
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 */