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

Commit f4cc8979 authored by Kyunglyul Hyun's avatar Kyunglyul Hyun Committed by Android (Google) Code Review
Browse files

Merge "MediaRouter: Add setControlCategories to MediaRouter"

parents ed998e24 2f43fd6c
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ interface IMediaRouterService {
    void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
    void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
    void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
    void setControlCategories(IMediaRouterClient client, in List<String> categories);

    // Methods for media router 2
    void registerClient2(IMediaRouter2Client client, String packageName);
@@ -52,7 +53,7 @@ interface IMediaRouterService {
     * @param route the route to be selected
     */
    void selectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route);
    void setControlCategories(IMediaRouter2Client client, in List<String> categories);
    void setControlCategories2(IMediaRouter2Client client, in List<String> categories);

    void registerManager(IMediaRouter2Manager manager, String packageName);
    void unregisterManager(IMediaRouter2Manager manager);
+29 −0
Original line number Diff line number Diff line
@@ -49,6 +49,8 @@ import android.view.Display;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@@ -82,6 +84,7 @@ public class MediaRouter {

        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
        List<String> mControlCategories = Collections.emptyList();

        final RouteCategory mSystemCategory;

@@ -358,6 +361,18 @@ public class MediaRouter {
            return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
        }

        public void setControlCategories(Collection<String> controlCategories) {
            List<String> newControlCategories = new ArrayList<>(controlCategories);
            mControlCategories = newControlCategories;
            if (mClient != null) {
                try {
                    mMediaRouterService.setControlCategories(mClient, newControlCategories);
                } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to set control categories.", ex);
                }
            }
        }

        private void updatePresentationDisplays(int changedDisplayId) {
            final int count = mRoutes.size();
            for (int i = 0; i < count; i++) {
@@ -406,6 +421,7 @@ public class MediaRouter {
                try {
                    Client client = new Client();
                    mMediaRouterService.registerClientAsUser(client, mPackageName, userId);
                    mMediaRouterService.setControlCategories(client, mControlCategories);
                    mClient = client;
                } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to register media router client.", ex);
@@ -1302,6 +1318,19 @@ public class MediaRouter {
        sStatic.rebindAsUser(userId);
    }

    /**
     * Sets the control categories of the application.
     * Routes that support at least one of the given control categories only exists and are handled
     * by the media router.
     *
     * @hide
     */
    public void setControlCategories(@NonNull Collection<String> controlCategories) {
        Objects.requireNonNull(controlCategories, "control categories must not be null");

        sStatic.setControlCategories(controlCategories);
    }

    static void updateRoute(final RouteInfo info) {
        dispatchRouteChanged(info);
    }
+2 −2
Original line number Diff line number Diff line
@@ -129,7 +129,7 @@ public class MediaRouter2 {
                Client client = new Client();
                try {
                    mMediaRouterService.registerClient2(client, mPackageName);
                    mMediaRouterService.setControlCategories(client, mControlCategories);
                    mMediaRouterService.setControlCategories2(client, mControlCategories);
                    mClient = client;
                } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to register media router.", ex);
@@ -188,7 +188,7 @@ public class MediaRouter2 {
        }
        if (client != null) {
            try {
                mMediaRouterService.setControlCategories(client, newControlCategories);
                mMediaRouterService.setControlCategories2(client, newControlCategories);
            } catch (RemoteException ex) {
                Log.e(TAG, "Unable to set control categories.", ex);
            }
+48 −19
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRoute2Info;
import android.media.MediaRouter;
import android.media.MediaRouter2;
import android.media.MediaRouter2Manager;
import android.support.test.InstrumentationRegistry;
@@ -72,7 +73,8 @@ public class MediaRouterManagerTest {

    private Context mContext;
    private MediaRouter2Manager mManager;
    private MediaRouter2 mRouter;
    private MediaRouter mRouter;
    private MediaRouter2 mRouter2;
    private Executor mExecutor;
    private String mPackageName;

@@ -89,7 +91,8 @@ public class MediaRouterManagerTest {
    public void setUp() throws Exception {
        mContext = InstrumentationRegistry.getTargetContext();
        mManager = MediaRouter2Manager.getInstance(mContext);
        mRouter = MediaRouter2.getInstance(mContext);
        mRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
        mRouter2 = MediaRouter2.getInstance(mContext);
        //TODO: If we need to support thread pool executors, change this to thread pool executor.
        mExecutor = Executors.newSingleThreadExecutor();
        mPackageName = mContext.getPackageName();
@@ -131,12 +134,12 @@ public class MediaRouterManagerTest {

        //TODO: Figure out a more proper way to test.
        // (Control requests shouldn't be used in this way.)
        mRouter.setControlCategories(CONTROL_CATEGORIES_ALL);
        mRouter.registerCallback(mExecutor, mockRouterCallback);
        mRouter.sendControlRequest(
        mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
        mRouter2.registerCallback(mExecutor, mockRouterCallback);
        mRouter2.sendControlRequest(
                new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(),
                new Intent(ACTION_REMOVE_ROUTE));
        mRouter.unregisterCallback(mockRouterCallback);
        mRouter2.unregisterCallback(mockRouterCallback);

        verify(mockCallback, timeout(TIMEOUT_MS)).onRouteRemoved(argThat(
                (MediaRoute2Info info) ->
@@ -144,6 +147,32 @@ public class MediaRouterManagerTest {
        mManager.unregisterCallback(mockCallback);
    }

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

        MediaRouter.Callback mockRouterCallback = mock(MediaRouter.Callback.class);

        mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
        mRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, mockRouterCallback);

        verify(mockCallback, timeout(TIMEOUT_MS))
                .onRoutesChanged(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));

        mRouter.removeCallback(mockRouterCallback);
        mManager.unregisterCallback(mockCallback);
    }

    /**
     * Tests if we get proper routes for application that has special control category.
     */
@@ -154,9 +183,9 @@ public class MediaRouterManagerTest {

        MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);

        mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
        mRouter.registerCallback(mExecutor, mockRouterCallback);
        mRouter.unregisterCallback(mockRouterCallback);
        mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
        mRouter2.registerCallback(mExecutor, mockRouterCallback);
        mRouter2.unregisterCallback(mockRouterCallback);

        verify(mockCallback, timeout(TIMEOUT_MS))
                .onRoutesChanged(argThat(routes -> routes.size() > 0));
@@ -177,15 +206,15 @@ public class MediaRouterManagerTest {
    public void testGetRoutes() throws Exception {
        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);

        mRouter.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
        mRouter.registerCallback(mExecutor, mockCallback);
        mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
        mRouter2.registerCallback(mExecutor, mockCallback);
        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
                .onRoutesChanged(argThat(routes -> routes.size() > 0));
        Map<String, MediaRoute2Info> routes = createRouteMap(mRouter.getRoutes());
        Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes());
        Assert.assertEquals(1, routes.size());
        Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));

        mRouter.unregisterCallback(mockCallback);
        mRouter2.unregisterCallback(mockCallback);
    }

    @Test
@@ -194,8 +223,8 @@ public class MediaRouterManagerTest {
        MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);

        mManager.registerCallback(mExecutor, managerCallback);
        mRouter.setControlCategories(CONTROL_CATEGORIES_ALL);
        mRouter.registerCallback(mExecutor, mockRouterCallback);
        mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
        mRouter2.registerCallback(mExecutor, mockRouterCallback);

        verify(managerCallback, timeout(TIMEOUT_MS))
                .onRoutesChanged(argThat(routes -> routes.size() > 0));
@@ -211,7 +240,7 @@ public class MediaRouterManagerTest {
                .onRouteAdded(argThat(route -> route.equals(routeToSelect)));

        mManager.unregisterCallback(managerCallback);
        mRouter.unregisterCallback(mockRouterCallback);
        mRouter2.unregisterCallback(mockRouterCallback);
    }

    /**
@@ -223,8 +252,8 @@ public class MediaRouterManagerTest {
        MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);

        mManager.registerCallback(mExecutor, managerCallback);
        mRouter.setControlCategories(CONTROL_CATEGORIES_ALL);
        mRouter.registerCallback(mExecutor, routerCallback);
        mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
        mRouter2.registerCallback(mExecutor, routerCallback);

        verify(managerCallback, timeout(TIMEOUT_MS))
                .onRoutesChanged(argThat(routes -> routes.size() > 0));
@@ -248,7 +277,7 @@ public class MediaRouterManagerTest {
                .onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID2, routeInfo.getId())
                        && TextUtils.equals(routeInfo.getClientPackageName(), null)));

        mRouter.unregisterCallback(routerCallback);
        mRouter2.unregisterCallback(routerCallback);
        mManager.unregisterCallback(managerCallback);
    }

+124 −32
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.IMediaRouter2Client;
import android.media.IMediaRouter2Manager;
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.os.Binder;
@@ -86,7 +87,7 @@ class MediaRouter2ServiceImpl {
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                registerClientLocked(client, uid, pid, packageName, userId, trusted);
                registerClient2Locked(client, uid, pid, packageName, userId, trusted);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
@@ -99,7 +100,7 @@ class MediaRouter2ServiceImpl {
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                unregisterClientLocked(client, false);
                unregisterClient2Locked(client, false);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
@@ -155,14 +156,30 @@ class MediaRouter2ServiceImpl {
        }
    }

    public void setControlCategories(@NonNull IMediaRouter2Client client,
    //TODO: What would happen if a media app used MediaRouter and MediaRouter2 simultaneously?
    public void setControlCategories(@NonNull IMediaRouterClient client,
            @Nullable List<String> categories) {
        Objects.requireNonNull(client, "client must not be null");
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
                setControlCategoriesLocked(clientRecord, categories);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    public void setControlCategories2(@NonNull IMediaRouter2Client client,
            @Nullable List<String> categories) {
        Objects.requireNonNull(client, "client must not be null");

        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                setControlCategoriesLocked(client, categories);
                ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
                setControlCategoriesLocked(clientRecord, categories);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
@@ -174,7 +191,7 @@ class MediaRouter2ServiceImpl {
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                selectRoute2Locked(client, route);
                selectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
@@ -193,6 +210,36 @@ class MediaRouter2ServiceImpl {
        }
    }

    public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) {
        Objects.requireNonNull(client, "client must not be null");

        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();
        final int userId = UserHandle.getUserId(uid);

        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                registerClient1Locked(client, packageName, userId);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    public void unregisterClient(@NonNull IMediaRouterClient client) {
        Objects.requireNonNull(client, "client must not be null");

        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                unregisterClient1Locked(client);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    //TODO: Review this is handling multi-user properly.
    void switchUser() {
        synchronized (mLock) {
@@ -217,9 +264,9 @@ class MediaRouter2ServiceImpl {
        }
    }

    void clientDied(ClientRecord clientRecord) {
    void clientDied(Client2Record clientRecord) {
        synchronized (mLock) {
            unregisterClientLocked(clientRecord.mClient, true);
            unregisterClient2Locked(clientRecord.mClient, true);
        }
    }

@@ -229,18 +276,18 @@ class MediaRouter2ServiceImpl {
        }
    }

    private void registerClientLocked(IMediaRouter2Client client,
    private void registerClient2Locked(IMediaRouter2Client client,
            int uid, int pid, String packageName, int userId, boolean trusted) {
        final IBinder binder = client.asBinder();
        ClientRecord clientRecord = mAllClientRecords.get(binder);
        if (clientRecord == null) {
        if (mAllClientRecords.get(binder) == null) {
            boolean newUser = false;
            UserRecord userRecord = mUserRecords.get(userId);
            if (userRecord == null) {
                userRecord = new UserRecord(userId);
                newUser = true;
            }
            clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
            Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
                    packageName, trusted);
            try {
                binder.linkToDeath(clientRecord, 0);
            } catch (RemoteException ex) {
@@ -261,8 +308,8 @@ class MediaRouter2ServiceImpl {
        }
    }

    private void unregisterClientLocked(IMediaRouter2Client client, boolean died) {
        ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
    private void unregisterClient2Locked(IMediaRouter2Client client, boolean died) {
        Client2Record clientRecord = (Client2Record) mAllClientRecords.remove(client.asBinder());
        if (clientRecord != null) {
            UserRecord userRecord = clientRecord.mUserRecord;
            userRecord.mClientRecords.remove(clientRecord);
@@ -272,8 +319,7 @@ class MediaRouter2ServiceImpl {
        }
    }

    private void selectRoute2Locked(IMediaRouter2Client client, MediaRoute2Info route) {
        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
    private void selectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
        if (clientRecord != null) {
            MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
            clientRecord.mSelectedRoute = route;
@@ -294,10 +340,7 @@ class MediaRouter2ServiceImpl {
        }
    }

    private void setControlCategoriesLocked(IMediaRouter2Client client, List<String> categories) {
        final IBinder binder = client.asBinder();
        ClientRecord clientRecord = mAllClientRecords.get(binder);

    private void setControlCategoriesLocked(ClientRecord clientRecord, List<String> categories) {
        if (clientRecord != null) {
            clientRecord.mControlCategories = categories;

@@ -349,9 +392,7 @@ class MediaRouter2ServiceImpl {
                    obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager,
                            userRecord.mHandler, manager));

            final int count = userRecord.mClientRecords.size();
            for (int i = 0; i < count; i++) {
                ClientRecord clientRecord = userRecord.mClientRecords.get(i);
            for (ClientRecord clientRecord : userRecord.mClientRecords) {
                clientRecord.mUserRecord.mHandler.sendMessage(
                        obtainMessage(UserHandler::updateClientUsage,
                            clientRecord.mUserRecord.mHandler, clientRecord));
@@ -378,7 +419,7 @@ class MediaRouter2ServiceImpl {
                Slog.w(TAG, "Ignoring route selection for unknown client.");
            }
            if (clientRecord != null && managerRecord.mTrusted) {
                selectRoute2Locked(clientRecord.mClient, route);
                selectRoute2Locked(clientRecord, route);
            }
        }
    }
@@ -409,6 +450,37 @@ class MediaRouter2ServiceImpl {
        }
    }

    private void registerClient1Locked(IMediaRouterClient client, String packageName,
            int userId) {
        final IBinder binder = client.asBinder();
        if (mAllClientRecords.get(binder) == null) {
            boolean newUser = false;
            UserRecord userRecord = mUserRecords.get(userId);
            if (userRecord == null) {
                userRecord = new UserRecord(userId);
                newUser = true;
            }
            ClientRecord clientRecord = new Client1Record(userRecord, client, packageName);

            if (newUser) {
                mUserRecords.put(userId, userRecord);
                initializeUserLocked(userRecord);
            }

            userRecord.mClientRecords.add(clientRecord);
            mAllClientRecords.put(binder, clientRecord);
        }
    }

    private void unregisterClient1Locked(IMediaRouterClient client) {
        ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
        if (clientRecord != null) {
            UserRecord userRecord = clientRecord.mUserRecord;
            userRecord.mClientRecords.remove(clientRecord);
            disposeUserIfNeededLocked(userRecord);
        }
    }

    final class UserRecord {
        public final int mUserId;
        final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
@@ -430,25 +502,43 @@ class MediaRouter2ServiceImpl {
        }
    }

    final class ClientRecord implements IBinder.DeathRecipient {
    class ClientRecord {
        public final UserRecord mUserRecord;
        public final String mPackageName;
        public List<String> mControlCategories;
        public MediaRoute2Info mSelectedRoute;

        ClientRecord(UserRecord userRecord, String packageName) {
            mUserRecord = userRecord;
            mPackageName = packageName;
            mControlCategories = Collections.emptyList();
        }
    }

    final class Client1Record extends ClientRecord {
        public final IMediaRouterClient mClient;

        Client1Record(UserRecord userRecord, IMediaRouterClient client,
                String packageName) {
            super(userRecord, packageName);
            mClient = client;
        }
    }

    final class Client2Record extends ClientRecord
            implements IBinder.DeathRecipient {
        public final IMediaRouter2Client mClient;
        public final int mUid;
        public final int mPid;
        public final String mPackageName;
        public final boolean mTrusted;
        public List<String> mControlCategories;
        public MediaRoute2Info mSelectedRoute;

        ClientRecord(UserRecord userRecord, IMediaRouter2Client client,
        Client2Record(UserRecord userRecord, IMediaRouter2Client client,
                int uid, int pid, String packageName, boolean trusted) {
            mUserRecord = userRecord;
            super(userRecord, packageName);
            mClient = client;
            mUid = uid;
            mPid = pid;
            mPackageName = packageName;
            mTrusted = trusted;
            mControlCategories = Collections.emptyList();
        }

        public void dispose() {
@@ -622,7 +712,9 @@ class MediaRouter2ServiceImpl {
                    managers.add(managerRecord.mManager);
                }
                for (ClientRecord clientRecord : mUserRecord.mClientRecords) {
                    clients.add(clientRecord.mClient);
                    if (clientRecord instanceof Client2Record) {
                        clients.add(((Client2Record) clientRecord).mClient);
                    }
                }
            }
            for (IMediaRouter2Manager manager : managers) {
Loading