Media: add MediaRouter2.getRoutes()

As with this CL, media apps can get the list of available routes using
MediaRouter2.getRoutes() or MediaRouter2.Callback.onRouteListChanged().

For that MediaRouterService notifies MediaRouter2 of providerinfos.

This CL also clarifies provider info notification logic.

1) When a new client or a new manager is registered, it will be
notified.
2) When a provider info is updated, it will be notified.

onRouteListChanged will be called for a newly registerd callback.

Fixed a bug that MediaRoute2ProviderProxy didn't report state update
when the provider is disonnected.

Test: atest mediaroutertest
Change-Id: I50f5c3cabce80e7fb0a1b5596883c35911d6f122
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index 8d08beb..6f44d45 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -22,5 +22,5 @@
  * @hide
  */
 oneway interface IMediaRoute2ProviderClient {
-    void notifyProviderInfoUpdated(in MediaRoute2ProviderInfo info);
+    void updateProviderInfo(in MediaRoute2ProviderInfo info);
 }
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index 774d6a7..26184af 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -16,10 +16,12 @@
 
 package android.media;
 
+import android.media.MediaRoute2ProviderInfo;
+
 /**
  * @hide
  */
 oneway interface IMediaRouter2Client {
-    void notifyStateChanged();
     void notifyRestoreRoute();
+    void notifyProviderInfosUpdated(in List<MediaRoute2ProviderInfo> providers);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 08266a5..1b713b6 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -42,7 +42,7 @@
     void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
 
     // Methods for media router 2
-    void registerClient2AsUser(IMediaRouter2Client client, String packageName, int userId);
+    void registerClient2(IMediaRouter2Client client, String packageName);
     void unregisterClient2(IMediaRouter2Client client);
     void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
     /**
@@ -54,8 +54,7 @@
     void selectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route);
     void setControlCategories(IMediaRouter2Client client, in List<String> categories);
 
-    void registerManagerAsUser(IMediaRouter2Manager manager,
-            String packageName, int userId);
+    void registerManager(IMediaRouter2Manager manager, String packageName);
     void unregisterManager(IMediaRouter2Manager manager);
     /**
      * Changes the selected route of an application.
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index b89b0b1..e8e0f82 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -98,7 +98,7 @@
             return;
         }
         try {
-            mClient.notifyProviderInfoUpdated(mProviderInfo);
+            mClient.updateProviderInfo(mProviderInfo);
         } catch (RemoteException ex) {
             Log.w(TAG, "Failed to send onProviderInfoUpdated");
         }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f4dffa2..8e29e34 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -16,13 +16,16 @@
 
 package android.media;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -58,6 +61,12 @@
     private Client mClient;
 
     private final String mPackageName;
+    final Handler mHandler;
+
+    List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList();
+    volatile List<MediaRoute2Info> mRoutes = Collections.emptyList();
+
+    MediaRoute2Info mSelectedRoute;
 
     /**
      * Gets an instance of the media router associated with the context.
@@ -78,6 +87,7 @@
                 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
         mPackageName = mContext.getPackageName();
         //TODO: read control categories from the manifest
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     /**
@@ -100,25 +110,10 @@
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(callback, "callback must not be null");
 
+        CallbackRecord record;
         // This is required to prevent adding the same callback twice.
         synchronized (mCallbackRecords) {
-            if (mCallbackRecords.size() == 0) {
-                synchronized (sLock) {
-                    Client client = new Client();
-                    try {
-                        mMediaRouterService.registerClient2AsUser(client, mPackageName,
-                                UserHandle.myUserId());
-                        //TODO: We should merge control categories of callbacks.
-                        mMediaRouterService.setControlCategories(client, mControlCategories);
-                        mClient = client;
-                    } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to register media router.", ex);
-                    }
-                }
-            }
-
             final int index = findCallbackRecordIndexLocked(callback);
-            CallbackRecord record;
             if (index < 0) {
                 record = new CallbackRecord(callback);
                 mCallbackRecords.add(record);
@@ -129,6 +124,20 @@
             record.mFlags = flags;
         }
 
+        synchronized (sLock) {
+            if (mClient == null) {
+                Client client = new Client();
+                try {
+                    mMediaRouterService.registerClient2(client, mPackageName);
+                    mMediaRouterService.setControlCategories(client, mControlCategories);
+                    mClient = client;
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to register media router.", ex);
+                }
+            }
+        }
+        record.notifyRoutes();
+
         //TODO: Update discovery request here.
     }
 
@@ -172,10 +181,9 @@
         Objects.requireNonNull(controlCategories, "control categories must not be null");
 
         Client client;
-        List<String> newControlCategories;
+        List<String> newControlCategories = new ArrayList<>(controlCategories);
         synchronized (sLock) {
-            mControlCategories = new ArrayList<>(controlCategories);
-            newControlCategories = mControlCategories;
+            mControlCategories = newControlCategories;
             client = mClient;
         }
         if (client != null) {
@@ -185,8 +193,29 @@
                 Log.e(TAG, "Unable to set control categories.", ex);
             }
         }
+        mHandler.sendMessage(obtainMessage(MediaRouter2::refreshAndNotifyRoutes, this));
     }
 
+    /**
+     * Gets the list of {@link MediaRoute2Info routes} currently known to the media router.
+     *
+     * @return the list of routes that support at least one of the control categories set by
+     * the application
+     */
+    @NonNull
+    public List<MediaRoute2Info> getRoutes() {
+        return mRoutes;
+    }
+
+    /**
+     * Gets the currently selected route.
+     *
+     * @return the selected route
+     */
+    @NonNull
+    public MediaRoute2Info getSelectedRoute() {
+        return mSelectedRoute;
+    }
 
     /**
      * Selects the specified route.
@@ -199,6 +228,7 @@
 
         Client client;
         synchronized (sLock) {
+            mSelectedRoute = route;
             client = mClient;
         }
         if (client != null) {
@@ -247,6 +277,61 @@
         return -1;
     }
 
+    void onProviderInfosUpdated(List<MediaRoute2ProviderInfo> providers) {
+        if (providers == null) {
+            Log.w(TAG, "Providers info is null.");
+            return;
+        }
+
+        mProviders = providers;
+        refreshAndNotifyRoutes();
+    }
+
+    void refreshAndNotifyRoutes() {
+        ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+
+        List<String> controlCategories;
+        synchronized (sLock) {
+            controlCategories = mControlCategories;
+        }
+
+        for (MediaRoute2ProviderInfo provider : mProviders) {
+            updateProvider(provider, controlCategories, routes);
+        }
+
+        //TODO: Can orders be changed?
+        if (!Objects.equals(mRoutes, routes)) {
+            mRoutes = Collections.unmodifiableList(routes);
+            notifyRouteListChanged(mRoutes);
+        }
+    }
+
+    void updateProvider(MediaRoute2ProviderInfo provider, List<String> controlCategories,
+            List<MediaRoute2Info> outRoutes) {
+        if (provider == null || !provider.isValid()) {
+            Log.w(TAG, "Ignoring invalid provider : " + provider);
+        }
+
+        final Collection<MediaRoute2Info> routes = provider.getRoutes();
+        for (MediaRoute2Info route : routes) {
+            if (!route.isValid()) {
+                Log.w(TAG, "Ignoring invalid route : " + route);
+                continue;
+            }
+            if (!route.supportsControlCategory(controlCategories)) {
+                continue;
+            }
+            outRoutes.add(route);
+        }
+    }
+
+    void notifyRouteListChanged(List<MediaRoute2Info> routes) {
+        for (CallbackRecord record: mCallbackRecords) {
+            record.mExecutor.execute(
+                    () -> record.mCallback.onRoutesChanged(routes));
+        }
+    }
+
     /**
      * Interface for receiving events about media routing changes.
      */
@@ -265,9 +350,14 @@
          * Called when a route is removed.
          */
         public void onRouteRemoved(MediaRoute2Info routeInfo) {}
+
+        /**
+         * Called when the list of routes is changed.
+         */
+        public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
     }
 
