diff options
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 } |