From f7d5e044a8ca2602d996dde9f4fd66805f3aca86 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Mon, 11 Nov 2019 13:56:28 +0900 Subject: [PATCH] MediaRouter: send control hints from provider to client This CL enables providers to send control hints to the clients so connections can be established between clients and providers. This CL also adds a hidden flag such that MR2 can notice whether the route is selected by itself or MR2Manager. Uses of Mockito is completely removed from the test for better test environment. Callbacks will be unregistered automatically when a test is ended so there will be no unregistered callbacks remaining after test failures. Test: atest mediaroutertest Change-Id: I1411bad3ddd7890fbf76020107f870daa2413077 --- .../media/IMediaRoute2ProviderClient.aidl | 3 + .../media/MediaRoute2ProviderService.java | 69 ++++++- media/java/android/media/MediaRouter.java | 1 + media/java/android/media/MediaRouter2.java | 13 +- .../SampleMediaRoute2ProviderService.java | 4 +- .../MediaRouterManagerTest.java | 188 ++++++++++-------- .../server/media/MediaRoute2Provider.java | 8 +- .../media/MediaRoute2ProviderProxy.java | 30 +++ .../server/media/MediaRouter2ServiceImpl.java | 24 ++- 9 files changed, 233 insertions(+), 107 deletions(-) diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index 6f44d45c0a12b..f4fb7f450fb8f 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -17,10 +17,13 @@ package android.media; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRoute2Info; +import android.os.Bundle; /** * @hide */ oneway interface IMediaRoute2ProviderClient { void updateProviderInfo(in MediaRoute2ProviderInfo info); + void notifyRouteSelected(String packageName, String routeId, in Bundle controlHints, int seq); } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 386d2dc54a883..1b6183e361c8f 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -18,14 +18,19 @@ package android.media; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Service; import android.content.Intent; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import java.util.Objects; + /** * @hide */ @@ -44,7 +49,7 @@ public abstract class MediaRoute2ProviderService extends Service { } @Override - public IBinder onBind(Intent intent) { + public IBinder onBind(@NonNull Intent intent) { //TODO: Allow binding from media router service only? if (SERVICE_INTERFACE.equals(intent.getAction())) { if (mStub == null) { @@ -57,11 +62,17 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Called when selectRoute is called on a route of the provider. + * Once the route is ready to be used , call {@link #notifyRouteSelected(SelectToken, Bundle)} + * to notify that. * * @param packageName the package name of the application that selected the route * @param routeId the id of the route being selected + * @param token token that contains select info + * + * @see #notifyRouteSelected */ - public abstract void onSelectRoute(String packageName, String routeId); + public abstract void onSelectRoute(@NonNull String packageName, @NonNull String routeId, + @NonNull SelectToken token); /** * Called when unselectRoute is called on a route of the provider. @@ -69,7 +80,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param packageName the package name of the application that has selected the route. * @param routeId the id of the route being unselected */ - public abstract void onUnselectRoute(String packageName, String routeId); + public abstract void onUnselectRoute(@NonNull String packageName, @NonNull String routeId); /** * Called when sendControlRequest is called on a route of the provider @@ -78,21 +89,21 @@ public abstract class MediaRoute2ProviderService extends Service { * @param request the media control request intent */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) - public abstract void onControlRequest(String routeId, Intent request); + public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); /** * Called when requestSetVolume is called on a route of the provider * @param routeId the id of the route * @param volume the target volume */ - public abstract void onSetVolume(String routeId, int volume); + public abstract void onSetVolume(@NonNull String routeId, int volume); /** * Called when requestUpdateVolume is called on a route of the provider * @param routeId id of the route * @param delta the delta to add to the current volume */ - public abstract void onUpdateVolume(String routeId, int delta); + public abstract void onUpdateVolume(@NonNull String routeId, int delta); /** * Updates provider info and publishes routes @@ -102,6 +113,29 @@ public abstract class MediaRoute2ProviderService extends Service { publishState(); } + /** + * Notifies the client of that the selected route is ready for use. If the selected route can be + * controlled, pass a {@link Bundle} that contains how to control it. + * + * @param token token passed in {@link #onSelectRoute} + * @param controlHints a {@link Bundle} that contains how to control the given route. + * Pass {@code null} if the route is not available. + */ + public final void notifyRouteSelected(@NonNull SelectToken token, + @Nullable Bundle controlHints) { + Objects.requireNonNull(token, "token must not be null"); + + if (mClient == null) { + return; + } + try { + mClient.notifyRouteSelected(token.mPackageName, token.mRouteId, + controlHints, token.mSeq); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify route selected"); + } + } + void setClient(IMediaRoute2ProviderClient client) { mClient = client; publishState(); @@ -118,6 +152,23 @@ public abstract class MediaRoute2ProviderService extends Service { } } + /** + * Route selection information. + * + * @see #notifyRouteSelected + */ + public final class SelectToken { + final String mPackageName; + final String mRouteId; + final int mSeq; + + SelectToken(String packageName, String routeId, int seq) { + mPackageName = packageName; + mRouteId = routeId; + mSeq = seq; + } + } + final class ProviderStub extends IMediaRoute2Provider.Stub { ProviderStub() { } @@ -129,10 +180,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestSelectRoute(String packageName, String id, int seq) { - // TODO: When introducing MediaRoute2ProviderService#sendConnectionHints(), - // use the sequence number here properly. mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, - MediaRoute2ProviderService.this, packageName, id)); + MediaRoute2ProviderService.this, packageName, id, + new SelectToken(packageName, id, seq))); + } @Override diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 9cb78696f19b4..7b15d95545344 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -1318,6 +1318,7 @@ public class MediaRouter { sStatic.rebindAsUser(userId); } + //TODO: remove this and Client1Record in MediaRouter2ServiceImpl. /** * Sets the control categories of the application. * Routes that support at least one of the given control categories only exists and are handled diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 94ac77af3ac35..35cb066f97905 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -57,7 +57,8 @@ public class MediaRouter2 { @IntDef(value = { SELECT_REASON_UNKNOWN, SELECT_REASON_USER_SELECTED, - SELECT_REASON_FALLBACK}) + SELECT_REASON_FALLBACK, + SELECT_REASON_SYSTEM_SELECTED}) public @interface SelectReason {} /** @@ -80,6 +81,13 @@ public class MediaRouter2 { */ public static final int SELECT_REASON_FALLBACK = 2; + /** + * This is passed from {@link com.android.server.media.MediaRouterService} when the route + * is selected in response to a request from other apps (e.g. System UI). + * @hide + */ + public static final int SELECT_REASON_SYSTEM_SELECTED = 3; + private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sLock = new Object(); @@ -485,6 +493,9 @@ public class MediaRouter2 { } mSelectingRoute = null; } + if (reason == SELECT_REASON_SYSTEM_SELECTED) { + reason = SELECT_REASON_USER_SELECTED; + } mSelectedRoute = route; notifyRouteSelected(route, reason, controlHints); } 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 f4f8d0b736581..6650f9618638e 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.os.Bundle; import android.os.IBinder; import java.util.HashMap; @@ -95,7 +96,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onSelectRoute(String packageName, String routeId) { + public void onSelectRoute(String packageName, String routeId, SelectToken token) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null) { return; @@ -104,6 +105,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .setClientPackageName(packageName) .build()); publishRoutes(); + notifyRouteSelected(token, Bundle.EMPTY); } @Override diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index d0f7c780ceb22..c70ad8d8755c2 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -23,23 +23,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRouter2; import android.media.MediaRouter2Manager; +import android.os.Bundle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.TextUtils; -import org.junit.Assert; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,9 +91,14 @@ public class MediaRouterManagerTest { private Executor mExecutor; private String mPackageName; + private final List mManagerCallbacks = new ArrayList<>(); + private final List mRouterCallbacks = new ArrayList<>(); + private Map mRoutes; + private static final List CATEGORIES_ALL = new ArrayList(); private static final List CATEGORIES_SPECIAL = new ArrayList(); private static final List CATEGORIES_LIVE_AUDIO = new ArrayList<>(); + static { CATEGORIES_ALL.add(CATEGORY_SAMPLE); CATEGORIES_ALL.add(CATEGORY_SPECIAL); @@ -108,6 +109,7 @@ public class MediaRouterManagerTest { CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO); } + @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); @@ -116,6 +118,16 @@ public class MediaRouterManagerTest { //TODO: If we need to support thread pool executors, change this to thread pool executor. mExecutor = Executors.newSingleThreadExecutor(); mPackageName = mContext.getPackageName(); + + // ensure media router 2 client + addRouterCallback(new MediaRouter2.Callback()); + mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + } + + @After + public void tearDown() { + // unregister callbacks + clearCallbacks(); } //TODO: Move to a separate file @@ -132,10 +144,13 @@ public class MediaRouterManagerTest { assertNotEquals(routeInfo1, routeInfo3); } + /** + * Tests if routes are added correctly when a new callback is registered. + */ @Test public void testOnRoutesAdded() throws Exception { CountDownLatch latch = new CountDownLatch(1); - MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() { + addManagerCallback(new MediaRouter2Manager.Callback() { @Override public void onRoutesAdded(List routes) { assertTrue(routes.size() > 0); @@ -145,27 +160,15 @@ public class MediaRouterManagerTest { } } } - }; - mManager.registerCallback(mExecutor, callback); + }); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - - mManager.unregisterCallback(callback); } @Test public void testOnRoutesRemoved() throws Exception { - MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); - mManager.registerCallback(mExecutor, mockCallback); - - MediaRouter2.Callback routerCallback = new MediaRouter2.Callback(); - mRouter2.registerCallback(mExecutor, routerCallback); - - Map routes = - waitAndGetRoutesWithManager(CATEGORIES_ALL); - CountDownLatch latch = new CountDownLatch(1); - MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() { + addManagerCallback(new MediaRouter2Manager.Callback() { @Override public void onRoutesRemoved(List routes) { assertTrue(routes.size() > 0); @@ -175,16 +178,12 @@ public class MediaRouterManagerTest { } } } - }; - mManager.registerCallback(mExecutor, callback); + }); //TODO: Figure out a more proper way to test. // (Control requests shouldn't be used in this way.) - mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE)); + mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE)); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - - mRouter2.unregisterCallback(routerCallback); - mManager.unregisterCallback(mockCallback); } /** @@ -192,16 +191,10 @@ public class MediaRouterManagerTest { */ @Test public void testControlCategory() throws Exception { - MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); - mManager.registerCallback(mExecutor, mockCallback); + Map routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL); - Map routes = - waitAndGetRoutesWithManager(CATEGORIES_SPECIAL); - - Assert.assertEquals(1, routes.size()); - Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); - - mManager.unregisterCallback(mockCallback); + assertEquals(1, routes.size()); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); } /** @@ -209,37 +202,60 @@ public class MediaRouterManagerTest { */ @Test public void testGetRoutes() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - mRouter2.registerCallback(mExecutor, mockCallback); - Map routes = waitAndGetRoutes(CATEGORIES_SPECIAL); - Assert.assertEquals(1, routes.size()); - Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); - - mRouter2.unregisterCallback(mockCallback); + assertEquals(1, routes.size()); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); } + /** + * Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager. + */ @Test - public void testOnRouteSelected() throws Exception { - MediaRouter2.Callback routerCallback = new MediaRouter2.Callback(); - MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class); + public void testRouterOnRouteSelected() throws Exception { + CountDownLatch latch = new CountDownLatch(1); - mManager.registerCallback(mExecutor, managerCallback); - mRouter2.registerCallback(mExecutor, routerCallback); + addRouterCallback(new MediaRouter2.Callback() { + @Override + public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) { + if (route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) { + latch.countDown(); + } + } + }); - Map routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); - - MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1); + MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1); assertNotNull(routeToSelect); mManager.selectRoute(mPackageName, routeToSelect); - verify(managerCallback, timeout(TIMEOUT_MS)) - .onRouteSelected(eq(mPackageName), - argThat(route -> route != null && route.equals(routeToSelect))); - mRouter2.unregisterCallback(routerCallback); - mManager.unregisterCallback(managerCallback); + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + /** + * Tests if MR2Manager.Callback.onRouteSelected is called + * when a route is selected by MR2Manager. + */ + @Test + public void testManagerOnRouteSelected() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + + 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 = mRoutes.get(ROUTE_ID1); + assertNotNull(routeToSelect); + + mManager.selectRoute(mPackageName, routeToSelect); + + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } /** @@ -247,19 +263,13 @@ public class MediaRouterManagerTest { */ @Test public void testSingleProviderSelect() throws Exception { - MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class); - - mRouter2.registerCallback(mExecutor, routerCallback); - - Map routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); - awaitOnRouteChangedManager( - () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)), + () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); awaitOnRouteChangedManager( - () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)), + () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)), ROUTE_ID2, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); @@ -267,8 +277,6 @@ public class MediaRouterManagerTest { () -> mManager.unselectRoute(mPackageName), ROUTE_ID2, route -> TextUtils.equals(route.getClientPackageName(), null)); - - mRouter2.unregisterCallback(routerCallback); } @Test @@ -292,12 +300,7 @@ public class MediaRouterManagerTest { @Test public void testControlVolumeWithManager() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - - mRouter2.registerCallback(mExecutor, mockCallback); - Map routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); - - MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); + MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1); @@ -310,24 +313,16 @@ public class MediaRouterManagerTest { () -> mManager.requestSetVolume(volRoute, originalVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume)); - - mRouter2.unregisterCallback(mockCallback); } @Test public void testVolumeHandling() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - mRouter2.registerCallback(mExecutor, mockCallback); - Map routes = waitAndGetRoutes(CATEGORIES_ALL); - - MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME); - MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); + MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME); + MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME); assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling()); assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling()); assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax()); - - mRouter2.unregisterCallback(mockCallback); } @Test @@ -368,6 +363,7 @@ public class MediaRouterManagerTest { latch.countDown(); } } + @Override public void onControlCategoriesChanged(String packageName) { if (TextUtils.equals(mPackageName, packageName)) { @@ -401,7 +397,7 @@ public class MediaRouterManagerTest { }; mRouter2.registerCallback(mExecutor, callback); try { - new Thread(task).start(); + task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } finally { mRouter2.unregisterCallback(callback); @@ -422,7 +418,7 @@ public class MediaRouterManagerTest { }; mManager.registerCallback(mExecutor, callback); try { - new Thread(task).start(); + task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } finally { mManager.unregisterCallback(callback); @@ -433,9 +429,31 @@ public class MediaRouterManagerTest { static Map createRouteMap(List routes) { Map routeMap = new HashMap<>(); for (MediaRoute2Info route : routes) { - // intentionally not route.getUniqueId() for convenience. + // intentionally not using route.getUniqueId() for convenience. routeMap.put(route.getId(), route); } return routeMap; } + + private void addManagerCallback(MediaRouter2Manager.Callback callback) { + mManagerCallbacks.add(callback); + mManager.registerCallback(mExecutor, callback); + } + + private void addRouterCallback(MediaRouter2.Callback callback) { + mRouterCallbacks.add(callback); + mRouter2.registerCallback(mExecutor, callback); + } + + private void clearCallbacks() { + for (MediaRouter2Manager.Callback callback : mManagerCallbacks) { + mManager.unregisterCallback(callback); + } + mManagerCallbacks.clear(); + + for (MediaRouter2.Callback callback : mRouterCallbacks) { + mRouter2.unregisterCallback(callback); + } + mRouterCallbacks.clear(); + } } diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 91c9253269a37..9a49c166e2b2c 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.os.Bundle; import java.util.Objects; @@ -29,7 +30,7 @@ abstract class MediaRoute2Provider { final ComponentName mComponentName; final String mUniqueId; - private Callback mCallback; + Callback mCallback; private MediaRoute2ProviderInfo mProviderInfo; MediaRoute2Provider(@NonNull ComponentName componentName) { @@ -77,6 +78,9 @@ abstract class MediaRoute2Provider { } public interface Callback { - void onProviderStateChanged(MediaRoute2Provider provider); + void onProviderStateChanged(@Nullable MediaRoute2Provider provider); + void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider, + @NonNull String clientPackageName, @NonNull MediaRoute2Info route, + @Nullable Bundle controlHints, int seq); } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 3b6580ad73578..a5abb1835e7bb 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -26,6 +26,7 @@ import android.media.IMediaRoute2ProviderClient; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -253,6 +254,20 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv setAndNotifyProviderInfo(info); } + private void onRouteSelected(Connection connection, + String packageName, String routeId, Bundle controlHints, int seq) { + if (mActiveConnection != connection) { + return; + } + MediaRoute2ProviderInfo providerInfo = getProviderInfo(); + MediaRoute2Info route = (providerInfo == null) ? null : providerInfo.getRoute(routeId); + if (route == null) { + Slog.w(TAG, this + ": Unknown route " + routeId + " is selected from remove provider"); + return; + } + mCallback.onRouteSelected(this, packageName, route, controlHints, seq); + } + private void disconnect() { if (mActiveConnection != null) { mConnectionReady = false; @@ -341,6 +356,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv void postProviderInfoUpdated(MediaRoute2ProviderInfo info) { mHandler.post(() -> onProviderInfoUpdated(Connection.this, info)); } + + void postRouteSelected(String packageName, String routeId, Bundle controlHints, int seq) { + mHandler.post(() -> onRouteSelected(Connection.this, + packageName, routeId, controlHints, seq)); + } } private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub { @@ -361,5 +381,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv connection.postProviderInfoUpdated(info); } } + + @Override + public void notifyRouteSelected(String packageName, String routeId, + Bundle controlHints, int seq) { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postRouteSelected(packageName, routeId, controlHints, seq); + } + } + } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 2cf920d1aba45..2c478dff46795 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -77,7 +77,7 @@ class MediaRouter2ServiceImpl { @GuardedBy("mLock") private int mCurrentUserId = -1; @GuardedBy("mLock") - private int mSelectRouteRequestSequenceNumber = 0; + private int mSelectRouteRequestSequenceNumber = 1; MediaRouter2ServiceImpl(Context context) { mContext = context; @@ -218,7 +218,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route); + requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route); } } finally { Binder.restoreCallingIdentity(token); @@ -399,10 +399,12 @@ class MediaRouter2ServiceImpl { } } - private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) { + private void requestSelectRoute2Locked(ClientRecord clientRecord, boolean selectedByManager, + MediaRoute2Info route) { if (clientRecord != null) { MediaRoute2Info oldRoute = clientRecord.mSelectedRoute; clientRecord.mSelectingRoute = route; + clientRecord.mIsManagerSelecting = selectedByManager; UserHandler handler = clientRecord.mUserRecord.mHandler; //TODO: Handle transfer instead of unselect and select @@ -417,7 +419,6 @@ class MediaRouter2ServiceImpl { handler.sendMessage(obtainMessage( UserHandler::requestSelectRoute, handler, clientRecord.mPackageName, route, seq)); - // Remove all previous timeout messages for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) { clientRecord.mUserRecord.mHandler.removeMessages(previousSeq); @@ -543,7 +544,7 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "Ignoring route selection for unknown client."); } if (clientRecord != null && managerRecord.mTrusted) { - requestSelectRoute2Locked(clientRecord, route); + requestSelectRoute2Locked(clientRecord, true, route); } } } @@ -656,7 +657,9 @@ class MediaRouter2ServiceImpl { public final UserRecord mUserRecord; public final String mPackageName; public final List mSelectRouteSequenceNumbers; + public List mControlCategories; + public boolean mIsManagerSelecting; public MediaRoute2Info mSelectingRoute; public MediaRoute2Info mSelectedRoute; @@ -802,9 +805,8 @@ class MediaRouter2ServiceImpl { sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider)); } - // TODO: When introducing MediaRoute2ProviderService#sendControlHints(), - // Make this method to be called. - public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider, + @Override + public void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider, String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) { sendMessage(PooledLambda.obtainMessage( UserHandler::updateSelectedRoute, this, provider, clientPackageName, route, @@ -917,6 +919,8 @@ class MediaRouter2ServiceImpl { return; } + //TODO: handle a case such that controlHints is null. (How should we notify MR2?) + if (clientRecord.mSelectingRoute == null || !TextUtils.equals( clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) { Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute=" @@ -929,7 +933,9 @@ class MediaRouter2ServiceImpl { notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient, selectedRoute, - MediaRouter2.SELECT_REASON_USER_SELECTED, + clientRecord.mIsManagerSelecting + ? MediaRouter2.SELECT_REASON_SYSTEM_SELECTED : + MediaRouter2.SELECT_REASON_USER_SELECTED, controlHints); updateClientUsage(clientRecord);