-    static final class CallbackRecord {
+    final class CallbackRecord {
         public final Callback mCallback;
         public Executor mExecutor;
         public int mFlags;
@@ -275,13 +365,25 @@
         CallbackRecord(@NonNull Callback callback) {
             mCallback = callback;
         }
+
+        void notifyRoutes() {
+            final List<MediaRoute2Info> routes = mRoutes;
+            // notify only when bound to media router service.
+            //TODO: Correct the condition when control category, default rotue, .. are finalized.
+            if (routes.size() > 0) {
+                mExecutor.execute(() -> mCallback.onRoutesChanged(routes));
+            }
+        }
     }
 
     class Client extends IMediaRouter2Client.Stub {
         @Override
-        public void notifyStateChanged() throws RemoteException {}
+        public void notifyRestoreRoute() throws RemoteException {}
 
         @Override
-        public void notifyRestoreRoute() throws RemoteException {}
+        public void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> info) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2::onProviderInfosUpdated,
+                    MediaRouter2.this, info));
+        }
     }
 }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 6c53f7d..0b64569 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -25,7 +25,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -104,26 +103,28 @@
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(callback, "callback must not be null");
 
+        CallbackRecord callbackRecord;
         synchronized (mCallbacks) {
             if (findCallbackRecordIndexLocked(callback) >= 0) {
                 Log.w(TAG, "Ignoring to add the same callback twice.");
                 return;
             }
-            synchronized (sLock) {
-                if (mCallbacks.size() == 0) {
-                    Client client = new Client();
-                    try {
-                        mMediaRouterService.registerManagerAsUser(client, mPackageName,
-                                UserHandle.myUserId());
-                        mClient = client;
-                    } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to register media router manager.", ex);
-                    }
+            callbackRecord = new CallbackRecord(executor, callback);
+            mCallbacks.add(callbackRecord);
+        }
+
+        synchronized (sLock) {
+            if (mClient == null) {
+                Client client = new Client();
+                try {
+                    mMediaRouterService.registerManager(client, mPackageName);
+                    mClient = client;
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to register media router manager.", ex);
                 }
+            } else {
+                callbackRecord.notifyRoutes();
             }
-            CallbackRecord record = new CallbackRecord(executor, callback);
-            mCallbacks.add(record);
-            record.notifyRoutes();
         }
     }
 
