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

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

Merge "MediaRouter: Add session-releated APIs to route provider"

parents cb874f07 24cc66f9
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -46,9 +46,9 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
    };

    @Nullable
    private final String mUniqueId;
    final String mUniqueId;
    @NonNull
    private final ArrayMap<String, MediaRoute2Info> mRoutes;
    final ArrayMap<String, MediaRoute2Info> mRoutes;

    MediaRoute2ProviderInfo(@NonNull Builder builder) {
        Objects.requireNonNull(builder, "builder must not be null.");
@@ -142,6 +142,7 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
        public Builder(@NonNull MediaRoute2ProviderInfo descriptor) {
            Objects.requireNonNull(descriptor, "descriptor must not be null");

            mUniqueId = descriptor.mUniqueId;
            mRoutes = new ArrayMap<>(descriptor.mRoutes);
        }

+174 −14
Original line number Diff line number Diff line
@@ -27,8 +27,13 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;

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

/**
@@ -40,10 +45,14 @@ public abstract class MediaRoute2ProviderService extends Service {
    public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";

    private final Handler mHandler;
    private final Object mSessionLock = new Object();
    private ProviderStub mStub;
    private IMediaRoute2ProviderClient mClient;
    private MediaRoute2ProviderInfo mProviderInfo;

    @GuardedBy("mSessionLock")
    private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();

    public MediaRoute2ProviderService() {
        mHandler = new Handler(Looper.getMainLooper());
    }
@@ -93,6 +102,7 @@ public abstract class MediaRoute2ProviderService extends Service {

    /**
     * Called when requestSetVolume is called on a route of the provider
     *
     * @param routeId the id of the route
     * @param volume the target volume
     */
@@ -100,15 +110,164 @@ public abstract class MediaRoute2ProviderService extends Service {

    /**
     * Called when requestUpdateVolume is called on a route of the provider
     *
     * @param routeId id of the route
     * @param delta the delta to add to the current volume
     */
    public abstract void onUpdateVolume(@NonNull String routeId, int delta);

    /**
     * Updates provider info and publishes routes
     * Gets information of the session with the given id.
     *
     * @param sessionId id of the session
     * @return information of the session with the given id.
     *         null if the session is destroyed or id is not valid.
     */
    @Nullable
    public final RouteSessionInfo getSessionInfo(int sessionId) {
        synchronized (mSessionLock) {
            return mSessionInfo.get(sessionId);
        }
    }

    /**
     * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
     */
    @NonNull
    public final List<RouteSessionInfo> getAllSessionInfo() {
        synchronized (mSessionLock) {
            return new ArrayList<>(mSessionInfo.values());
        }
    }

    /**
     * Sets the information of the session with the given id.
     * If there is no session matched with the given id, it will be ignored.
     * A session will be destroyed if it has no selected route.
     * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of
     * session info changes.
     *
     * @param sessionId id of the session that should update its information
     * @param sessionInfo new session information
     */
    public final void setSessionInfo(int sessionId, @NonNull RouteSessionInfo sessionInfo) {
        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");

        synchronized (mSessionLock) {
            if (mSessionInfo.containsKey(sessionId)) {
                mSessionInfo.put(sessionId, sessionInfo);
            } else {
                Log.w(TAG, "Ignoring session info update.");
            }
        }
    }

    /**
     * Notifies clients of that the session is created and ready for use. If the session can be
     * controlled, pass a {@link Bundle} that contains how to control it.
     *
     * @param sessionId id of the session
     * @param sessionInfo information of the new session.
     *                    Pass {@code null} to reject the request or inform clients that
     *                    session creation has failed.
     * @param controlHints a {@link Bundle} that contains how to control the session.
     */
    //TODO: fail reason?
    public final void notifySessionCreated(int sessionId, @Nullable RouteSessionInfo sessionInfo,
            @Nullable Bundle controlHints) {
        //TODO: validate sessionId (it must be in "waiting list")
        synchronized (mSessionLock) {
            mSessionInfo.put(sessionId, sessionInfo);
            //TODO: notify media router service of session creation.
        }
    }

    /**
     * Releases a session with the given id.
     * {@link #onDestroySession} is called if the session is released.
     *
     * @param sessionId id of the session to be released
     * @see #onDestroySession(int, RouteSessionInfo)
     */
    public final void setProviderInfo(MediaRoute2ProviderInfo info) {
    public final void releaseSession(int sessionId) {
        RouteSessionInfo sessionInfo;
        synchronized (mSessionLock) {
            sessionInfo = mSessionInfo.put(sessionId, null);
        }
        if (sessionInfo != null) {
            mHandler.sendMessage(obtainMessage(
                    MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo));
        }
    }

    /**
     * Called when a session should be created.
     * You should create and maintain your own session and notifies the client of
     * session info. Call {@link #notifySessionCreated(int, RouteSessionInfo, Bundle)}
     * to notify the information of a new session.
     * If you can't create the session or want to reject the request, pass {@code null}
     * as session info in {@link #notifySessionCreated(int, RouteSessionInfo, Bundle)}.
     *
     * @param packageName the package name of the application that selected the route
     * @param routeId the id of the route initially being connected
     * @param controlCategory the control category of the new session
     * @param sessionId the id of a new session
     */
    public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
            @NonNull String controlCategory, int sessionId);

    /**
     * Called when a session is about to be destroyed.
     * You can clean up your session here. This can happen by the
     * client or provider itself.
     *
     * @param sessionId id of the session being destroyed.
     * @param lastSessionInfo information of the session being destroyed.
     * @see #releaseSession(int)
     */
    public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);

    //TODO: make a way to reject the request
    /**
     * Called when a client requests adding a route to a session.
     * After the route is added, call {@link #setSessionInfo(int, RouteSessionInfo)} to update
     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
     * clients of updated session info.
     *
     * @param sessionId id of the session
     * @param routeId id of the route
     * @see #setSessionInfo(int, RouteSessionInfo)
     */
    public abstract void onAddRoute(int sessionId, @NonNull String routeId);

    //TODO: make a way to reject the request
    /**
     * Called when a client requests removing a route from a session.
     * After the route is removed, call {@link #setSessionInfo(int, RouteSessionInfo)} to update
     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
     * clients of updated session info.
     *
     * @param sessionId id of the session
     * @param routeId id of the route
     */
    public abstract void onRemoveRoute(int sessionId, @NonNull String routeId);

    //TODO: make a way to reject the request
    /**
     * Called when a client requests transferring a session to a route.
     * After the transfer is finished, call {@link #setSessionInfo(int, RouteSessionInfo)} to update
     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
     * clients of updated session info.
     *
     * @param sessionId id of the session
     * @param routeId id of the route
     */
    public abstract void onTransferRoute(int sessionId, @NonNull String routeId);

    /**
     * Updates provider info and publishes routes and session info.
     */
    public final void updateProviderInfo(MediaRoute2ProviderInfo info) {
        mProviderInfo = info;
        publishState();
    }
@@ -142,6 +301,7 @@ public abstract class MediaRoute2ProviderService extends Service {
    }

    void publishState() {
        //TODO: sends session info
        if (mClient == null) {
            return;
        }
@@ -179,35 +339,35 @@ public abstract class MediaRoute2ProviderService extends Service {
        }

        @Override
        public void requestSelectRoute(String packageName, String id, int seq) {
        public void requestSelectRoute(String packageName, String routeId, int seq) {
            //TODO: call onCreateSession instead
            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
                    MediaRoute2ProviderService.this, packageName, id,
                    new SelectToken(packageName, id, seq)));

                    MediaRoute2ProviderService.this, packageName, routeId,
                    new SelectToken(packageName, routeId, seq)));
        }

        @Override
        public void unselectRoute(String packageName, String id) {
        public void unselectRoute(String packageName, String routeId) {
            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUnselectRoute,
                    MediaRoute2ProviderService.this, packageName, id));
                    MediaRoute2ProviderService.this, packageName, routeId));
        }

        @Override
        public void notifyControlRequestSent(String id, Intent request) {
        public void notifyControlRequestSent(String routeId, Intent request) {
            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
                    MediaRoute2ProviderService.this, id, request));
                    MediaRoute2ProviderService.this, routeId, request));
        }

        @Override
        public void requestSetVolume(String id, int volume) {
        public void requestSetVolume(String routeId, int volume) {
            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
                    MediaRoute2ProviderService.this, id, volume));
                    MediaRoute2ProviderService.this, routeId, volume));
        }

        @Override
        public void requestUpdateVolume(String id, int delta) {
        public void requestUpdateVolume(String routeId, int delta) {
            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
                    MediaRoute2ProviderService.this, id, delta));
                    MediaRoute2ProviderService.this, routeId, delta));
        }
    }
}
+62 −25
Original line number Diff line number Diff line
@@ -218,79 +218,116 @@ public class RouteSessionInfo implements Parcelable {
        }

        /**
         * Adds a selected route
         * Clears the selected routes.
         */
        @NonNull
        public Builder addSelectedRoute(String routeId) {
            mSelectedRoutes.add(routeId);
        public Builder clearSelectedRoutes() {
            mSelectedRoutes.clear();
            return this;
        }

        /**
         * Removes a selected route
         * Adds a route to the selected routes.
         */
        @NonNull
        public Builder removeSelectedRoute(String routeId) {
            mSelectedRoutes.remove(routeId);
        public Builder addSelectedRoute(@NonNull String routeId) {
            mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Adds a deselectable route
         * Removes a route from the selected routes.
         */
        @NonNull
        public Builder addDeselectableRoute(String routeId) {
            mDeselectableRoutes.add(routeId);
        public Builder removeSelectedRoute(@NonNull String routeId) {
            mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Removes a deselecable route
         * Clears the deselectable routes.
         */
        @NonNull
        public Builder removeDeselectableRoute(String routeId) {
            mDeselectableRoutes.remove(routeId);
        public Builder clearDeselectableRoutes() {
            mDeselectableRoutes.clear();
            return this;
        }

        /**
         * Adds a groupable route
         * Adds a route to the deselectable routes.
         */
        @NonNull
        public Builder addGroupableRoute(String routeId) {
            mGroupableRoutes.add(routeId);
        public Builder addDeselectableRoute(@NonNull String routeId) {
            mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Removes a groupable route
         * Removes a route from the deselectable routes.
         */
        @NonNull
        public Builder removeGroupableRoute(String routeId) {
            mGroupableRoutes.remove(routeId);
        public Builder removeDeselectableRoute(@NonNull String routeId) {
            mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Adds a transferrable route
         * Clears the groupable routes.
         */
        @NonNull
        public Builder addTransferrableRoute(String routeId) {
            mTransferrableRoutes.add(routeId);
        public Builder clearGroupableRoutes() {
            mGroupableRoutes.clear();
            return this;
        }

        /**
         * Removes a transferrable route
         * Adds a route to the groupable routes.
         */
        @NonNull
        public Builder removeTransferrableRoute(String routeId) {
            mTransferrableRoutes.remove(routeId);
        public Builder addGroupableRoute(@NonNull String routeId) {
            mGroupableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Builds a route session info
         * Removes a route from the groupable routes.
         */
        @NonNull
        public Builder removeGroupableRoute(@NonNull String routeId) {
            mGroupableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Clears the transferrable routes.
         */
        @NonNull
        public Builder clearTransferrableRoutes() {
            mTransferrableRoutes.clear();
            return this;
        }

        /**
         * Adds a route to the transferrable routes.
         */
        @NonNull
        public Builder addTransferrableRoute(@NonNull String routeId) {
            mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Removes a route from the transferrable routes.
         */
        @NonNull
        public Builder removeTransferrableRoute(@NonNull String routeId) {
            mTransferrableRoutes.remove(
                    Objects.requireNonNull(routeId, "routeId must not be null"));
            return this;
        }

        /**
         * Builds a route session info.
         */
        @NonNull
        public RouteSessionInfo build() {
+47 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import android.os.IBinder;

@@ -159,10 +160,55 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
        publishRoutes();
    }

    @Override
    public void onCreateSession(String packageName, String routeId, String controlCategory,
            int sessionId) {
        RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
                sessionId, packageName, controlCategory)
                .addSelectedRoute(routeId)
                .build();
        notifySessionCreated(sessionId, sessionInfo, null);
    }

    @Override
    public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {}

    @Override
    public void onAddRoute(int sessionId, String routeId) {
        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
        //TODO: we may want to remove route if it belongs to another session
        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
                .addSelectedRoute(routeId)
                .build();
        setSessionInfo(sessionId, newSessionInfo);
        publishRoutes();
    }

    @Override
    public void onRemoveRoute(int sessionId, String routeId) {
        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
                .removeSelectedRoute(routeId)
                .build();
        setSessionInfo(sessionId, newSessionInfo);
        publishRoutes();
    }

    @Override
    public void onTransferRoute(int sessionId, String routeId) {
        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
                .clearSelectedRoutes()
                .addSelectedRoute(routeId)
                .build();
        setSessionInfo(sessionId, newSessionInfo);
        publishRoutes();
    }

    void publishRoutes() {
        MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
                .addRoutes(mRoutes.values())
                .build();
        setProviderInfo(info);
        updateProviderInfo(info);
    }
}