MediaRouter: add routing controller in MRM
This CL introduces RoutingController into MediaRouter2Manager
to enable it to control remote routing sessions.
Tests are also revised according to MRM changes.
Bug: 147787194
Test: atest mediaroutertest
Change-Id: Id4ab61fcb690fccac42df049e1fb74d89bcf3d6a
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index e9add17..ffad659 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,12 +18,14 @@
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
+import android.media.RoutingSessionInfo;
/**
* {@hide}
*/
oneway interface IMediaRouter2Manager {
- void notifyRouteSelected(String packageName, in MediaRoute2Info route);
+ void notifySessionCreated(in RoutingSessionInfo sessionInfo);
+ void notifySessionsUpdated();
void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 2d3e185..b435f25 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -71,4 +71,12 @@
in MediaRoute2Info route, int direction);
List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+ void selectClientRoute(IMediaRouter2Manager manager,
+ String sessionId, in MediaRoute2Info route);
+ void deselectClientRoute(IMediaRouter2Manager manager,
+ String sessionId, in MediaRoute2Info route);
+ void transferToClientRoute(IMediaRouter2Manager manager,
+ String sessionId, in MediaRoute2Info route);
+ void releaseClientSession(IMediaRouter2Manager manager, String sessionId);
+
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 5a3de6d..1443538 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -177,7 +177,6 @@
}
mSessionInfo.put(sessionInfo.getId(), sessionInfo);
}
- schedulePublishState();
if (mClient == null) {
return;
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 7022933..5cb32d6 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -131,7 +132,7 @@
Objects.requireNonNull(callback, "callback must not be null");
if (!mCallbackRecords.remove(new CallbackRecord(null, callback))) {
- Log.w(TAG, "Ignore removing unknown callback. " + callback);
+ Log.w(TAG, "unregisterCallback: Ignore unknown callback. " + callback);
return;
}
@@ -175,6 +176,29 @@
return routes;
}
+ /**
+ * Gets routing controllers of an application with the given package name.
+ * If the application isn't running or it doesn't use {@link MediaRouter2}, an empty list
+ * will be returned.
+ */
+ @NonNull
+ public List<RoutingController> getRoutingControllers(@NonNull String packageName) {
+ Objects.requireNonNull(packageName, "packageName must not be null");
+
+ List<RoutingController> controllers = new ArrayList<>();
+
+ for (RoutingSessionInfo sessionInfo : getActiveSessions()) {
+ if (TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) {
+ controllers.add(new RoutingController(sessionInfo));
+ }
+ }
+ return controllers;
+ }
+
+ /**
+ * Gets the list of all active routing sessions. It doesn't include default routing sessions
+ * of applications.
+ */
@NonNull
public List<RoutingSessionInfo> getActiveSessions() {
Client client;
@@ -192,23 +216,7 @@
}
/**
- * Gets the list of routes that are actively used by {@link MediaRouter2}.
- */
- @NonNull
- public List<MediaRoute2Info> getActiveRoutes() {
- List<MediaRoute2Info> routes = new ArrayList<>();
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (!TextUtils.isEmpty(route.getClientPackageName())) {
- routes.add(route);
- }
- }
- }
- return routes;
- }
-
- /**
- * Gets the list of discovered routes
+ * Gets the list of all discovered routes
*/
@NonNull
public List<MediaRoute2Info> getAllRoutes() {
@@ -222,6 +230,10 @@
/**
* Selects media route for the specified package name.
*
+ * If the given route is {@link RoutingController#getTransferrableRoutes() a transferrable
+ * route} of a routing session of the application, the session will be transferred to
+ * the route. If not, a new routing session will be created.
+ *
* @param packageName the package name of the application that should change it's media route
* @param route the route to be selected.
*/
@@ -229,6 +241,13 @@
Objects.requireNonNull(packageName, "packageName must not be null");
Objects.requireNonNull(route, "route must not be null");
+ for (RoutingController controller : getRoutingControllers(packageName)) {
+ if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) {
+ controller.transferToRoute(route);
+ return;
+ }
+ }
+
Client client;
synchronized (sLock) {
client = mClient;
@@ -238,6 +257,7 @@
int requestId = mNextRequestId.getAndIncrement();
mMediaRouterService.requestCreateClientSession(
client, packageName, route, requestId);
+ //TODO: release the previous session?
} catch (RemoteException ex) {
Log.e(TAG, "Unable to select media route", ex);
}
@@ -245,7 +265,7 @@
}
/**
- * Requests a volume change for the route asynchronously.
+ * Requests a volume change for a route asynchronously.
* <p>
* It may have no effect if the route is currently not selected.
* </p>
@@ -346,9 +366,16 @@
}
}
- void notifyRouteSelected(String packageName, MediaRoute2Info route) {
+ void notifySessionCreated(RoutingSessionInfo sessionInfo) {
for (CallbackRecord record : mCallbackRecords) {
- record.mExecutor.execute(() -> record.mCallback.onRouteSelected(packageName, route));
+ record.mExecutor.execute(() -> record.mCallback.onSessionCreated(
+ new RoutingController(sessionInfo)));
+ }
+ }
+
+ void notifySessionInfosChanged() {
+ for (CallbackRecord record : mCallbackRecords) {
+ record.mExecutor.execute(() -> record.mCallback.onSessionsUpdated());
}
}
@@ -365,6 +392,275 @@
}
/**
+ * @hide
+ */
+ public RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) {
+ return new RoutingController(sessionInfo);
+ }
+
+ /**
+ * A class to control media routing session in media route provider.
+ * With routing controller, an application can select a route into the session or deselect
+ * a route in the session.
+ */
+ public final class RoutingController {
+ private final Object mControllerLock = new Object();
+ @GuardedBy("mControllerLock")
+ private RoutingSessionInfo mSessionInfo;
+
+ RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
+ mSessionInfo = sessionInfo;
+ }
+
+ /**
+ * Gets the ID of the session
+ */
+ @NonNull
+ public String getSessionId() {
+ synchronized (mControllerLock) {
+ return mSessionInfo.getId();
+ }
+ }
+
+ /**
+ * Gets the client package name of the session
+ */
+ @NonNull
+ public String getClientPackageName() {
+ synchronized (mControllerLock) {
+ return mSessionInfo.getClientPackageName();
+ }
+ }
+
+ /**
+ * @return the control hints used to control route session if available.
+ */
+ @Nullable
+ public Bundle getControlHints() {
+ synchronized (mControllerLock) {
+ return mSessionInfo.getControlHints();
+ }
+ }
+
+ /**
+ * @return the unmodifiable list of currently selected routes
+ */
+ @NonNull
+ public List<MediaRoute2Info> getSelectedRoutes() {
+ List<String> routeIds;
+ synchronized (mControllerLock) {
+ routeIds = mSessionInfo.getSelectedRoutes();
+ }
+ return getRoutesWithIds(routeIds);
+ }
+
+ /**
+ * @return the unmodifiable list of selectable routes for the session.
+ */
+ @NonNull
+ public List<MediaRoute2Info> getSelectableRoutes() {
+ List<String> routeIds;
+ synchronized (mControllerLock) {
+ routeIds = mSessionInfo.getSelectableRoutes();
+ }
+ return getRoutesWithIds(routeIds);
+ }
+
+ /**
+ * @return the unmodifiable list of deselectable routes for the session.
+ */
+ @NonNull
+ public List<MediaRoute2Info> getDeselectableRoutes() {
+ List<String> routeIds;
+ synchronized (mControllerLock) {
+ routeIds = mSessionInfo.getDeselectableRoutes();
+ }
+ return getRoutesWithIds(routeIds);
+ }
+
+ /**
+ * @return the unmodifiable list of transferrable routes for the session.
+ */
+ @NonNull
+ public List<MediaRoute2Info> getTransferrableRoutes() {
+ List<String> routeIds;
+ synchronized (mControllerLock) {
+ routeIds = mSessionInfo.getTransferrableRoutes();
+ }
+ return getRoutesWithIds(routeIds);
+ }
+
+ /**
+ * Selects a route for the remote session. The given route must satisfy all of the
+ * following conditions:
+ * <ul>
+ * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
+ * <li>ID should be included in {@link #getSelectableRoutes()}</li>
+ * </ul>
+ * If the route doesn't meet any of above conditions, it will be ignored.
+ *
+ * @see #getSelectedRoutes()
+ * @see #getSelectableRoutes()
+ */
+ public void selectRoute(@NonNull MediaRoute2Info route) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ RoutingSessionInfo sessionInfo;
+ synchronized (mControllerLock) {
+ sessionInfo = mSessionInfo;
+ }
+ if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
+ return;
+ }
+
+ if (!sessionInfo.getSelectableRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
+ return;
+ }
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.selectClientRoute(mClient, getSessionId(), route);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to select route for session.", ex);
+ }
+ }
+ }
+
+ /**
+ * Deselects a route from the remote session. The given route must satisfy all of the
+ * following conditions:
+ * <ul>
+ * <li>ID should be included in {@link #getSelectedRoutes()}</li>
+ * <li>ID should be included in {@link #getDeselectableRoutes()}</li>
+ * </ul>
+ * If the route doesn't meet any of above conditions, it will be ignored.
+ *
+ * @see #getSelectedRoutes()
+ * @see #getDeselectableRoutes()
+ */
+ public void deselectRoute(@NonNull MediaRoute2Info route) {
+ Objects.requireNonNull(route, "route must not be null");
+ RoutingSessionInfo sessionInfo;
+ synchronized (mControllerLock) {
+ sessionInfo = mSessionInfo;
+ }
+
+ if (!sessionInfo.getSelectedRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
+ return;
+ }
+
+ if (!sessionInfo.getDeselectableRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
+ return;
+ }
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.deselectClientRoute(mClient, getSessionId(), route);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to remove route from session.", ex);
+ }
+ }
+ }
+
+ /**
+ * Transfers to a given route for the remote session. The given route must satisfy
+ * all of the following conditions:
+ * <ul>
+ * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
+ * <li>ID should be included in {@link #getTransferrableRoutes()}</li>
+ * </ul>
+ * If the route doesn't meet any of above conditions, it will be ignored.
+ *
+ * @see #getSelectedRoutes()
+ * @see #getTransferrableRoutes()
+ */
+ public void transferToRoute(@NonNull MediaRoute2Info route) {
+ Objects.requireNonNull(route, "route must not be null");
+ RoutingSessionInfo sessionInfo;
+ synchronized (mControllerLock) {
+ sessionInfo = mSessionInfo;
+ }
+
+ if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring transferring to a route that is already added. route="
+ + route);
+ return;
+ }
+
+ if (!sessionInfo.getTransferrableRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+ return;
+ }
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.transferToClientRoute(mClient, getSessionId(), route);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to transfer to route for session.", ex);
+ }
+ }
+ }
+
+ /**
+ * Release this session.
+ * Any operation on this session after calling this method will be ignored.
+ */
+ public void release() {
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.releaseClientSession(mClient, getSessionId());
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to notify of controller release", ex);
+ }
+ }
+ }
+
+ /**
+ * Gets the session info of the session
+ * @hide
+ */
+ @NonNull
+ public RoutingSessionInfo getSessionInfo() {
+ synchronized (mControllerLock) {
+ return mSessionInfo;
+ }
+ }
+
+ private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
+ List<MediaRoute2Info> routes = new ArrayList<>();
+ synchronized (mRoutesLock) {
+ for (String routeId : routeIds) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route != null) {
+ routes.add(route);
+ }
+ }
+ }
+ return Collections.unmodifiableList(routes);
+ }
+ }
+
+ /**
* Interface for receiving events about media routing changes.
*/
public static class Callback {
@@ -388,14 +684,17 @@
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
/**
- * Called when a route is selected for an application.
+ * Called when a routing session is created.
*
- * @param packageName the package name of the application
- * @param route the selected route of the application.
- * It is null if the application has no selected route.
+ * @param controller the controller to control the created session
*/
- public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {}
+ public void onSessionCreated(@NonNull RoutingController controller) {}
+ /**
+ * Called when at least one session info is changed.
+ * Call {@link #getActiveSessions()} to get current active session info.
+ */
+ public void onSessionsUpdated() {}
/**
* Called when the preferred route features of an app is changed.
@@ -435,11 +734,19 @@
class Client extends IMediaRouter2Manager.Stub {
@Override
- public void notifyRouteSelected(String packageName, MediaRoute2Info route) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRouteSelected,
- MediaRouter2Manager.this, packageName, route));
+ public void notifySessionCreated(RoutingSessionInfo sessionInfo) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionCreated,
+ MediaRouter2Manager.this, sessionInfo));
}
+ @Override
+ public void notifySessionsUpdated() {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionInfosChanged,
+ MediaRouter2Manager.this));
+ // do nothing
+ }
+
+ @Override
public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
MediaRouter2Manager.this, packageName, features));
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index cc2d1b1..d3b5826 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -210,6 +210,7 @@
}
}
notifySessionReleased(sessionId);
+ publishRoutes();
}
@Override
@@ -265,6 +266,27 @@
@Override
public void onTransferToRoute(String sessionId, String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+ MediaRoute2Info route = mRoutes.get(routeId);
+
+ if (sessionInfo == null || route == null) {
+ return;
+ }
+
+ for (String selectedRouteId : sessionInfo.getSelectedRoutes()) {
+ mRouteIdToSessionId.remove(selectedRouteId);
+ MediaRoute2Info selectedRoute = mRoutes.get(selectedRouteId);
+ if (selectedRoute != null) {
+ mRoutes.put(selectedRouteId, new MediaRoute2Info.Builder(selectedRoute)
+ .setClientPackageName(null)
+ .build());
+ }
+ }
+
+ mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+ .setClientPackageName(sessionInfo.getClientPackageName())
+ .build());
+ mRouteIdToSessionId.put(routeId, sessionId);
+
RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.clearSelectedRoutes()
.addSelectedRoute(routeId)
@@ -272,6 +294,7 @@
.removeTransferrableRoute(routeId)
.build();
notifySessionUpdated(newSessionInfo);
+ publishRoutes();
}
void maybeDeselectRoute(String routeId) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 83dd0c0..cba8452 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -31,6 +31,7 @@
import android.media.MediaRouter2.SessionCallback;
import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -38,7 +39,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -138,6 +138,8 @@
@After
public void tearDown() {
+ // order matters (callbacks should be cleared at the last)
+ releaseAllSessions();
// unregister callbacks
clearCallbacks();
}
@@ -223,110 +225,83 @@
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- try {
- mManager.selectRoute(mPackageName, routeToSelect);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } finally {
- //TODO: release the session
- //mManager.selectRoute(mPackageName, null);
- }
- }
-
- /**
- * Tests if MR2Manager.Callback.onRouteSelected is called
- * when a route is selected by MR2Manager.
- */
- @Test
- @Ignore("TODO: test session created callback instead of onRouteSelected")
- public void testManagerOnRouteSelected() throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
-
- addRouterCallback(new RouteCallback());
- addManagerCallback(new MediaRouter2Manager.Callback() {
- @Override
- public void onRouteSelected(String packageName, MediaRoute2Info route) {
- if (TextUtils.equals(mPackageName, packageName)
- && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
- latch.countDown();
- }
- }
- });
-
- MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
- assertNotNull(routeToSelect);
-
- try {
- mManager.selectRoute(mPackageName, routeToSelect);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } finally {
- //TODO: release the session
- //mManager.selectRoute(mPackageName, null);
- }
+ mManager.selectRoute(mPackageName, routeToSelect);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(1, mManager.getActiveSessions().size());
}
@Test
- @Ignore("TODO: enable this when 'releasing session' is implemented")
- public void testGetActiveRoutes() throws Exception {
+ public void testGetRoutingControllers() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
- public void onRouteSelected(String packageName, MediaRoute2Info route) {
- if (TextUtils.equals(mPackageName, packageName)
- && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
+ public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
+ if (TextUtils.equals(mPackageName, controller.getClientPackageName())
+ && createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
latch.countDown();
}
}
});
- //TODO: it fails due to not releasing session
- assertEquals(0, mManager.getActiveSessions().size());
+ assertEquals(0, mManager.getRoutingControllers(mPackageName).size());
mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertEquals(1, mManager.getActiveSessions().size());
+ List<MediaRouter2Manager.RoutingController> controllers =
+ mManager.getRoutingControllers(mPackageName);
- //TODO: release the session
- /*
+ assertEquals(1, controllers.size());
+
+ MediaRouter2Manager.RoutingController routingController = controllers.get(0);
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, null),
+ () -> routingController.release(),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), null));
- assertEquals(0, mManager.getActiveRoutes().size());
- */
+ assertEquals(0, mManager.getRoutingControllers(mPackageName).size());
}
/**
- * Tests selecting and unselecting routes of a single provider.
+ * Tests select, transfer, release of routes of a provider
*/
@Test
- @Ignore("TODO: enable when session is released")
- public void testSingleProviderSelect() throws Exception {
+ public void testSelectAndTransferAndRelease() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
+ CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
+
+ addManagerCallback(new MediaRouter2Manager.Callback() {
+ @Override
+ public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
+ assertNotNull(controller);
+ onSessionCreatedLatch.countDown();
+ }
+ });
awaitOnRouteChangedManager(
() -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
+ assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ List<MediaRouter2Manager.RoutingController> controllers =
+ mManager.getRoutingControllers(mPackageName);
+
+ assertEquals(1, controllers.size());
+ MediaRouter2Manager.RoutingController routingController = controllers.get(0);
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
- ROUTE_ID2,
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+ ROUTE_ID5_TO_TRANSFER_TO,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
- //TODO: release the session
- /*
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, null),
- ROUTE_ID2,
+ () -> routingController.release(),
+ ROUTE_ID5_TO_TRANSFER_TO,
route -> TextUtils.equals(route.getClientPackageName(), null));
-
- */
}
@Test
@@ -460,4 +435,13 @@
}
mSessionCallbacks.clear();
}
+
+ private void releaseAllSessions() {
+ // ensure ManagerRecord in MediaRouter2ServiceImpl
+ addManagerCallback(new MediaRouter2Manager.Callback());
+
+ for (RoutingSessionInfo session : mManager.getActiveSessions()) {
+ mManager.getControllerForSession(session).release();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 45d50b3..6e3154c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -22,6 +22,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -34,7 +35,6 @@
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -63,12 +63,12 @@
import java.util.concurrent.atomic.AtomicInteger;
/**
- * TODO: Merge this to MediaRouterService once it's finished.
+ * Implements features related to {@link android.media.MediaRouter2} and
+ * {@link android.media.MediaRouter2Manager}.
*/
class MediaRouter2ServiceImpl {
private static final String TAG = "MR2ServiceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final long ROUTE_SELECTION_REQUEST_TIMEOUT_MS = 5000L;
private final Context mContext;
private final Object mLock = new Object();
@@ -377,6 +377,53 @@
}
}
+ public void selectClientRoute(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ selectClientRouteLocked(manager, sessionId, route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void deselectClientRoute(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ deselectClientRouteLocked(manager, sessionId, route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void transferToClientRoute(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ transferClientRouteLocked(manager, sessionId, route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void releaseClientSession(IMediaRouter2Manager manager, String sessionId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ releaseClientSessionLocked(manager, sessionId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
//TODO: Review this is handling multi-user properly.
void switchUser() {
synchronized (mLock) {
@@ -417,6 +464,7 @@
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = client.asBinder();
if (mAllClientRecords.get(binder) == null) {
+
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
packageName, trusted);
@@ -653,6 +701,7 @@
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
if (managerRecord == null) {
+ Slog.w(TAG, "getActiveSessionLocked: Ignoring unknown manager");
return Collections.emptyList();
}
@@ -676,6 +725,93 @@
return userRecord;
}
+ private void selectClientRouteLocked(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord == null) {
+ Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown manager.");
+ return;
+ }
+ //TODO: we shouldn't ignore selecting request for unknown clients. (RCN?)
+ Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+ .findClientforSessionLocked(sessionId);
+ if (clientRecord == null) {
+ Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown session.");
+ return;
+ }
+
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::selectRouteOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, sessionId, route));
+ }
+
+ private void deselectClientRouteLocked(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord == null) {
+ Slog.w(TAG, "deselectClientRouteLocked: Ignoring unknown manager.");
+ return;
+ }
+ //TODO: we shouldn't ignore selecting request for unknown clients. (RCN?)
+ Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+ .findClientforSessionLocked(sessionId);
+ if (clientRecord == null) {
+ Slog.w(TAG, "deslectClientRouteLocked: Ignoring unknown session.");
+ return;
+ }
+
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::deselectRouteOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, sessionId, route));
+ }
+
+ private void transferClientRouteLocked(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord == null) {
+ Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown manager.");
+ return;
+ }
+ //TODO: we shouldn't ignore selecting request for unknown clients. (RCN?)
+ Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+ .findClientforSessionLocked(sessionId);
+ if (clientRecord == null) {
+ Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown session.");
+ return;
+ }
+
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::transferToRouteOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, sessionId, route));
+ }
+
+ private void releaseClientSessionLocked(IMediaRouter2Manager manager, String sessionId) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord == null) {
+ Slog.w(TAG, "releaseClientSessionLocked: Ignoring unknown manager.");
+ return;
+ }
+
+ Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+ .findClientforSessionLocked(sessionId);
+
+ managerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::releaseSessionOnHandler,
+ managerRecord.mUserRecord.mHandler,
+ clientRecord, sessionId));
+ }
+
private void disposeUserIfNeededLocked(UserRecord userRecord) {
// If there are no records left and the user is no longer current then go ahead
// and purge the user record and all of its associated state. If the user is current
@@ -896,6 +1032,11 @@
this, provider, sessionInfo));
}
+ @Nullable
+ public Client2Record findClientforSessionLocked(@NonNull String sessionId) {
+ return mSessionToClientMap.get(sessionId);
+ }
+
//TODO: notify session info updates
private void onProviderStateChangedOnHandler(MediaRoute2Provider provider) {
int providerIndex = getProviderInfoIndex(provider.getUniqueId());
@@ -1128,9 +1269,10 @@
private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo, long requestId) {
+ notifySessionCreatedToManagers(getManagers(), sessionInfo);
+
if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
// The session is created without any matching request.
- // TODO: Tell managers for the session creation
return;
}
@@ -1176,7 +1318,6 @@
notifySessionCreated(matchingRequest.mClientRecord,
sessionInfo, toClientRequestId(requestId));
mSessionToClientMap.put(sessionInfo.getId(), client2Record);
- // TODO: Tell managers for the session creation
}
private void onSessionCreationFailedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -1205,29 +1346,29 @@
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
+ List<IMediaRouter2Manager> managers = getManagers();
+ notifySessionInfosChangedToManagers(managers);
Client2Record client2Record = mSessionToClientMap.get(
sessionInfo.getId());
if (client2Record == null) {
Slog.w(TAG, "No matching client found for session=" + sessionInfo);
- // TODO: Tell managers for the session update
return;
}
notifySessionInfoChanged(client2Record, sessionInfo);
- // TODO: Tell managers for the session update
}
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
+ List<IMediaRouter2Manager> managers = getManagers();
+ notifySessionInfosChangedToManagers(managers);
Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
if (client2Record == null) {
Slog.w(TAG, "No matching client found for session=" + sessionInfo);
- // TODO: Tell managers for the session release
return;
}
notifySessionReleased(client2Record, sessionInfo);
- // TODO: Tell managers for the session release
}
private void notifySessionCreated(Client2Record clientRecord,
@@ -1333,11 +1474,6 @@
}
}
- // TODO: Remove notifyRouteSelected* methods
- private void notifyRouteSelectedToClient(IMediaRouter2Client client,
- MediaRoute2Info route, int reason, Bundle controlHints) {
- }
-
private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
List<MediaRoute2Info> routes) {
for (IMediaRouter2Client client : clients) {
@@ -1419,6 +1555,29 @@
}
}
+ private void notifySessionCreatedToManagers(List<IMediaRouter2Manager> managers,
+ RoutingSessionInfo sessionInfo) {
+ for (IMediaRouter2Manager manager : managers) {
+ try {
+ manager.notifySessionCreated(sessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "notifySessionCreatedToManagers: "
+ + "failed to notify. Manager probably died.", ex);
+ }
+ }
+ }
+
+ private void notifySessionInfosChangedToManagers(List<IMediaRouter2Manager> managers) {
+ for (IMediaRouter2Manager manager : managers) {
+ try {
+ manager.notifySessionsUpdated();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "notifySessionInfosChangedToManagers: "
+ + "failed to notify. Manager probably died.", ex);
+ }
+ }
+ }
+
private void updateClientUsage(Client2Record clientRecord) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
@@ -1432,8 +1591,6 @@
}
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifyRouteSelected(clientRecord.mPackageName,
- clientRecord.mSelectedRoute);
manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
clientRecord.mDiscoveryPreference.getPreferredFeatures());
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index aad9636..b33f5aa 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -556,6 +556,33 @@
return mService2.getActiveSessions(manager);
}
+ // Binder call
+ @Override
+ public void selectClientRoute(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ mService2.selectClientRoute(manager, sessionId, route);
+ }
+
+ // Binder call
+ @Override
+ public void deselectClientRoute(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ mService2.deselectClientRoute(manager, sessionId, route);
+ }
+
+ // Binder call
+ @Override
+ public void transferToClientRoute(IMediaRouter2Manager manager, String sessionId,
+ MediaRoute2Info route) {
+ mService2.transferToClientRoute(manager, sessionId, route);
+ }
+
+ // Binder call
+ @Override
+ public void releaseClientSession(IMediaRouter2Manager manager, String sessionId) {
+ mService2.releaseClientSession(manager, sessionId);
+ }
+
void restoreBluetoothA2dp() {
try {
boolean a2dpOn;