@@ -149,6 +150,7 @@
                     } catch (RemoteException ex) {
                         Log.e(TAG, "Unable to unregister media router manager", ex);
                     }
+                    mClient.notifyProviderInfosUpdated(Collections.emptyList());
                     mClient = null;
                 }
             }
@@ -255,6 +257,10 @@
             final MediaRoute2ProviderInfo prevProvider = mProviders.get(index);
             final Set<String> updatedRouteIds = new HashSet<>();
             for (MediaRoute2Info routeInfo : routes) {
+                if (!routeInfo.isValid()) {
+                    Log.w(TAG, "Ignoring invalid route : " + routeInfo);
+                    continue;
+                }
                 final MediaRoute2Info prevRoute = prevProvider.getRoute(routeInfo.getId());
                 if (prevRoute == null) {
                     notifyRouteAdded(routeInfo);
@@ -303,7 +309,7 @@
     void notifyRouteListChanged() {
         for (CallbackRecord record: mCallbacks) {
             record.mExecutor.execute(
-                    () -> record.mCallback.onRouteListChanged(mRoutes));
+                    () -> record.mCallback.onRoutesChanged(mRoutes));
         }
     }
 
@@ -369,10 +375,10 @@
         public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {}
 
         /**
-         * Called when the list of routes are changed.
+         * Called when the list of routes is changed.
          * A client may refresh available routes for each application.
          */
-        public void onRouteListChanged(@NonNull List<MediaRoute2Info> routes) {}
+        public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
     }
 
     final class CallbackRecord {
@@ -385,6 +391,7 @@
         }
 
         void notifyRoutes() {
+            mExecutor.execute(() -> mCallback.onRoutesChanged(mRoutes));
             for (MediaRoute2Info routeInfo : mRoutes) {
                 mExecutor.execute(
                         () -> mCallback.onRouteAdded(routeInfo));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 946fb5e..3abf0a4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
@@ -42,10 +43,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -96,6 +95,7 @@
         mPackageName = mContext.getPackageName();
     }
 
+    //TODO: Move to a seperate file
     @Test
     public void testMediaRoute2Info() {
         MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name")
@@ -159,7 +159,7 @@
         mRouter.unregisterCallback(mockRouterCallback);
 
         verify(mockCallback, timeout(TIMEOUT_MS))
-                .onRouteListChanged(argThat(routes -> routes.size() > 0));
+                .onRoutesChanged(argThat(routes -> routes.size() > 0));
 
         Map<String, MediaRoute2Info> routes =
                 createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -170,40 +170,48 @@
         mManager.unregisterCallback(mockCallback);
     }
 
+    /**
+     * Tests if we get proper routes for application that has special control category.
+     */
     @Test
-    public void onRouteSelectedTest() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
+    public void testGetRoutes() throws Exception {
+        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
 
+        mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
+        mRouter.registerCallback(mExecutor, mockCallback);
+        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+                .onRoutesChanged(argThat(routes -> routes.size() > 0));
+        Map<String, MediaRoute2Info> routes = createRouteMap(mRouter.getRoutes());
+        Assert.assertEquals(1, routes.size());
+        Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+
+        mRouter.unregisterCallback(mockCallback);
+    }
+
+    @Test
+    public void testOnRouteSelected() throws Exception {
         MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
-        mRouter.registerCallback(mExecutor, mockRouterCallback);
-
-        MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
-            MediaRoute2Info mSelectedRoute = null;
-
-            @Override
-            public void onRouteAdded(MediaRoute2Info routeInfo) {
-                if (mSelectedRoute == null) {
-                    mSelectedRoute = routeInfo;
-                    mManager.selectRoute(mPackageName, mSelectedRoute);
-                }
-            }
-
-            @Override
-            public void onRouteSelected(String packageName, MediaRoute2Info route) {
-                if (TextUtils.equals(packageName, mPackageName)
-                        && mSelectedRoute != null
-                        && route != null
-                        && TextUtils.equals(route.getId(), mSelectedRoute.getId())) {
-                    latch.countDown();
-                }
-            }
-        };
+        MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
 
         mManager.registerCallback(mExecutor, managerCallback);
+        mRouter.setControlCategories(CONTROL_CATEGORIES_ALL);
+        mRouter.registerCallback(mExecutor, mockRouterCallback);
 
-        Assert.assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        verify(managerCallback, timeout(TIMEOUT_MS))
+                .onRoutesChanged(argThat(routes -> routes.size() > 0));
+
+        Map<String, MediaRoute2Info> routes =
+                createRouteMap(mManager.getAvailableRoutes(mPackageName));
+
+        MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
+        mManager.selectRoute(mPackageName, routeToSelect);
+
+        assertNotNull(routeToSelect);
+        verify(managerCallback, timeout(TIMEOUT_MS))
+                .onRouteAdded(argThat(route -> route.equals(routeToSelect)));
 
         mManager.unregisterCallback(managerCallback);
+        mRouter.unregisterCallback(mockRouterCallback);
     }
 
     /**
@@ -219,7 +227,7 @@
         mRouter.registerCallback(mExecutor, routerCallback);
 
         verify(managerCallback, timeout(TIMEOUT_MS))
-                .onRouteListChanged(argThat(routes -> routes.size() > 0));
+                .onRoutesChanged(argThat(routes -> routes.size() > 0));
 
         Map<String, MediaRoute2Info> routes =
                 createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -244,7 +252,8 @@
         mManager.unregisterCallback(managerCallback);
     }
 
-    Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+    // Helper for getting routes easily
+    static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
         for (MediaRoute2Info route : routes) {
             routeMap.put(route.getId(), route);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 9e34018..e753a7b 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -43,7 +43,7 @@
  * Maintains a connection to a particular media route provider service.
  */
 final class MediaRoute2ProviderProxy implements ServiceConnection {
-    private static final String TAG = "MediaRoute2ProviderProxy";
+    private static final String TAG = "MediaRoute2Provider";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Context mContext;
@@ -54,7 +54,6 @@
 
     private Callback mCallback;
 
-    //TODO: make it nonnull
     private MediaRoute2ProviderInfo mProviderInfo;
 
     // Connection state
@@ -246,13 +245,21 @@
         if (mActiveConnection != connection) {
             return;
         }
-        // Set a unique provider id for identifying providers.
-        mProviderInfo = new MediaRoute2ProviderInfo.Builder(info)
-                .setUniqueId(mUniqueId)
-                .build();
         if (DEBUG) {
             Slog.d(TAG, this + ": State changed ");
         }
+        setAndNotifyProviderInfo(info);
+    }
+
+    private void setAndNotifyProviderInfo(MediaRoute2ProviderInfo info) {
+        //TODO: check if info is not updated
+        if (info == null) {
+            mProviderInfo = null;
+        } else {
+            mProviderInfo = new MediaRoute2ProviderInfo.Builder(info)
+                .setUniqueId(mUniqueId)
+                .build();
+        }
         mHandler.post(mStateChanged);
     }
 
