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

Commit 057b4b8d authored by Hyundo Moon's avatar Hyundo Moon
Browse files

System MediaRouter2: Resolve API review

This CL resolves API reviews on system APIs on MediaRouter2.

1) It checks permission when registering the MediaRoute2Manager
   in system server. Therefore, app cannot bypass the permission
   checks via reflection.
   The system api calls will be no-op without permission.

2) It adds @RequiresPermission annotations to related APIs.

Also, this CL changes the required permission
from MODIFY_AUDIO_ROUTING to MEDIA_CONTENT_CONTROL,
as the media routing can be applied to video, not only to audio.

Note that MediaRouter2#getInstance(Context, String)
does not throw SecurityException anymore.

Bug: 183428114
Test: atest android.media.cts.MediaRouter2Test
      atest android.media.cts.SystemMediaRouterTest
      (MediaRouter2ManagerTest is already broken, and
       that will be handled out of this CL.)
Change-Id: I55013a5b4b954ca1c8e2adb586f49f01103563db
parent 6ffbb956
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -23322,6 +23322,7 @@ package android.media {
  }
  public final class MediaRouter2 {
    method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
    method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
    method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
    method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
+5 −6
Original line number Diff line number Diff line
@@ -5301,12 +5301,11 @@ package android.media {
  public final class MediaRouter2 {
    method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
    method @Nullable public String getClientPackageName();
    method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
    method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
    method public void startScan();
    method public void stopScan();
    method public void transfer(@NonNull android.media.MediaRouter2.RoutingController, @NonNull android.media.MediaRoute2Info);
    method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void startScan();
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void stopScan();
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void transfer(@NonNull android.media.MediaRouter2.RoutingController, @NonNull android.media.MediaRoute2Info);
  }
  public abstract static class MediaRouter2.RouteCallback {
+1 −1
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ interface IMediaRouterService {
    // MediaRouterService.java for readability.

    // Methods for MediaRouter2
    void checkModifyAudioRoutingPermission();
    void enforceMediaContentControlPermission();
    List<MediaRoute2Info> getSystemRoutes();
    RoutingSessionInfo getSystemSessionInfo();

+57 −60
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.media;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -93,7 +94,7 @@ public final class MediaRouter2 {

    // TODO: Specify the fields that are only used (or not used) by system media router.
    private final String mClientPackageName;
    private final ManagerCallback mManagerCallback;
    final ManagerCallback mManagerCallback;

    private final String mPackageName;

@@ -164,13 +165,24 @@ public final class MediaRouter2 {
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
    @Nullable
    public static MediaRouter2 getInstance(@NonNull Context context,
            @NonNull String clientPackageName) {
        Objects.requireNonNull(context, "context must not be null");
        Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");

        // Note: Even though this check could be somehow bypassed, the other permission checks
        // in system server will not allow MediaRouter2Manager to be registered.
        IMediaRouterService serviceBinder = IMediaRouterService.Stub.asInterface(
                ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
        try {
            // SecurityException will be thrown if there's no permission.
            serviceBinder.enforceMediaContentControlPermission();
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to check MEDIA_CONTENT_CONTROL permission.");
        }

        PackageManager pm = context.getPackageManager();
        try {
            pm.getPackageInfo(clientPackageName, 0);
@@ -183,20 +195,13 @@ public final class MediaRouter2 {
            MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName);
            if (instance == null) {
                if (sManager == null) {
                    IMediaRouterService serviceBinder = IMediaRouterService.Stub.asInterface(
                            ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
                    try {
                        // MediaRouterService will throw a SecurityException if the caller
                        // doesn't have MODIFY_AUDIO_ROUTING permission.
                        serviceBinder.checkModifyAudioRoutingPermission();
                    } catch (RemoteException e) {
                        e.rethrowAsRuntimeException();
                    }
                    sManager = MediaRouter2Manager.getInstance(context.getApplicationContext());
                }
                instance = new MediaRouter2(context, clientPackageName);
                sSystemMediaRouter2Map.put(clientPackageName, instance);
                instance.registerManagerCallbackForSystemRouter();
                // Using direct executor here, since MediaRouter2Manager also posts
                // to the main handler.
                sManager.registerCallback(Runnable::run, instance.mManagerCallback);
            }
            return instance;
        }
@@ -213,11 +218,15 @@ public final class MediaRouter2 {
     * Use {@link RouteCallback} to get the route related events.
     * <p>
     * Note that calling start/stopScan is applied to all system routers in the same process.
     * <p>
     * This will be no-op for non-system media routers.
     *
     * @see #stopScan()
     * @see #getInstance(Context, String)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void startScan() {
        if (isSystemRouter()) {
            sManager.startScan();
@@ -236,11 +245,15 @@ public final class MediaRouter2 {
     * Use {@link RouteCallback} to get the route related events.
     * <p>
     * Note that calling start/stopScan is applied to all system routers in the same process.
     * <p>
     * This will be no-op for non-system media routers.
     *
     * @see #startScan()
     * @see #getInstance(Context, String)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void stopScan() {
        if (isSystemRouter()) {
            sManager.stopScan();
@@ -314,7 +327,8 @@ public final class MediaRouter2 {

    /**
     * Gets the client package name of the app which this media router controls.
     * This is only non-null when the router instance is created with the client package name.
     * <p>
     * This will return null for non-system media routers.
     *
     * @see #getInstance(Context, String)
     * @hide
@@ -573,9 +587,25 @@ public final class MediaRouter2 {
            return;
        }

        Objects.requireNonNull(route, "route must not be null");
        Log.v(TAG, "Transferring to route: " + route);
        transfer(getCurrentController(), route);

        boolean routeFound;
        synchronized (mLock) {
            // TODO: Check thread-safety
            routeFound = mRoutes.containsKey(route.getId());
        }
        if (!routeFound) {
            notifyTransferFailure(route);
            return;
        }

        RoutingController controller = getCurrentController();
        if (controller.getRoutingSessionInfo().getTransferableRoutes().contains(route.getId())) {
            controller.transferToRoute(route);
            return;
        }

        requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
    }

    /**
@@ -594,36 +624,20 @@ public final class MediaRouter2 {

    /**
     * Transfers the media of a routing controller to the given route.
     * <p>
     * This will be no-op for non-system media routers.
     *
     * @param controller a routing controller controlling media routing.
     * @param route the route you want to transfer the media to.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
        if (isSystemRouter()) {
            sManager.transfer(controller.getRoutingSessionInfo(), route);
            return;
        }

        Objects.requireNonNull(controller, "controller must not be null");
        Objects.requireNonNull(route, "route must not be null");

        boolean routeFound;
        synchronized (mLock) {
            // TODO: Check thread-safety
            routeFound = mRoutes.containsKey(route.getId());
        }
        if (!routeFound) {
            notifyTransferFailure(route);
            return;
        }

        if (controller.getRoutingSessionInfo().getTransferableRoutes().contains(route.getId())) {
            controller.transferToRoute(route);
            return;
        }

        requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
    }

    void requestCreateController(@NonNull RoutingController controller,
@@ -687,9 +701,7 @@ public final class MediaRouter2 {
    /**
     * Gets a {@link RoutingController} whose ID is equal to the given ID.
     * Returns {@code null} if there is no matching controller.
     * @hide
     */
    @SystemApi
    @Nullable
    public RoutingController getController(@NonNull String id) {
        Objects.requireNonNull(id, "id must not be null");
@@ -739,14 +751,16 @@ public final class MediaRouter2 {

    /**
     * Requests a volume change for the route asynchronously.
     * <p>
     * It may have no effect if the route is currently not selected.
     * </p>
     * <p>
     * This will be no-op for non-system media routers.
     *
     * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
     * @see #getInstance(Context, String)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
        Objects.requireNonNull(route, "route must not be null");

@@ -754,18 +768,7 @@ public final class MediaRouter2 {
            sManager.setRouteVolume(route, volume);
            return;
        }

        MediaRouter2Stub stub;
        synchronized (mLock) {
            stub = mStub;
        }
        if (stub != null) {
            try {
                mMediaRouterService.setRouteVolumeWithRouter2(stub, route, volume);
            } catch (RemoteException ex) {
                Log.e(TAG, "Unable to set route volume.", ex);
            }
        }
        // If this API needs to be public, use IMediaRouterService#setRouteVolumeWithRouter2()
    }

    void syncRoutesOnHandler(List<MediaRoute2Info> currentRoutes,
@@ -1039,15 +1042,6 @@ public final class MediaRouter2 {
        return mClientPackageName != null;
    }

    /**
     * Registers {@link MediaRouter2Manager.Callback} for getting events.
     * Should only used for system media routers.
     */
    private void registerManagerCallbackForSystemRouter() {
        // Using direct executor here, since MediaRouter2Manager also posts to the main handler.
        sManager.registerCallback(Runnable::run, mManagerCallback);
    }

    /**
     * Returns a {@link RoutingSessionInfo} which has the client package name.
     * The client package name is set only when the given sessionInfo doesn't have it.
@@ -1073,6 +1067,9 @@ public final class MediaRouter2 {
    }

    private void updateAllRoutesFromManager() {
        if (!isSystemRouter()) {
            return;
        }
        synchronized (mLock) {
            mRoutes.clear();
            for (MediaRoute2Info route : sManager.getAllRoutes()) {
+2 −0
Original line number Diff line number Diff line
@@ -49,6 +49,8 @@ import java.util.stream.Collectors;

/**
 * A class that monitors and controls media routing of other apps.
 * {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} is required to use this class,
 * or {@link SecurityException} will be thrown.
 * @hide
 */
public final class MediaRouter2Manager {
Loading