From a3365d753770169c853a98c1018734bd7dc14686 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Thu, 20 Feb 2020 13:46:45 +0900 Subject: [PATCH] MediaRouter: Cleanup MediaRouter2Manager APIs As a following CL, RoutingController will be removed. Instead, MediaRouter2Manager will use RoutingSesionInfo. This CL adds necessary methods for that. Added Callback#onTransferred and #onTransferFailed. Bug: 150571233 Test: atest mediaroutertest Change-Id: If2c23c483f80b6c93a14d1f1f93dfc40463ede1a --- media/java/android/media/MediaRouter2.java | 2 +- .../android/media/MediaRouter2Manager.java | 477 +++++++++++------- .../android/media/RoutingSessionInfo.java | 13 +- .../MediaRouter2ManagerTest.java | 12 +- 4 files changed, 325 insertions(+), 179 deletions(-) diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 68df7d2b192ef..fd2408935ffff 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -1071,7 +1071,7 @@ public class MediaRouter2 { try { mMediaRouterService.deselectRouteWithRouter2(stub, getId(), route); } catch (RemoteException ex) { - Log.e(TAG, "Unable to remove route from session.", ex); + Log.e(TAG, "Unable to deselect route from session.", ex); } } } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index ff2c863aa7b0f..fb45ae1704611 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -44,8 +44,10 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** + * A class that monitors and controls media routing of other apps. * @hide */ public class MediaRouter2Manager { @@ -200,11 +202,25 @@ public class MediaRouter2Manager { return routes; } + /** + * Gets the system routing session associated with no specific application. + */ + @NonNull + public RoutingSessionInfo getSystemRoutingSession() { + for (RoutingSessionInfo sessionInfo : getActiveSessions()) { + if (sessionInfo.isSystemSession()) { + return sessionInfo; + } + } + throw new IllegalStateException("No system routing session"); + } + /** * Gets routing sessions of an application with the given package name. - * The first element of the returned list is the system routing controller. + * The first element of the returned list is the system routing session. * - * @see MediaRouter2#getSystemController() + * @param packageName the package name of the application that is routing. + * @see #getSystemRoutingSession() */ @NonNull public List getRoutingSessions(@NonNull String packageName) { @@ -213,8 +229,11 @@ public class MediaRouter2Manager { List sessions = new ArrayList<>(); for (RoutingSessionInfo sessionInfo : getActiveSessions()) { - if (sessionInfo.isSystemSession() - || TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) { + if (sessionInfo.isSystemSession()) { + sessions.add(new RoutingSessionInfo.Builder(sessionInfo) + .setClientPackageName(packageName) + .build()); + } else if (TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) { sessions.add(sessionInfo); } } @@ -223,10 +242,15 @@ public class MediaRouter2Manager { /** * Gets the list of all active routing sessions. + *

