Implement runtime account update notifications.

Test: manual, cts.
Change-Id: Iaf9fe4814c3d3c2ca2dca0a5bfa0f997e060bfeb
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 75d4f32..6d4b812 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -344,6 +344,17 @@
         "android.accounts.LOGIN_ACCOUNTS_CHANGED";
 
     /**
+     * Action sent as a broadcast Intent to specific package by the AccountsService
+     * when account visibility or account's credentials (saved password, etc) are changed.
+     *
+     * @see #addOnAccountsUpdatedListener
+     *
+     * @hide
+     */
+    public static final String ACTION_VISIBLE_ACCOUNTS_CHANGED =
+        "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
+
+    /**
      * Key to set default visibility for applications targeting API level
      * {@link android.os.Build.VERSION_CODES#O} or above and don't have the same signature as
      * authenticator See {@link #getAccountVisibility}. If the value was not set by authenticator
@@ -1057,8 +1068,8 @@
 
     /**
      * Gets the previous name associated with the account or {@code null}, if
-     * none. This is intended so that clients of {@link
-     * #LOGIN_ACCOUNTS_CHANGED_ACTION} broadcasts can determine if an
+     * none. This is intended so that clients of
+     * {@link OnAccountsUpdateListener} can determine if an
      * authenticator has renamed an account.
      *
      * <p>It is safe to call this method from the main thread.
@@ -1555,7 +1566,8 @@
      * <p>In that case, you may need to wait until the user responds, which
      * could take hours or days or forever.  When the user does respond and
      * supply a new password, the account manager will broadcast the
-     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+     * notify {@link OnAccountsUpdateListener} which applications can
      * use to try again.
      *
      * <p>If notifyAuthFailure is not set, it is the application's
@@ -1631,7 +1643,8 @@
      * <p>In that case, you may need to wait until the user responds, which
      * could take hours or days or forever.  When the user does respond and
      * supply a new password, the account manager will broadcast the
-     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+     * notify {@link OnAccountsUpdateListener} which applications can
      * use to try again.
      *
      * <p>If notifyAuthFailure is not set, it is the application's
@@ -2811,7 +2824,7 @@
             Maps.newHashMap();
 
     /**
-     * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
+     * BroadcastReceiver that listens for the ACTION_VISIBLE_ACCOUNTS_CHANGED intent
      * so that it can read the updated list of accounts and send them to the listener
      * in mAccountsUpdatedListeners.
      */
@@ -2881,22 +2894,27 @@
             mAccountsUpdatedListeners.put(listener, handler);
             if (accountTypes != null) {
                 mAccountsUpdatedListenersTypes.put(listener,
-                        new HashSet<String>(Arrays.asList(accountTypes)));
+                    new HashSet<String>(Arrays.asList(accountTypes)));
+            } else {
+                mAccountsUpdatedListenersTypes.put(listener, null);
             }
 
             if (wasEmpty) {
                 // Register a broadcast receiver to monitor account changes
                 IntentFilter intentFilter = new IntentFilter();
-                // TODO get rid of the broadcast receiver
-                // create android.os.ResultReceiver
-                // send it to the service via aidl
-                // handle onReceiveResult
-                intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
+                intentFilter.addAction(ACTION_VISIBLE_ACCOUNTS_CHANGED);
                 // To recover from disk-full.
                 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
-                // Register a broadcast receiver to monitor account changes
                 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
             }
+
+            try {
+                // Notify AccountManagedService about new receiver.
+                // The receiver must be unregistered later exactly one time
+                mService.registerAccountListener(accountTypes, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
         if (updateImmediately) {
             postToHandler(handler, listener, getAccounts());
@@ -2923,11 +2941,23 @@
                 Log.e(TAG, "Listener was not previously added");
                 return;
             }
+            Set<String> accountTypes = mAccountsUpdatedListenersTypes.get(listener);
+            String[] accountsArray;
+            if (accountTypes != null) {
+                accountsArray = accountTypes.toArray(new String[accountTypes.size()]);
+            } else {
+                accountsArray = null;
+            }
             mAccountsUpdatedListeners.remove(listener);
             mAccountsUpdatedListenersTypes.remove(listener);
             if (mAccountsUpdatedListeners.isEmpty()) {
                 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
             }
+            try {
+                mService.unregisterAccountListener(accountsArray, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }