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