* The first element of the list is the system routing session containing * phone speakers, wired headset, Bluetooth devices. * The system routing session is shared by apps such that controlling it will affect * all apps. + * If you want to transfer media of an application, use {@link #getRoutingSessions(String)}. + * + * @see #getRoutingSessions(String) + * @see #getSystemRoutingSession() */ @NonNull public List getActiveSessions() { @@ -258,31 +282,46 @@ public class MediaRouter2Manager { /** * Selects media route for the specified package name. - * - * If the given route is {@link RoutingController#getTransferableRoutes() a transferable - * route} of a routing session of the application, the session will be transferred to - * the route. If not, a new routing session will be created. - * - * @param packageName the package name of the application that should change it's media route - * @param route the route to be selected. */ public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) { Objects.requireNonNull(packageName, "packageName must not be null"); Objects.requireNonNull(route, "route must not be null"); - boolean transferred = false; - //TODO: instead of release all controllers, add an API to specify controllers that - // should be released (or is the system controller). - for (RoutingSessionInfo sessionInfo : getRoutingSessions(packageName)) { - if (!transferred && sessionInfo.getTransferableRoutes().contains(route.getId())) { - new RoutingController(sessionInfo).transferToRoute(route); - transferred = true; - } else if (!sessionInfo.isSystemSession()) { - new RoutingController(sessionInfo).release(); - } + List sessionInfos = getRoutingSessions(packageName); + RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1); + transfer(targetSession, route); + } + + /** + * Transfers a routing session to a media route. + *

{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called + * depending on the result. + * + * @param sessionInfo the routing session info to transfer + * @param route the route transfer to + * + * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo) + * @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info) + */ + public void transfer(@NonNull RoutingSessionInfo sessionInfo, + @Nullable MediaRoute2Info route) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + if (route == null) { + releaseSession(sessionInfo); + return; } - if (transferred) { + //TODO: Ignore unknown route. + if (sessionInfo.getTransferableRoutes().contains(route.getId())) { + //TODO: callbacks must be called after this. + transferToRoute(sessionInfo, route); + return; + } + + if (TextUtils.isEmpty(sessionInfo.getClientPackageName())) { + Log.w(TAG, "transfer: Ignoring transfer without package name."); + notifyTransferFailed(sessionInfo, route); return; } @@ -294,7 +333,7 @@ public class MediaRouter2Manager { try { int requestId = mNextRequestId.getAndIncrement(); mMediaRouterService.requestCreateSessionWithManager( - client, packageName, route, requestId); + client, sessionInfo.getClientPackageName(), route, requestId); //TODO: release the previous session? } catch (RemoteException ex) { Log.e(TAG, "Unable to select media route", ex); @@ -451,6 +490,18 @@ public class MediaRouter2Manager { } } + void notifyTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) { + for (CallbackRecord record : mCallbackRecords) { + record.mExecutor.execute(() -> record.mCallback.onTransferred(oldSession, newSession)); + } + } + + void notifyTransferFailed(RoutingSessionInfo sessionInfo, MediaRoute2Info route) { + for (CallbackRecord record : mCallbackRecords) { + record.mExecutor.execute(() -> record.mCallback.onTransferFailed(sessionInfo, route)); + } + } + void updatePreferredFeatures(String packageName, List preferredFeatures) { List prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures); if ((prevFeatures == null && preferredFeatures.size() == 0) @@ -474,6 +525,204 @@ public class MediaRouter2Manager { return new RoutingController(sessionInfo); } + /** + * Gets the unmodifiable list of selected routes for the session. + */ + @NonNull + public List getSelectedRoutes(@NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + List routeIds = sessionInfo.getSelectedRoutes(); + return getRoutesWithIds(routeIds); + } + + /** + * Gets the unmodifiable list of selectable routes for the session. + */ + @NonNull + public List getSelectableRoutes(@NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + List routeIds = sessionInfo.getSelectableRoutes(); + return getRoutesWithIds(routeIds); + } + + /** + * Gets the unmodifiable list of deselectable routes for the session. + */ + @NonNull + public List getDeselectableRoutes(@NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + List routeIds = sessionInfo.getDeselectableRoutes(); + return getRoutesWithIds(routeIds); + } + + /** + * Selects a route for the remote session. After a route is selected, the media is expected + * to be played to the all the selected routes. This is different from {@link + * #transfer(RoutingSessionInfo, MediaRoute2Info)} transferring to a route}, + * where the media is expected to 'move' from one route to another. + *

+ * The given route must satisfy all of the following conditions: + *

    + *
  • it should not be included in {@link #getSelectedRoutes(RoutingSessionInfo)}
  • + *
  • it should be included in {@link #getSelectableRoutes(RoutingSessionInfo)}
  • + *
+ * If the route doesn't meet any of above conditions, it will be ignored. + * + * @see #getSelectedRoutes(RoutingSessionInfo) + * @see #getSelectableRoutes(RoutingSessionInfo) + * @see Callback#onSessionsUpdated() + */ + public void selectRoute(@NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + Objects.requireNonNull(route, "route must not be null"); + + if (sessionInfo.getSelectedRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route); + return; + } + + if (!sessionInfo.getSelectableRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring selecting a non-selectable route=" + route); + return; + } + + Client client; + synchronized (sLock) { + client = mClient; + } + if (client != null) { + try { + int requestId = mNextRequestId.getAndIncrement(); + mMediaRouterService.selectRouteWithManager( + mClient, sessionInfo.getId(), route, requestId); + } catch (RemoteException ex) { + Log.e(TAG, "selectRoute: Failed to send a request.", ex); + } + } + } + + /** + * Deselects a route from the remote session. After a route is deselected, the media is + * expected to be stopped on the deselected routes. + *

+ * The given route must satisfy all of the following conditions: + *

    + *
  • it should be included in {@link #getSelectedRoutes(RoutingSessionInfo)}
  • + *
  • it should be included in {@link #getDeselectableRoutes(RoutingSessionInfo)}
  • + *
+ * If the route doesn't meet any of above conditions, it will be ignored. + * + * @see #getSelectedRoutes(RoutingSessionInfo) + * @see #getDeselectableRoutes(RoutingSessionInfo) + * @see Callback#onSessionsUpdated() + */ + public void deselectRoute(@NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + Objects.requireNonNull(route, "route must not be null"); + + if (!sessionInfo.getSelectedRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route); + return; + } + + if (!sessionInfo.getDeselectableRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route); + return; + } + + Client client; + synchronized (sLock) { + client = mClient; + } + if (client != null) { + try { + int requestId = mNextRequestId.getAndIncrement(); + mMediaRouterService.deselectRouteWithManager( + mClient, sessionInfo.getId(), route, requestId); + } catch (RemoteException ex) { + Log.e(TAG, "deselectRoute: Failed to send a request.", ex); + } + } + } + + /** + * Transfers to a given route for the remote session. + * + * @hide + */ + void transferToRoute(@NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + Objects.requireNonNull(route, "route must not be null"); + + if (sessionInfo.getSelectedRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring transferring to a route that is already added. route=" + + route); + return; + } + + if (!sessionInfo.getTransferableRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route); + return; + } + + Client client; + synchronized (sLock) { + client = mClient; + } + if (client != null) { + try { + int requestId = mNextRequestId.getAndIncrement(); + mMediaRouterService.transferToRouteWithManager( + mClient, sessionInfo.getId(), route, requestId); + } catch (RemoteException ex) { + Log.e(TAG, "transferToRoute: Failed to send a request.", ex); + } + } + } + + /** + * Requests releasing a session. + *

+ * If a session is released, any operation on the session will be ignored. + * {@link Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)} with {@code null} + * session will be called when the session is released. + *

+ * + * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo) + */ + public void releaseSession(@NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + Client client; + synchronized (sLock) { + client = mClient; + } + if (client != null) { + try { + int requestId = mNextRequestId.getAndIncrement(); + mMediaRouterService.releaseSessionWithManager( + mClient, sessionInfo.getId(), requestId); + } catch (RemoteException ex) { + Log.e(TAG, "releaseSession: Failed to send a request", ex); + } + } + } + + private List getRoutesWithIds(List routeIds) { + synchronized (sLock) { + return routeIds.stream().map(mRoutes::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + } + + //TODO: Remove this. /** * A class to control media routing session in media route provider. * With routing controller, an application can select a route into the session or deselect @@ -488,6 +737,15 @@ public class MediaRouter2Manager { mSessionInfo = sessionInfo; } + /** + * Releases the session + */ + public void release() { + synchronized (mControllerLock) { + releaseSession(mSessionInfo); + } + } + /** * Gets the ID of the session */ @@ -523,11 +781,7 @@ public class MediaRouter2Manager { */ @NonNull public List getSelectedRoutes() { - List routeIds; - synchronized (mControllerLock) { - routeIds = mSessionInfo.getSelectedRoutes(); - } - return getRoutesWithIds(routeIds); + return MediaRouter2Manager.this.getSelectedRoutes(mSessionInfo); } /** @@ -535,11 +789,7 @@ public class MediaRouter2Manager { */ @NonNull public List getSelectableRoutes() { - List routeIds; - synchronized (mControllerLock) { - routeIds = mSessionInfo.getSelectableRoutes(); - } - return getRoutesWithIds(routeIds); + return MediaRouter2Manager.this.getSelectableRoutes(mSessionInfo); } /** @@ -547,11 +797,7 @@ public class MediaRouter2Manager { */ @NonNull public List getDeselectableRoutes() { - List routeIds; - synchronized (mControllerLock) { - routeIds = mSessionInfo.getDeselectableRoutes(); - } - return getRoutesWithIds(routeIds); + return MediaRouter2Manager.this.getDeselectableRoutes(mSessionInfo); } /** @@ -579,35 +825,7 @@ public class MediaRouter2Manager { * @see #getSelectableRoutes() */ public void selectRoute(@NonNull MediaRoute2Info route) { - Objects.requireNonNull(route, "route must not be null"); - - RoutingSessionInfo sessionInfo; - synchronized (mControllerLock) { - sessionInfo = mSessionInfo; - } - if (sessionInfo.getSelectedRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route); - return; - } - - if (!sessionInfo.getSelectableRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring selecting a non-selectable route=" + route); - return; - } - - Client client; - synchronized (sLock) { - client = mClient; - } - if (client != null) { - try { - int requestId = mNextRequestId.getAndIncrement(); - mMediaRouterService.selectRouteWithManager( - mClient, getSessionId(), route, requestId); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to select route for session.", ex); - } - } + MediaRouter2Manager.this.selectRoute(mSessionInfo, route); } /** @@ -623,104 +841,19 @@ public class MediaRouter2Manager { * @see #getDeselectableRoutes() */ public void deselectRoute(@NonNull MediaRoute2Info route) { - Objects.requireNonNull(route, "route must not be null"); - RoutingSessionInfo sessionInfo; - synchronized (mControllerLock) { - sessionInfo = mSessionInfo; - } - - if (!sessionInfo.getSelectedRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route); - return; - } - - if (!sessionInfo.getDeselectableRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route); - return; - } - - Client client; - synchronized (sLock) { - client = mClient; - } - if (client != null) { - try { - int requestId = mNextRequestId.getAndIncrement(); - mMediaRouterService.deselectRouteWithManager( - mClient, getSessionId(), route, requestId); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to remove route from session.", ex); - } - } + MediaRouter2Manager.this.deselectRoute(mSessionInfo, route); } /** - * Transfers to a given route for the remote session. The given route must satisfy - * all of the following conditions: - *
    - *
  • ID should not be included in {@link #getSelectedRoutes()}
  • - *
  • ID should be included in {@link #getTransferableRoutes()}
  • - *
- * If the route doesn't meet any of above conditions, it will be ignored. - * - * @see #getSelectedRoutes() - * @see #getTransferableRoutes() + * Transfers session to the given rotue. */ public void transferToRoute(@NonNull MediaRoute2Info route) { - Objects.requireNonNull(route, "route must not be null"); - RoutingSessionInfo sessionInfo; - synchronized (mControllerLock) { - sessionInfo = mSessionInfo; - } - - if (sessionInfo.getSelectedRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring transferring to a route that is already added. route=" - + route); - return; - } - - if (!sessionInfo.getTransferableRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route); - return; - } - - Client client; - synchronized (sLock) { - client = mClient; - } - if (client != null) { - try { - int requestId = mNextRequestId.getAndIncrement(); - mMediaRouterService.transferToRouteWithManager( - mClient, getSessionId(), route, requestId); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to transfer to route for session.", ex); - } - } - } - - /** - * Release this session. - * Any operation on this session after calling this method will be ignored. - */ - public void release() { - Client client; - synchronized (sLock) { - client = mClient; - } - if (client != null) { - try { - int requestId = mNextRequestId.getAndIncrement(); - mMediaRouterService.releaseSessionWithManager( - mClient, getSessionId(), requestId); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to notify of controller release", ex); - } - } + MediaRouter2Manager.this.transferToRoute(mSessionInfo, route); } /** * Gets the session info of the session + * * @hide */ @NonNull @@ -729,19 +862,6 @@ public class MediaRouter2Manager { return mSessionInfo; } } - - private List getRoutesWithIds(List routeIds) { - List routes = new ArrayList<>(); - synchronized (mRoutesLock) { - for (String routeId : routeIds) { - MediaRoute2Info route = mRoutes.get(routeId); - if (route != null) { - routes.add(route); - } - } - } - return Collections.unmodifiableList(routes); - } } /** @@ -780,7 +900,24 @@ public class MediaRouter2Manager { */ public void onSessionsUpdated() {} - //TODO: remove this + //TODO: Call this. + /** + * Called when media is transferred. + * + * @param oldSession the previous session + * @param newSession the new session or {@code null} if the session is released. + */ + public void onTransferred(@NonNull RoutingSessionInfo oldSession, + @Nullable RoutingSessionInfo newSession) { } + + //TODO: Call this. + /** + * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails. + */ + public void onTransferFailed(@NonNull RoutingSessionInfo session, + @NonNull MediaRoute2Info route) { } + + //TODO: Remove this. /** * Called when the preferred route features of an app is changed. * diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 9d81fbbb793a8..2276b6aba770c 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -357,7 +357,7 @@ public final class RoutingSessionInfo implements Parcelable { // TODO: Reorder these (important ones first) final String mId; CharSequence mName; - final String mClientPackageName; + String mClientPackageName; String mProviderId; final List mSelectedRoutes; final List mSelectableRoutes; @@ -433,6 +433,17 @@ public final class RoutingSessionInfo implements Parcelable { return this; } + /** + * Sets the client package name of the session. + * + * @hide + */ + @NonNull + public Builder setClientPackageName(@Nullable String packageName) { + mClientPackageName = packageName; + return this; + } + /** * Sets the provider ID of the session. * diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 4e12859fa575e..230b9e4bfaad2 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -248,10 +248,9 @@ public class MediaRouter2ManagerTest { assertEquals(2, sessions.size()); - MediaRouter2Manager.RoutingController routingController = - mManager.getControllerForSession(sessions.get(1)); + RoutingSessionInfo sessionInfo = sessions.get(1); awaitOnRouteChangedManager( - () -> routingController.release(), + () -> mManager.releaseSession(sessionInfo), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), null)); assertEquals(1, mManager.getRoutingSessions(mPackageName).size()); @@ -283,8 +282,7 @@ public class MediaRouter2ManagerTest { List sessions = mManager.getRoutingSessions(mPackageName); assertEquals(2, sessions.size()); - MediaRouter2Manager.RoutingController routingController = - mManager.getControllerForSession(sessions.get(1)); + RoutingSessionInfo sessionInfo = sessions.get(1); awaitOnRouteChangedManager( () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)), @@ -292,7 +290,7 @@ public class MediaRouter2ManagerTest { route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); awaitOnRouteChangedManager( - () -> routingController.release(), + () -> mManager.releaseSession(sessionInfo), ROUTE_ID5_TO_TRANSFER_TO, route -> TextUtils.equals(route.getClientPackageName(), null)); } @@ -573,7 +571,7 @@ public class MediaRouter2ManagerTest { addManagerCallback(new MediaRouter2Manager.Callback()); for (RoutingSessionInfo session : mManager.getActiveSessions()) { - mManager.getControllerForSession(session).release(); + mManager.releaseSession(session); } } }