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

Commit 72522b92 authored by Hyundo Moon's avatar Hyundo Moon Committed by Android (Google) Code Review
Browse files

Merge "System MediaRouter2: Add RouteCallback functionality" into sc-dev

parents bc80405a 3e241ceb
Loading
Loading
Loading
Loading
+171 −40
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ import java.util.stream.Collectors;
public final class MediaRouter2 {
    private static final String TAG = "MR2";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final Object sSystemRouterLock = new Object();
    private static final Object sRouterLock = new Object();

    // The maximum time for the old routing controller available after transfer.
@@ -67,8 +68,8 @@ public final class MediaRouter2 {
    // The manager request ID representing that no manager is involved.
    private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE;

    @GuardedBy("sRouterLock")
    private static Map<String, MediaRouter2> sMediaRouter2Map = new ArrayMap<>();
    @GuardedBy("sSystemRouterLock")
    private static Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>();
    private static MediaRouter2Manager sManager;

    @GuardedBy("sRouterLock")
@@ -76,6 +77,7 @@ public final class MediaRouter2 {

    private final Context mContext;
    private final IMediaRouterService mMediaRouterService;
    private final Object mLock = new Object();

    private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
            new CopyOnWriteArrayList<>();
@@ -88,27 +90,29 @@ public final class MediaRouter2 {
            new CopyOnWriteArrayList<>();

    private final String mClientPackageName;
    private final ManagerCallback mManagerCallback;

    private final String mPackageName;

    @GuardedBy("sRouterLock")
    @GuardedBy("mLock")
    final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();

    final RoutingController mSystemController;

    @GuardedBy("sRouterLock")
    @GuardedBy("mLock")
    private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;

    // TODO: Make MediaRouter2 is always connected to the MediaRouterService.
    @GuardedBy("sRouterLock")
    @GuardedBy("mLock")
    MediaRouter2Stub mStub;

    @GuardedBy("sRouterLock")
    @GuardedBy("mLock")
    private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();

    private final AtomicInteger mNextRequestId = new AtomicInteger(1);

    final Handler mHandler;
    @GuardedBy("sRouterLock")
    @GuardedBy("mLock")
    private boolean mShouldUpdateRoutes = true;
    private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
    private volatile OnGetControllerHintsListener mOnGetControllerHintsListener;
@@ -130,6 +134,11 @@ public final class MediaRouter2 {
    /**
     * Gets an instance of the media router which controls the app's media routing.
     * Returns {@code null} if the given package name is invalid.
     * <p>
     * Note: For media routers created with this method, the discovery preference passed to
     * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
     * with the client app's discovery preference. Therefore, it is recommended to pass
     * {@link RouteDiscoveryPreference#EMPTY} there.
     *
     * @param clientPackageName the package name of the app to control
     * @hide
@@ -149,15 +158,17 @@ public final class MediaRouter2 {
            return null;
        }

        synchronized (sRouterLock) {
            MediaRouter2 instance = sMediaRouter2Map.get(clientPackageName);
        synchronized (sSystemRouterLock) {
            MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName);
            if (instance == null) {
                // TODO: Add permission check here using MODIFY_AUDIO_ROUTING.
                if (sManager == null) {
                    sManager = MediaRouter2Manager.getInstance(context.getApplicationContext());
                }
                instance = new MediaRouter2(context, clientPackageName);
                sMediaRouter2Map.put(clientPackageName, instance);
                sSystemMediaRouter2Map.put(clientPackageName, instance);
                // TODO: Remove router instance once it is not needed.
                instance.registerManagerCallback();
            }
            return instance;
        }
@@ -192,11 +203,14 @@ public final class MediaRouter2 {
        }
        mSystemController = new SystemRoutingController(currentSystemSessionInfo);

        // Only used by system MediaRouter2.
        mClientPackageName = null;
        mManagerCallback = null;
    }

    private MediaRouter2(Context context, String clientPackageName) {
        mClientPackageName = clientPackageName;
        mManagerCallback = new ManagerCallback();
        mContext = context;
        mMediaRouterService = null;
        mPackageName = null;
@@ -220,8 +234,8 @@ public final class MediaRouter2 {
    }

    /**
     * Gets the target package name of the app which this media router controls.
     * This is only non-null when the router instance is created with the target package name.
     * 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.
     *
     * @see #getInstance(Context, String)
     * @hide
@@ -245,6 +259,9 @@ public final class MediaRouter2 {
        Objects.requireNonNull(executor, "executor must not be null");
        Objects.requireNonNull(routeCallback, "callback must not be null");
        Objects.requireNonNull(preference, "preference must not be null");
        if (isSystemRouter()) {
            preference = RouteDiscoveryPreference.EMPTY;
        }

        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);

@@ -253,7 +270,11 @@ public final class MediaRouter2 {
        // is happening but it's okay because either this or the other registration should be done.
        mRouteCallbackRecords.addIfAbsent(record);

        synchronized (sRouterLock) {
        if (isSystemRouter()) {
            return;
        }

        synchronized (mLock) {
            if (mStub == null) {
                MediaRouter2Stub stub = new MediaRouter2Stub();
                try {
@@ -289,7 +310,11 @@ public final class MediaRouter2 {
            return;
        }

        synchronized (sRouterLock) {
        if (isSystemRouter()) {
            return;
        }

        synchronized (mLock) {
            if (mStub == null) {
                return;
            }
@@ -325,23 +350,38 @@ public final class MediaRouter2 {
        return true;
    }

    /**
     * Gets the list of all discovered routes.
     * This list includes the routes that are not related to the client app.
     * <p>
     * This will return an empty list for non-system media routers.
     *
     * @hide
     */
    //@SystemApi
    public List<MediaRoute2Info> getAllRoutes() {
        if (isSystemRouter()) {
            return sManager.getAllRoutes();
        }
        return Collections.emptyList();
    }

    /**
     * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
     * known to the media router.
     * <p>
     * Please note that the list can be changed before callbacks are invoked.
     * </p>
     *
     * @return the list of routes that contains at least one of the route features in discovery
     * preferences registered by the application
     */
    @NonNull
    public List<MediaRoute2Info> getRoutes() {
        if (mClientPackageName != null) {
        if (isSystemRouter()) {
            return sManager.getAvailableRoutes(mClientPackageName);
        }

        synchronized (sRouterLock) {
        synchronized (mLock) {
            if (mShouldUpdateRoutes) {
                mShouldUpdateRoutes = false;

@@ -449,7 +489,7 @@ public final class MediaRouter2 {
     * @see TransferCallback#onTransferFailure
     */
    public void transferTo(@NonNull MediaRoute2Info route) {
        if (mClientPackageName != null) {
        if (isSystemRouter()) {
            sManager.selectRoute(mClientPackageName, route);
            return;
        }
@@ -464,7 +504,7 @@ public final class MediaRouter2 {
     * controls the media routing, this method is a no-op.
     */
    public void stop() {
        if (mClientPackageName != null) {
        if (isSystemRouter()) {
            List<RoutingSessionInfo> sessionInfos = sManager.getRoutingSessions(mClientPackageName);
            RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1);
            sManager.releaseSession(sessionToRelease);
@@ -481,7 +521,7 @@ public final class MediaRouter2 {
     */
    //@SystemApi
    public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
        if (mClientPackageName != null) {
        if (isSystemRouter()) {
            sManager.transfer(controller.getRoutingSessionInfo(), route);
            return;
        }
@@ -490,7 +530,7 @@ public final class MediaRouter2 {
        Objects.requireNonNull(route, "route must not be null");

        boolean routeFound;
        synchronized (sRouterLock) {
        synchronized (mLock) {
            // TODO: Check thread-safety
            routeFound = mRoutes.containsKey(route.getId());
        }
@@ -526,7 +566,7 @@ public final class MediaRouter2 {
        }

        MediaRouter2Stub stub;
        synchronized (sRouterLock) {
        synchronized (mLock) {
            stub = mStub;
        }
        if (stub != null) {
@@ -576,7 +616,7 @@ public final class MediaRouter2 {
    public List<RoutingController> getControllers() {
        // TODO: Do not create the controller instances every time,
        //       Instead, update the list using the sessions' ID and session related callbacks.
        if (mClientPackageName != null) {
        if (isSystemRouter()) {
            return sManager.getRoutingSessions(mClientPackageName).stream()
                    .map(info -> new RoutingController(info))
                    .collect(Collectors.toList());
@@ -584,7 +624,7 @@ public final class MediaRouter2 {

        List<RoutingController> result = new ArrayList<>();
        result.add(0, mSystemController);
        synchronized (sRouterLock) {
        synchronized (mLock) {
            result.addAll(mNonSystemRoutingControllers.values());
        }
        return result;
@@ -603,7 +643,7 @@ public final class MediaRouter2 {
        Objects.requireNonNull(route, "route must not be null");

        MediaRouter2Stub stub;
        synchronized (sRouterLock) {
        synchronized (mLock) {
            stub = mStub;
        }
        if (stub != null) {
@@ -627,7 +667,7 @@ public final class MediaRouter2 {
        List<MediaRoute2Info> removedRoutes = new ArrayList<>();
        List<MediaRoute2Info> changedRoutes = new ArrayList<>();

        synchronized (sRouterLock) {
        synchronized (mLock) {
            List<String> currentRoutesIds = currentRoutes.stream().map(MediaRoute2Info::getId)
                    .collect(Collectors.toList());

@@ -685,7 +725,7 @@ public final class MediaRouter2 {

    void addRoutesOnHandler(List<MediaRoute2Info> routes) {
        List<MediaRoute2Info> addedRoutes = new ArrayList<>();
        synchronized (sRouterLock) {
        synchronized (mLock) {
            for (MediaRoute2Info route : routes) {
                mRoutes.put(route.getId(), route);
                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -701,7 +741,7 @@ public final class MediaRouter2 {

    void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
        List<MediaRoute2Info> removedRoutes = new ArrayList<>();
        synchronized (sRouterLock) {
        synchronized (mLock) {
            for (MediaRoute2Info route : routes) {
                mRoutes.remove(route.getId());
                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -717,7 +757,7 @@ public final class MediaRouter2 {

    void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
        List<MediaRoute2Info> changedRoutes = new ArrayList<>();
        synchronized (sRouterLock) {
        synchronized (mLock) {
            for (MediaRoute2Info route : routes) {
                mRoutes.put(route.getId(), route);
                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -789,7 +829,7 @@ public final class MediaRouter2 {
            newController.setRoutingSessionInfo(sessionInfo);
        } else {
            newController = new RoutingController(sessionInfo);
            synchronized (sRouterLock) {
            synchronized (mLock) {
                mNonSystemRoutingControllers.put(newController.getId(), newController);
            }
        }
@@ -812,7 +852,7 @@ public final class MediaRouter2 {
        }

        RoutingController matchingController;
        synchronized (sRouterLock) {
        synchronized (mLock) {
            matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
        }

@@ -840,7 +880,7 @@ public final class MediaRouter2 {
        }

        RoutingController matchingController;
        synchronized (sRouterLock) {
        synchronized (mLock) {
            matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
        }

@@ -868,7 +908,7 @@ public final class MediaRouter2 {
        if (oldSession.isSystemSession()) {
            controller = getSystemController();
        } else {
            synchronized (sRouterLock) {
            synchronized (mLock) {
                controller = mNonSystemRoutingControllers.get(oldSession.getId());
            }
        }
@@ -878,6 +918,22 @@ public final class MediaRouter2 {
        requestCreateController(controller, route, managerRequestId);
    }

    /**
     * Returns whether this router is created with {@link #getInstance(Context, String)}.
     * This kind of router can control the target app's media routing.
     */
    private boolean isSystemRouter() {
        return mClientPackageName != null;
    }

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

    private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
            RouteDiscoveryPreference discoveryRequest) {
        return routes.stream()
@@ -1236,7 +1292,7 @@ public final class MediaRouter2 {
            }

            MediaRouter2Stub stub;
            synchronized (sRouterLock) {
            synchronized (mLock) {
                stub = mStub;
            }
            if (stub != null) {
@@ -1283,7 +1339,7 @@ public final class MediaRouter2 {
            }

            MediaRouter2Stub stub;
            synchronized (sRouterLock) {
            synchronized (mLock) {
                stub = mStub;
            }
            if (stub != null) {
@@ -1318,7 +1374,7 @@ public final class MediaRouter2 {
            }

            MediaRouter2Stub stub;
            synchronized (sRouterLock) {
            synchronized (mLock) {
                stub = mStub;
            }
            if (stub != null) {
@@ -1352,7 +1408,7 @@ public final class MediaRouter2 {
                return;
            }
            MediaRouter2Stub stub;
            synchronized (sRouterLock) {
            synchronized (mLock) {
                stub = mStub;
            }
            if (stub != null) {
@@ -1386,7 +1442,7 @@ public final class MediaRouter2 {
                mState = CONTROLLER_STATE_RELEASING;
            }

            synchronized (sRouterLock) {
            synchronized (mLock) {
                // It could happen if the controller is released by the another thread
                // in between two locks
                if (!mNonSystemRoutingControllers.remove(getId(), this)) {
@@ -1415,7 +1471,7 @@ public final class MediaRouter2 {
                mState = CONTROLLER_STATE_RELEASED;
            }

            synchronized (sRouterLock) {
            synchronized (mLock) {
                mNonSystemRoutingControllers.remove(getId(), this);

                if (shouldReleaseSession && mStub != null) {
@@ -1483,7 +1539,7 @@ public final class MediaRouter2 {
        }

        private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
            synchronized (sRouterLock) {
            synchronized (mLock) {
                return routeIds.stream().map(mRoutes::get)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList());
@@ -1665,4 +1721,79 @@ public final class MediaRouter2 {
                    MediaRouter2.this, oldSession, route, managerRequestId));
        }
    }

    class ManagerCallback implements MediaRouter2Manager.Callback {

        @Override
        public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
            List<MediaRoute2Info> filteredRoutes =
                    sManager.filterRoutesForPackage(routes, mClientPackageName);
            if (filteredRoutes.isEmpty()) {
                return;
            }
            for (RouteCallbackRecord record: mRouteCallbackRecords) {
                record.mExecutor.execute(
                        () -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
            }
        }

        @Override
        public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
            List<MediaRoute2Info> filteredRoutes =
                    sManager.filterRoutesForPackage(routes, mClientPackageName);
            if (filteredRoutes.isEmpty()) {
                return;
            }
            for (RouteCallbackRecord record: mRouteCallbackRecords) {
                record.mExecutor.execute(
                        () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
            }
        }

        @Override
        public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
            List<MediaRoute2Info> filteredRoutes =
                    sManager.filterRoutesForPackage(routes, mClientPackageName);
            if (filteredRoutes.isEmpty()) {
                return;
            }
            for (RouteCallbackRecord record: mRouteCallbackRecords) {
                record.mExecutor.execute(
                        () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
            }
        }

        @Override
        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
            // TODO: Call ControllerCallback.onControllerUpdated
        }

        @Override
        public void onTransferred(@NonNull RoutingSessionInfo oldSession,
                @Nullable RoutingSessionInfo newSession) {
            // TODO: Call TransferCallback.onTransfer
        }

        @Override
        public void onTransferFailed(@NonNull RoutingSessionInfo session,
                @NonNull MediaRoute2Info route) {
            // TODO: Call TransferCallback.onTransferFailure
        }

        @Override
        public void onSessionReleased(@NonNull RoutingSessionInfo session) {
            // TODO: Call TransferCallback.onStop()
        }

        @Override
        public void onPreferredFeaturesChanged(@NonNull String packageName,
                @NonNull List<String> preferredFeatures) {
            // Does nothing.
        }

        @Override
        public void onRequestFailed(int reason) {
            // Does nothing.
        }
    }
}
+30 −0
Original line number Diff line number Diff line
@@ -236,6 +236,36 @@ public final class MediaRouter2Manager {
        return routes;
    }

    /**
     * Returns a list of routes which are related to the given package name in the given route list.
     */
    @NonNull
    public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
            @NonNull String packageName) {
        Objects.requireNonNull(routes, "routes must not be null");
        Objects.requireNonNull(packageName, "packageName must not be null");

        List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
        RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);

        List<MediaRoute2Info> result = new ArrayList<>();
        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
        if (preferredFeatures == null) {
            preferredFeatures = Collections.emptyList();
        }

        synchronized (mRoutesLock) {
            for (MediaRoute2Info route : routes) {
                if (route.hasAnyFeatures(preferredFeatures)
                        || sessionInfo.getSelectedRoutes().contains(route.getId())
                        || sessionInfo.getTransferableRoutes().contains(route.getId())) {
                    result.add(route);
                }
            }
        }
        return result;
    }

    /**
     * Gets the system routing session associated with no specific application.
     */