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

This commit is contained in:
Kyunglyul Hyun
2019-12-23 06:30:51 +00:00
committed by Android (Google) Code Review
4 changed files with 286 additions and 42 deletions

View File

@@ -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);
}

View File

@@ -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.
*/
public final void setProviderInfo(MediaRoute2ProviderInfo info) {
@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 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));
}
}
}

View File

@@ -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() {

View File

@@ -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);
}
}