@@ -261,6 +268,7 @@
             mConnectionReady = false;
             mActiveConnection.dispose();
             mActiveConnection = null;
+            setAndNotifyProviderInfo(null);
         }
     }
 
@@ -337,7 +345,7 @@
             mHandler.post(() -> onConnectionDied(Connection.this));
         }
 
-        void postProviderUpdated(MediaRoute2ProviderInfo info) {
+        void postProviderInfoUpdated(MediaRoute2ProviderInfo info) {
             mHandler.post(() -> onProviderInfoUpdated(Connection.this, info));
         }
     }
@@ -354,10 +362,10 @@
         }
 
         @Override
-        public void notifyProviderInfoUpdated(MediaRoute2ProviderInfo info) {
+        public void updateProviderInfo(MediaRoute2ProviderInfo info) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
-                connection.postProviderUpdated(info);
+                connection.postProviderInfoUpdated(info);
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 12137fe..043c834 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -33,6 +33,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -72,21 +73,20 @@
         mContext = context;
     }
 
-    public void registerClientAsUser(@NonNull IMediaRouter2Client client,
-            @NonNull String packageName, int userId) {
+    public void registerClient(@NonNull IMediaRouter2Client client,
+            @NonNull String packageName) {
         Objects.requireNonNull(client, "client must not be null");
 
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
-        final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
-                false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
+        final int userId = UserHandle.getUserId(uid);
         final boolean trusted = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
                 == PackageManager.PERMISSION_GRANTED;
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted);
+                registerClientLocked(client, uid, pid, packageName, userId, trusted);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -106,20 +106,20 @@
         }
     }
 
