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 */