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

Commit 3a33128e authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "MediaRouter: Cleanup MediaRouter2Manager APIs" into rvc-dev am:...

Merge "MediaRouter: Cleanup MediaRouter2Manager APIs" into rvc-dev am: f8ac47cd am: 2a4aba32 am: ea80ccb8 am: 8b0dd863

Change-Id: Ica9284b5760a7d8c0d018342efbfc9f4e0fbecce
parents 1dc19f06 8b0dd863
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1071,7 +1071,7 @@ public class MediaRouter2 {
                try {
                    mMediaRouterService.deselectRouteWithRouter2(stub, getId(), route);
                } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to remove route from session.", ex);
                    Log.e(TAG, "Unable to deselect route from session.", ex);
                }
            }
        }
+307 −170
Original line number Diff line number Diff line
@@ -44,8 +44,10 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * A class that monitors and controls media routing of other apps.
 * @hide
 */
public class MediaRouter2Manager {
@@ -200,11 +202,25 @@ public class MediaRouter2Manager {
        return routes;
    }

    /**
     * Gets the system routing session associated with no specific application.
     */
    @NonNull
    public RoutingSessionInfo getSystemRoutingSession() {
        for (RoutingSessionInfo sessionInfo : getActiveSessions()) {
            if (sessionInfo.isSystemSession()) {
                return sessionInfo;
            }
        }
        throw new IllegalStateException("No system routing session");
    }

