diff --git a/core/api/current.txt b/core/api/current.txt index 1cd8253aac36b..ab877010d2fe1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -23187,9 +23187,10 @@ package android.media { public abstract static class MediaRouter2.RouteCallback { ctor public MediaRouter2.RouteCallback(); - method public void onRoutesAdded(@NonNull java.util.List); - method public void onRoutesChanged(@NonNull java.util.List); - method public void onRoutesRemoved(@NonNull java.util.List); + method @Deprecated public void onRoutesAdded(@NonNull java.util.List); + method @Deprecated public void onRoutesChanged(@NonNull java.util.List); + method @Deprecated public void onRoutesRemoved(@NonNull java.util.List); + method public void onRoutesUpdated(@NonNull java.util.List); } public class MediaRouter2.RoutingController { diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl index fe15f0e67b1dc..29bfd1acae173 100644 --- a/media/java/android/media/IMediaRouter2.aidl +++ b/media/java/android/media/IMediaRouter2.aidl @@ -26,9 +26,7 @@ import android.os.Bundle; oneway interface IMediaRouter2 { void notifyRouterRegistered(in List currentRoutes, in RoutingSessionInfo currentSystemSessionInfo); - void notifyRoutesAdded(in List routes); - void notifyRoutesRemoved(in List routes); - void notifyRoutesChanged(in List routes); + void notifyRoutesUpdated(in List routes); void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo); void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo); void notifySessionReleased(in RoutingSessionInfo sessionInfo); diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index 71dc2a781ba9a..9f3c3ff890321 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -30,8 +30,6 @@ oneway interface IMediaRouter2Manager { void notifySessionReleased(in RoutingSessionInfo session); void notifyDiscoveryPreferenceChanged(String packageName, in RouteDiscoveryPreference discoveryPreference); - void notifyRoutesAdded(in List routes); - void notifyRoutesRemoved(in List routes); - void notifyRoutesChanged(in List routes); + void notifyRoutesUpdated(in List routes); void notifyRequestFailed(int requestId, int reason); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index a7a21e7a20133..26cb9f8e9ee12 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -132,7 +132,7 @@ public final class MediaRouter2 { /** * Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback * dispatch. This is only used to determine what callback a route should be assigned to (added, - * removed, changed) in {@link #dispatchFilteredRoutesChangedLocked(List)}. + * removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}. */ private volatile ArrayMap mPreviousRoutes = new ArrayMap<>(); @@ -820,7 +820,7 @@ public final class MediaRouter2 { } } - void dispatchFilteredRoutesChangedLocked(List newRoutes) { + void dispatchFilteredRoutesUpdatedOnHandler(List newRoutes) { List addedRoutes = new ArrayList<>(); List removedRoutes = new ArrayList<>(); List changedRoutes = new ArrayList<>(); @@ -863,29 +863,16 @@ public final class MediaRouter2 { if (!changedRoutes.isEmpty()) { notifyRoutesChanged(changedRoutes); } - } - void addRoutesOnHandler(List routes) { - synchronized (mLock) { - for (MediaRoute2Info route : routes) { - mRoutes.put(route.getId(), route); - } - updateFilteredRoutesLocked(); + // Note: We don't notify clients of changes in route ordering. + if (!addedRoutes.isEmpty() || !removedRoutes.isEmpty() || !changedRoutes.isEmpty()) { + notifyRoutesUpdated(newRoutes); } } - void removeRoutesOnHandler(List routes) { - synchronized (mLock) { - for (MediaRoute2Info route : routes) { - mRoutes.remove(route.getId()); - } - updateFilteredRoutesLocked(); - } - } - - void changeRoutesOnHandler(List routes) { - List changedRoutes = new ArrayList<>(); + void updateRoutesOnHandler(List routes) { synchronized (mLock) { + mRoutes.clear(); for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); } @@ -900,8 +887,10 @@ public final class MediaRouter2 { Collections.unmodifiableList( filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values()))); mHandler.sendMessage( - obtainMessage(MediaRouter2::dispatchFilteredRoutesChangedLocked, - this, mFilteredRoutes)); + obtainMessage( + MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler, + this, + mFilteredRoutes)); } /** @@ -1211,6 +1200,14 @@ public final class MediaRouter2 { } } + private void notifyRoutesUpdated(List routes) { + for (RouteCallbackRecord record : mRouteCallbackRecords) { + List filteredRoutes = + filterRoutesWithIndividualPreference(routes, record.mPreference); + record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes)); + } + } + private void notifyPreferredFeaturesChanged(List features) { for (RouteCallbackRecord record : mRouteCallbackRecords) { record.mExecutor.execute( @@ -1246,28 +1243,43 @@ public final class MediaRouter2 { /** Callback for receiving events about media route discovery. */ public abstract static class RouteCallback { /** - * Called when routes are added. Whenever you registers a callback, this will be invoked - * with known routes. + * Called when routes are added. Whenever you register a callback, this will be invoked with + * known routes. * * @param routes the list of routes that have been added. It's never empty. + * @deprecated Use {@link #onRoutesUpdated(List)} instead. */ + @Deprecated public void onRoutesAdded(@NonNull List routes) {} /** * Called when routes are removed. * * @param routes the list of routes that have been removed. It's never empty. + * @deprecated Use {@link #onRoutesUpdated(List)} instead. */ + @Deprecated public void onRoutesRemoved(@NonNull List routes) {} /** - * Called when routes are changed. For example, it is called when the route's name or volume - * have been changed. + * Called when the properties of one or more existing routes are changed. For example, it is + * called when a route's name or volume have changed. * * @param routes the list of routes that have been changed. It's never empty. + * @deprecated Use {@link #onRoutesUpdated(List)} instead. */ + @Deprecated public void onRoutesChanged(@NonNull List routes) {} + /** + * Called when the route list is updated, which can happen when routes are added, removed, + * or modified. It will also be called when a route callback is registered. + * + * @param routes the updated list of routes filtered by the callback's individual discovery + * preferences. + */ + public void onRoutesUpdated(@NonNull List routes) {} + /** * Called when the client app's preferred features are changed. When this is called, it is * recommended to {@link #getRoutes()} to get the routes that are currently available to the @@ -1985,21 +1997,9 @@ public final class MediaRouter2 { } @Override - public void notifyRoutesAdded(List routes) { + public void notifyRoutesUpdated(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)); + obtainMessage(MediaRouter2::updateRoutesOnHandler, MediaRouter2.this, routes)); } @Override @@ -2047,17 +2047,7 @@ public final class MediaRouter2 { class ManagerCallback implements MediaRouter2Manager.Callback { @Override - public void onRoutesAdded(@NonNull List routes) { - updateAllRoutesFromManager(); - } - - @Override - public void onRoutesRemoved(@NonNull List routes) { - updateAllRoutesFromManager(); - } - - @Override - public void onRoutesChanged(@NonNull List routes) { + public void onRoutesUpdated() { updateAllRoutesFromManager(); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 44c0b54546bee..8afc7d999d2e1 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -546,37 +546,15 @@ public final class MediaRouter2Manager { } } - void addRoutesOnHandler(List routes) { + void updateRoutesOnHandler(@NonNull List routes) { synchronized (mRoutesLock) { + mRoutes.clear(); for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); } } - if (routes.size() > 0) { - notifyRoutesAdded(routes); - } - } - void removeRoutesOnHandler(List routes) { - synchronized (mRoutesLock) { - for (MediaRoute2Info route : routes) { - mRoutes.remove(route.getId()); - } - } - if (routes.size() > 0) { - notifyRoutesRemoved(routes); - } - } - - void changeRoutesOnHandler(List routes) { - synchronized (mRoutesLock) { - for (MediaRoute2Info route : routes) { - mRoutes.put(route.getId(), route); - } - } - if (routes.size() > 0) { - notifyRoutesChanged(routes); - } + notifyRoutesUpdated(); } void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) { @@ -650,24 +628,9 @@ public final class MediaRouter2Manager { notifySessionUpdated(sessionInfo); } - private void notifyRoutesAdded(List routes) { + private void notifyRoutesUpdated() { 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)); + record.mExecutor.execute(() -> record.mCallback.onRoutesUpdated()); } } @@ -963,23 +926,12 @@ public final class MediaRouter2Manager { * Interface for receiving events about media routing changes. */ public interface Callback { - /** - * Called when routes are added. - * @param routes the list of routes that have been added. It's never empty. - */ - default void onRoutesAdded(@NonNull List routes) {} /** - * Called when routes are removed. - * @param routes the list of routes that have been removed. It's never empty. + * Called when the routes list changes. This includes adding, modifying, or removing + * individual routes. */ - default void onRoutesRemoved(@NonNull List routes) {} - - /** - * Called when routes are changed. - * @param routes the list of routes that have been changed. It's never empty. - */ - default void onRoutesChanged(@NonNull List routes) {} + default void onRoutesUpdated() {} /** * Called when a session is changed. @@ -1115,21 +1067,12 @@ public final class MediaRouter2Manager { } @Override - public void notifyRoutesAdded(List routes) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler, - MediaRouter2Manager.this, routes)); - } - - @Override - public void notifyRoutesRemoved(List routes) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler, - MediaRouter2Manager.this, routes)); - } - - @Override - public void notifyRoutesChanged(List routes) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler, - MediaRouter2Manager.this, routes)); + public void notifyRoutesUpdated(List routes) { + mHandler.sendMessage( + obtainMessage( + MediaRouter2Manager::updateRoutesOnHandler, + MediaRouter2Manager.this, + routes)); } } } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 4086dec99218d..37c836762da07 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -32,7 +32,6 @@ import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_I import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME; import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE; import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME; -import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2; import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX; import static org.junit.Assert.assertEquals; @@ -56,10 +55,10 @@ import android.media.RoutingSessionInfo; import android.os.Bundle; import android.text.TextUtils; -import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.PollingCheck; @@ -69,6 +68,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -115,7 +115,7 @@ public class MediaRouter2ManagerTest { @Before public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL, Manifest.permission.MODIFY_AUDIO_ROUTING); @@ -170,51 +170,95 @@ public class MediaRouter2ManagerTest { } @Test - public void testOnRoutesRemovedAndAdded() throws Exception { - RouteCallback routeCallback = new RouteCallback() {}; - mRouteCallbacks.add(routeCallback); - mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build()); + public void testOnRoutesUpdated() throws Exception { + final String routeId0 = "routeId0"; + final String routeName0 = "routeName0"; + final String routeId1 = "routeId1"; + final String routeName1 = "routeName1"; + final List features = Collections.singletonList("customFeature"); - Map routes = waitAndGetRoutesWithManager(FEATURES_ALL); + final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED; + + final List routes = new ArrayList<>(); + routes.add(new MediaRoute2Info.Builder(routeId0, routeName0).addFeatures(features).build()); + routes.add(new MediaRoute2Info.Builder(routeId1, routeName1).addFeatures(features).build()); - CountDownLatch removedLatch = new CountDownLatch(1); CountDownLatch addedLatch = new CountDownLatch(1); + CountDownLatch changedLatch = new CountDownLatch(1); + CountDownLatch removedLatch = new CountDownLatch(1); - addManagerCallback(new MediaRouter2Manager.Callback() { - @Override - public void onRoutesRemoved(List routes) { - assertTrue(routes.size() > 0); - for (MediaRoute2Info route : routes) { - if (route.getOriginalId().equals(ROUTE_ID2) - && route.getName().equals(ROUTE_NAME2)) { - removedLatch.countDown(); + addManagerCallback( + new MediaRouter2Manager.Callback() { + @Override + public void onRoutesUpdated() { + if (addedLatch.getCount() == 1 + && checkRoutesMatch(mManager.getAllRoutes(), routes)) { + addedLatch.countDown(); + } else if (changedLatch.getCount() == 1 + && checkRoutesMatch( + mManager.getAllRoutes(), routes.subList(1, 2))) { + changedLatch.countDown(); + } else if (removedLatch.getCount() == 1 + && checkRoutesRemoved(mManager.getAllRoutes(), routes)) { + removedLatch.countDown(); + } } - } + }); + + mService.addRoutes(routes); + assertTrue( + "Added routes not found or onRoutesUpdated() never called.", + addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + MediaRoute2Info newRoute2 = + new MediaRoute2Info.Builder(routes.get(1)) + .setConnectionState(newConnectionState) + .build(); + routes.set(1, newRoute2); + mService.addRoute(routes.get(1)); + assertTrue( + "Modified route not found or onRoutesUpdated() never called.", + changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + List routeIds = new ArrayList<>(); + routeIds.add(routeId0); + routeIds.add(routeId1); + + mService.removeRoutes(routeIds); + assertTrue( + "Removed routes not found or onRoutesUpdated() never called.", + removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + private static boolean checkRoutesMatch( + List routesReceived, List expectedRoutes) { + for (MediaRoute2Info expectedRoute : expectedRoutes) { + MediaRoute2Info matchingRoute = + routesReceived.stream() + .filter(r -> r.getOriginalId().equals(expectedRoute.getOriginalId())) + .findFirst() + .orElse(null); + + if (matchingRoute == null) { + return false; } - @Override - public void onRoutesAdded(List routes) { - assertTrue(routes.size() > 0); - if (removedLatch.getCount() > 0) { - return; - } - for (MediaRoute2Info route : routes) { - if (route.getOriginalId().equals(ROUTE_ID2) - && route.getName().equals(ROUTE_NAME2)) { - addedLatch.countDown(); - } - } + assertTrue(TextUtils.equals(expectedRoute.getName(), matchingRoute.getName())); + assertEquals(expectedRoute.getFeatures(), matchingRoute.getFeatures()); + assertEquals(expectedRoute.getConnectionState(), matchingRoute.getConnectionState()); + } + + return true; + } + + private static boolean checkRoutesRemoved( + List routesReceived, List routesRemoved) { + for (MediaRoute2Info removedRoute : routesRemoved) { + if (routesReceived.stream() + .anyMatch(r -> r.getOriginalId().equals(removedRoute.getOriginalId()))) { + return false; } - }); - - MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2); - assertNotNull(routeToRemove); - - mService.removeRoute(ROUTE_ID2); - assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - - mService.addRoute(routeToRemove); - assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + return true; } @Test @@ -874,28 +918,31 @@ public class MediaRouter2ManagerTest { // A dummy callback is required to send route feature info. RouteCallback routeCallback = new RouteCallback() {}; - MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() { - @Override - public void onRoutesAdded(List routes) { - for (MediaRoute2Info route : routes) { - if (!route.isSystemRoute() - && hasMatchingFeature(route.getFeatures(), preference - .getPreferredFeatures())) { - addedLatch.countDown(); - break; + MediaRouter2Manager.Callback managerCallback = + new MediaRouter2Manager.Callback() { + @Override + public void onRoutesUpdated() { + List routes = mManager.getAllRoutes(); + for (MediaRoute2Info route : routes) { + if (!route.isSystemRoute() + && hasMatchingFeature( + route.getFeatures(), + preference.getPreferredFeatures())) { + addedLatch.countDown(); + break; + } + } } - } - } - @Override - public void onDiscoveryPreferenceChanged(String packageName, - RouteDiscoveryPreference discoveryPreference) { - if (TextUtils.equals(mPackageName, packageName) - && Objects.equals(preference, discoveryPreference)) { - preferenceLatch.countDown(); - } - } - }; + @Override + public void onDiscoveryPreferenceChanged( + String packageName, RouteDiscoveryPreference discoveryPreference) { + if (TextUtils.equals(mPackageName, packageName) + && Objects.equals(preference, discoveryPreference)) { + preferenceLatch.countDown(); + } + } + }; mManager.registerCallback(mExecutor, managerCallback); mRouter2.registerRouteCallback(mExecutor, routeCallback, preference); @@ -923,15 +970,17 @@ public class MediaRouter2ManagerTest { void awaitOnRouteChangedManager(Runnable task, String routeId, Predicate predicate) throws Exception { CountDownLatch latch = new CountDownLatch(1); - MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() { - @Override - public void onRoutesChanged(List changed) { - MediaRoute2Info route = createRouteMap(changed).get(routeId); - if (route != null && predicate.test(route)) { - latch.countDown(); - } - } - }; + MediaRouter2Manager.Callback callback = + new MediaRouter2Manager.Callback() { + @Override + public void onRoutesUpdated() { + MediaRoute2Info route = + createRouteMap(mManager.getAllRoutes()).get(routeId); + if (route != null && predicate.test(route)) { + latch.countDown(); + } + } + }; mManager.registerCallback(mExecutor, callback); try { task.run(); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java index a51e3714b6f7e..a7ae5f45b7952 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java @@ -30,7 +30,9 @@ import android.os.Bundle; import android.os.IBinder; import android.text.TextUtils; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -146,19 +148,44 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService { * they have the same route id. */ public void addRoute(@NonNull MediaRoute2Info route) { - Objects.requireNonNull(route, "route must not be null"); - mRoutes.put(route.getOriginalId(), route); - publishRoutes(); + addRoutes(Collections.singletonList(route)); } /** - * Removes a route and publishes it. + * Adds a list of routes and publishes it. It will replace existing routes with matching ids. + * + * @param routes list of routes to be added. */ + public void addRoutes(@NonNull List routes) { + Objects.requireNonNull(routes, "Routes must not be null."); + for (MediaRoute2Info route : routes) { + Objects.requireNonNull(route, "Route must not be null"); + mRoutes.put(route.getOriginalId(), route); + } + publishRoutes(); + } + + /** Removes a route and publishes it. */ public void removeRoute(@NonNull String routeId) { - Objects.requireNonNull(routeId, "routeId must not be null"); - MediaRoute2Info route = mRoutes.get(routeId); - if (route != null) { - mRoutes.remove(routeId); + removeRoutes(Collections.singletonList(routeId)); + } + + /** + * Removes a list of routes and publishes the changes. + * + * @param routes list of route ids to be removed. + */ + public void removeRoutes(@NonNull List routes) { + Objects.requireNonNull(routes, "Routes must not be null"); + boolean hasRemovedRoutes = false; + for (String routeId : routes) { + MediaRoute2Info route = mRoutes.get(routeId); + if (route != null) { + mRoutes.remove(routeId); + hasRemovedRoutes = true; + } + } + if (hasRemovedRoutes) { publishRoutes(); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index d9262cce3cb99..766c036d521c6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -528,7 +528,7 @@ public class InfoMediaManager extends MediaManager { class RouterManagerCallback implements MediaRouter2Manager.Callback { @Override - public void onRoutesAdded(List routes) { + public void onRoutesUpdated() { refreshDevices(); } @@ -539,16 +539,6 @@ public class InfoMediaManager extends MediaManager { } } - @Override - public void onRoutesChanged(List routes) { - refreshDevices(); - } - - @Override - public void onRoutesRemoved(List routes) { - refreshDevices(); - } - @Override public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) { if (DEBUG) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index ee7b7d6b180ff..f4af6e852580e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -112,7 +112,7 @@ public class InfoMediaManagerTest { final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); assertThat(mediaDevice).isNull(); - mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); @@ -135,7 +135,7 @@ public class InfoMediaManagerTest { assertThat(mediaDevice).isNull(); mInfoMediaManager.mPackageName = ""; - mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); @@ -199,7 +199,7 @@ public class InfoMediaManagerTest { final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); assertThat(mediaDevice).isNull(); - mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); @@ -222,7 +222,7 @@ public class InfoMediaManagerTest { assertThat(mediaDevice).isNull(); mInfoMediaManager.mPackageName = ""; - mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); @@ -263,7 +263,7 @@ public class InfoMediaManagerTest { final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); assertThat(mediaDevice).isNull(); - mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); @@ -286,7 +286,7 @@ public class InfoMediaManagerTest { assertThat(mediaDevice).isNull(); mInfoMediaManager.mPackageName = ""; - mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index e27cbeaab139f..bfa8af957208e 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -927,8 +927,9 @@ class MediaRouter2ServiceImpl { routerRecord.mUserRecord.mHandler, routerRecord, manager)); } - userRecord.mHandler.sendMessage(obtainMessage(UserHandler::notifyRoutesToManager, - userRecord.mHandler, manager)); + userRecord.mHandler.sendMessage( + obtainMessage( + UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager)); } private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) { @@ -1311,6 +1312,36 @@ class MediaRouter2ServiceImpl { new CopyOnWriteArrayList<>(); private final Map mSessionToRouterMap = new ArrayMap<>(); + /** + * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and + * {@link android.media.MediaRouter2Manager managers}. + * + *

Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2} + * that have {@code MODIFY_AUDIO_ROUTING} permission. + * + *

This list contains all routes exposed by route providers. This includes routes from + * both system route providers and user route providers. + * + *

See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}. + */ + private final Map mLastNotifiedRoutesToPrivilegedRouters = + new ArrayMap<>(); + + /** + * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}. + * + *

Non-privileged routers are instances of {@link android.media.MediaRouter2 + * MediaRouter2} that do not have {@code MODIFY_AUDIO_ROUTING} permission. + * + *

This list contains all routes exposed by user route providers. It might also include + * the current default route from {@link #mSystemProvider} to expose local route updates + * (e.g. volume changes) to non-privileged routers. + * + *

See {@link SystemMediaRoute2Provider#mDefaultRoute}. + */ + private final Map mLastNotifiedRoutesToNonPrivilegedRouters = + new ArrayMap<>(); + private boolean mRunning; // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler. @@ -1425,91 +1456,182 @@ class MediaRouter2ServiceImpl { } private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) { - int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId()); MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo(); - MediaRoute2ProviderInfo prevInfo = - (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex); - if (Objects.equals(prevInfo, currentInfo)) return; - List addedRoutes = new ArrayList<>(); - List removedRoutes = new ArrayList<>(); - List changedRoutes = new ArrayList<>(); + int providerInfoIndex = + indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos); + + MediaRoute2ProviderInfo prevInfo = + providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex); + + // Ignore if no changes + if (Objects.equals(prevInfo, currentInfo)) { + return; + } + + boolean hasAddedOrModifiedRoutes = false; + boolean hasRemovedRoutes = false; + + boolean isSystemProvider = provider.mIsSystemRouteProvider; + if (prevInfo == null) { + // Provider is being added. mLastProviderInfos.add(currentInfo); - addedRoutes.addAll(currentInfo.getRoutes()); + addToRoutesMap(currentInfo.getRoutes(), isSystemProvider); + // Check if new provider exposes routes. + hasAddedOrModifiedRoutes = !currentInfo.getRoutes().isEmpty(); } else if (currentInfo == null) { + // Provider is being removed. + hasRemovedRoutes = true; mLastProviderInfos.remove(prevInfo); - removedRoutes.addAll(prevInfo.getRoutes()); + removeFromRoutesMap(prevInfo.getRoutes(), isSystemProvider); } else { + // Provider is being updated. mLastProviderInfos.set(providerInfoIndex, currentInfo); - final Collection prevRoutes = prevInfo.getRoutes(); final Collection currentRoutes = currentInfo.getRoutes(); + // Checking for individual routes. for (MediaRoute2Info route : currentRoutes) { if (!route.isValid()) { - Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : " - + route); + Slog.w( + TAG, + "onProviderStateChangedOnHandler: Ignoring invalid route : " + + route); continue; } + MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId()); - if (prevRoute == null) { - addedRoutes.add(route); - } else if (!Objects.equals(prevRoute, route)) { - changedRoutes.add(route); + if (prevRoute == null || !Objects.equals(prevRoute, route)) { + hasAddedOrModifiedRoutes = true; + mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route); + if (!isSystemProvider) { + mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route); + } } } + // Checking for individual removals for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) { if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) { - removedRoutes.add(prevRoute); + hasRemovedRoutes = true; + mLastNotifiedRoutesToPrivilegedRouters.remove(prevRoute.getId()); + if (!isSystemProvider) { + mLastNotifiedRoutesToNonPrivilegedRouters.remove(prevRoute.getId()); + } } } } + dispatchUpdates( + hasAddedOrModifiedRoutes, + hasRemovedRoutes, + isSystemProvider, + mSystemProvider.getDefaultRoute()); + } + + /** + * Adds provided routes to {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also adds them + * to {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were provided by a + * non-system route provider. Overwrites any route with matching id that already exists. + * + * @param routes list of routes to be added. + * @param isSystemRoutes indicates whether routes come from a system route provider. + */ + private void addToRoutesMap( + @NonNull Collection routes, boolean isSystemRoutes) { + for (MediaRoute2Info route : routes) { + if (!isSystemRoutes) { + mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route); + } + mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route); + } + } + + /** + * Removes provided routes from {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also + * removes them from {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were + * provided by a non-system route provider. + * + * @param routes list of routes to be removed. + * @param isSystemRoutes whether routes come from a system route provider. + */ + private void removeFromRoutesMap( + @NonNull Collection routes, boolean isSystemRoutes) { + for (MediaRoute2Info route : routes) { + if (!isSystemRoutes) { + mLastNotifiedRoutesToNonPrivilegedRouters.remove(route.getId()); + } + mLastNotifiedRoutesToPrivilegedRouters.remove(route.getId()); + } + } + + /** + * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters} + * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link + * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call + * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes + * were made. + * + * @param hasAddedOrModifiedRoutes whether routes were added or modified. + * @param hasRemovedRoutes whether routes were removed. + * @param isSystemProvider whether the latest update was caused by a system provider. + * @param defaultRoute the current default route in {@link #mSystemProvider}. + */ + private void dispatchUpdates( + boolean hasAddedOrModifiedRoutes, + boolean hasRemovedRoutes, + boolean isSystemProvider, + MediaRoute2Info defaultRoute) { + + // Ignore if no changes. + if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) { + return; + } + List routersWithModifyAudioRoutingPermission = getRouters(true); List routersWithoutModifyAudioRoutingPermission = getRouters(false); List managers = getManagers(); - List defaultRoute = new ArrayList<>(); - defaultRoute.add(mSystemProvider.getDefaultRoute()); - if (addedRoutes.size() > 0) { - notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes); - if (!provider.mIsSystemRouteProvider) { - notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission, - addedRoutes); - } else if (prevInfo == null) { - notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission, - defaultRoute); - } // 'else' is handled as changed routes - notifyRoutesAddedToManagers(managers, addedRoutes); - } - if (removedRoutes.size() > 0) { - notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission, - removedRoutes); - if (!provider.mIsSystemRouteProvider) { - notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission, - removedRoutes); - } - notifyRoutesRemovedToManagers(managers, removedRoutes); - } - if (changedRoutes.size() > 0) { - notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission, - changedRoutes); - if (!provider.mIsSystemRouteProvider) { - notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission, - changedRoutes); - } else if (prevInfo != null) { - notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission, - defaultRoute); - } // 'else' is handled as added routes - notifyRoutesChangedToManagers(managers, changedRoutes); + // Managers receive all provider updates with all routes. + notifyRoutesUpdatedToManagers( + managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values())); + + // Routers with modify audio permission (usually system routers) receive all provider + // updates with all routes. + notifyRoutesUpdatedToRouters( + routersWithModifyAudioRoutingPermission, + new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values())); + + if (!isSystemProvider) { + // Regular routers receive updates from all non-system providers with all non-system + // routes. + notifyRoutesUpdatedToRouters( + routersWithoutModifyAudioRoutingPermission, + new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values())); + } else if (hasAddedOrModifiedRoutes) { + // On system provider updates, regular routers receive the updated default route. + // This is the only system route they should receive. + mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute); + notifyRoutesUpdatedToRouters( + routersWithoutModifyAudioRoutingPermission, + new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values())); } } - private int getLastProviderInfoIndex(@NonNull String providerId) { - for (int i = 0; i < mLastProviderInfos.size(); i++) { - MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i); - if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) { + /** + * Returns the index of the first element in {@code lastProviderInfos} that matches the + * specified unique id. + * + * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found. + * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}. + * @return index of found element, or -1 if not found. + */ + private static int indexOfRouteProviderInfoByUniqueId( + @NonNull String uniqueId, + @NonNull List lastProviderInfos) { + for (int i = 0; i < lastProviderInfos.size(); i++) { + MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i); + if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) { return i; } } @@ -1989,41 +2111,19 @@ class MediaRouter2ServiceImpl { } } - private void notifyRoutesAddedToRouters(@NonNull List routers, - @NonNull List routes) { + private void notifyRoutesUpdatedToRouters( + @NonNull List routers, @NonNull List routes) { for (IMediaRouter2 router : routers) { try { - router.notifyRoutesAdded(routes); + router.notifyRoutesUpdated(routes); } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify routes added. Router probably died.", ex); + Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex); } } } - private void notifyRoutesRemovedToRouters(@NonNull List routers, - @NonNull List routes) { - for (IMediaRouter2 router : routers) { - try { - router.notifyRoutesRemoved(routes); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify routes removed. Router probably died.", ex); - } - } - } - - private void notifyRoutesChangedToRouters(@NonNull List routers, - @NonNull List routes) { - for (IMediaRouter2 router : routers) { - try { - router.notifyRoutesChanged(routes); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify routes changed. Router probably died.", ex); - } - } - } - - private void notifySessionInfoChangedToRouters(@NonNull List routers, - @NonNull RoutingSessionInfo sessionInfo) { + private void notifySessionInfoChangedToRouters( + @NonNull List routers, @NonNull RoutingSessionInfo sessionInfo) { for (IMediaRouter2 router : routers) { try { router.notifySessionInfoChanged(sessionInfo); @@ -2033,48 +2133,31 @@ class MediaRouter2ServiceImpl { } } - private void notifyRoutesToManager(@NonNull IMediaRouter2Manager manager) { - List routes = new ArrayList<>(); - for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) { - routes.addAll(providerInfo.getRoutes()); - } - if (routes.size() == 0) { + /** + * Notifies {@code manager} with all known routes. This only happens once after {@code + * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String) + * registerManager()}. + * + * @param manager {@link IMediaRouter2Manager} to be notified. + */ + private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) { + if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) { return; } try { - manager.notifyRoutesAdded(routes); + manager.notifyRoutesUpdated( + new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values())); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex); } } - private void notifyRoutesAddedToManagers(@NonNull List managers, + private void notifyRoutesUpdatedToManagers( + @NonNull List managers, @NonNull List routes) { for (IMediaRouter2Manager manager : managers) { try { - manager.notifyRoutesAdded(routes); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex); - } - } - } - - private void notifyRoutesRemovedToManagers(@NonNull List managers, - @NonNull List routes) { - for (IMediaRouter2Manager manager : managers) { - try { - manager.notifyRoutesRemoved(routes); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex); - } - } - } - - private void notifyRoutesChangedToManagers(@NonNull List managers, - @NonNull List routes) { - for (IMediaRouter2Manager manager : managers) { - try { - manager.notifyRoutesChanged(routes); + manager.notifyRoutesUpdated(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex); }