System MediaRouter2: Add RouteCallback functionality
This CL makes the system MediaRouter2 can receive route related
events for the client application.
Bug: 181122201
Test: Locally tested. Test code will be added when the system APIs
are actually makred as @SystemApi.
Change-Id: I516d90c725b6a72ba323b30c4c8a013edd737483
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index dc9c58e..b4db305 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -60,6 +60,7 @@
public final class MediaRouter2 {
private static final String TAG = "MR2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final Object sSystemRouterLock = new Object();
private static final Object sRouterLock = new Object();
// The maximum time for the old routing controller available after transfer.
@@ -67,8 +68,8 @@
// The manager request ID representing that no manager is involved.
private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE;
- @GuardedBy("sRouterLock")
- private static Map<String, MediaRouter2> sMediaRouter2Map = new ArrayMap<>();
+ @GuardedBy("sSystemRouterLock")
+ private static Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>();
private static MediaRouter2Manager sManager;
@GuardedBy("sRouterLock")
@@ -76,6 +77,7 @@
private final Context mContext;
private final IMediaRouterService mMediaRouterService;
+ private final Object mLock = new Object();
private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
new CopyOnWriteArrayList<>();
@@ -88,27 +90,29 @@
new CopyOnWriteArrayList<>();
private final String mClientPackageName;
+ private final ManagerCallback mManagerCallback;
+
private final String mPackageName;
- @GuardedBy("sRouterLock")
+ @GuardedBy("mLock")
final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
final RoutingController mSystemController;
- @GuardedBy("sRouterLock")
+ @GuardedBy("mLock")
private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
- @GuardedBy("sRouterLock")
+ @GuardedBy("mLock")
MediaRouter2Stub mStub;
- @GuardedBy("sRouterLock")
+ @GuardedBy("mLock")
private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
final Handler mHandler;
- @GuardedBy("sRouterLock")
+ @GuardedBy("mLock")
private boolean mShouldUpdateRoutes = true;
private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
private volatile OnGetControllerHintsListener mOnGetControllerHintsListener;
@@ -130,6 +134,11 @@
/**
* Gets an instance of the media router which controls the app's media routing.
* Returns {@code null} if the given package name is invalid.
+ * <p>
+ * Note: For media routers created with this method, the discovery preference passed to
+ * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
+ * with the client app's discovery preference. Therefore, it is recommended to pass
+ * {@link RouteDiscoveryPreference#EMPTY} there.
*
* @param clientPackageName the package name of the app to control
* @hide
@@ -149,15 +158,17 @@
return null;
}
- synchronized (sRouterLock) {
- MediaRouter2 instance = sMediaRouter2Map.get(clientPackageName);
+ synchronized (sSystemRouterLock) {
+ MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName);
if (instance == null) {
// TODO: Add permission check here using MODIFY_AUDIO_ROUTING.
if (sManager == null) {
sManager = MediaRouter2Manager.getInstance(context.getApplicationContext());
}
instance = new MediaRouter2(context, clientPackageName);
- sMediaRouter2Map.put(clientPackageName, instance);
+ sSystemMediaRouter2Map.put(clientPackageName, instance);
+ // TODO: Remove router instance once it is not needed.
+ instance.registerManagerCallback();
}
return instance;
}
@@ -192,11 +203,14 @@
}
mSystemController = new SystemRoutingController(currentSystemSessionInfo);
+ // Only used by system MediaRouter2.
mClientPackageName = null;
+ mManagerCallback = null;
}
private MediaRouter2(Context context, String clientPackageName) {
mClientPackageName = clientPackageName;
+ mManagerCallback = new ManagerCallback();
mContext = context;
mMediaRouterService = null;
mPackageName = null;
@@ -220,8 +234,8 @@
}
/**
- * Gets the target package name of the app which this media router controls.
- * This is only non-null when the router instance is created with the target package name.
+ * Gets the client package name of the app which this media router controls.
+ * This is only non-null when the router instance is created with the client package name.
*
* @see #getInstance(Context, String)
* @hide
@@ -245,6 +259,9 @@
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeCallback, "callback must not be null");
Objects.requireNonNull(preference, "preference must not be null");
+ if (isSystemRouter()) {
+ preference = RouteDiscoveryPreference.EMPTY;
+ }
RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
@@ -253,7 +270,11 @@
// is happening but it's okay because either this or the other registration should be done.
mRouteCallbackRecords.addIfAbsent(record);
- synchronized (sRouterLock) {
+ if (isSystemRouter()) {
+ return;
+ }
+
+ synchronized (mLock) {
if (mStub == null) {
MediaRouter2Stub stub = new MediaRouter2Stub();
try {
@@ -289,7 +310,11 @@
return;
}
- synchronized (sRouterLock) {
+ if (isSystemRouter()) {
+ return;
+ }
+
+ synchronized (mLock) {
if (mStub == null) {
return;
}
@@ -326,22 +351,37 @@
}
/**
+ * Gets the list of all discovered routes.
+ * This list includes the routes that are not related to the client app.
+ * <p>
+ * This will return an empty list for non-system media routers.
+ *
+ * @hide
+ */
+ //@SystemApi
+ public List<MediaRoute2Info> getAllRoutes() {
+ if (isSystemRouter()) {
+ return sManager.getAllRoutes();
+ }
+ return Collections.emptyList();
+ }
+
+ /**
* Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
* known to the media router.
* <p>
* Please note that the list can be changed before callbacks are invoked.
* </p>
- *
* @return the list of routes that contains at least one of the route features in discovery
* preferences registered by the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
- if (mClientPackageName != null) {
+ if (isSystemRouter()) {
return sManager.getAvailableRoutes(mClientPackageName);
}
- synchronized (sRouterLock) {
+ synchronized (mLock) {
if (mShouldUpdateRoutes) {
mShouldUpdateRoutes = false;
@@ -449,7 +489,7 @@
* @see TransferCallback#onTransferFailure
*/
public void transferTo(@NonNull MediaRoute2Info route) {
- if (mClientPackageName != null) {
+ if (isSystemRouter()) {
sManager.selectRoute(mClientPackageName, route);
return;
}
@@ -464,7 +504,7 @@
* controls the media routing, this method is a no-op.
*/
public void stop() {
- if (mClientPackageName != null) {
+ if (isSystemRouter()) {
List<RoutingSessionInfo> sessionInfos = sManager.getRoutingSessions(mClientPackageName);
RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1);
sManager.releaseSession(sessionToRelease);
@@ -481,7 +521,7 @@
*/
//@SystemApi
public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
- if (mClientPackageName != null) {
+ if (isSystemRouter()) {
sManager.transfer(controller.getRoutingSessionInfo(), route);
return;
}
@@ -490,7 +530,7 @@
Objects.requireNonNull(route, "route must not be null");
boolean routeFound;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
// TODO: Check thread-safety
routeFound = mRoutes.containsKey(route.getId());
}
@@ -526,7 +566,7 @@
}
MediaRouter2Stub stub;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -576,7 +616,7 @@
public List<RoutingController> getControllers() {
// TODO: Do not create the controller instances every time,
// Instead, update the list using the sessions' ID and session related callbacks.
- if (mClientPackageName != null) {
+ if (isSystemRouter()) {
return sManager.getRoutingSessions(mClientPackageName).stream()
.map(info -> new RoutingController(info))
.collect(Collectors.toList());
@@ -584,7 +624,7 @@
List<RoutingController> result = new ArrayList<>();
result.add(0, mSystemController);
- synchronized (sRouterLock) {
+ synchronized (mLock) {
result.addAll(mNonSystemRoutingControllers.values());
}
return result;
@@ -603,7 +643,7 @@
Objects.requireNonNull(route, "route must not be null");
MediaRouter2Stub stub;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -627,7 +667,7 @@
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
- synchronized (sRouterLock) {
+ synchronized (mLock) {
List<String> currentRoutesIds = currentRoutes.stream().map(MediaRoute2Info::getId)
.collect(Collectors.toList());
@@ -685,7 +725,7 @@
void addRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- synchronized (sRouterLock) {
+ synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -701,7 +741,7 @@
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- synchronized (sRouterLock) {
+ synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.remove(route.getId());
if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -717,7 +757,7 @@
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
- synchronized (sRouterLock) {
+ synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -789,7 +829,7 @@
newController.setRoutingSessionInfo(sessionInfo);
} else {
newController = new RoutingController(sessionInfo);
- synchronized (sRouterLock) {
+ synchronized (mLock) {
mNonSystemRoutingControllers.put(newController.getId(), newController);
}
}
@@ -812,7 +852,7 @@
}
RoutingController matchingController;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
}
@@ -840,7 +880,7 @@
}
RoutingController matchingController;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
}
@@ -868,7 +908,7 @@
if (oldSession.isSystemSession()) {
controller = getSystemController();
} else {
- synchronized (sRouterLock) {
+ synchronized (mLock) {
controller = mNonSystemRoutingControllers.get(oldSession.getId());
}
}
@@ -878,6 +918,22 @@
requestCreateController(controller, route, managerRequestId);
}
+ /**
+ * Returns whether this router is created with {@link #getInstance(Context, String)}.
+ * This kind of router can control the target app's media routing.
+ */
+ private boolean isSystemRouter() {
+ return mClientPackageName != null;
+ }
+
+ /**
+ * Registers {@link MediaRouter2Manager.Callback} for getting events.
+ */
+ private void registerManagerCallback() {
+ // Using direct executor here, since MediaRouter2Manager also posts to the main handler.
+ sManager.registerCallback(Runnable::run, mManagerCallback);
+ }
+
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
RouteDiscoveryPreference discoveryRequest) {
return routes.stream()
@@ -1236,7 +1292,7 @@
}
MediaRouter2Stub stub;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1283,7 +1339,7 @@
}
MediaRouter2Stub stub;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1318,7 +1374,7 @@
}
MediaRouter2Stub stub;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1352,7 +1408,7 @@
return;
}
MediaRouter2Stub stub;
- synchronized (sRouterLock) {
+ synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1386,7 +1442,7 @@
mState = CONTROLLER_STATE_RELEASING;
}
- synchronized (sRouterLock) {
+ synchronized (mLock) {
// It could happen if the controller is released by the another thread
// in between two locks
if (!mNonSystemRoutingControllers.remove(getId(), this)) {
@@ -1415,7 +1471,7 @@
mState = CONTROLLER_STATE_RELEASED;
}
- synchronized (sRouterLock) {
+ synchronized (mLock) {
mNonSystemRoutingControllers.remove(getId(), this);
if (shouldReleaseSession && mStub != null) {
@@ -1483,7 +1539,7 @@
}
private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
- synchronized (sRouterLock) {
+ synchronized (mLock) {
return routeIds.stream().map(mRoutes::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
@@ -1665,4 +1721,79 @@
MediaRouter2.this, oldSession, route, managerRequestId));
}
}
+
+ class ManagerCallback implements MediaRouter2Manager.Callback {
+
+ @Override
+ public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
+ List<MediaRoute2Info> filteredRoutes =
+ sManager.filterRoutesForPackage(routes, mClientPackageName);
+ if (filteredRoutes.isEmpty()) {
+ return;
+ }
+ for (RouteCallbackRecord record: mRouteCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
+ }
+ }
+
+ @Override
+ public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
+ List<MediaRoute2Info> filteredRoutes =
+ sManager.filterRoutesForPackage(routes, mClientPackageName);
+ if (filteredRoutes.isEmpty()) {
+ return;
+ }
+ for (RouteCallbackRecord record: mRouteCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
+ }
+ }
+
+ @Override
+ public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
+ List<MediaRoute2Info> filteredRoutes =
+ sManager.filterRoutesForPackage(routes, mClientPackageName);
+ if (filteredRoutes.isEmpty()) {
+ return;
+ }
+ for (RouteCallbackRecord record: mRouteCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
+ }
+ }
+
+ @Override
+ public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
+ // TODO: Call ControllerCallback.onControllerUpdated
+ }
+
+ @Override
+ public void onTransferred(@NonNull RoutingSessionInfo oldSession,
+ @Nullable RoutingSessionInfo newSession) {
+ // TODO: Call TransferCallback.onTransfer
+ }
+
+ @Override
+ public void onTransferFailed(@NonNull RoutingSessionInfo session,
+ @NonNull MediaRoute2Info route) {
+ // TODO: Call TransferCallback.onTransferFailure
+ }
+
+ @Override
+ public void onSessionReleased(@NonNull RoutingSessionInfo session) {
+ // TODO: Call TransferCallback.onStop()
+ }
+
+ @Override
+ public void onPreferredFeaturesChanged(@NonNull String packageName,
+ @NonNull List<String> preferredFeatures) {
+ // Does nothing.
+ }
+
+ @Override
+ public void onRequestFailed(int reason) {
+ // Does nothing.
+ }
+ }
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 5e732f9..ca619d4 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -237,6 +237,36 @@
}
/**
+ * Returns a list of routes which are related to the given package name in the given route list.
+ */
+ @NonNull
+ public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
+ @NonNull String packageName) {
+ Objects.requireNonNull(routes, "routes must not be null");
+ Objects.requireNonNull(packageName, "packageName must not be null");
+
+ List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
+ RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
+
+ List<MediaRoute2Info> result = new ArrayList<>();
+ List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+ if (preferredFeatures == null) {
+ preferredFeatures = Collections.emptyList();
+ }
+
+ synchronized (mRoutesLock) {
+ for (MediaRoute2Info route : routes) {
+ if (route.hasAnyFeatures(preferredFeatures)
+ || sessionInfo.getSelectedRoutes().contains(route.getId())
+ || sessionInfo.getTransferableRoutes().contains(route.getId())) {
+ result.add(route);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
* Gets the system routing session associated with no specific application.
*/
@NonNull