From 7af7301eb8806f3fd309f868785c138fec5909a9 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Fri, 11 Oct 2019 20:01:30 +0900 Subject: [PATCH] MediaRouter2: Revise MediaRouter2.Callback.onRoute** This CL revised callbacks for MediaRouter2 so that it gives a list instead of a single route. This will effectively reduce the number of invokings. This CL also fixes binder call between media router service and media router such that media router service notifies only changes of routes instead of all provider information. Following CLs will update manager callbacks as well. Test: atest mediaroutertest Change-Id: Ibbc23aa5f12f5450d44c81756e3fcefe3e0d7f4b --- .../android/media/IMediaRouter2Client.aidl | 6 +- media/java/android/media/MediaRoute2Info.java | 2 +- media/java/android/media/MediaRouter2.java | 230 +++++++++++------- .../android/media/MediaRouter2Manager.java | 4 +- .../MediaRouterManagerTest.java | 64 +++-- .../media/MediaRoute2ProviderProxy.java | 7 +- .../server/media/MediaRouter2ServiceImpl.java | 175 ++++++++++--- 7 files changed, 334 insertions(+), 154 deletions(-) diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl index 26184af699dba..72c33f9943499 100644 --- a/media/java/android/media/IMediaRouter2Client.aidl +++ b/media/java/android/media/IMediaRouter2Client.aidl @@ -16,12 +16,14 @@ package android.media; -import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRoute2Info; /** * @hide */ oneway interface IMediaRouter2Client { void notifyRestoreRoute(); - void notifyProviderInfosUpdated(in List providers); + void notifyRoutesAdded(in List routes); + void notifyRoutesRemoved(in List routes); + void notifyRoutesChanged(in List routes); } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 59bd96ffb1bd9..457ccb788d0d3 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -314,7 +314,7 @@ public final class MediaRoute2Info implements Parcelable { List mSupportedCategories; int mVolume; int mVolumeMax; - int mVolumeHandling; + int mVolumeHandling = PLAYBACK_VOLUME_FIXED; Bundle mExtras; public Builder(@NonNull String id, @NonNull String name) { diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index b52e2d647e5a7..ce18ab3b3c83c 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -33,7 +33,9 @@ import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; @@ -51,23 +53,23 @@ public class MediaRouter2 { @GuardedBy("sLock") private static MediaRouter2 sInstance; - private Context mContext; + private final Context mContext; private final IMediaRouterService mMediaRouterService; private final CopyOnWriteArrayList mCallbackRecords = new CopyOnWriteArrayList<>(); - @GuardedBy("sLock") - private List mControlCategories = Collections.emptyList(); + + private final String mPackageName; + private final Map mRoutes = new HashMap<>(); + + private volatile List mControlCategories = Collections.emptyList(); + + private MediaRoute2Info mSelectedRoute; @GuardedBy("sLock") private Client mClient; - private final String mPackageName; final Handler mHandler; - - List mProviders = Collections.emptyList(); - volatile List mRoutes = Collections.emptyList(); - - MediaRoute2Info mSelectedRoute; + volatile List mFilteredRoutes = Collections.emptyList(); /** * Gets an instance of the media router associated with the context. @@ -137,6 +139,7 @@ public class MediaRouter2 { } } } + //TODO: Is it thread-safe? record.notifyRoutes(); //TODO: Update discovery request here. @@ -181,31 +184,22 @@ public class MediaRouter2 { public void setControlCategories(@NonNull Collection controlCategories) { Objects.requireNonNull(controlCategories, "control categories must not be null"); - Client client; - List newControlCategories = new ArrayList<>(controlCategories); - synchronized (sLock) { - mControlCategories = newControlCategories; - client = mClient; - } - if (client != null) { - try { - mMediaRouterService.setControlCategories2(client, newControlCategories); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to set control categories.", ex); - } - } - mHandler.sendMessage(obtainMessage(MediaRouter2::refreshAndNotifyRoutes, this)); + // To ensure invoking callbacks correctly according to control categories + mHandler.sendMessage(obtainMessage(MediaRouter2::setControlCategoriesOnHandler, + MediaRouter2.this, new ArrayList<>(controlCategories))); } + /** - * Gets the list of {@link MediaRoute2Info routes} currently known to the media router. + * Gets the unmodifiable 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 getRoutes() { - return mRoutes; + return mFilteredRoutes; } /** @@ -326,102 +320,145 @@ public class MediaRouter2 { return -1; } - void onProviderInfosUpdated(List providers) { - if (providers == null) { - Log.w(TAG, "Providers info is null."); - return; - } + private void setControlCategoriesOnHandler(List newControlCategories) { + List prevControlCategories = mControlCategories; + List addedRoutes = new ArrayList<>(); + List removedRoutes = new ArrayList<>(); + List filteredRoutes = new ArrayList<>(); - mProviders = providers; - refreshAndNotifyRoutes(); - } - - void refreshAndNotifyRoutes() { - ArrayList routes = new ArrayList<>(); - - List controlCategories; + mControlCategories = newControlCategories; + Client client; synchronized (sLock) { - controlCategories = mControlCategories; + client = mClient; + } + if (client != null) { + try { + mMediaRouterService.setControlCategories2(client, mControlCategories); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to set control categories.", ex); + } } - for (MediaRoute2ProviderInfo provider : mProviders) { - updateProvider(provider, controlCategories, routes); + for (MediaRoute2Info route : mRoutes.values()) { + boolean preSupported = route.supportsControlCategory(prevControlCategories); + boolean postSupported = route.supportsControlCategory(newControlCategories); + if (postSupported) { + filteredRoutes.add(route); + } + if (preSupported == postSupported) { + continue; + } + if (preSupported) { + removedRoutes.add(route); + } else { + addedRoutes.add(route); + } } + mFilteredRoutes = Collections.unmodifiableList(filteredRoutes); - //TODO: Can orders be changed? - if (!Objects.equals(mRoutes, routes)) { - mRoutes = Collections.unmodifiableList(routes); - notifyRouteListChanged(mRoutes); + if (removedRoutes.size() > 0) { + notifyRoutesRemoved(removedRoutes); + } + if (addedRoutes.size() > 0) { + notifyRoutesAdded(addedRoutes); } } - void updateProvider(MediaRoute2ProviderInfo provider, List controlCategories, - List outRoutes) { - if (provider == null || !provider.isValid()) { - Log.w(TAG, "Ignoring invalid provider : " + provider); - return; - } - - final Collection routes = provider.getRoutes(); + void addRoutesOnHandler(List routes) { + List addedRoutes = new ArrayList<>(); for (MediaRoute2Info route : routes) { - if (!route.isValid()) { - Log.w(TAG, "Ignoring invalid route : " + route); - continue; + mRoutes.put(route.getUniqueId(), route); + if (route.supportsControlCategory(mControlCategories)) { + addedRoutes.add(route); } - if (!route.supportsControlCategory(controlCategories)) { - continue; - } - MediaRoute2Info preRoute = findRouteById(route.getId()); - if (!route.equals(preRoute)) { - notifyRouteChanged(route); - } - outRoutes.add(route); + } + if (addedRoutes.size() > 0) { + refreshFilteredRoutes(); + notifyRoutesAdded(addedRoutes); } } - MediaRoute2Info findRouteById(String id) { - for (MediaRoute2Info route : mRoutes) { - if (route.getId().equals(id)) return route; + void removeRoutesOnHandler(List routes) { + List removedRoutes = new ArrayList<>(); + for (MediaRoute2Info route : routes) { + mRoutes.remove(route.getUniqueId()); + if (route.supportsControlCategory(mControlCategories)) { + removedRoutes.add(route); + } + } + if (removedRoutes.size() > 0) { + refreshFilteredRoutes(); + notifyRoutesRemoved(removedRoutes); } - return null; } - void notifyRouteListChanged(List routes) { + void changeRoutesOnHandler(List routes) { + List changedRoutes = new ArrayList<>(); + for (MediaRoute2Info route : routes) { + mRoutes.put(route.getUniqueId(), route); + if (route.supportsControlCategory(mControlCategories)) { + changedRoutes.add(route); + } + } + if (changedRoutes.size() > 0) { + refreshFilteredRoutes(); + notifyRoutesChanged(changedRoutes); + } + } + + private void refreshFilteredRoutes() { + List filteredRoutes = new ArrayList<>(); + + for (MediaRoute2Info route : mRoutes.values()) { + if (route.supportsControlCategory(mControlCategories)) { + filteredRoutes.add(route); + } + } + mFilteredRoutes = Collections.unmodifiableList(filteredRoutes); + } + + private void notifyRoutesAdded(List routes) { + for (CallbackRecord record: mCallbackRecords) { + record.mExecutor.execute( + () -> record.mCallback.onRoutesAdded(routes)); + } + } + + private void notifyRoutesRemoved(List routes) { + for (CallbackRecord record: mCallbackRecords) { + record.mExecutor.execute( + () -> record.mCallback.onRoutesRemoved(routes)); + } + } + + private void notifyRoutesChanged(List routes) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( () -> record.mCallback.onRoutesChanged(routes)); } } - void notifyRouteChanged(MediaRoute2Info route) { - for (CallbackRecord record: mCallbackRecords) { - record.mExecutor.execute( - () -> record.mCallback.onRouteChanged(route)); - } - } - /** * Interface for receiving events about media routing changes. */ public static class Callback { //TODO: clean up these callbacks - /** - * Called when a route is added. - */ - public void onRouteAdded(MediaRoute2Info routeInfo) {} /** - * Called when a route is changed. + * Called when routes are added. + * @param routes the list of routes that have been added. It's never empty. */ - public void onRouteChanged(MediaRoute2Info routeInfo) {} + public void onRoutesAdded(@NonNull List routes) {} /** - * Called when a route is removed. + * Called when routes are removed. + * @param routes the list of routes that have been removed. It's never empty. */ - public void onRouteRemoved(MediaRoute2Info routeInfo) {} + public void onRoutesRemoved(@NonNull List routes) {} /** - * Called when the list of routes is changed. + * Called when routes are changed. + * @param routes the list of routes that have been changed. It's never empty. */ public void onRoutesChanged(@NonNull List routes) {} } @@ -436,11 +473,10 @@ public class MediaRouter2 { } void notifyRoutes() { - final List routes = mRoutes; + final List routes = mFilteredRoutes; // notify only when bound to media router service. - //TODO: Correct the condition when control category, default route, .. are finalized. if (routes.size() > 0) { - mExecutor.execute(() -> mCallback.onRoutesChanged(routes)); + mExecutor.execute(() -> mCallback.onRoutesAdded(routes)); } } } @@ -450,9 +486,21 @@ public class MediaRouter2 { public void notifyRestoreRoute() throws RemoteException {} @Override - public void notifyProviderInfosUpdated(List info) { - mHandler.sendMessage(obtainMessage(MediaRouter2::onProviderInfosUpdated, - MediaRouter2.this, info)); + public void notifyRoutesAdded(List routes) { + mHandler.sendMessage(obtainMessage(MediaRouter2::addRoutesOnHandler, + MediaRouter2.this, routes)); + } + + @Override + public void notifyRoutesRemoved(List routes) { + mHandler.sendMessage(obtainMessage(MediaRouter2::removeRoutesOnHandler, + MediaRouter2.this, routes)); + } + + @Override + public void notifyRoutesChanged(List routes) { + mHandler.sendMessage(obtainMessage(MediaRouter2::changeRoutesOnHandler, + MediaRouter2.this, routes)); } } } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 0d7b6ff0ea916..7e848a0bdf906 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -67,7 +67,7 @@ public class MediaRouter2Manager { @NonNull List mRoutes = Collections.emptyList(); @NonNull - ConcurrentMap> mControlCategoryMap = new ConcurrentHashMap<>(); + final ConcurrentMap> mControlCategoryMap = new ConcurrentHashMap<>(); /** * Gets an instance of media router manager that controls media route of other applications. @@ -427,6 +427,8 @@ public class MediaRouter2Manager { * A client may refresh available routes for each application. */ public void onRoutesChanged(@NonNull List routes) {} + + //TODO: add onControlCategoriesChanged to notify available routes are changed } final class CallbackRecord { diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index ca43d04573f3e..acf899881600e 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -22,6 +22,7 @@ import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; @@ -272,29 +273,28 @@ public class MediaRouterManagerTest { @Test public void testControlVolumeWithRouter() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - Map routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL); - mRouter2.registerCallback(mExecutor, mockCallback); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1); - int targetVolume = originalVolume + deltaVolume; - mRouter2.requestSetVolume(volRoute, targetVolume); - verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) - .onRouteChanged(argThat(route -> - route.getId().equals(volRoute.getId()) - && route.getVolume() == targetVolume)); + CountDownLatch latch1 = new CountDownLatch(1); + MediaRouter2.Callback callback1 = + createVolumeChangeCallback(ROUTE_ID_VARIABLE_VOLUME, + originalVolume + deltaVolume, latch1); + mRouter2.registerCallback(mExecutor, callback1); + mRouter2.requestUpdateVolume(volRoute, deltaVolume); + assertTrue(latch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + mRouter2.unregisterCallback(callback1); - mRouter2.requestUpdateVolume(volRoute, -deltaVolume); - verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) - .onRouteChanged(argThat(route -> - route.getId().equals(volRoute.getId()) - && route.getVolume() == originalVolume)); - - mRouter2.unregisterCallback(mockCallback); + CountDownLatch latch2 = new CountDownLatch(1); + MediaRouter2.Callback callback2 = + createVolumeChangeCallback(ROUTE_ID_VARIABLE_VOLUME, originalVolume, latch2); + mRouter2.registerCallback(mExecutor, callback2); + mRouter2.requestSetVolume(volRoute, originalVolume); + assertTrue(latch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + mRouter2.unregisterCallback(callback1); } @Test @@ -312,13 +312,13 @@ public class MediaRouterManagerTest { int targetVolume = originalVolume + deltaVolume; mManager.requestSetVolume(volRoute, targetVolume); - verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) + verify(managerCallback, timeout(TIMEOUT_MS).atLeastOnce()) .onRouteChanged(argThat(route -> route.getId().equals(volRoute.getId()) && route.getVolume() == targetVolume)); mManager.requestUpdateVolume(volRoute, -deltaVolume); - verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) + verify(managerCallback, timeout(TIMEOUT_MS).atLeastOnce()) .onRouteChanged(argThat(route -> route.getId().equals(volRoute.getId()) && route.getVolume() == originalVolume)); @@ -347,14 +347,14 @@ public class MediaRouterManagerTest { CountDownLatch latch = new CountDownLatch(1); MediaRouter2.Callback callback = new MediaRouter2.Callback() { @Override - public void onRoutesChanged(List routes) { - if (routes.size() > 0) latch.countDown(); + public void onRoutesAdded(List added) { + if (added.size() > 0) latch.countDown(); } }; mRouter2.setControlCategories(controlCategories); mRouter2.registerCallback(mExecutor, callback); try { - latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); return createRouteMap(mRouter2.getRoutes()); } finally { mRouter2.unregisterCallback(callback); @@ -370,14 +370,18 @@ public class MediaRouterManagerTest { MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() { @Override public void onRoutesChanged(List routes) { - if (routes.size() > 0) latch.countDown(); + if (routes.size() > 0) { + latch.countDown(); + } } }; mManager.registerCallback(mExecutor, managerCallback); mRouter2.setControlCategories(controlCategories); mRouter2.registerCallback(mExecutor, routerCallback); try { - latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + //TODO: currently this returns empty list occasionally. + //Maybe due to control category is not set yet return createRouteMap(mManager.getAvailableRoutes(mPackageName)); } finally { mRouter2.unregisterCallback(routerCallback); @@ -385,6 +389,20 @@ public class MediaRouterManagerTest { } } + MediaRouter2.Callback createVolumeChangeCallback(String routeId, + int targetVolume, CountDownLatch latch) { + MediaRouter2.Callback callback = new MediaRouter2.Callback() { + @Override + public void onRoutesChanged(List changed) { + MediaRoute2Info volRoute = createRouteMap(changed).get(routeId); + if (volRoute != null && volRoute.getVolume() == targetVolume) { + latch.countDown(); + } + } + }; + return callback; + } + // Helper for getting routes easily static Map createRouteMap(List routes) { Map routeMap = new HashMap<>(); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 4816ceb5d76c2..626bf1cc361be 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -119,6 +119,11 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { } } + @NonNull + public String getUniqueId() { + return mUniqueId; + } + @Nullable public MediaRoute2ProviderInfo getProviderInfo() { return mProviderInfo; @@ -294,7 +299,7 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { } public interface Callback { - void onProviderStateChanged(MediaRoute2ProviderProxy provider); + void onProviderStateChanged(@NonNull MediaRoute2ProviderProxy provider); } private final class Connection implements DeathRecipient { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 361dc36736650..44642d4f85cda 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -47,9 +47,12 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** * TODO: Merge this to MediaRouterService once it's finished. @@ -357,8 +360,7 @@ class MediaRouter2ServiceImpl { mAllClientRecords.put(binder, clientRecord); userRecord.mHandler.sendMessage( - obtainMessage(UserHandler::notifyProviderInfosUpdatedToClient, - userRecord.mHandler, client)); + obtainMessage(UserHandler::notifyRoutesToClient, userRecord.mHandler, client)); } } @@ -586,6 +588,7 @@ class MediaRouter2ServiceImpl { final class UserRecord { public final int mUserId; + //TODO: make records private for thread-safety final ArrayList mClientRecords = new ArrayList<>(); final ArrayList mManagerRecords = new ArrayList<>(); final UserHandler mHandler; @@ -705,7 +708,7 @@ class MediaRouter2ServiceImpl { //TODO: Make this thread-safe. private final ArrayList mMediaProviders = new ArrayList<>(); - private List mProviderInfos; + private final List mProviderInfos = new ArrayList<>(); private boolean mRunning; private boolean mProviderInfosUpdateScheduled; @@ -745,14 +748,89 @@ class MediaRouter2ServiceImpl { } @Override - public void onProviderStateChanged(MediaRoute2ProviderProxy provider) { - updateProvider(provider); + public void onProviderStateChanged(@NonNull MediaRoute2ProviderProxy provider) { + sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider)); } private void updateProvider(MediaRoute2ProviderProxy provider) { + int providerIndex = getProviderInfoIndex(provider.getUniqueId()); + MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo(); + MediaRoute2ProviderInfo prevInfo = + (providerIndex < 0) ? null : mProviderInfos.get(providerIndex); + + if (Objects.equals(prevInfo, providerInfo)) return; + + if (prevInfo == null) { + mProviderInfos.add(providerInfo); + Collection addedRoutes = providerInfo.getRoutes(); + if (addedRoutes.size() > 0) { + sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToClients, + this, getClients(), new ArrayList<>(addedRoutes))); + } + } else if (providerInfo == null) { + mProviderInfos.remove(prevInfo); + Collection removedRoutes = prevInfo.getRoutes(); + if (removedRoutes.size() > 0) { + sendMessage(PooledLambda.obtainMessage( + UserHandler::notifyRoutesRemovedToClients, + this, getClients(), new ArrayList<>(removedRoutes))); + } + } else { + mProviderInfos.set(providerIndex, providerInfo); + List addedRoutes = new ArrayList<>(); + List removedRoutes = new ArrayList<>(); + List changedRoutes = new ArrayList<>(); + + final Collection currentRoutes = providerInfo.getRoutes(); + final Set updatedRouteIds = new HashSet<>(); + + for (MediaRoute2Info route : currentRoutes) { + if (!route.isValid()) { + Slog.w(TAG, "Ignoring invalid route : " + route); + continue; + } + MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId()); + + if (prevRoute != null) { + if (!Objects.equals(prevRoute, route)) { + changedRoutes.add(route); + } + updatedRouteIds.add(route.getId()); + } else { + addedRoutes.add(route); + } + } + + for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) { + if (!updatedRouteIds.contains(prevRoute.getId())) { + removedRoutes.add(prevRoute); + } + } + + List clients = getClients(); + if (addedRoutes.size() > 0) { + notifyRoutesAddedToClients(clients, addedRoutes); + } + if (removedRoutes.size() > 0) { + notifyRoutesRemovedToClients(clients, removedRoutes); + } + if (changedRoutes.size() > 0) { + notifyRoutesChangedToClients(clients, changedRoutes); + } + } scheduleUpdateProviderInfos(); } + private int getProviderInfoIndex(String providerId) { + for (int i = 0; i < mProviderInfos.size(); i++) { + MediaRoute2ProviderInfo providerInfo = mProviderInfos.get(i); + if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) { + return i; + } + } + return -1; + } + private void selectRoute(ClientRecord clientRecord, MediaRoute2Info route) { if (route != null) { MediaRoute2ProviderProxy provider = findProvider(route.getProviderId()); @@ -803,6 +881,7 @@ class MediaRouter2ServiceImpl { } } + //TODO: should be replaced into notifyRoutes...ToManagers private void updateProviderInfos() { mProviderInfosUpdateScheduled = false; @@ -810,55 +889,83 @@ class MediaRouter2ServiceImpl { if (service == null) { return; } - final List providers = new ArrayList<>(); - for (MediaRoute2ProviderProxy mediaProvider : mMediaProviders) { - final MediaRoute2ProviderInfo providerInfo = - mediaProvider.getProviderInfo(); - if (providerInfo == null || !providerInfo.isValid()) { - Slog.w(TAG, "Ignoring invalid provider info : " + providerInfo); - } else { - providers.add(providerInfo); - } - } - mProviderInfos = providers; final List managers = new ArrayList<>(); - final List clients = new ArrayList<>(); synchronized (service.mLock) { for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { managers.add(managerRecord.mManager); } + } + for (IMediaRouter2Manager manager : managers) { + notifyProviderInfosUpdatedToManager(manager); + } + } + + private List getClients() { + final List clients = new ArrayList<>(); + MediaRouter2ServiceImpl service = mServiceRef.get(); + if (service == null) { + return clients; + } + synchronized (service.mLock) { for (ClientRecord clientRecord : mUserRecord.mClientRecords) { if (clientRecord instanceof Client2Record) { clients.add(((Client2Record) clientRecord).mClient); } } } - for (IMediaRouter2Manager manager : managers) { - notifyProviderInfosUpdatedToManager(manager); - } - for (IMediaRouter2Client client : clients) { - notifyProviderInfosUpdatedToClient(client); - } + return clients; } - private void notifyProviderInfosUpdatedToClient(IMediaRouter2Client client) { - if (mProviderInfos == null) { - scheduleUpdateProviderInfos(); + private void notifyRoutesToClient(IMediaRouter2Client client) { + List routes = new ArrayList<>(); + for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) { + routes.addAll(providerInfo.getRoutes()); + } + if (routes.size() == 0) { return; } try { - client.notifyProviderInfosUpdated(mProviderInfos); + client.notifyRoutesAdded(routes); } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify provider infos updated. Client probably died."); + Slog.w(TAG, "Failed to notify all routes. Client probably died.", ex); + } + } + + private void notifyRoutesAddedToClients(List clients, + List routes) { + for (IMediaRouter2Client client : clients) { + try { + client.notifyRoutesAdded(routes); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify routes added. Client probably died.", ex); + } + } + } + + private void notifyRoutesRemovedToClients(List clients, + List routes) { + for (IMediaRouter2Client client : clients) { + try { + client.notifyRoutesRemoved(routes); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify routes removed. Client probably died.", ex); + } + } + } + + private void notifyRoutesChangedToClients(List clients, + List routes) { + for (IMediaRouter2Client client : clients) { + try { + client.notifyRoutesChanged(routes); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify routes changed. Client probably died.", ex); + } } } private void notifyProviderInfosUpdatedToManager(IMediaRouter2Manager manager) { - if (mProviderInfos == null) { - scheduleUpdateProviderInfos(); - return; - } try { manager.notifyProviderInfosUpdated(mProviderInfos); } catch (RemoteException ex) { @@ -891,9 +998,7 @@ class MediaRouter2ServiceImpl { private MediaRoute2ProviderProxy findProvider(String providerId) { for (MediaRoute2ProviderProxy provider : mMediaProviders) { - final MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo(); - if (providerInfo != null - && TextUtils.equals(providerInfo.getUniqueId(), providerId)) { + if (TextUtils.equals(provider.getUniqueId(), providerId)) { return provider; } }