summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kyunglyul Hyun <klhyun@google.com> 2019-12-19 19:04:20 +0900
committer Kyunglyul Hyun <klhyun@google.com> 2019-12-23 10:29:30 +0900
commit24cc66f946ff4ec5da3f245a54286339410ec85b (patch)
tree097edac26cf7901d5b294f308c933b29d515c690
parent3e8b77e040e0256909608f2c855d30315bea2451 (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
-rw-r--r--media/java/android/media/MediaRoute2ProviderInfo.java5
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java188
-rw-r--r--media/java/android/media/RouteSessionInfo.java87
-rw-r--r--media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java48
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);
}
}