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:
Kyunglyul Hyun
2019-07-15 16:06:26 +09:00
parent 620942687b
commit e23e5ed72b
4 changed files with 145 additions and 33 deletions

View File

@@ -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.
*/

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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)));