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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -272,6 +311,39 @@ public final class MediaRoute2Info implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,15 +357,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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user