diff options
4 files changed, 145 insertions, 33 deletions
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index f13a64c1520b..5dcbb05184fe 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -23,6 +23,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Objects; /** @@ -53,6 +56,8 @@ public final class MediaRoute2Info implements Parcelable { final String mDescription; @Nullable final String mClientPackageName; + @NonNull + final List<String> mSupportedCategories; @Nullable final Bundle mExtras; @@ -62,6 +67,7 @@ public final class MediaRoute2Info implements Parcelable { mName = builder.mName; mDescription = builder.mDescription; mClientPackageName = builder.mClientPackageName; + mSupportedCategories = builder.mSupportedCategories; mExtras = builder.mExtras; } @@ -71,6 +77,7 @@ public final class MediaRoute2Info implements Parcelable { mName = in.readString(); mDescription = in.readString(); mClientPackageName = in.readString(); + mSupportedCategories = in.createStringArrayList(); mExtras = in.readBundle(); } @@ -103,13 +110,14 @@ public final class MediaRoute2Info implements Parcelable { && Objects.equals(mName, other.mName) && Objects.equals(mDescription, other.mDescription) && Objects.equals(mClientPackageName, other.mClientPackageName) + && Objects.equals(mSupportedCategories, other.mSupportedCategories) //TODO: This will be evaluated as false in most cases. Try not to. && Objects.equals(mExtras, other.mExtras); } @Override public int hashCode() { - return Objects.hash(mId, mName, mDescription); + return Objects.hash(mId, mName, mDescription, mSupportedCategories); } @NonNull @@ -146,11 +154,38 @@ public final class MediaRoute2Info implements Parcelable { return mClientPackageName; } + /** + * Gets the supported categories of the route. + */ + @NonNull + public List<String> getSupportedCategories() { + return mSupportedCategories; + } + @Nullable public Bundle getExtras() { return mExtras; } + //TODO: Move this if we re-define control category / selector things. + /** + * Returns true if the route supports at least one of the specified control categories + * + * @param controlCategories the list of control categories to consider + * @return true if the route supports at least one category + */ + public boolean supportsControlCategory(@NonNull Collection<String> controlCategories) { + Objects.requireNonNull(controlCategories, "control categories must not be null"); + for (String controlCategory : controlCategories) { + for (String supportedCategory : getSupportedCategories()) { + if (TextUtils.equals(controlCategory, supportedCategory)) { + return true; + } + } + } + return false; + } + @Override public int describeContents() { return 0; @@ -163,6 +198,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeString(mName); dest.writeString(mDescription); dest.writeString(mClientPackageName); + dest.writeStringList(mSupportedCategories); dest.writeBundle(mExtras); } @@ -187,6 +223,7 @@ public final class MediaRoute2Info implements Parcelable { String mName; String mDescription; String mClientPackageName; + List<String> mSupportedCategories; Bundle mExtras; public Builder(@NonNull String id, @NonNull String name) { @@ -198,6 +235,7 @@ public final class MediaRoute2Info implements Parcelable { } setId(id); setName(name); + mSupportedCategories = new ArrayList<>(); } public Builder(@NonNull MediaRoute2Info routeInfo) { @@ -212,6 +250,7 @@ public final class MediaRoute2Info implements Parcelable { setName(routeInfo.mName); mDescription = routeInfo.mDescription; setClientPackageName(routeInfo.mClientPackageName); + setSupportedCategories(routeInfo.mSupportedCategories); if (routeInfo.mExtras != null) { mExtras = new Bundle(routeInfo.mExtras); } @@ -273,6 +312,39 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets the supported categories of the route. + */ + @NonNull + public Builder setSupportedCategories(@NonNull Collection<String> categories) { + mSupportedCategories = new ArrayList<>(); + return addSupportedCategories(categories); + } + + /** + * Adds supported categories for the route. + */ + @NonNull + public Builder addSupportedCategories(@NonNull Collection<String> categories) { + Objects.requireNonNull(categories, "categories must not be null"); + for (String category: categories) { + addSupportedCategory(category); + } + return this; + } + + /** + * Add a supported category for the route. + */ + @NonNull + public Builder addSupportedCategory(@NonNull String category) { + if (TextUtils.isEmpty(category)) { + throw new IllegalArgumentException("category must not be null or empty"); + } + mSupportedCategories.add(category); + return this; + } + + /** * Sets a bundle of extras for the route. */ @NonNull diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5fc37a579d92..85105e069aaa 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -38,6 +38,8 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; @@ -66,6 +68,8 @@ public class MediaRouter2Manager { List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList(); @NonNull List<MediaRoute2Info> mRoutes = Collections.emptyList(); + @NonNull + ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>(); /** * Gets an instance of media router manager that controls media route of other applications. @@ -160,6 +164,8 @@ public class MediaRouter2Manager { return -1; } + //TODO: Use cache not to create array. For now, it's unclear when to purge the cache. + //Do this when we finalize how to set control categories. /** * Gets available routes for an application. * @@ -167,8 +173,17 @@ public class MediaRouter2Manager { */ @NonNull public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) { - //TODO: filter irrelevant routes. - return Collections.unmodifiableList(mRoutes); + List<String> controlCategories = mControlCategoryMap.get(packageName); + if (controlCategories == null) { + return Collections.emptyList(); + } + List<MediaRoute2Info> routes = new ArrayList<>(); + for (MediaRoute2Info route : mRoutes) { + if (route.supportsControlCategory(controlCategories)) { + routes.add(route); + } + } + return routes; } /** @@ -310,11 +325,8 @@ public class MediaRouter2Manager { } } - void notifyControlCategoriesChanged(String packageName, List<String> categories) { - for (CallbackRecord record : mCallbacks) { - record.mExecutor.execute( - () -> record.mCallback.onControlCategoriesChanged(packageName, categories)); - } + void updateControlCategories(String packageName, List<String> categories) { + mControlCategoryMap.put(packageName, categories); } /** @@ -346,15 +358,6 @@ public class MediaRouter2Manager { public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {} /** - * Called when the control categories of an application is changed. - * - * @param packageName the package name of the app that changed control categories - * @param categories the changed categories - */ - public void onControlCategoriesChanged(@NonNull String packageName, - @NonNull List<String> categories) {} - - /** * Called when the list of routes are changed. * A client may refresh available routes for each application. */ @@ -389,7 +392,7 @@ public class MediaRouter2Manager { @Override public void notifyControlCategoriesChanged(String packageName, List<String> categories) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyControlCategoriesChanged, + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateControlCategories, MediaRouter2Manager.this, packageName, categories)); } 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 2cdc6a8101d7..1267aa88a939 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -32,19 +32,35 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ROUTE_NAME1 = "Sample Route 1"; public static final String ROUTE_ID2 = "route_id2"; public static final String ROUTE_NAME2 = "Sample Route 2"; + + public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category"; + public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route"; + public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; + public static final String CATEGORY_SAMPLE = + "com.android.mediarouteprovider.CATEGORY_SAMPLE"; + public static final String CATEGORY_SPECIAL = + "com.android.mediarouteprovider.CATEGORY_SPECIAL"; + Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); private void initializeRoutes() { MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1) + .addSupportedCategory(CATEGORY_SAMPLE) .build(); MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2) + .addSupportedCategory(CATEGORY_SAMPLE) .build(); - + MediaRoute2Info routeSpecial = + new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY) + .addSupportedCategory(CATEGORY_SAMPLE) + .addSupportedCategory(CATEGORY_SPECIAL) + .build(); mRoutes.put(route1.getId(), route1); mRoutes.put(route2.getId(), route2); + mRoutes.put(routeSpecial.getId(), routeSpecial); } @Override diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 03b43e215c25..4282a5bcea01 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -58,9 +58,18 @@ public class MediaRouterManagerTest { public static final String ROUTE_NAME1 = "Sample Route 1"; public static final String ROUTE_ID2 = "route_id2"; public static final String ROUTE_NAME2 = "Sample Route 2"; + + public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category"; + public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route"; + public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; + public static final String CATEGORY_SAMPLE = + "com.android.mediarouteprovider.CATEGORY_SAMPLE"; + public static final String CATEGORY_SPECIAL = + "com.android.mediarouteprovider.CATEGORY_SPECIAL"; + private static final int TIMEOUT_MS = 5000; private Context mContext; @@ -69,12 +78,13 @@ public class MediaRouterManagerTest { private Executor mExecutor; private String mPackageName; - private static final List<String> TEST_CONTROL_CATEGORIES = new ArrayList(); - private static final String CONTROL_CATEGORY_1 = "android.media.mediarouter.MEDIA1"; - private static final String CONTROL_CATEGORY_2 = "android.media.mediarouter.MEDIA2"; + private static final List<String> CONTROL_CATEGORIES_ALL = new ArrayList(); + private static final List<String> CONTROL_CATEGORIES_SPECIAL = new ArrayList(); static { - TEST_CONTROL_CATEGORIES.add(CONTROL_CATEGORY_1); - TEST_CONTROL_CATEGORIES.add(CONTROL_CATEGORY_2); + CONTROL_CATEGORIES_ALL.add(CATEGORY_SAMPLE); + CONTROL_CATEGORIES_ALL.add(CATEGORY_SPECIAL); + + CONTROL_CATEGORIES_SPECIAL.add(CATEGORY_SPECIAL); } @Before @@ -125,7 +135,7 @@ public class MediaRouterManagerTest { // (Control requests shouldn't be used in this way.) InstrumentationRegistry.getInstrumentation().runOnMainSync( (Runnable) () -> { - mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback); + mRouter.addCallback(CONTROL_CATEGORIES_ALL, mExecutor, mockRouterCallback); mRouter.sendControlRequest( new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(), new Intent(ACTION_REMOVE_ROUTE)); @@ -138,8 +148,11 @@ public class MediaRouterManagerTest { mManager.removeCallback(mockCallback); } + /** + * Tests if we get proper routes for application that has special control category. + */ @Test - public void controlCategoryTest() throws Exception { + public void testControlCategory() throws Exception { MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); mManager.addCallback(mExecutor, mockCallback); @@ -147,12 +160,19 @@ public class MediaRouterManagerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> { - mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback); + mRouter.addCallback(CONTROL_CATEGORIES_SPECIAL, + mExecutor, mockRouterCallback); mRouter.removeCallback(mockRouterCallback); } ); - verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) - .onControlCategoriesChanged(mPackageName, TEST_CONTROL_CATEGORIES); + verify(mockCallback, timeout(TIMEOUT_MS)) + .onRouteListChanged(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)); mManager.removeCallback(mockCallback); } @@ -164,7 +184,7 @@ public class MediaRouterManagerTest { MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> { - mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback); + mRouter.addCallback(CONTROL_CATEGORIES_ALL, mExecutor, mockRouterCallback); } ); @@ -197,10 +217,10 @@ public class MediaRouterManagerTest { mManager.removeCallback(managerCallback); } - @Test /** * Tests selecting and unselecting routes of a single provider. */ + @Test public void testSingleProviderSelect() { MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class); MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class); @@ -208,7 +228,7 @@ public class MediaRouterManagerTest { mManager.addCallback(mExecutor, managerCallback); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> { - mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, routerCallback); + mRouter.addCallback(CONTROL_CATEGORIES_ALL, mExecutor, routerCallback); } ); verify(managerCallback, timeout(TIMEOUT_MS)) @@ -218,7 +238,8 @@ public class MediaRouterManagerTest { createRouteMap(mManager.getAvailableRoutes(mPackageName)); mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)); - verify(managerCallback, timeout(TIMEOUT_MS)) + verify(managerCallback, timeout(TIMEOUT_MS) + ) .onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID1, routeInfo.getId()) && TextUtils.equals(routeInfo.getClientPackageName(), mPackageName))); |