Add MediaRouter2 API atomic route callbacks

Improve and add support for atomic route callbacks on the MediaRouter2
API, from MediaRouter2ServiceImpl to MediaRouter2 and
MediaRouter2Manager.

Bug: 235829292
Test: atest CtsMediaBetterTogetherTestCases mediaroutertest

Change-Id: I6a0fb1a03814fba494fd5d1e3550050cfef5d930
This commit is contained in:
Iván Budnik
2022-07-13 17:55:39 +00:00
parent 81d5a3ee13
commit fed8dd2637
10 changed files with 421 additions and 342 deletions

View File

@@ -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<android.media.MediaRoute2Info>);
method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
method @Deprecated public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
method @Deprecated public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
method @Deprecated public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
public class MediaRouter2.RoutingController {

View File

@@ -26,9 +26,7 @@ import android.os.Bundle;
oneway interface IMediaRouter2 {
void notifyRouterRegistered(in List<MediaRoute2Info> currentRoutes,
in RoutingSessionInfo currentSystemSessionInfo);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
void notifySessionReleased(in RoutingSessionInfo sessionInfo);

View File

@@ -30,8 +30,6 @@ oneway interface IMediaRouter2Manager {
void notifySessionReleased(in RoutingSessionInfo session);
void notifyDiscoveryPreferenceChanged(String packageName,
in RouteDiscoveryPreference discoveryPreference);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifyRequestFailed(int requestId, int reason);
}

View File

@@ -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<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
@@ -820,7 +820,7 @@ public final class MediaRouter2 {
}
}
void dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes) {
void dispatchFilteredRoutesUpdatedOnHandler(List<MediaRoute2Info> newRoutes) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -863,29 +863,16 @@ public final class MediaRouter2 {
if (!changedRoutes.isEmpty()) {
notifyRoutesChanged(changedRoutes);
}
}
void addRoutesOnHandler(List<MediaRoute2Info> 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<MediaRoute2Info> routes) {
synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.remove(route.getId());
}
updateFilteredRoutesLocked();
}
}
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
void updateRoutesOnHandler(List<MediaRoute2Info> 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<MediaRoute2Info> routes) {
for (RouteCallbackRecord record : mRouteCallbackRecords) {
List<MediaRoute2Info> filteredRoutes =
filterRoutesWithIndividualPreference(routes, record.mPreference);
record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes));
}
}
private void notifyPreferredFeaturesChanged(List<String> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> routes) {
public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes));
}
@Override
public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
obtainMessage(MediaRouter2::removeRoutesOnHandler, MediaRouter2.this, routes));
}
@Override
public void notifyRoutesChanged(List<MediaRoute2Info> 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<MediaRoute2Info> routes) {
updateAllRoutesFromManager();
}
@Override
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
updateAllRoutesFromManager();
}
@Override
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
public void onRoutesUpdated() {
updateAllRoutesFromManager();
}

View File

@@ -546,37 +546,15 @@ public final class MediaRouter2Manager {
}
}
void addRoutesOnHandler(List<MediaRoute2Info> routes) {
void updateRoutesOnHandler(@NonNull List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
mRoutes.clear();
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
}
}
if (routes.size() > 0) {
notifyRoutesAdded(routes);
}
}
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
mRoutes.remove(route.getId());
}
}
if (routes.size() > 0) {
notifyRoutesRemoved(routes);
}
}
void changeRoutesOnHandler(List<MediaRoute2Info> 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<MediaRoute2Info> routes) {
private void notifyRoutesUpdated() {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRoutesAdded(routes));
}
}
private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRoutesRemoved(routes));
}
}
private void notifyRoutesChanged(List<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> routes) {}
default void onRoutesUpdated() {}
/**
* Called when a session is changed.
@@ -1115,21 +1067,12 @@ public final class MediaRouter2Manager {
}
@Override
public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler,
MediaRouter2Manager.this, routes));
}
@Override
public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler,
MediaRouter2Manager.this, routes));
}
@Override
public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler,
MediaRouter2Manager.this, routes));
public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
obtainMessage(
MediaRouter2Manager::updateRoutesOnHandler,
MediaRouter2Manager.this,
routes));
}
}
}

View File

@@ -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<String> features = Collections.singletonList("customFeature");
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
final List<MediaRoute2Info> 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<MediaRoute2Info> 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<String> 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<MediaRoute2Info> routesReceived, List<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> routesReceived, List<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesChanged(List<MediaRoute2Info> 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();

View File

@@ -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<MediaRoute2Info> 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<String> 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();
}
}

View File

@@ -528,7 +528,7 @@ public class InfoMediaManager extends MediaManager {
class RouterManagerCallback implements MediaRouter2Manager.Callback {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
public void onRoutesUpdated() {
refreshDevices();
}
@@ -539,16 +539,6 @@ public class InfoMediaManager extends MediaManager {
}
}
@Override
public void onRoutesChanged(List<MediaRoute2Info> routes) {
refreshDevices();
}
@Override
public void onRoutesRemoved(List<MediaRoute2Info> routes) {
refreshDevices();
}
@Override
public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
if (DEBUG) {

View File

@@ -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);

View File

@@ -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<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
/**
* Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
* {@link android.media.MediaRouter2Manager managers}.
*
* <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
* that have {@code MODIFY_AUDIO_ROUTING} permission.
*
* <p>This list contains all routes exposed by route providers. This includes routes from
* both system route providers and user route providers.
*
* <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
*/
private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
new ArrayMap<>();
/**
* Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
*
* <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
* MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
*
* <p>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.
*
* <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
*/
private final Map<String, MediaRoute2Info> 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<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> 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<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
final Collection<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
List<IMediaRouter2Manager> managers = getManagers();
List<MediaRoute2Info> 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<MediaRoute2ProviderInfo> 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<IMediaRouter2> routers,
@NonNull List<MediaRoute2Info> routes) {
private void notifyRoutesUpdatedToRouters(
@NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> 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<IMediaRouter2> routers,
@NonNull List<MediaRoute2Info> 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<IMediaRouter2> routers,
@NonNull List<MediaRoute2Info> 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<IMediaRouter2> routers,
@NonNull RoutingSessionInfo sessionInfo) {
private void notifySessionInfoChangedToRouters(
@NonNull List<IMediaRouter2> 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<MediaRoute2Info> 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<IMediaRouter2Manager> managers,
private void notifyRoutesUpdatedToManagers(
@NonNull List<IMediaRouter2Manager> managers,
@NonNull List<MediaRoute2Info> 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<IMediaRouter2Manager> managers,
@NonNull List<MediaRoute2Info> 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<IMediaRouter2Manager> managers,
@NonNull List<MediaRoute2Info> 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);
}