diff options
| author | 2019-12-19 19:04:20 +0900 | |
|---|---|---|
| committer | 2019-12-23 10:29:30 +0900 | |
| commit | 24cc66f946ff4ec5da3f245a54286339410ec85b (patch) | |
| tree | 097edac26cf7901d5b294f308c933b29d515c690 | |
| parent | 3e8b77e040e0256909608f2c855d30315bea2451 (diff) | |
MediaRouter: Add session-releated APIs to route provider
This CL introduces "session" into MediaRouterProviderService.
When a client wants to use a route of a provider, it should
request the provider to create a new session and then,
the client will control the session instead of a route.
Clients can add/remove routes into/from the session
(a.k.a. dynamic grouping) and transfer media to the other
route in a provider with less delay.
RouteSessionInfo holds information about a session and
MediaRoute2ProviderService must set session info and implement
abstract methods.
Bug: 142762829
Test: atest mediaroutertest
Change-Id: Iaf668d9a2c360cd9c7dbc5f1766d14e0d81bb3e3
4 files changed, 286 insertions, 42 deletions
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java index b5de88ae1da9..7078d4a0b568 100644 --- a/media/java/android/media/MediaRoute2ProviderInfo.java +++ b/media/java/android/media/MediaRoute2ProviderInfo.java @@ -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); } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 1b6183e361c8..8d6f2551f9f6 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -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)); } } } diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java index 0878e6b31ec6..53c8eec8bb9f 100644 --- a/media/java/android/media/RouteSessionInfo.java +++ b/media/java/android/media/RouteSessionInfo.java @@ -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() { diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index 6650f9618638..df6345fb7db0 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -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); } } |