Media : Implement getAvailableRoutes in MediaRouter2Manager
This CL adds capability to media route 2 info, which can be used
to get available routes for each app, which set control categories.
The test for control category is changed to test getAvailableRoutes().
Test: atest mediaroutertest
Change-Id: If93d64f02b4868b5e04b737431291b18a52177de
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index f13a64c..5dcbb05 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -23,6 +23,9 @@
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 @@
final String mDescription;
@Nullable
final String mClientPackageName;
+ @NonNull
+ final List<String> mSupportedCategories;
@Nullable
final Bundle mExtras;
@@ -62,6 +67,7 @@
mName = builder.mName;
mDescription = builder.mDescription;
mClientPackageName = builder.mClientPackageName;
+ mSupportedCategories = builder.mSupportedCategories;
mExtras = builder.mExtras;
}
@@ -71,6 +77,7 @@
mName = in.readString();
mDescription = in.readString();
mClientPackageName = in.readString();
+ mSupportedCategories = in.createStringArrayList();
mExtras = in.readBundle();
}
@@ -103,13 +110,14 @@
&& 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 @@
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 @@
dest.writeString(mName);
dest.writeString(mDescription);
dest.writeString(mClientPackageName);
+ dest.writeStringList(mSupportedCategories);
dest.writeBundle(mExtras);
}
@@ -187,6 +223,7 @@
String mName;
String mDescription;
String mClientPackageName;
+ List<String> mSupportedCategories;
Bundle mExtras;
public Builder(@NonNull String id, @NonNull String name) {
@@ -198,6 +235,7 @@
}
setId(id);
setName(name);
+ mSupportedCategories = new ArrayList<>();
}
public Builder(@NonNull MediaRoute2Info routeInfo) {
@@ -212,6 +250,7 @@
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 @@
}
/**
+ * 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 5fc37a5..85105e0 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -38,6 +38,8 @@
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 @@
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 @@
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 @@
*/
@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 @@
}
}
- 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 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 @@
@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 2cdc6a8..1267aa8 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 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 03b43e2..4282a5bc 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 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 @@
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 @@
// (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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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)));