Loading media/java/android/media/IMediaRouterService.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ interface IMediaRouterService { void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); void requestSetVolume(IMediaRouterClient client, String routeId, int volume); void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction); void setControlCategories(IMediaRouterClient client, in List<String> categories); // Methods for media router 2 void registerClient2(IMediaRouter2Client client, String packageName); Loading @@ -52,7 +53,7 @@ interface IMediaRouterService { * @param route the route to be selected */ void selectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route); void setControlCategories(IMediaRouter2Client client, in List<String> categories); void setControlCategories2(IMediaRouter2Client client, in List<String> categories); void registerManager(IMediaRouter2Manager manager, String packageName); void unregisterManager(IMediaRouter2Manager manager); Loading media/java/android/media/MediaRouter.java +29 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,8 @@ import android.view.Display; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Objects; Loading Loading @@ -82,6 +84,7 @@ public class MediaRouter { final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>(); List<String> mControlCategories = Collections.emptyList(); final RouteCategory mSystemCategory; Loading Loading @@ -358,6 +361,18 @@ public class MediaRouter { return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); } public void setControlCategories(Collection<String> controlCategories) { List<String> newControlCategories = new ArrayList<>(controlCategories); mControlCategories = newControlCategories; if (mClient != null) { try { mMediaRouterService.setControlCategories(mClient, newControlCategories); } catch (RemoteException ex) { Log.e(TAG, "Unable to set control categories.", ex); } } } private void updatePresentationDisplays(int changedDisplayId) { final int count = mRoutes.size(); for (int i = 0; i < count; i++) { Loading Loading @@ -406,6 +421,7 @@ public class MediaRouter { try { Client client = new Client(); mMediaRouterService.registerClientAsUser(client, mPackageName, userId); mMediaRouterService.setControlCategories(client, mControlCategories); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router client.", ex); Loading Loading @@ -1302,6 +1318,19 @@ public class MediaRouter { sStatic.rebindAsUser(userId); } /** * Sets the control categories of the application. * Routes that support at least one of the given control categories only exists and are handled * by the media router. * * @hide */ public void setControlCategories(@NonNull Collection<String> controlCategories) { Objects.requireNonNull(controlCategories, "control categories must not be null"); sStatic.setControlCategories(controlCategories); } static void updateRoute(final RouteInfo info) { dispatchRouteChanged(info); } Loading media/java/android/media/MediaRouter2.java +2 −2 Original line number Diff line number Diff line Loading @@ -129,7 +129,7 @@ public class MediaRouter2 { Client client = new Client(); try { mMediaRouterService.registerClient2(client, mPackageName); mMediaRouterService.setControlCategories(client, mControlCategories); mMediaRouterService.setControlCategories2(client, mControlCategories); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router.", ex); Loading Loading @@ -188,7 +188,7 @@ public class MediaRouter2 { } if (client != null) { try { mMediaRouterService.setControlCategories(client, newControlCategories); mMediaRouterService.setControlCategories2(client, newControlCategories); } catch (RemoteException ex) { Log.e(TAG, "Unable to set control categories.", ex); } Loading media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +48 −19 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRouter; import android.media.MediaRouter2; import android.media.MediaRouter2Manager; import android.support.test.InstrumentationRegistry; Loading Loading @@ -72,7 +73,8 @@ public class MediaRouterManagerTest { private Context mContext; private MediaRouter2Manager mManager; private MediaRouter2 mRouter; private MediaRouter mRouter; private MediaRouter2 mRouter2; private Executor mExecutor; private String mPackageName; Loading @@ -89,7 +91,8 @@ public class MediaRouterManagerTest { public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); mManager = MediaRouter2Manager.getInstance(mContext); mRouter = MediaRouter2.getInstance(mContext); mRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mRouter2 = MediaRouter2.getInstance(mContext); //TODO: If we need to support thread pool executors, change this to thread pool executor. mExecutor = Executors.newSingleThreadExecutor(); mPackageName = mContext.getPackageName(); Loading Loading @@ -131,12 +134,12 @@ public class MediaRouterManagerTest { //TODO: Figure out a more proper way to test. // (Control requests shouldn't be used in this way.) mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, mockRouterCallback); mRouter.sendControlRequest( mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter2.registerCallback(mExecutor, mockRouterCallback); mRouter2.sendControlRequest( new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(), new Intent(ACTION_REMOVE_ROUTE)); mRouter.unregisterCallback(mockRouterCallback); mRouter2.unregisterCallback(mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)).onRouteRemoved(argThat( (MediaRoute2Info info) -> Loading @@ -144,6 +147,32 @@ public class MediaRouterManagerTest { mManager.unregisterCallback(mockCallback); } /** * Tests if we get proper routes for application that has special control category. */ @Test public void testControlCategoryWithMediaRouter() throws Exception { MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); mManager.registerCallback(mExecutor, mockCallback); MediaRouter.Callback mockRouterCallback = mock(MediaRouter.Callback.class); mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Map<String, MediaRoute2Info> routes = createRouteMap(mManager.getAvailableRoutes(mPackageName)); Assert.assertEquals(1, routes.size()); Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); mRouter.removeCallback(mockRouterCallback); mManager.unregisterCallback(mockCallback); } /** * Tests if we get proper routes for application that has special control category. */ Loading @@ -154,9 +183,9 @@ public class MediaRouterManagerTest { MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class); mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter.registerCallback(mExecutor, mockRouterCallback); mRouter.unregisterCallback(mockRouterCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter2.registerCallback(mExecutor, mockRouterCallback); mRouter2.unregisterCallback(mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Loading @@ -177,15 +206,15 @@ public class MediaRouterManagerTest { public void testGetRoutes() throws Exception { MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter.registerCallback(mExecutor, mockCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter2.registerCallback(mExecutor, mockCallback); verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Map<String, MediaRoute2Info> routes = createRouteMap(mRouter.getRoutes()); Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes()); Assert.assertEquals(1, routes.size()); Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); mRouter.unregisterCallback(mockCallback); mRouter2.unregisterCallback(mockCallback); } @Test Loading @@ -194,8 +223,8 @@ public class MediaRouterManagerTest { MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class); mManager.registerCallback(mExecutor, managerCallback); mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, mockRouterCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter2.registerCallback(mExecutor, mockRouterCallback); verify(managerCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Loading @@ -211,7 +240,7 @@ public class MediaRouterManagerTest { .onRouteAdded(argThat(route -> route.equals(routeToSelect))); mManager.unregisterCallback(managerCallback); mRouter.unregisterCallback(mockRouterCallback); mRouter2.unregisterCallback(mockRouterCallback); } /** Loading @@ -223,8 +252,8 @@ public class MediaRouterManagerTest { MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class); mManager.registerCallback(mExecutor, managerCallback); mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, routerCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter2.registerCallback(mExecutor, routerCallback); verify(managerCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Loading @@ -248,7 +277,7 @@ public class MediaRouterManagerTest { .onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID2, routeInfo.getId()) && TextUtils.equals(routeInfo.getClientPackageName(), null))); mRouter.unregisterCallback(routerCallback); mRouter2.unregisterCallback(routerCallback); mManager.unregisterCallback(managerCallback); } Loading services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +124 −32 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.media.IMediaRouter2Client; import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.os.Binder; Loading Loading @@ -86,7 +87,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { registerClientLocked(client, uid, pid, packageName, userId, trusted); registerClient2Locked(client, uid, pid, packageName, userId, trusted); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -99,7 +100,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { unregisterClientLocked(client, false); unregisterClient2Locked(client, false); } } finally { Binder.restoreCallingIdentity(token); Loading Loading @@ -155,14 +156,30 @@ class MediaRouter2ServiceImpl { } } public void setControlCategories(@NonNull IMediaRouter2Client client, //TODO: What would happen if a media app used MediaRouter and MediaRouter2 simultaneously? public void setControlCategories(@NonNull IMediaRouterClient client, @Nullable List<String> categories) { Objects.requireNonNull(client, "client must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); setControlCategoriesLocked(clientRecord, categories); } } finally { Binder.restoreCallingIdentity(token); } } public void setControlCategories2(@NonNull IMediaRouter2Client client, @Nullable List<String> categories) { Objects.requireNonNull(client, "client must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { setControlCategoriesLocked(client, categories); ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); setControlCategoriesLocked(clientRecord, categories); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -174,7 +191,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { selectRoute2Locked(client, route); selectRoute2Locked(mAllClientRecords.get(client.asBinder()), route); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -193,6 +210,36 @@ class MediaRouter2ServiceImpl { } } public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) { Objects.requireNonNull(client, "client must not be null"); final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); final int userId = UserHandle.getUserId(uid); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { registerClient1Locked(client, packageName, userId); } } finally { Binder.restoreCallingIdentity(token); } } public void unregisterClient(@NonNull IMediaRouterClient client) { Objects.requireNonNull(client, "client must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { unregisterClient1Locked(client); } } finally { Binder.restoreCallingIdentity(token); } } //TODO: Review this is handling multi-user properly. void switchUser() { synchronized (mLock) { Loading @@ -217,9 +264,9 @@ class MediaRouter2ServiceImpl { } } void clientDied(ClientRecord clientRecord) { void clientDied(Client2Record clientRecord) { synchronized (mLock) { unregisterClientLocked(clientRecord.mClient, true); unregisterClient2Locked(clientRecord.mClient, true); } } Loading @@ -229,18 +276,18 @@ class MediaRouter2ServiceImpl { } } private void registerClientLocked(IMediaRouter2Client client, private void registerClient2Locked(IMediaRouter2Client client, int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); if (clientRecord == null) { if (mAllClientRecords.get(binder) == null) { boolean newUser = false; UserRecord userRecord = mUserRecords.get(userId); if (userRecord == null) { userRecord = new UserRecord(userId); newUser = true; } clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted); Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid, packageName, trusted); try { binder.linkToDeath(clientRecord, 0); } catch (RemoteException ex) { Loading @@ -261,8 +308,8 @@ class MediaRouter2ServiceImpl { } } private void unregisterClientLocked(IMediaRouter2Client client, boolean died) { ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); private void unregisterClient2Locked(IMediaRouter2Client client, boolean died) { Client2Record clientRecord = (Client2Record) mAllClientRecords.remove(client.asBinder()); if (clientRecord != null) { UserRecord userRecord = clientRecord.mUserRecord; userRecord.mClientRecords.remove(clientRecord); Loading @@ -272,8 +319,7 @@ class MediaRouter2ServiceImpl { } } private void selectRoute2Locked(IMediaRouter2Client client, MediaRoute2Info route) { ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); private void selectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) { if (clientRecord != null) { MediaRoute2Info oldRoute = clientRecord.mSelectedRoute; clientRecord.mSelectedRoute = route; Loading @@ -294,10 +340,7 @@ class MediaRouter2ServiceImpl { } } private void setControlCategoriesLocked(IMediaRouter2Client client, List<String> categories) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); private void setControlCategoriesLocked(ClientRecord clientRecord, List<String> categories) { if (clientRecord != null) { clientRecord.mControlCategories = categories; Loading Loading @@ -349,9 +392,7 @@ class MediaRouter2ServiceImpl { obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager, userRecord.mHandler, manager)); final int count = userRecord.mClientRecords.size(); for (int i = 0; i < count; i++) { ClientRecord clientRecord = userRecord.mClientRecords.get(i); for (ClientRecord clientRecord : userRecord.mClientRecords) { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateClientUsage, clientRecord.mUserRecord.mHandler, clientRecord)); Loading @@ -378,7 +419,7 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "Ignoring route selection for unknown client."); } if (clientRecord != null && managerRecord.mTrusted) { selectRoute2Locked(clientRecord.mClient, route); selectRoute2Locked(clientRecord, route); } } } Loading Loading @@ -409,6 +450,37 @@ class MediaRouter2ServiceImpl { } } private void registerClient1Locked(IMediaRouterClient client, String packageName, int userId) { final IBinder binder = client.asBinder(); if (mAllClientRecords.get(binder) == null) { boolean newUser = false; UserRecord userRecord = mUserRecords.get(userId); if (userRecord == null) { userRecord = new UserRecord(userId); newUser = true; } ClientRecord clientRecord = new Client1Record(userRecord, client, packageName); if (newUser) { mUserRecords.put(userId, userRecord); initializeUserLocked(userRecord); } userRecord.mClientRecords.add(clientRecord); mAllClientRecords.put(binder, clientRecord); } } private void unregisterClient1Locked(IMediaRouterClient client) { ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); if (clientRecord != null) { UserRecord userRecord = clientRecord.mUserRecord; userRecord.mClientRecords.remove(clientRecord); disposeUserIfNeededLocked(userRecord); } } final class UserRecord { public final int mUserId; final ArrayList<ClientRecord> mClientRecords = new ArrayList<>(); Loading @@ -430,25 +502,43 @@ class MediaRouter2ServiceImpl { } } final class ClientRecord implements IBinder.DeathRecipient { class ClientRecord { public final UserRecord mUserRecord; public final String mPackageName; public List<String> mControlCategories; public MediaRoute2Info mSelectedRoute; ClientRecord(UserRecord userRecord, String packageName) { mUserRecord = userRecord; mPackageName = packageName; mControlCategories = Collections.emptyList(); } } final class Client1Record extends ClientRecord { public final IMediaRouterClient mClient; Client1Record(UserRecord userRecord, IMediaRouterClient client, String packageName) { super(userRecord, packageName); mClient = client; } } final class Client2Record extends ClientRecord implements IBinder.DeathRecipient { public final IMediaRouter2Client mClient; public final int mUid; public final int mPid; public final String mPackageName; public final boolean mTrusted; public List<String> mControlCategories; public MediaRoute2Info mSelectedRoute; ClientRecord(UserRecord userRecord, IMediaRouter2Client client, Client2Record(UserRecord userRecord, IMediaRouter2Client client, int uid, int pid, String packageName, boolean trusted) { mUserRecord = userRecord; super(userRecord, packageName); mClient = client; mUid = uid; mPid = pid; mPackageName = packageName; mTrusted = trusted; mControlCategories = Collections.emptyList(); } public void dispose() { Loading Loading @@ -622,7 +712,9 @@ class MediaRouter2ServiceImpl { managers.add(managerRecord.mManager); } for (ClientRecord clientRecord : mUserRecord.mClientRecords) { clients.add(clientRecord.mClient); if (clientRecord instanceof Client2Record) { clients.add(((Client2Record) clientRecord).mClient); } } } for (IMediaRouter2Manager manager : managers) { Loading Loading
media/java/android/media/IMediaRouterService.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ interface IMediaRouterService { void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); void requestSetVolume(IMediaRouterClient client, String routeId, int volume); void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction); void setControlCategories(IMediaRouterClient client, in List<String> categories); // Methods for media router 2 void registerClient2(IMediaRouter2Client client, String packageName); Loading @@ -52,7 +53,7 @@ interface IMediaRouterService { * @param route the route to be selected */ void selectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route); void setControlCategories(IMediaRouter2Client client, in List<String> categories); void setControlCategories2(IMediaRouter2Client client, in List<String> categories); void registerManager(IMediaRouter2Manager manager, String packageName); void unregisterManager(IMediaRouter2Manager manager); Loading
media/java/android/media/MediaRouter.java +29 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,8 @@ import android.view.Display; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Objects; Loading Loading @@ -82,6 +84,7 @@ public class MediaRouter { final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>(); List<String> mControlCategories = Collections.emptyList(); final RouteCategory mSystemCategory; Loading Loading @@ -358,6 +361,18 @@ public class MediaRouter { return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); } public void setControlCategories(Collection<String> controlCategories) { List<String> newControlCategories = new ArrayList<>(controlCategories); mControlCategories = newControlCategories; if (mClient != null) { try { mMediaRouterService.setControlCategories(mClient, newControlCategories); } catch (RemoteException ex) { Log.e(TAG, "Unable to set control categories.", ex); } } } private void updatePresentationDisplays(int changedDisplayId) { final int count = mRoutes.size(); for (int i = 0; i < count; i++) { Loading Loading @@ -406,6 +421,7 @@ public class MediaRouter { try { Client client = new Client(); mMediaRouterService.registerClientAsUser(client, mPackageName, userId); mMediaRouterService.setControlCategories(client, mControlCategories); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router client.", ex); Loading Loading @@ -1302,6 +1318,19 @@ public class MediaRouter { sStatic.rebindAsUser(userId); } /** * Sets the control categories of the application. * Routes that support at least one of the given control categories only exists and are handled * by the media router. * * @hide */ public void setControlCategories(@NonNull Collection<String> controlCategories) { Objects.requireNonNull(controlCategories, "control categories must not be null"); sStatic.setControlCategories(controlCategories); } static void updateRoute(final RouteInfo info) { dispatchRouteChanged(info); } Loading
media/java/android/media/MediaRouter2.java +2 −2 Original line number Diff line number Diff line Loading @@ -129,7 +129,7 @@ public class MediaRouter2 { Client client = new Client(); try { mMediaRouterService.registerClient2(client, mPackageName); mMediaRouterService.setControlCategories(client, mControlCategories); mMediaRouterService.setControlCategories2(client, mControlCategories); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router.", ex); Loading Loading @@ -188,7 +188,7 @@ public class MediaRouter2 { } if (client != null) { try { mMediaRouterService.setControlCategories(client, newControlCategories); mMediaRouterService.setControlCategories2(client, newControlCategories); } catch (RemoteException ex) { Log.e(TAG, "Unable to set control categories.", ex); } Loading
media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +48 −19 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRouter; import android.media.MediaRouter2; import android.media.MediaRouter2Manager; import android.support.test.InstrumentationRegistry; Loading Loading @@ -72,7 +73,8 @@ public class MediaRouterManagerTest { private Context mContext; private MediaRouter2Manager mManager; private MediaRouter2 mRouter; private MediaRouter mRouter; private MediaRouter2 mRouter2; private Executor mExecutor; private String mPackageName; Loading @@ -89,7 +91,8 @@ public class MediaRouterManagerTest { public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); mManager = MediaRouter2Manager.getInstance(mContext); mRouter = MediaRouter2.getInstance(mContext); mRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mRouter2 = MediaRouter2.getInstance(mContext); //TODO: If we need to support thread pool executors, change this to thread pool executor. mExecutor = Executors.newSingleThreadExecutor(); mPackageName = mContext.getPackageName(); Loading Loading @@ -131,12 +134,12 @@ public class MediaRouterManagerTest { //TODO: Figure out a more proper way to test. // (Control requests shouldn't be used in this way.) mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, mockRouterCallback); mRouter.sendControlRequest( mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter2.registerCallback(mExecutor, mockRouterCallback); mRouter2.sendControlRequest( new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(), new Intent(ACTION_REMOVE_ROUTE)); mRouter.unregisterCallback(mockRouterCallback); mRouter2.unregisterCallback(mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)).onRouteRemoved(argThat( (MediaRoute2Info info) -> Loading @@ -144,6 +147,32 @@ public class MediaRouterManagerTest { mManager.unregisterCallback(mockCallback); } /** * Tests if we get proper routes for application that has special control category. */ @Test public void testControlCategoryWithMediaRouter() throws Exception { MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); mManager.registerCallback(mExecutor, mockCallback); MediaRouter.Callback mockRouterCallback = mock(MediaRouter.Callback.class); mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Map<String, MediaRoute2Info> routes = createRouteMap(mManager.getAvailableRoutes(mPackageName)); Assert.assertEquals(1, routes.size()); Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); mRouter.removeCallback(mockRouterCallback); mManager.unregisterCallback(mockCallback); } /** * Tests if we get proper routes for application that has special control category. */ Loading @@ -154,9 +183,9 @@ public class MediaRouterManagerTest { MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class); mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter.registerCallback(mExecutor, mockRouterCallback); mRouter.unregisterCallback(mockRouterCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter2.registerCallback(mExecutor, mockRouterCallback); mRouter2.unregisterCallback(mockRouterCallback); verify(mockCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Loading @@ -177,15 +206,15 @@ public class MediaRouterManagerTest { public void testGetRoutes() throws Exception { MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter.registerCallback(mExecutor, mockCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL); mRouter2.registerCallback(mExecutor, mockCallback); verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Map<String, MediaRoute2Info> routes = createRouteMap(mRouter.getRoutes()); Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes()); Assert.assertEquals(1, routes.size()); Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); mRouter.unregisterCallback(mockCallback); mRouter2.unregisterCallback(mockCallback); } @Test Loading @@ -194,8 +223,8 @@ public class MediaRouterManagerTest { MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class); mManager.registerCallback(mExecutor, managerCallback); mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, mockRouterCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter2.registerCallback(mExecutor, mockRouterCallback); verify(managerCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Loading @@ -211,7 +240,7 @@ public class MediaRouterManagerTest { .onRouteAdded(argThat(route -> route.equals(routeToSelect))); mManager.unregisterCallback(managerCallback); mRouter.unregisterCallback(mockRouterCallback); mRouter2.unregisterCallback(mockRouterCallback); } /** Loading @@ -223,8 +252,8 @@ public class MediaRouterManagerTest { MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class); mManager.registerCallback(mExecutor, managerCallback); mRouter.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter.registerCallback(mExecutor, routerCallback); mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); mRouter2.registerCallback(mExecutor, routerCallback); verify(managerCallback, timeout(TIMEOUT_MS)) .onRoutesChanged(argThat(routes -> routes.size() > 0)); Loading @@ -248,7 +277,7 @@ public class MediaRouterManagerTest { .onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID2, routeInfo.getId()) && TextUtils.equals(routeInfo.getClientPackageName(), null))); mRouter.unregisterCallback(routerCallback); mRouter2.unregisterCallback(routerCallback); mManager.unregisterCallback(managerCallback); } Loading
services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +124 −32 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.media.IMediaRouter2Client; import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.os.Binder; Loading Loading @@ -86,7 +87,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { registerClientLocked(client, uid, pid, packageName, userId, trusted); registerClient2Locked(client, uid, pid, packageName, userId, trusted); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -99,7 +100,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { unregisterClientLocked(client, false); unregisterClient2Locked(client, false); } } finally { Binder.restoreCallingIdentity(token); Loading Loading @@ -155,14 +156,30 @@ class MediaRouter2ServiceImpl { } } public void setControlCategories(@NonNull IMediaRouter2Client client, //TODO: What would happen if a media app used MediaRouter and MediaRouter2 simultaneously? public void setControlCategories(@NonNull IMediaRouterClient client, @Nullable List<String> categories) { Objects.requireNonNull(client, "client must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); setControlCategoriesLocked(clientRecord, categories); } } finally { Binder.restoreCallingIdentity(token); } } public void setControlCategories2(@NonNull IMediaRouter2Client client, @Nullable List<String> categories) { Objects.requireNonNull(client, "client must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { setControlCategoriesLocked(client, categories); ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); setControlCategoriesLocked(clientRecord, categories); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -174,7 +191,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { selectRoute2Locked(client, route); selectRoute2Locked(mAllClientRecords.get(client.asBinder()), route); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -193,6 +210,36 @@ class MediaRouter2ServiceImpl { } } public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) { Objects.requireNonNull(client, "client must not be null"); final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); final int userId = UserHandle.getUserId(uid); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { registerClient1Locked(client, packageName, userId); } } finally { Binder.restoreCallingIdentity(token); } } public void unregisterClient(@NonNull IMediaRouterClient client) { Objects.requireNonNull(client, "client must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { unregisterClient1Locked(client); } } finally { Binder.restoreCallingIdentity(token); } } //TODO: Review this is handling multi-user properly. void switchUser() { synchronized (mLock) { Loading @@ -217,9 +264,9 @@ class MediaRouter2ServiceImpl { } } void clientDied(ClientRecord clientRecord) { void clientDied(Client2Record clientRecord) { synchronized (mLock) { unregisterClientLocked(clientRecord.mClient, true); unregisterClient2Locked(clientRecord.mClient, true); } } Loading @@ -229,18 +276,18 @@ class MediaRouter2ServiceImpl { } } private void registerClientLocked(IMediaRouter2Client client, private void registerClient2Locked(IMediaRouter2Client client, int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); if (clientRecord == null) { if (mAllClientRecords.get(binder) == null) { boolean newUser = false; UserRecord userRecord = mUserRecords.get(userId); if (userRecord == null) { userRecord = new UserRecord(userId); newUser = true; } clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted); Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid, packageName, trusted); try { binder.linkToDeath(clientRecord, 0); } catch (RemoteException ex) { Loading @@ -261,8 +308,8 @@ class MediaRouter2ServiceImpl { } } private void unregisterClientLocked(IMediaRouter2Client client, boolean died) { ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); private void unregisterClient2Locked(IMediaRouter2Client client, boolean died) { Client2Record clientRecord = (Client2Record) mAllClientRecords.remove(client.asBinder()); if (clientRecord != null) { UserRecord userRecord = clientRecord.mUserRecord; userRecord.mClientRecords.remove(clientRecord); Loading @@ -272,8 +319,7 @@ class MediaRouter2ServiceImpl { } } private void selectRoute2Locked(IMediaRouter2Client client, MediaRoute2Info route) { ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); private void selectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) { if (clientRecord != null) { MediaRoute2Info oldRoute = clientRecord.mSelectedRoute; clientRecord.mSelectedRoute = route; Loading @@ -294,10 +340,7 @@ class MediaRouter2ServiceImpl { } } private void setControlCategoriesLocked(IMediaRouter2Client client, List<String> categories) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); private void setControlCategoriesLocked(ClientRecord clientRecord, List<String> categories) { if (clientRecord != null) { clientRecord.mControlCategories = categories; Loading Loading @@ -349,9 +392,7 @@ class MediaRouter2ServiceImpl { obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager, userRecord.mHandler, manager)); final int count = userRecord.mClientRecords.size(); for (int i = 0; i < count; i++) { ClientRecord clientRecord = userRecord.mClientRecords.get(i); for (ClientRecord clientRecord : userRecord.mClientRecords) { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateClientUsage, clientRecord.mUserRecord.mHandler, clientRecord)); Loading @@ -378,7 +419,7 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "Ignoring route selection for unknown client."); } if (clientRecord != null && managerRecord.mTrusted) { selectRoute2Locked(clientRecord.mClient, route); selectRoute2Locked(clientRecord, route); } } } Loading Loading @@ -409,6 +450,37 @@ class MediaRouter2ServiceImpl { } } private void registerClient1Locked(IMediaRouterClient client, String packageName, int userId) { final IBinder binder = client.asBinder(); if (mAllClientRecords.get(binder) == null) { boolean newUser = false; UserRecord userRecord = mUserRecords.get(userId); if (userRecord == null) { userRecord = new UserRecord(userId); newUser = true; } ClientRecord clientRecord = new Client1Record(userRecord, client, packageName); if (newUser) { mUserRecords.put(userId, userRecord); initializeUserLocked(userRecord); } userRecord.mClientRecords.add(clientRecord); mAllClientRecords.put(binder, clientRecord); } } private void unregisterClient1Locked(IMediaRouterClient client) { ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); if (clientRecord != null) { UserRecord userRecord = clientRecord.mUserRecord; userRecord.mClientRecords.remove(clientRecord); disposeUserIfNeededLocked(userRecord); } } final class UserRecord { public final int mUserId; final ArrayList<ClientRecord> mClientRecords = new ArrayList<>(); Loading @@ -430,25 +502,43 @@ class MediaRouter2ServiceImpl { } } final class ClientRecord implements IBinder.DeathRecipient { class ClientRecord { public final UserRecord mUserRecord; public final String mPackageName; public List<String> mControlCategories; public MediaRoute2Info mSelectedRoute; ClientRecord(UserRecord userRecord, String packageName) { mUserRecord = userRecord; mPackageName = packageName; mControlCategories = Collections.emptyList(); } } final class Client1Record extends ClientRecord { public final IMediaRouterClient mClient; Client1Record(UserRecord userRecord, IMediaRouterClient client, String packageName) { super(userRecord, packageName); mClient = client; } } final class Client2Record extends ClientRecord implements IBinder.DeathRecipient { public final IMediaRouter2Client mClient; public final int mUid; public final int mPid; public final String mPackageName; public final boolean mTrusted; public List<String> mControlCategories; public MediaRoute2Info mSelectedRoute; ClientRecord(UserRecord userRecord, IMediaRouter2Client client, Client2Record(UserRecord userRecord, IMediaRouter2Client client, int uid, int pid, String packageName, boolean trusted) { mUserRecord = userRecord; super(userRecord, packageName); mClient = client; mUid = uid; mPid = pid; mPackageName = packageName; mTrusted = trusted; mControlCategories = Collections.emptyList(); } public void dispose() { Loading Loading @@ -622,7 +712,9 @@ class MediaRouter2ServiceImpl { managers.add(managerRecord.mManager); } for (ClientRecord clientRecord : mUserRecord.mClientRecords) { clients.add(clientRecord.mClient); if (clientRecord instanceof Client2Record) { clients.add(((Client2Record) clientRecord).mClient); } } } for (IMediaRouter2Manager manager : managers) { Loading