Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e23e5ed7 authored by Kyunglyul Hyun's avatar Kyunglyul Hyun
Browse files

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
parent 62094268
Loading
Loading
Loading
Loading
+73 −1
Original line number Original line Diff line number Diff line
@@ -23,6 +23,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.TextUtils;


import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Objects;


/**
/**
@@ -53,6 +56,8 @@ public final class MediaRoute2Info implements Parcelable {
    final String mDescription;
    final String mDescription;
    @Nullable
    @Nullable
    final String mClientPackageName;
    final String mClientPackageName;
    @NonNull
    final List<String> mSupportedCategories;
    @Nullable
    @Nullable
    final Bundle mExtras;
    final Bundle mExtras;


@@ -62,6 +67,7 @@ public final class MediaRoute2Info implements Parcelable {
        mName = builder.mName;
        mName = builder.mName;
        mDescription = builder.mDescription;
        mDescription = builder.mDescription;
        mClientPackageName = builder.mClientPackageName;
        mClientPackageName = builder.mClientPackageName;
        mSupportedCategories = builder.mSupportedCategories;
        mExtras = builder.mExtras;
        mExtras = builder.mExtras;
    }
    }


@@ -71,6 +77,7 @@ public final class MediaRoute2Info implements Parcelable {
        mName = in.readString();
        mName = in.readString();
        mDescription = in.readString();
        mDescription = in.readString();
        mClientPackageName = in.readString();
        mClientPackageName = in.readString();
        mSupportedCategories = in.createStringArrayList();
        mExtras = in.readBundle();
        mExtras = in.readBundle();
    }
    }


@@ -103,13 +110,14 @@ public final class MediaRoute2Info implements Parcelable {
                && Objects.equals(mName, other.mName)
                && Objects.equals(mName, other.mName)
                && Objects.equals(mDescription, other.mDescription)
                && Objects.equals(mDescription, other.mDescription)
                && Objects.equals(mClientPackageName, other.mClientPackageName)
                && Objects.equals(mClientPackageName, other.mClientPackageName)
                && Objects.equals(mSupportedCategories, other.mSupportedCategories)
                //TODO: This will be evaluated as false in most cases. Try not to.
                //TODO: This will be evaluated as false in most cases. Try not to.
                && Objects.equals(mExtras, other.mExtras);
                && Objects.equals(mExtras, other.mExtras);
    }
    }


    @Override
    @Override
    public int hashCode() {
    public int hashCode() {
        return Objects.hash(mId, mName, mDescription);
        return Objects.hash(mId, mName, mDescription, mSupportedCategories);
    }
    }


    @NonNull
    @NonNull
@@ -146,11 +154,38 @@ public final class MediaRoute2Info implements Parcelable {
        return mClientPackageName;
        return mClientPackageName;
    }
    }


    /**
     * Gets the supported categories of the route.
     */
    @NonNull
    public List<String> getSupportedCategories() {
        return mSupportedCategories;
    }

    @Nullable
    @Nullable
    public Bundle getExtras() {
    public Bundle getExtras() {
        return mExtras;
        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
    @Override
    public int describeContents() {
    public int describeContents() {
        return 0;
        return 0;
@@ -163,6 +198,7 @@ public final class MediaRoute2Info implements Parcelable {
        dest.writeString(mName);
        dest.writeString(mName);
        dest.writeString(mDescription);
        dest.writeString(mDescription);
        dest.writeString(mClientPackageName);
        dest.writeString(mClientPackageName);
        dest.writeStringList(mSupportedCategories);
        dest.writeBundle(mExtras);
        dest.writeBundle(mExtras);
    }
    }


@@ -187,6 +223,7 @@ public final class MediaRoute2Info implements Parcelable {
        String mName;
        String mName;
        String mDescription;
        String mDescription;
        String mClientPackageName;
        String mClientPackageName;
        List<String> mSupportedCategories;
        Bundle mExtras;
        Bundle mExtras;


        public Builder(@NonNull String id, @NonNull String name) {
        public Builder(@NonNull String id, @NonNull String name) {
@@ -198,6 +235,7 @@ public final class MediaRoute2Info implements Parcelable {
            }
            }
            setId(id);
            setId(id);
            setName(name);
            setName(name);
            mSupportedCategories = new ArrayList<>();
        }
        }


        public Builder(@NonNull MediaRoute2Info routeInfo) {
        public Builder(@NonNull MediaRoute2Info routeInfo) {
@@ -212,6 +250,7 @@ public final class MediaRoute2Info implements Parcelable {
            setName(routeInfo.mName);
            setName(routeInfo.mName);
            mDescription = routeInfo.mDescription;
            mDescription = routeInfo.mDescription;
            setClientPackageName(routeInfo.mClientPackageName);
            setClientPackageName(routeInfo.mClientPackageName);
            setSupportedCategories(routeInfo.mSupportedCategories);
            if (routeInfo.mExtras != null) {
            if (routeInfo.mExtras != null) {
                mExtras = new Bundle(routeInfo.mExtras);
                mExtras = new Bundle(routeInfo.mExtras);
            }
            }
@@ -272,6 +311,39 @@ public final class MediaRoute2Info implements Parcelable {
            return this;
            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.
         * Sets a bundle of extras for the route.
         */
         */
+20 −17
Original line number Original line Diff line number Diff line
@@ -38,6 +38,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;


@@ -66,6 +68,8 @@ public class MediaRouter2Manager {
    List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList();
    List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList();
    @NonNull
    @NonNull
    List<MediaRoute2Info> mRoutes = Collections.emptyList();
    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.
     * Gets an instance of media router manager that controls media route of other applications.
@@ -160,6 +164,8 @@ public class MediaRouter2Manager {
        return -1;
        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.
     * Gets available routes for an application.
     *
     *
@@ -167,8 +173,17 @@ public class MediaRouter2Manager {
     */
     */
    @NonNull
    @NonNull
    public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
    public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
        //TODO: filter irrelevant routes.
        List<String> controlCategories = mControlCategoryMap.get(packageName);
        return Collections.unmodifiableList(mRoutes);
        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) {
    void updateControlCategories(String packageName, List<String> categories) {
        for (CallbackRecord record : mCallbacks) {
        mControlCategoryMap.put(packageName, categories);
            record.mExecutor.execute(
                    () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
        }
    }
    }


    /**
    /**
@@ -345,15 +357,6 @@ public class MediaRouter2Manager {
         */
         */
        public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {}
        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.
         * Called when the list of routes are changed.
         * A client may refresh available routes for each application.
         * A client may refresh available routes for each application.
@@ -389,7 +392,7 @@ public class MediaRouter2Manager {


        @Override
        @Override
        public void notifyControlCategoriesChanged(String packageName, List<String> categories) {
        public void notifyControlCategoriesChanged(String packageName, List<String> categories) {
            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyControlCategoriesChanged,
            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateControlCategories,
                    MediaRouter2Manager.this, packageName, categories));
                    MediaRouter2Manager.this, packageName, categories));
        }
        }


+17 −1
Original line number Original line Diff line number Diff line
@@ -32,19 +32,35 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
    public static final String ROUTE_NAME1 = "Sample Route 1";
    public static final String ROUTE_NAME1 = "Sample Route 1";
    public static final String ROUTE_ID2 = "route_id2";
    public static final String ROUTE_ID2 = "route_id2";
    public static final String ROUTE_NAME2 = "Sample Route 2";
    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 =
    public static final String ACTION_REMOVE_ROUTE =
            "com.android.mediarouteprovider.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<>();
    Map<String, MediaRoute2Info> mRoutes = new HashMap<>();


    private void initializeRoutes() {
    private void initializeRoutes() {
        MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
        MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
                .addSupportedCategory(CATEGORY_SAMPLE)
                .build();
                .build();
        MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
        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();
                        .build();

        mRoutes.put(route1.getId(), route1);
        mRoutes.put(route1.getId(), route1);
        mRoutes.put(route2.getId(), route2);
        mRoutes.put(route2.getId(), route2);
        mRoutes.put(routeSpecial.getId(), routeSpecial);
    }
    }


    @Override
    @Override
+35 −14
Original line number Original line Diff line number Diff line
@@ -58,9 +58,18 @@ public class MediaRouterManagerTest {
    public static final String ROUTE_NAME1 = "Sample Route 1";
    public static final String ROUTE_NAME1 = "Sample Route 1";
    public static final String ROUTE_ID2 = "route_id2";
    public static final String ROUTE_ID2 = "route_id2";
    public static final String ROUTE_NAME2 = "Sample Route 2";
    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 =
    public static final String ACTION_REMOVE_ROUTE =
            "com.android.mediarouteprovider.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 static final int TIMEOUT_MS = 5000;


    private Context mContext;
    private Context mContext;
@@ -69,12 +78,13 @@ public class MediaRouterManagerTest {
    private Executor mExecutor;
    private Executor mExecutor;
    private String mPackageName;
    private String mPackageName;


    private static final List<String> TEST_CONTROL_CATEGORIES = new ArrayList();
    private static final List<String> CONTROL_CATEGORIES_ALL = new ArrayList();
    private static final String CONTROL_CATEGORY_1 = "android.media.mediarouter.MEDIA1";
    private static final List<String> CONTROL_CATEGORIES_SPECIAL = new ArrayList();
    private static final String CONTROL_CATEGORY_2 = "android.media.mediarouter.MEDIA2";
    static {
    static {
        TEST_CONTROL_CATEGORIES.add(CONTROL_CATEGORY_1);
        CONTROL_CATEGORIES_ALL.add(CATEGORY_SAMPLE);
        TEST_CONTROL_CATEGORIES.add(CONTROL_CATEGORY_2);
        CONTROL_CATEGORIES_ALL.add(CATEGORY_SPECIAL);

        CONTROL_CATEGORIES_SPECIAL.add(CATEGORY_SPECIAL);
    }
    }


    @Before
    @Before
@@ -125,7 +135,7 @@ public class MediaRouterManagerTest {
        // (Control requests shouldn't be used in this way.)
        // (Control requests shouldn't be used in this way.)
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                (Runnable) () -> {
                (Runnable) () -> {
                    mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback);
                    mRouter.addCallback(CONTROL_CATEGORIES_ALL, mExecutor, mockRouterCallback);
                    mRouter.sendControlRequest(
                    mRouter.sendControlRequest(
                            new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(),
                            new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(),
                            new Intent(ACTION_REMOVE_ROUTE));
                            new Intent(ACTION_REMOVE_ROUTE));
@@ -138,8 +148,11 @@ public class MediaRouterManagerTest {
        mManager.removeCallback(mockCallback);
        mManager.removeCallback(mockCallback);
    }
    }


    /**
     * Tests if we get proper routes for application that has special control category.
     */
    @Test
    @Test
    public void controlCategoryTest() throws Exception {
    public void testControlCategory() throws Exception {
        MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
        MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
        mManager.addCallback(mExecutor, mockCallback);
        mManager.addCallback(mExecutor, mockCallback);


@@ -147,12 +160,19 @@ public class MediaRouterManagerTest {


        InstrumentationRegistry.getInstrumentation().runOnMainSync(
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> {
                () -> {
                    mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback);
                    mRouter.addCallback(CONTROL_CATEGORIES_SPECIAL,
                            mExecutor, mockRouterCallback);
                    mRouter.removeCallback(mockRouterCallback);
                    mRouter.removeCallback(mockRouterCallback);
                }
                }
        );
        );
        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
        verify(mockCallback, timeout(TIMEOUT_MS))
                .onControlCategoriesChanged(mPackageName, TEST_CONTROL_CATEGORIES);
                .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);
        mManager.removeCallback(mockCallback);
    }
    }
@@ -164,7 +184,7 @@ public class MediaRouterManagerTest {
        MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
        MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
        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);
        mManager.removeCallback(managerCallback);
    }
    }


    @Test
    /**
    /**
     * Tests selecting and unselecting routes of a single provider.
     * Tests selecting and unselecting routes of a single provider.
     */
     */
    @Test
    public void testSingleProviderSelect() {
    public void testSingleProviderSelect() {
        MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
        MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
        MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
        MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
@@ -208,7 +228,7 @@ public class MediaRouterManagerTest {
        mManager.addCallback(mExecutor, managerCallback);
        mManager.addCallback(mExecutor, managerCallback);
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> {
                () -> {
                    mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, routerCallback);
                    mRouter.addCallback(CONTROL_CATEGORIES_ALL, mExecutor, routerCallback);
                }
                }
        );
        );
        verify(managerCallback, timeout(TIMEOUT_MS))
        verify(managerCallback, timeout(TIMEOUT_MS))
@@ -218,7 +238,8 @@ public class MediaRouterManagerTest {
                createRouteMap(mManager.getAvailableRoutes(mPackageName));
                createRouteMap(mManager.getAvailableRoutes(mPackageName));


        mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
        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())
                .onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID1, routeInfo.getId())
                        && TextUtils.equals(routeInfo.getClientPackageName(), mPackageName)));
                        && TextUtils.equals(routeInfo.getClientPackageName(), mPackageName)));