    /**
     * Gets routing sessions of an application with the given package name.
     * The first element of the returned list is the system routing controller.
     * The first element of the returned list is the system routing session.
     *
     * @see MediaRouter2#getSystemController()
     * @param packageName the package name of the application that is routing.
     * @see #getSystemRoutingSession()
     */
    @NonNull
    public List<RoutingSessionInfo> getRoutingSessions(@NonNull String packageName) {
@@ -213,8 +229,11 @@ public class MediaRouter2Manager {
        List<RoutingSessionInfo> sessions = new ArrayList<>();

        for (RoutingSessionInfo sessionInfo : getActiveSessions()) {
            if (sessionInfo.isSystemSession()
                    || TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) {
            if (sessionInfo.isSystemSession()) {
                sessions.add(new RoutingSessionInfo.Builder(sessionInfo)
                        .setClientPackageName(packageName)
                        .build());
            } else if (TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) {
                sessions.add(sessionInfo);
            }
        }
@@ -223,10 +242,15 @@ public class MediaRouter2Manager {

    /**
     * Gets the list of all active routing sessions.
     * <p>
     * The first element of the list is the system routing session containing
     * phone speakers, wired headset, Bluetooth devices.
     * The system routing session is shared by apps such that controlling it will affect
     * all apps.
     * If you want to transfer media of an application, use {@link #getRoutingSessions(String)}.
     *
     * @see #getRoutingSessions(String)
     * @see #getSystemRoutingSession()
     */
    @NonNull
    public List<RoutingSessionInfo> getActiveSessions() {
@@ -258,31 +282,46 @@ public class MediaRouter2Manager {

    /**
     * Selects media route for the specified package name.
     *
     * If the given route is {@link RoutingController#getTransferableRoutes() a transferable
     * route} of a routing session of the application, the session will be transferred to
     * the route. If not, a new routing session will be created.
     *
     * @param packageName the package name of the application that should change it's media route
     * @param route the route to be selected.
     */
    public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) {
        Objects.requireNonNull(packageName, "packageName must not be null");
        Objects.requireNonNull(route, "route must not be null");

        boolean transferred = false;
        //TODO: instead of release all controllers, add an API to specify controllers that
        // should be released (or is the system controller).
        for (RoutingSessionInfo sessionInfo : getRoutingSessions(packageName)) {
            if (!transferred && sessionInfo.getTransferableRoutes().contains(route.getId())) {
                new RoutingController(sessionInfo).transferToRoute(route);
                transferred = true;
            } else if (!sessionInfo.isSystemSession()) {
                new RoutingController(sessionInfo).release();
        List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
        RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
        transfer(targetSession, route);
    }

    /**
     * Transfers a routing session to a media route.
     * <p>{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called
     * depending on the result.
     *
     * @param sessionInfo the routing session info to transfer
     * @param route the route transfer to
     *
     * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
     * @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info)
     */
    public void transfer(@NonNull RoutingSessionInfo sessionInfo,
            @Nullable MediaRoute2Info route) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");

        if (route == null) {
            releaseSession(sessionInfo);
            return;
        }

        //TODO: Ignore unknown route.
        if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
            //TODO: callbacks must be called after this.
            transferToRoute(sessionInfo, route);
            return;
        }

        if (transferred) {
        if (TextUtils.isEmpty(sessionInfo.getClientPackageName())) {
            Log.w(TAG, "transfer: Ignoring transfer without package name.");
            notifyTransferFailed(sessionInfo, route);
            return;
        }

@@ -294,7 +333,7 @@ public class MediaRouter2Manager {
            try {
                int requestId = mNextRequestId.getAndIncrement();
                mMediaRouterService.requestCreateSessionWithManager(
                        client, packageName, route, requestId);
                        client, sessionInfo.getClientPackageName(), route, requestId);
                //TODO: release the previous session?
            } catch (RemoteException ex) {
                Log.e(TAG, "Unable to select media route", ex);
@@ -451,6 +490,18 @@ public class MediaRouter2Manager {
        }
    }

    void notifyTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
        for (CallbackRecord record : mCallbackRecords) {
            record.mExecutor.execute(() -> record.mCallback.onTransferred(oldSession, newSession));
        }
    }

    void notifyTransferFailed(RoutingSessionInfo sessionInfo, MediaRoute2Info route) {
        for (CallbackRecord record : mCallbackRecords) {
            record.mExecutor.execute(() -> record.mCallback.onTransferFailed(sessionInfo, route));
        }
    }

    void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
        List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
        if ((prevFeatures == null && preferredFeatures.size() == 0)
@@ -475,116 +526,60 @@ public class MediaRouter2Manager {
    }

    /**
     * A class to control media routing session in media route provider.
     * With routing controller, an application can select a route into the session or deselect
     * a route in the session.
     */
    public final class RoutingController {
        private final Object mControllerLock = new Object();
        @GuardedBy("mControllerLock")
        private RoutingSessionInfo mSessionInfo;

        RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
            mSessionInfo = sessionInfo;
        }

        /**
         * Gets the ID of the session
     * Gets the unmodifiable list of selected routes for the session.
     */
    @NonNull
        public String getSessionId() {
            synchronized (mControllerLock) {
                return mSessionInfo.getId();
            }
        }

        /**
         * Gets the client package name of the session
         */
        @NonNull
        public String getClientPackageName() {
            synchronized (mControllerLock) {
                return mSessionInfo.getClientPackageName();
            }
        }

        /**
         * @return the control hints used to control route session if available.
         */
        @Nullable
        public Bundle getControlHints() {
            synchronized (mControllerLock) {
                return mSessionInfo.getControlHints();
            }
        }
    public List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo sessionInfo) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");

        /**
         * @return the unmodifiable list of currently selected routes
         */
        @NonNull
        public List<MediaRoute2Info> getSelectedRoutes() {
            List<String> routeIds;
            synchronized (mControllerLock) {
                routeIds = mSessionInfo.getSelectedRoutes();
            }
        List<String> routeIds = sessionInfo.getSelectedRoutes();
        return getRoutesWithIds(routeIds);
    }

    /**
         * @return the unmodifiable list of selectable routes for the session.
     * Gets the unmodifiable list of selectable routes for the session.
     */
    @NonNull
        public List<MediaRoute2Info> getSelectableRoutes() {
            List<String> routeIds;
            synchronized (mControllerLock) {
                routeIds = mSessionInfo.getSelectableRoutes();
            }
            return getRoutesWithIds(routeIds);
        }
    public List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");

        /**
         * @return the unmodifiable list of deselectable routes for the session.
         */
        @NonNull
        public List<MediaRoute2Info> getDeselectableRoutes() {
            List<String> routeIds;
            synchronized (mControllerLock) {
                routeIds = mSessionInfo.getDeselectableRoutes();
            }
        List<String> routeIds = sessionInfo.getSelectableRoutes();
        return getRoutesWithIds(routeIds);
    }

    /**
         * @return the unmodifiable list of transferable routes for the session.
     * Gets the unmodifiable list of deselectable routes for the session.
     */
    @NonNull
        public List<MediaRoute2Info> getTransferableRoutes() {
            List<String> routeIds;
            synchronized (mControllerLock) {
                routeIds = mSessionInfo.getTransferableRoutes();
            }
    public List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");

        List<String> routeIds = sessionInfo.getDeselectableRoutes();
        return getRoutesWithIds(routeIds);
    }

    /**
         * Selects a route for the remote session. The given route must satisfy all of the
         * following conditions:
     * Selects a route for the remote session. After a route is selected, the media is expected
     * to be played to the all the selected routes. This is different from {@link
     * #transfer(RoutingSessionInfo, MediaRoute2Info)} transferring to a route},
     * where the media is expected to 'move' from one route to another.
     * <p>
     * The given route must satisfy all of the following conditions:
     * <ul>
         * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
         * <li>ID should be included in {@link #getSelectableRoutes()}</li>
     * <li>it should not be included in {@link #getSelectedRoutes(RoutingSessionInfo)}</li>
     * <li>it should be included in {@link #getSelectableRoutes(RoutingSessionInfo)}</li>
     * </ul>
     * If the route doesn't meet any of above conditions, it will be ignored.
     *
         * @see #getSelectedRoutes()
         * @see #getSelectableRoutes()
     * @see #getSelectedRoutes(RoutingSessionInfo)
     * @see #getSelectableRoutes(RoutingSessionInfo)
     * @see Callback#onSessionsUpdated()
     */
        public void selectRoute(@NonNull MediaRoute2Info route) {
    public void selectRoute(@NonNull RoutingSessionInfo sessionInfo,
            @NonNull MediaRoute2Info route) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
        Objects.requireNonNull(route, "route must not be null");

            RoutingSessionInfo sessionInfo;
            synchronized (mControllerLock) {
                sessionInfo = mSessionInfo;
            }
        if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
            Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
            return;
@@ -603,31 +598,32 @@ public class MediaRouter2Manager {
            try {
                int requestId = mNextRequestId.getAndIncrement();
                mMediaRouterService.selectRouteWithManager(
                            mClient, getSessionId(), route, requestId);
                        mClient, sessionInfo.getId(), route, requestId);
            } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to select route for session.", ex);
                Log.e(TAG, "selectRoute: Failed to send a request.", ex);
            }
        }
    }

    /**
         * Deselects a route from the remote session. The given route must satisfy all of the
         * following conditions:
     * Deselects a route from the remote session. After a route is deselected, the media is
     * expected to be stopped on the deselected routes.
     * <p>
     * The given route must satisfy all of the following conditions:
     * <ul>
         * <li>ID should be included in {@link #getSelectedRoutes()}</li>
         * <li>ID should be included in {@link #getDeselectableRoutes()}</li>
     * <li>it should be included in {@link #getSelectedRoutes(RoutingSessionInfo)}</li>
     * <li>it should be included in {@link #getDeselectableRoutes(RoutingSessionInfo)}</li>
     * </ul>
     * If the route doesn't meet any of above conditions, it will be ignored.
     *
         * @see #getSelectedRoutes()
         * @see #getDeselectableRoutes()
     * @see #getSelectedRoutes(RoutingSessionInfo)
     * @see #getDeselectableRoutes(RoutingSessionInfo)
     * @see Callback#onSessionsUpdated()
     */
        public void deselectRoute(@NonNull MediaRoute2Info route) {
    public void deselectRoute(@NonNull RoutingSessionInfo sessionInfo,
            @NonNull MediaRoute2Info route) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
        Objects.requireNonNull(route, "route must not be null");
            RoutingSessionInfo sessionInfo;
            synchronized (mControllerLock) {
                sessionInfo = mSessionInfo;
            }

        if (!sessionInfo.getSelectedRoutes().contains(route.getId())) {
            Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
@@ -647,31 +643,22 @@ public class MediaRouter2Manager {
            try {
                int requestId = mNextRequestId.getAndIncrement();
                mMediaRouterService.deselectRouteWithManager(
                            mClient, getSessionId(), route, requestId);
                        mClient, sessionInfo.getId(), route, requestId);
            } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to remove route from session.", ex);
                Log.e(TAG, "deselectRoute: Failed to send a request.", ex);
            }
        }
    }

    /**
         * Transfers to a given route for the remote session. The given route must satisfy
         * all of the following conditions:
         * <ul>
         * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
         * <li>ID should be included in {@link #getTransferableRoutes()}</li>
         * </ul>
         * If the route doesn't meet any of above conditions, it will be ignored.
     * Transfers to a given route for the remote session.
     *
         * @see #getSelectedRoutes()
         * @see #getTransferableRoutes()
     * @hide
     */
        public void transferToRoute(@NonNull MediaRoute2Info route) {
    void transferToRoute(@NonNull RoutingSessionInfo sessionInfo,
            @NonNull MediaRoute2Info route) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
        Objects.requireNonNull(route, "route must not be null");
            RoutingSessionInfo sessionInfo;
            synchronized (mControllerLock) {
                sessionInfo = mSessionInfo;
            }

        if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
            Log.w(TAG, "Ignoring transferring to a route that is already added. route="
@@ -692,18 +679,26 @@ public class MediaRouter2Manager {
            try {
                int requestId = mNextRequestId.getAndIncrement();
                mMediaRouterService.transferToRouteWithManager(
                            mClient, getSessionId(), route, requestId);
                        mClient, sessionInfo.getId(), route, requestId);
            } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to transfer to route for session.", ex);
                Log.e(TAG, "transferToRoute: Failed to send a request.", ex);
            }
        }
    }

    /**
         * Release this session.
         * Any operation on this session after calling this method will be ignored.
     * Requests releasing a session.
     * <p>
     * If a session is released, any operation on the session will be ignored.
     * {@link Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)} with {@code null}
     * session will be called when the session is released.
     * </p>
     *
     * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
     */
        public void release() {
    public void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");

        Client client;
        synchronized (sLock) {
            client = mClient;
@@ -712,35 +707,160 @@ public class MediaRouter2Manager {
            try {
                int requestId = mNextRequestId.getAndIncrement();
                mMediaRouterService.releaseSessionWithManager(
                            mClient, getSessionId(), requestId);
                        mClient, sessionInfo.getId(), requestId);
            } catch (RemoteException ex) {
                    Log.e(TAG, "Unable to notify of controller release", ex);
                Log.e(TAG, "releaseSession: Failed to send a request", ex);
            }
        }
    }

    private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
        synchronized (sLock) {
            return routeIds.stream().map(mRoutes::get)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }
    }

    //TODO: Remove this.
    /**
         * Gets the session info of the session
         * @hide
     * A class to control media routing session in media route provider.
     * With routing controller, an application can select a route into the session or deselect
     * a route in the session.
     */
    public final class RoutingController {
        private final Object mControllerLock = new Object();
        @GuardedBy("mControllerLock")
        private RoutingSessionInfo mSessionInfo;

        RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
            mSessionInfo = sessionInfo;
        }

        /**
         * Releases the session
         */
        public void release() {
            synchronized (mControllerLock) {
                releaseSession(mSessionInfo);
            }
        }

        /**
         * Gets the ID of the session
         */
        @NonNull
        public RoutingSessionInfo getSessionInfo() {
        public String getSessionId() {
            synchronized (mControllerLock) {
                return mSessionInfo;
                return mSessionInfo.getId();
            }
        }

        private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
            List<MediaRoute2Info> routes = new ArrayList<>();
            synchronized (mRoutesLock) {
                for (String routeId : routeIds) {
                    MediaRoute2Info route = mRoutes.get(routeId);
                    if (route != null) {
                        routes.add(route);
        /**
         * Gets the client package name of the session
         */
        @NonNull
        public String getClientPackageName() {
            synchronized (mControllerLock) {
                return mSessionInfo.getClientPackageName();
            }
        }

        /**
         * @return the control hints used to control route session if available.
         */
        @Nullable
        public Bundle getControlHints() {
            synchronized (mControllerLock) {
                return mSessionInfo.getControlHints();
            }
        }

        /**
         * @return the unmodifiable list of currently selected routes
         */
        @NonNull
        public List<MediaRoute2Info> getSelectedRoutes() {
            return MediaRouter2Manager.this.getSelectedRoutes(mSessionInfo);
        }

        /**
         * @return the unmodifiable list of selectable routes for the session.
         */
        @NonNull
        public List<MediaRoute2Info> getSelectableRoutes() {
            return MediaRouter2Manager.this.getSelectableRoutes(mSessionInfo);
        }

        /**
         * @return the unmodifiable list of deselectable routes for the session.
         */
        @NonNull
        public List<MediaRoute2Info> getDeselectableRoutes() {
            return MediaRouter2Manager.this.getDeselectableRoutes(mSessionInfo);
        }

        /**
         * @return the unmodifiable list of transferable routes for the session.
         */
        @NonNull
        public List<MediaRoute2Info> getTransferableRoutes() {
            List<String> routeIds;
            synchronized (mControllerLock) {
                routeIds = mSessionInfo.getTransferableRoutes();
            }
            return getRoutesWithIds(routeIds);
        }

        /**
         * Selects a route for the remote session. The given route must satisfy all of the
         * following conditions:
         * <ul>
         * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
         * <li>ID should be included in {@link #getSelectableRoutes()}</li>
         * </ul>
         * If the route doesn't meet any of above conditions, it will be ignored.
         *
         * @see #getSelectedRoutes()
         * @see #getSelectableRoutes()
         */
        public void selectRoute(@NonNull MediaRoute2Info route) {
            MediaRouter2Manager.this.selectRoute(mSessionInfo, route);
        }

        /**
         * Deselects a route from the remote session. The given route must satisfy all of the
         * following conditions:
         * <ul>
         * <li>ID should be included in {@link #getSelectedRoutes()}</li>
         * <li>ID should be included in {@link #getDeselectableRoutes()}</li>
         * </ul>
         * If the route doesn't meet any of above conditions, it will be ignored.
         *
         * @see #getSelectedRoutes()
         * @see #getDeselectableRoutes()
         */
        public void deselectRoute(@NonNull MediaRoute2Info route) {
            MediaRouter2Manager.this.deselectRoute(mSessionInfo, route);
        }

        /**
         * Transfers session to the given rotue.
         */
        public void transferToRoute(@NonNull MediaRoute2Info route) {
            MediaRouter2Manager.this.transferToRoute(mSessionInfo, route);
        }

        /**
         * Gets the session info of the session
         *
         * @hide
         */
        @NonNull
        public RoutingSessionInfo getSessionInfo() {
            synchronized (mControllerLock) {
                return mSessionInfo;
            }
            return Collections.unmodifiableList(routes);
        }
    }

@@ -780,7 +900,24 @@ public class MediaRouter2Manager {
         */
        public void onSessionsUpdated() {}

        //TODO: remove this
        //TODO: Call this.
        /**
         * Called when media is transferred.
         *
         * @param oldSession the previous session
         * @param newSession the new session or {@code null} if the session is released.
         */
        public void onTransferred(@NonNull RoutingSessionInfo oldSession,
                @Nullable RoutingSessionInfo newSession) { }

        //TODO: Call this.
        /**
         * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
         */
        public void onTransferFailed(@NonNull RoutingSessionInfo session,
                @NonNull MediaRoute2Info route) { }

        //TODO: Remove this.
        /**
         * Called when the preferred route features of an app is changed.
         *