-    public void registerManagerAsUser(@NonNull IMediaRouter2Manager manager,
-            @NonNull String packageName, int userId) {
+    public void registerManager(@NonNull IMediaRouter2Manager manager,
+            @NonNull String packageName) {
         Objects.requireNonNull(manager, "manager must not be null");
         //TODO: should check permission
         final boolean trusted = true;
 
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
-        final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
-                false /*allowAll*/, true /*requireFull*/, "registerManagerAsUser", packageName);
+        final int userId = UserHandle.getUserId(uid);
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                registerManagerLocked(manager, uid, pid, packageName, resolvedUserId, trusted);
+                registerManagerLocked(manager, uid, pid, packageName, userId, trusted);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -254,6 +254,10 @@
 
             userRecord.mClientRecords.add(clientRecord);
             mAllClientRecords.put(binder, clientRecord);
+
+            userRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::notifyProviderInfosUpdatedToClient,
+                            userRecord.mHandler, client));
         }
     }
 
@@ -341,9 +345,9 @@
             userRecord.mManagerRecords.add(managerRecord);
             mAllManagerRecords.put(binder, managerRecord);
 
-            //TODO: remove this when it's unnecessary
-            // Sends published routes to newly added manager.
-            userRecord.mHandler.scheduleUpdateManagerState();
+            userRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager,
+                            userRecord.mHandler, manager));
 
             final int count = userRecord.mClientRecords.size();
             for (int i = 0; i < count; i++) {
@@ -504,14 +508,14 @@
         private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
         private final UserRecord mUserRecord;
         private final MediaRoute2ProviderWatcher mWatcher;
-        private final ArrayList<IMediaRouter2Manager> mTempManagers = new ArrayList<>();
 
         //TODO: Make this thread-safe.
         private final ArrayList<MediaRoute2ProviderProxy> mMediaProviders =
                 new ArrayList<>();
+        private List<MediaRoute2ProviderInfo> mProviderInfos;
 
         private boolean mRunning;
-        private boolean mManagerStateUpdateScheduled;
+        private boolean mProviderInfosUpdateScheduled;
 
         UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) {
             super(Looper.getMainLooper(), null, true);
@@ -553,14 +557,14 @@
         }
 
         private void updateProvider(MediaRoute2ProviderProxy provider) {
-            scheduleUpdateManagerState();
+            scheduleUpdateProviderInfos();
         }
 
         private void selectRoute(ClientRecord clientRecord, MediaRoute2Info route) {
             if (route != null) {
                 MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
                 if (provider == null) {
-                    Log.w(TAG, "Ignoring to select route of unknown provider " + route);
+                    Slog.w(TAG, "Ignoring to select route of unknown provider " + route);
                 } else {
                     provider.selectRoute(clientRecord.mPackageName, route.getId());
                 }
@@ -571,7 +575,7 @@
             if (route != null) {
                 MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
                 if (provider == null) {
-                    Log.w(TAG, "Ignoring to unselect route of unknown provider " + route);
+                    Slog.w(TAG, "Ignoring to unselect route of unknown provider " + route);
                 } else {
                     provider.unselectRoute(clientRecord.mPackageName, route.getId());
                 }
@@ -585,49 +589,71 @@
             }
         }
 
-        private void scheduleUpdateManagerState() {
-            if (!mManagerStateUpdateScheduled) {
-                mManagerStateUpdateScheduled = true;
-                sendMessage(PooledLambda.obtainMessage(UserHandler::updateManagerState, this));
+        private void scheduleUpdateProviderInfos() {
+            if (!mProviderInfosUpdateScheduled) {
+                mProviderInfosUpdateScheduled = true;
+                sendMessage(PooledLambda.obtainMessage(UserHandler::updateProviderInfos, this));
             }
         }
 
-        private void updateManagerState() {
-            mManagerStateUpdateScheduled = false;
+        private void updateProviderInfos() {
+            mProviderInfosUpdateScheduled = false;
 
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
                 return;
             }
-            //TODO: Consider using a member variable (like mTempManagers).
+            final List<IMediaRouter2Manager> managers = new ArrayList<>();
+            final List<IMediaRouter2Client> clients = new ArrayList<>();
             final List<MediaRoute2ProviderInfo> providers = new ArrayList<>();
             for (MediaRoute2ProviderProxy mediaProvider : mMediaProviders) {
                 final MediaRoute2ProviderInfo providerInfo =
                         mediaProvider.getProviderInfo();
                 if (providerInfo == null || !providerInfo.isValid()) {
-                    Log.w(TAG, "Ignoring invalid provider info : " + providerInfo);
+                    Slog.w(TAG, "Ignoring invalid provider info : " + providerInfo);
                 } else {
                     providers.add(providerInfo);
                 }
             }
+            mProviderInfos = providers;
 
+            synchronized (service.mLock) {
+                for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
+                    managers.add(managerRecord.mManager);
+                }
+                for (ClientRecord clientRecord : mUserRecord.mClientRecords) {
+                    clients.add(clientRecord.mClient);
+                }
+            }
+            for (IMediaRouter2Manager manager : managers) {
+                notifyProviderInfosUpdatedToManager(manager);
+            }
+            for (IMediaRouter2Client client : clients) {
+                notifyProviderInfosUpdatedToClient(client);
+            }
+        }
+
+        private void notifyProviderInfosUpdatedToClient(IMediaRouter2Client client) {
+            if (mProviderInfos == null) {
+                scheduleUpdateProviderInfos();
+                return;
+            }
             try {
-                synchronized (service.mLock) {
-                    final int count = mUserRecord.mManagerRecords.size();
-                    for (int i = 0; i < count; i++) {
-                        mTempManagers.add(mUserRecord.mManagerRecords.get(i).mManager);
-                    }
-                }
-                for (IMediaRouter2Manager tempManager : mTempManagers) {
-                    try {
-                        tempManager.notifyProviderInfosUpdated(providers);
-                    } catch (RemoteException ex) {
-                        Slog.w(TAG, "Failed to update manager state. Manager probably died.", ex);
-                    }
-                }
-            } finally {
-                // Clear the list in preparation for the next time.
-                mTempManagers.clear();
+                client.notifyProviderInfosUpdated(mProviderInfos);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify provider infos updated. Client probably died.");
+            }
+        }
+
+        private void notifyProviderInfosUpdatedToManager(IMediaRouter2Manager manager) {
+            if (mProviderInfos == null) {
+                scheduleUpdateProviderInfos();
+                return;
+            }
+            try {
+                manager.notifyProviderInfosUpdated(mProviderInfos);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify provider infos updated. Manager probably died.");
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index a43068b..2670cd8 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -435,12 +435,12 @@
 
     // Binder call
     @Override
-    public void registerClient2AsUser(IMediaRouter2Client client, String packageName, int userId) {
+    public void registerClient2(IMediaRouter2Client client, String packageName) {
         final int uid = Binder.getCallingUid();
         if (!validatePackageName(uid, packageName)) {
             throw new SecurityException("packageName must match the calling uid");
         }
-        mService2.registerClientAsUser(client, packageName, userId);
+        mService2.registerClient(client, packageName);
     }
 
     // Binder call
@@ -464,13 +464,12 @@
 
     // Binder call
     @Override
-    public void registerManagerAsUser(IMediaRouter2Manager manager,
-            String packageName, int userId) {
+    public void registerManager(IMediaRouter2Manager manager, String packageName) {
         final int uid = Binder.getCallingUid();
         if (!validatePackageName(uid, packageName)) {
             throw new SecurityException("packageName must match the calling uid");
         }
-        mService2.registerManagerAsUser(manager, packageName, userId);
+        mService2.registerManager(manager, packageName);
     }
 
     // Binder call