summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/IMediaRoute2Provider.aidl2
-rw-r--r--media/java/android/media/IMediaRoute2ProviderClient.aidl1
-rw-r--r--media/java/android/media/IMediaRouter2Client.aidl1
-rw-r--r--media/java/android/media/IMediaRouterService.aidl3
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java39
-rw-r--r--media/java/android/media/MediaRouter2.java240
-rw-r--r--media/java/android/media/RouteSessionInfo.java20
-rw-r--r--media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java31
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java197
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2Provider.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java32
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java225
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java19
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java2
15 files changed, 786 insertions, 36 deletions
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index cfe9b394823d..02a381669893 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -30,7 +30,7 @@ oneway interface IMediaRoute2Provider {
void selectRoute(int sessionId, String routeId);
void deselectRoute(int sessionId, String routeId);
- void transferRoute(int sessionId, String routeId);
+ void transferToRoute(int sessionId, String routeId);
void notifyControlRequestSent(String id, in Intent request);
void requestSetVolume(String id, int volume);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index b1f93a3ac82d..bcb2336dbf78 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -28,4 +28,5 @@ oneway interface IMediaRoute2ProviderClient {
void updateState(in MediaRoute2ProviderInfo providerInfo,
in List<RouteSessionInfo> sessionInfos);
void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
+ void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index 293116c15d11..18a6428f570e 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -29,4 +29,5 @@ oneway interface IMediaRouter2Client {
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
+ void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index e89fc1e6e372..4b7d802dbe9e 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -53,6 +53,9 @@ interface IMediaRouterService {
void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
String controlCategory, int requestId);
void setControlCategories(IMediaRouter2Client client, in List<String> categories);
+ void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
+ void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
+ void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void registerManager(IMediaRouter2Manager manager, String packageName);
void unregisterManager(IMediaRouter2Manager manager);
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 9b44c6e583a2..99bd1dcde3bb 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -134,7 +134,6 @@ public abstract class MediaRoute2ProviderService extends Service {
*/
public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
int sessionId = sessionInfo.getSessionId();
if (sessionInfo.getSelectedRoutes().isEmpty()) {
releaseSession(sessionId);
@@ -153,6 +152,35 @@ public abstract class MediaRoute2ProviderService extends Service {
}
/**
+ * Notifies the session is changed.
+ *
+ * TODO: This method is temporary, only created for tests. Remove when the alternative is ready.
+ * @hide
+ */
+ public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ int sessionId = sessionInfo.getSessionId();
+ synchronized (mSessionLock) {
+ if (mSessionInfo.containsKey(sessionId)) {
+ mSessionInfo.put(sessionId, sessionInfo);
+ } else {
+ Log.w(TAG, "Ignoring unknown session info.");
+ return;
+ }
+ }
+
+ if (mClient == null) {
+ return;
+ }
+ try {
+ mClient.notifySessionInfoChanged(sessionInfo);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session info changed.");
+ }
+ }
+
+ /**
* Notifies clients of that the session is created and ready for use. If the session can be
* controlled, pass a {@link Bundle} that contains how to control it.
*
@@ -162,7 +190,8 @@ public abstract class MediaRoute2ProviderService extends Service {
* session creation is failed.
* @param requestId id of the previous request to create this session
*/
- //TODO: fail reason?
+ // TODO: fail reason?
+ // TODO: Maybe better to create notifySessionCreationFailed?
public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
if (sessionInfo != null) {
int sessionId = sessionInfo.getSessionId();
@@ -269,7 +298,7 @@ public abstract class MediaRoute2ProviderService extends Service {
* @param sessionId id of the session
* @param routeId id of the route
*/
- public abstract void onTransferRoute(int sessionId, @NonNull String routeId);
+ public abstract void onTransferToRoute(int sessionId, @NonNull String routeId);
/**
* Updates provider info and publishes routes and session info.
@@ -364,11 +393,11 @@ public abstract class MediaRoute2ProviderService extends Service {
}
@Override
- public void transferRoute(int sessionId, String routeId) {
+ public void transferToRoute(int sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
- mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferRoute,
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 600e630c9b79..0be49d88d10c 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -32,6 +32,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -51,8 +52,12 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* A new Media Router
* @hide
+ *
+ * TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler)
+ * Not only MediaRouter2, but also to service / manager / provider.
*/
public class MediaRouter2 {
+
/** @hide */
@Retention(SOURCE)
@IntDef(value = {
@@ -119,6 +124,8 @@ public class MediaRouter2 {
@GuardedBy("sLock")
private Client2 mClient;
+ private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>();
+
private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
final Handler mHandler;
@@ -164,6 +171,21 @@ public class MediaRouter2 {
}
/**
+ * Returns whether any route in {@code routeList} has a same unique ID with given route.
+ *
+ * @hide
+ */
+ public static boolean checkRouteListContainsRouteId(@NonNull List<MediaRoute2Info> routeList,
+ @NonNull String uniqueRouteId) {
+ for (MediaRoute2Info info : routeList) {
+ if (TextUtils.equals(uniqueRouteId, info.getUniqueId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Registers a callback to discover routes and to receive events when they change.
*/
public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
@@ -548,7 +570,6 @@ public class MediaRouter2 {
MediaRoute2Info requestedRoute = matchingRequest.mRoute;
String requestedControlCategory = matchingRequest.mControlCategory;
- // TODO: Also check provider ID when RouteSessionInfo#getProviderId() is introduced.
if (sessionInfo == null) {
// TODO: We may need to distinguish between failure and rejection.
// One way can be introducing 'reason'.
@@ -565,12 +586,45 @@ public class MediaRouter2 {
+ ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+ ")");
notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+ } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) {
+ Log.w(TAG, "The session's provider ID does not match the requested route's. "
+ + "(requested route's providerId=" + requestedRoute.getProviderId()
+ + ", actual providerId=" + sessionInfo.getProviderId()
+ + ")");
+ notifySessionCreationFailed(requestedRoute, requestedControlCategory);
} else {
RouteSessionController controller = new RouteSessionController(sessionInfo);
+ mSessionControllers.put(controller.getUniqueSessionId(), controller);
notifySessionCreated(controller);
}
}
+ void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) {
+ if (sessionInfo == null) {
+ Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo.");
+ return;
+ }
+
+ RouteSessionController matchingController = mSessionControllers.get(
+ sessionInfo.getUniqueSessionId());
+
+ if (matchingController == null) {
+ Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId="
+ + sessionInfo.getUniqueSessionId());
+ return;
+ }
+
+ RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+ if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
+ Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old="
+ + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
+ return;
+ }
+
+ matchingController.setRouteSessionInfo(sessionInfo);
+ notifySessionInfoChanged(matchingController, oldInfo, sessionInfo);
+ }
+
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
@@ -606,6 +660,15 @@ public class MediaRouter2 {
}
}
+ private void notifySessionInfoChanged(RouteSessionController controller,
+ RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ for (SessionCallbackRecord record: mSessionCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mSessionCallback.onSessionInfoChanged(
+ controller, oldInfo, newInfo));
+ }
+ }
+
/**
* Callback for receiving events about media route discovery.
*/
@@ -647,7 +710,7 @@ public class MediaRouter2 {
*
* @param controller the controller to control the created session
*/
- public void onSessionCreated(RouteSessionController controller) {}
+ public void onSessionCreated(@NonNull RouteSessionController controller) {}
/**
* Called when the session creation request failed.
@@ -655,14 +718,21 @@ public class MediaRouter2 {
* @param requestedRoute the route info which was used for the request
* @param requestedControlCategory the control category which was used for the request
*/
- public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedControlCategory) {}
+ public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute,
+ @NonNull String requestedControlCategory) {}
/**
* Called when the session info has changed.
+ *
+ * @param oldInfo the session info before the session changed.
+ * @prarm newInfo the changed session info
+ *
+ * TODO: (Discussion) Do we really need newInfo? The controller has the newInfo.
+ * However. there can be timing issue if there is no newInfo.
*/
- void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo newInfo,
- RouteSessionInfo oldInfo) {}
+ public void onSessionInfoChanged(@NonNull RouteSessionController controller,
+ @NonNull RouteSessionInfo oldInfo,
+ @NonNull RouteSessionInfo newInfo) {}
/**
* Called when the session is released. Session can be released by the controller using
@@ -671,13 +741,16 @@ public class MediaRouter2 {
*
* TODO: When Provider#notifySessionDestroyed is introduced, add @see for the method.
*/
- void onSessionReleased(RouteSessionController controller, int reason, boolean shouldStop) {}
+ public void onSessionReleased(@NonNull RouteSessionController controller, int reason,
+ boolean shouldStop) {}
}
/**
* A class to control media route session in media route provider.
* For example, selecting/deselcting/transferring routes to session can be done through this
* class. Instances are created by {@link MediaRouter2}.
+ *
+ * TODO: Need to add toString()
*/
public final class RouteSessionController {
private final Object mLock = new Object();
@@ -693,7 +766,7 @@ public class MediaRouter2 {
}
/**
- * @return the ID of this controller
+ * @return the ID of the session
*/
public int getSessionId() {
synchronized (mLock) {
@@ -702,6 +775,17 @@ public class MediaRouter2 {
}
/**
+ * @return the unique ID of the session
+ * @hide
+ */
+ @NonNull
+ public String getUniqueSessionId() {
+ synchronized (mLock) {
+ return mSessionInfo.getUniqueSessionId();
+ }
+ }
+
+ /**
* @return the category of routes that the session includes.
*/
@NonNull
@@ -776,26 +860,127 @@ public class MediaRouter2 {
}
/**
- * Selects a route for the remote session. Route add requests that are currently in
- * {@link #getSelectedRoutes()} will be ignored.
+ * 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()
* @see SessionCallback#onSessionInfoChanged
*/
public void selectRoute(@NonNull MediaRoute2Info route) {
- // TODO: Implement this when the actual connection logic is implemented.
+ Objects.requireNonNull(route, "route must not be null");
+
+ List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
+ if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
+ return;
+ }
+
+ List<MediaRoute2Info> selectableRoutes = getSelectableRoutes();
+ if (!checkRouteListContainsRouteId(selectableRoutes, route.getUniqueId())) {
+ Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
+ return;
+ }
+
+ Client2 client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.selectRoute(mClient, getUniqueSessionId(), route);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to select route for session.", ex);
+ }
+ }
}
/**
- * Deselects a route from the remote session. Media may be stopped on those devices.
- * Route removal requests that are not currently in {@link #getSelectedRoutes()} will be
- * ignored.
+ * 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()
* @see SessionCallback#onSessionInfoChanged
*/
public void deselectRoute(@NonNull MediaRoute2Info route) {
- // TODO: Implement this when the actual connection logic is implemented.
+ Objects.requireNonNull(route, "route must not be null");
+
+ List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
+ if (!checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
+ return;
+ }
+
+ List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes();
+ if (!checkRouteListContainsRouteId(deselectableRoutes, route.getUniqueId())) {
+ Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
+ return;
+ }
+
+ Client2 client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.deselectRoute(mClient, getUniqueSessionId(), 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()
+ * @see SessionCallback#onSessionInfoChanged
+ */
+ public void transferToRoute(@NonNull MediaRoute2Info route) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
+ if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ Log.w(TAG, "Ignoring transferring to a route that is already added. route="
+ + route);
+ return;
+ }
+
+ List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
+ if (!checkRouteListContainsRouteId(transferrableRoutes, route.getUniqueId())) {
+ Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+ return;
+ }
+
+ Client2 client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.transferToRoute(mClient, getUniqueSessionId(), route);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to transfer to route for session.", ex);
+ }
+ }
}
/**
@@ -816,6 +1001,25 @@ public class MediaRouter2 {
// TODO: Use stopMedia variable when the actual connection logic is implemented.
}
+ /**
+ * @hide
+ */
+ @NonNull
+ public RouteSessionInfo getRouteSessionInfo() {
+ synchronized (mLock) {
+ return mSessionInfo;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
+ synchronized (mLock) {
+ mSessionInfo = info;
+ }
+ }
+
private List<MediaRoute2Info> getRoutesWithIdsLocked(List<String> routeIds) {
List<MediaRoute2Info> routes = new ArrayList<>();
synchronized (mLock) {
@@ -927,5 +1131,11 @@ public class MediaRouter2 {
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
MediaRouter2.this, sessionInfo, requestId));
}
+
+ @Override
+ public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler,
+ MediaRouter2.this, sessionInfo));
+ }
}
}
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
index 4bf9e1206905..b9cf15edb101 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RouteSessionInfo.java
@@ -105,6 +105,13 @@ public class RouteSessionInfo implements Parcelable {
}
/**
+ * Gets non-unique session id (int) from unique session id (string).
+ */
+ public static int getSessionId(@NonNull String uniqueSessionId, @NonNull String providerId) {
+ return Integer.parseInt(uniqueSessionId.substring(providerId.length() + 1));
+ }
+
+ /**
* Returns whether the session info is valid or not
*/
public boolean isValid() {
@@ -148,6 +155,19 @@ public class RouteSessionInfo implements Parcelable {
}
/**
+ * Gets the unique id of the session.
+ * @hide
+ */
+ @NonNull
+ public String getUniqueSessionId() {
+ StringBuilder sessionIdBuilder = new StringBuilder()
+ .append(mProviderId)
+ .append("/")
+ .append(mSessionId);
+ return sessionIdBuilder.toString();
+ }
+
+ /**
* Gets the list of ids of selected routes for the session. It shouldn't be empty.
*/
@NonNull
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 afca6cfbd99c..04fccc7e0f94 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -40,6 +40,11 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
"route_id3_session_creation_failed";
public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
+ public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
+ "route_id4_to_select_and_deselect";
+ public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
+ public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+ public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
@@ -75,6 +80,14 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
.addSupportedCategory(CATEGORY_SAMPLE)
.build();
+ MediaRoute2Info route4 = new MediaRoute2Info.Builder(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
+ .addSupportedCategory(CATEGORY_SAMPLE)
+ .build();
+ MediaRoute2Info route5 = new MediaRoute2Info.Builder(
+ ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
+ .addSupportedCategory(CATEGORY_SAMPLE)
+ .build();
MediaRoute2Info routeSpecial =
new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY)
.addSupportedCategory(CATEGORY_SAMPLE)
@@ -95,6 +108,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
mRoutes.put(route1.getId(), route1);
mRoutes.put(route2.getId(), route2);
mRoutes.put(route3.getId(), route3);
+ mRoutes.put(route4.getId(), route4);
+ mRoutes.put(route5.getId(), route5);
mRoutes.put(routeSpecial.getId(), routeSpecial);
mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute);
mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
@@ -173,6 +188,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
sessionId, packageName, controlCategory)
.addSelectedRoute(routeId)
+ .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+ .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
.build();
notifySessionCreated(sessionInfo, requestId);
publishRoutes();
@@ -207,9 +224,11 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
.addSelectedRoute(routeId)
+ .removeSelectableRoute(routeId)
+ .addDeselectableRoute(routeId)
.build();
updateSessionInfo(newSessionInfo);
- publishRoutes();
+ notifySessionInfoChanged(newSessionInfo);
}
@Override
@@ -227,20 +246,24 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
.removeSelectedRoute(routeId)
+ .addSelectableRoute(routeId)
+ .removeDeselectableRoute(routeId)
.build();
updateSessionInfo(newSessionInfo);
- publishRoutes();
+ notifySessionInfoChanged(newSessionInfo);
}
@Override
- public void onTransferRoute(int sessionId, String routeId) {
+ public void onTransferToRoute(int sessionId, String routeId) {
RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
.clearSelectedRoutes()
.addSelectedRoute(routeId)
+ .removeDeselectableRoute(routeId)
+ .removeTransferrableRoute(routeId)
.build();
updateSessionInfo(newSessionInfo);
- publishRoutes();
+ notifySessionInfoChanged(newSessionInfo);
}
void maybeDeselectRoute(String routeId) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 0c4c4370d135..10c17dc2c77b 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -30,6 +30,8 @@ import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SPECIA
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
@@ -41,12 +43,14 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
+import android.annotation.NonNull;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.RouteSessionController;
import android.media.MediaRouter2.SessionCallback;
+import android.media.RouteSessionInfo;
import android.net.Uri;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
@@ -284,6 +288,7 @@ public class MediaRouter2Test {
// onSessionCreationFailed should not be called.
assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
+ // TODO: Release controllers
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -329,6 +334,7 @@ public class MediaRouter2Test {
// onSessionCreated should not be called.
assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
+ // TODO: Release controllers
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -389,6 +395,7 @@ public class MediaRouter2Test {
assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller1.getControlCategory()));
assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller2.getControlCategory()));
} finally {
+ // TODO: Release controllers
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -435,11 +442,190 @@ public class MediaRouter2Test {
assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
+ // TODO: Release controllers
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
}
+ // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
+ @Test
+ public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
+ final List<String> sampleControlCategory = new ArrayList<>();
+ sampleControlCategory.add(CATEGORY_SAMPLE);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
+ assertNotNull(routeToCreateSessionWith);
+
+ final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
+ final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1);
+ final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1);
+ final List<RouteSessionController> controllers = new ArrayList<>();
+
+ // Create session with ROUTE_ID1
+ SessionCallback sessionCallback = new SessionCallback() {
+ @Override
+ public void onSessionCreated(RouteSessionController controller) {
+ assertNotNull(controller);
+ assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
+ assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+ controllers.add(controller);
+ onSessionCreatedLatch.countDown();
+ }
+
+ @Override
+ public void onSessionInfoChanged(RouteSessionController controller,
+ RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ if (onSessionCreatedLatch.getCount() != 0
+ || controllers.get(0).getSessionId() != controller.getSessionId()) {
+ return;
+ }
+
+ if (onSessionInfoChangedLatchForSelect.getCount() != 0) {
+ // Check oldInfo
+ assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+ assertEquals(1, oldInfo.getSelectedRoutes().size());
+ assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
+ assertTrue(oldInfo.getSelectableRoutes().contains(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ // Check newInfo
+ assertEquals(controller.getSessionId(), newInfo.getSessionId());
+ assertEquals(2, newInfo.getSelectedRoutes().size());
+ assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
+ assertTrue(newInfo.getSelectedRoutes().contains(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ assertFalse(newInfo.getSelectableRoutes().contains(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ onSessionInfoChangedLatchForSelect.countDown();
+ } else {
+ // Check newInfo
+ assertEquals(controller.getSessionId(), newInfo.getSessionId());
+ assertEquals(1, newInfo.getSelectedRoutes().size());
+ assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
+ assertFalse(newInfo.getSelectedRoutes().contains(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ assertTrue(newInfo.getSelectableRoutes().contains(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ onSessionInfoChangedLatchForDeselect.countDown();
+ }
+ }
+ };
+
+ // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+ RouteCallback routeCallback = new RouteCallback();
+ mRouter2.registerRouteCallback(mExecutor, routeCallback);
+
+ try {
+ mRouter2.registerSessionCallback(mExecutor, sessionCallback);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+ assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RouteSessionController controller = controllers.get(0);
+ assertTrue(getRouteIds(controller.getSelectableRoutes())
+ .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+ MediaRoute2Info routeToSelectAndDeselect = routes.get(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT);
+ assertNotNull(routeToSelectAndDeselect);
+
+ controller.selectRoute(routeToSelectAndDeselect);
+ assertTrue(onSessionInfoChangedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ controller.deselectRoute(routeToSelectAndDeselect);
+ assertTrue(onSessionInfoChangedLatchForDeselect.await(
+ TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ // TODO: Release controllers
+ controllers.clear();
+ mRouter2.unregisterRouteCallback(routeCallback);
+ mRouter2.unregisterSessionCallback(sessionCallback);
+ }
+ }
+
+ @Test
+ public void testRouteSessionControllerTransferToRoute() throws Exception {
+ final List<String> sampleControlCategory = new ArrayList<>();
+ sampleControlCategory.add(CATEGORY_SAMPLE);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
+ assertNotNull(routeToCreateSessionWith);
+
+ final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
+ final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
+ final List<RouteSessionController> controllers = new ArrayList<>();
+
+ // Create session with ROUTE_ID1
+ SessionCallback sessionCallback = new SessionCallback() {
+ @Override
+ public void onSessionCreated(RouteSessionController controller) {
+ assertNotNull(controller);
+ assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
+ assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+ controllers.add(controller);
+ onSessionCreatedLatch.countDown();
+ }
+
+ @Override
+ public void onSessionInfoChanged(RouteSessionController controller,
+ RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ if (onSessionCreatedLatch.getCount() != 0
+ || controllers.get(0).getSessionId() != controller.getSessionId()) {
+ return;
+ }
+
+ // Check oldInfo
+ assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+ assertEquals(1, oldInfo.getSelectedRoutes().size());
+ assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
+ assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
+
+ // Check newInfo
+ assertEquals(controller.getSessionId(), newInfo.getSessionId());
+ assertEquals(1, newInfo.getSelectedRoutes().size());
+ assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
+ assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
+ assertFalse(newInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
+
+ onSessionInfoChangedLatch.countDown();
+ }
+ };
+
+ // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+ RouteCallback routeCallback = new RouteCallback();
+ mRouter2.registerRouteCallback(mExecutor, routeCallback);
+
+ try {
+ mRouter2.registerSessionCallback(mExecutor, sessionCallback);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+ assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RouteSessionController controller = controllers.get(0);
+ assertTrue(getRouteIds(controller.getTransferrableRoutes())
+ .contains(ROUTE_ID5_TO_TRANSFER_TO));
+
+ // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+ MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+ assertNotNull(routeToTransferTo);
+
+ controller.transferToRoute(routeToTransferTo);
+ assertTrue(onSessionInfoChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ // TODO: Release controllers
+ controllers.clear();
+ mRouter2.unregisterRouteCallback(routeCallback);
+ mRouter2.unregisterSessionCallback(sessionCallback);
+ }
+
+ }
+
// Helper for getting routes easily
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
@@ -477,6 +663,17 @@ public class MediaRouter2Test {
}
}
+ /**
+ * Returns a list of IDs (not uniqueId) of the given route list.
+ */
+ List<String> getRouteIds(@NonNull List<MediaRoute2Info> routes) {
+ List<String> result = new ArrayList<>();
+ for (MediaRoute2Info route : routes) {
+ result.add(route.getId());
+ }
+ return result;
+ }
+
void awaitOnRouteChanged(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 768716d40f44..e1a26100a9e0 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -63,6 +63,11 @@ public class MediaRouterManagerTest {
public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
"route_id3_session_creation_failed";
public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
+ public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
+ "route_id4_to_select_and_deselect";
+ public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
+ public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+ public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 3cc18b6fe7d3..f11b70ebbf71 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -52,7 +52,7 @@ abstract class MediaRoute2Provider {
public abstract void selectRoute(int sessionId, MediaRoute2Info route);
public abstract void deselectRoute(int sessionId, MediaRoute2Info route);
- public abstract void transferRoute(int sessionId, MediaRoute2Info route);
+ public abstract void transferToRoute(int sessionId, MediaRoute2Info route);
public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
public abstract void requestSetVolume(MediaRoute2Info route, int volume);
@@ -105,5 +105,8 @@ abstract class MediaRoute2Provider {
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
@Nullable RouteSessionInfo sessionInfo, long requestId);
+ // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes.
+ void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
+ @NonNull RouteSessionInfo sessionInfo);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index f9cfe7d73cac..423001fcb84f 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -109,9 +109,9 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
}
@Override
- public void transferRoute(int sessionId, MediaRoute2Info route) {
+ public void transferToRoute(int sessionId, MediaRoute2Info route) {
if (mConnectionReady) {
- mActiveConnection.transferRoute(sessionId, route.getId());
+ mActiveConnection.transferToRoute(sessionId, route.getId());
}
}
@@ -289,6 +289,18 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
mCallback.onSessionCreated(this, sessionInfo, requestId);
}
+ private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) {
+ if (mActiveConnection != connection) {
+ return;
+ }
+ if (sessionInfo == null) {
+ Slog.w(TAG, "onSessionInfoChanged: Ignoring null sessionInfo sent from "
+ + mComponentName);
+ return;
+ }
+ mCallback.onSessionInfoChanged(this, sessionInfo);
+ }
+
private void disconnect() {
if (mActiveConnection != null) {
mConnectionReady = false;
@@ -363,9 +375,9 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
}
}
- public void transferRoute(int sessionId, String routeId) {
+ public void transferToRoute(int sessionId, String routeId) {
try {
- mProvider.transferRoute(sessionId, routeId);
+ mProvider.transferToRoute(sessionId, routeId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to transfer a session to a route.", ex);
}
@@ -410,6 +422,10 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
requestId));
}
+
+ void postSessionInfoChanged(RouteSessionInfo sessionInfo) {
+ mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo));
+ }
}
private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub {
@@ -439,5 +455,13 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
connection.postSessionCreated(sessionInfo, requestId);
}
}
+
+ @Override
+ public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postSessionInfoChanged(sessionInfo);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d78879f63de5..0bdcc97b95c2 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -52,6 +52,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -186,6 +187,52 @@ class MediaRouter2ServiceImpl {
}
}
+ public void selectRoute(IMediaRouter2Client client, String uniqueSessionId,
+ MediaRoute2Info route) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ selectRouteLocked(client, uniqueSessionId, route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+
+ public void deselectRoute(IMediaRouter2Client client, String uniqueSessionId,
+ MediaRoute2Info route) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ deselectRouteLocked(client, uniqueSessionId, route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void transferToRoute(IMediaRouter2Client client, String uniqueSessionId,
+ MediaRoute2Info route) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ transferToRouteLocked(client, uniqueSessionId, route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void sendControlRequest(@NonNull IMediaRouter2Client client,
@NonNull MediaRoute2Info route, @NonNull Intent request) {
Objects.requireNonNull(client, "client must not be null");
@@ -391,6 +438,45 @@ class MediaRouter2ServiceImpl {
}
}
+ private void selectRouteLocked(@NonNull IMediaRouter2Client client, String uniqueSessionId,
+ @NonNull MediaRoute2Info route) {
+ final IBinder binder = client.asBinder();
+ final Client2Record clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::selectRouteOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, uniqueSessionId, route));
+ }
+ }
+
+ private void deselectRouteLocked(@NonNull IMediaRouter2Client client, String uniqueSessionId,
+ @NonNull MediaRoute2Info route) {
+ final IBinder binder = client.asBinder();
+ final Client2Record clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::deselectRouteOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, uniqueSessionId, route));
+ }
+ }
+
+ private void transferToRouteLocked(@NonNull IMediaRouter2Client client, String uniqueSessionId,
+ @NonNull MediaRoute2Info route) {
+ final IBinder binder = client.asBinder();
+ final Client2Record clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::transferToRouteOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, uniqueSessionId, route));
+ }
+ }
+
private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
if (clientRecord != null) {
if (clientRecord.mControlCategories.equals(categories)) {
@@ -707,6 +793,7 @@ class MediaRouter2ServiceImpl {
private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
new CopyOnWriteArrayList<>();
+ private final Map<String, Client2Record> mSessionToClientMap = new ArrayMap<>();
private boolean mRunning;
@@ -758,6 +845,13 @@ class MediaRouter2ServiceImpl {
this, provider, sessionInfo, requestId));
}
+ @Override
+ public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
+ @NonNull RouteSessionInfo sessionInfo) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::updateSession,
+ this, provider, sessionInfo));
+ }
+
//TODO: notify session info updates
private void updateProvider(MediaRoute2Provider provider) {
int providerIndex = getProviderInfoIndex(provider.getUniqueId());
@@ -848,14 +942,14 @@ class MediaRouter2ServiceImpl {
if (provider == null) {
Slog.w(TAG, "Ignoring session creation request since no provider found for"
+ " given route=" + route);
- notifySessionCreationFailed(clientRecord, (int) requestId);
+ notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
return;
}
if (!route.getSupportedCategories().contains(controlCategory)) {
Slog.w(TAG, "Ignoring session creation request since the given route=" + route
+ " doesn't support the given category=" + controlCategory);
- notifySessionCreationFailed(clientRecord, (int) requestId);
+ notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
return;
}
@@ -868,6 +962,95 @@ class MediaRouter2ServiceImpl {
controlCategory, requestId);
}
+ private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
+ String uniqueSessionId, MediaRoute2Info route) {
+ if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
+ "selecting")) {
+ return;
+ }
+
+ final String providerId = route.getProviderId();
+ final MediaRoute2Provider provider = findProvider(providerId);
+ // TODO: Remove this null check when the mMediaProviders are referenced only in handler.
+ if (provider == null) {
+ return;
+ }
+ provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId, providerId), route);
+ }
+
+ private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
+ String uniqueSessionId, MediaRoute2Info route) {
+ if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
+ "deselecting")) {
+ return;
+ }
+
+ final String providerId = route.getProviderId();
+ final MediaRoute2Provider provider = findProvider(providerId);
+ // TODO: Remove this null check when the mMediaProviders are referenced only in handler.
+ if (provider == null) {
+ return;
+ }
+ provider.deselectRoute(
+ RouteSessionInfo.getSessionId(uniqueSessionId, providerId), route);
+ }
+
+ private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
+ String uniqueSessionId, MediaRoute2Info route) {
+ if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
+ "transferring to")) {
+ return;
+ }
+
+ final String providerId = route.getProviderId();
+ final MediaRoute2Provider provider = findProvider(providerId);
+ // TODO: Remove this null check when the mMediaProviders are referenced only in handler.
+ if (provider == null) {
+ return;
+ }
+ provider.transferToRoute(
+ RouteSessionInfo.getSessionId(uniqueSessionId, providerId), route);
+ }
+
+ private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
+ String uniqueSessionId, MediaRoute2Info route, @NonNull String description) {
+ if (route == null) {
+ Slog.w(TAG, "Ignoring " + description + " null route");
+ return false;
+ }
+
+ final String providerId = route.getProviderId();
+ final MediaRoute2Provider provider = findProvider(providerId);
+ if (provider == null) {
+ Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
+ + "given route=" + route);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(uniqueSessionId)) {
+ Slog.w(TAG, "Ignoring " + description + " route with empty unique session ID. "
+ + "route=" + route);
+ return false;
+ }
+
+ Client2Record matchingRecord = mSessionToClientMap.get(uniqueSessionId);
+ if (matchingRecord != clientRecord) {
+ Slog.w(TAG, "Ignoring " + description + " route from non-matching client. "
+ + "packageName=" + clientRecord.mPackageName + " route=" + route);
+ return false;
+ }
+
+ try {
+ RouteSessionInfo.getSessionId(uniqueSessionId, providerId);
+ } catch (Exception ex) {
+ Slog.w(TAG, "Failed to get int session id from unique session id. "
+ + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
+ return false;
+ }
+
+ return true;
+ }
+
private void handleCreateSessionResultOnHandler(
@NonNull MediaRoute2Provider provider, @Nullable RouteSessionInfo sessionInfo,
long requestId) {
@@ -898,7 +1081,8 @@ class MediaRouter2ServiceImpl {
if (sessionInfo == null) {
// Failed
- notifySessionCreationFailed(matchingRequest.mClientRecord, (int) requestId);
+ notifySessionCreationFailed(matchingRequest.mClientRecord,
+ toClientRequestId(requestId));
return;
}
@@ -908,6 +1092,8 @@ class MediaRouter2ServiceImpl {
String originalRouteId = matchingRequest.mRoute.getId();
String originalCategory = matchingRequest.mControlCategory;
+ Client2Record client2Record = matchingRequest.mClientRecord;
+
if (!sessionInfoWithProviderId.getSelectedRoutes().contains(originalRouteId)
|| !TextUtils.equals(originalCategory,
sessionInfoWithProviderId.getControlCategory())) {
@@ -915,16 +1101,35 @@ class MediaRouter2ServiceImpl {
+ " originalRouteId=" + originalRouteId
+ ", originalCategory=" + originalCategory + ", requestId=" + requestId
+ ", sessionInfo=" + sessionInfoWithProviderId);
- notifySessionCreationFailed(matchingRequest.mClientRecord, (int) requestId);
+ notifySessionCreationFailed(matchingRequest.mClientRecord,
+ toClientRequestId(requestId));
return;
}
// Succeeded
notifySessionCreated(matchingRequest.mClientRecord,
- sessionInfoWithProviderId, (int) requestId);
+ sessionInfoWithProviderId, toClientRequestId(requestId));
+ mSessionToClientMap.put(sessionInfoWithProviderId.getUniqueSessionId(), client2Record);
// TODO: Tell managers for the session creation
}
+ private void updateSession(@NonNull MediaRoute2Provider provider,
+ @NonNull RouteSessionInfo sessionInfo) {
+ RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
+ .setProviderId(provider.getUniqueId())
+ .build();
+
+ Client2Record client2Record = mSessionToClientMap.get(
+ sessionInfoWithProviderId.getUniqueSessionId());
+ if (client2Record == null) {
+ Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
+ // TODO: Tell managers for the session update
+ return;
+ }
+ notifySessionInfoChanged(client2Record, sessionInfoWithProviderId);
+ // TODO: Tell managers for the session update
+ }
+
private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
int requestId) {
try {
@@ -944,6 +1149,16 @@ class MediaRouter2ServiceImpl {
}
}
+ private void notifySessionInfoChanged(Client2Record clientRecord,
+ RouteSessionInfo sessionInfo) {
+ try {
+ clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify client of the session info change."
+ + " Client probably died.", ex);
+ }
+ }
+
private void sendControlRequest(MediaRoute2Info route, Intent request) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 8a0d2324cc43..3e2bf4e66aaa 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -465,6 +465,25 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
+ public void selectRoute(IMediaRouter2Client client, String sessionId, MediaRoute2Info route) {
+ mService2.selectRoute(client, sessionId, route);
+ }
+
+ // Binder call
+ @Override
+ public void deselectRoute(IMediaRouter2Client client, String sessionId, MediaRoute2Info route) {
+ mService2.deselectRoute(client, sessionId, route);
+ }
+
+ // Binder call
+ @Override
+ public void transferToRoute(IMediaRouter2Client client, String sessionId,
+ MediaRoute2Info route) {
+ mService2.transferToRoute(client, sessionId, route);
+ }
+
+ // Binder call
+ @Override
public void sendControlRequest(IMediaRouter2Client client, MediaRoute2Info route,
Intent request) {
mService2.sendControlRequest(client, route, request);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 263af70a7cad..8fdfcbfb2ad9 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -112,7 +112,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
@Override
- public void transferRoute(int sessionId, MediaRoute2Info route) {
+ public void transferToRoute(int sessionId, MediaRoute2Info route) {
//TODO: implement method
}