MediaRouter: Let provider release route controller
This CL lets MediaRouteProvider request a client
to release a route controller.
It can be used to support "Stop" in the output switcher.
In order to do that, we add a new message being used by
new client and new service.
For old clients, the service sends provider descriptor,
which contains a "disabled" route descriptor, which
eventually make the client unselect the route.
Bug: 163095048
Test: Run support v7 demos, cast to a sample provider,
tap "Stop" in the output switcher and confirm that onRouteUnselected is called.
The demos are built without MediaTransferReceiver not to use media router2.
CLINET_VERSION_3 and CLIENT_VERSION_4 are both tested as well.
Change-Id: Ieb2e9e529e90188466f6d91fe9d20ca3e9f665aa
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java
index ea8b6d7..4b90f65 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java
@@ -479,6 +479,8 @@
SessionRecord sessionRecord = new SessionRecord(controller, REQUEST_ID_NONE,
sessionFlags, clientRecord);
+ //TODO: Reconsider the logic if dynamic grouping is enabled for clients < CLIENT_VERSION_4
+ sessionRecord.mRouteId = routeId;
String sessionId = assignSessionId(sessionRecord);
mSessionIdMap.put(controllerId, sessionId);
@@ -547,7 +549,7 @@
private static class DynamicGroupRouteControllerProxy
extends DynamicGroupRouteController {
private final String mRouteId;
- private final RouteController mRouteController;
+ final RouteController mRouteController;
DynamicGroupRouteControllerProxy(String routeId, RouteController routeController) {
mRouteId = routeId;
@@ -633,6 +635,8 @@
private boolean mIsReleased;
private RoutingSessionInfo mSessionInfo;
String mSessionId;
+ // The ID of the route describing the session.
+ String mRouteId;
SessionRecord(DynamicGroupRouteController controller, long requestId, int flags) {
this(controller, requestId, flags, null);
@@ -697,6 +701,7 @@
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(sessionInfo);
if (groupRoute != null) {
+ mRouteId = groupRoute.getId();
builder.setName(groupRoute.getName())
.setVolume(groupRoute.getVolume())
.setVolumeMax(groupRoute.getVolumeMax())
@@ -768,8 +773,22 @@
}
if (shouldUnselect) {
- mController.onUnselect(MediaRouter.UNSELECT_REASON_STOPPED);
- mController.onRelease();
+ if ((mFlags & SESSION_FLAG_MR2) == 0) {
+ // Let the client release the controller
+ ClientRecord clientRecord = mClientRecord.get();
+ if (clientRecord != null) {
+ RouteController controller = mController;
+ if (mController instanceof DynamicGroupRouteControllerProxy) {
+ controller = ((DynamicGroupRouteControllerProxy) mController)
+ .mRouteController;
+ }
+ mServiceImpl.requestReleaseController(clientRecord,
+ controller, mRouteId);
+ }
+ } else {
+ mController.onUnselect(MediaRouter.UNSELECT_REASON_STOPPED);
+ mController.onRelease();
+ }
}
mIsReleased = true;
notifySessionReleased(mSessionId);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderProtocol.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderProtocol.java
index 5aa2914..27305d4 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderProtocol.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderProtocol.java
@@ -235,6 +235,13 @@
*/
public static final int SERVICE_MSG_DYNAMIC_ROUTE_DESCRIPTORS_CHANGED = 7;
+ /** (service v3) / (client v4)
+ * Request to release a route controller. (unsolicited event)
+ * - arg1 : reserved(0)
+ * - arg2 : controllerId
+ */
+ public static final int SERVICE_MSG_RELEASE_CONTROLLER = 8;
+
public static final String SERVICE_DATA_ERROR = "error";
/*
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java
index e3ba1d8..9ff3b77 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java
@@ -51,6 +51,7 @@
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_GENERIC_FAILURE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_GENERIC_SUCCESS;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
+import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_RELEASE_CONTROLLER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_CURRENT;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
import static androidx.mediarouter.media.MediaRouter.UNSELECT_REASON_UNKNOWN;
@@ -68,6 +69,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -1079,7 +1081,6 @@
}
}
- //TODO: We may need to change version number
@RequiresApi(api = Build.VERSION_CODES.R)
static class MediaRouteProviderServiceImplApi30 extends MediaRouteProviderServiceImplBase {
MediaRoute2ProviderServiceAdapter mMR2ProviderServiceAdapter;
@@ -1120,6 +1121,49 @@
mMR2ProviderServiceAdapter.setProviderDescriptor(descriptor);
}
+ void requestReleaseController(ClientRecord client,
+ RouteController controller, String routeId) {
+ if (client.mVersion >= CLIENT_VERSION_4) {
+ int controllerId = client.findControllerIdByController(controller);
+ if (controllerId < 0) {
+ Log.w(TAG, "requestReleaseController: Can't find the controller."
+ + " route ID=" + routeId);
+ return;
+ }
+ sendReply(client.mMessenger, SERVICE_MSG_RELEASE_CONTROLLER, 0, controllerId,
+ null, null);
+ return;
+ }
+
+ // The below is a workaround to unselect the selected route of previous clients.
+ // The logic is based on the behavior that MediaRouter unselects its selected route if
+ // the route becomes disabled.
+ MediaRouteProviderDescriptor lastDescriptor =
+ getService().getMediaRouteProvider().getDescriptor();
+ if (lastDescriptor == null) {
+ Log.w(TAG, "requestReleaseController: null provider descriptor found. "
+ + "It shouldn't happen.");
+ return;
+ }
+
+ List<MediaRouteDescriptor> routes = new ArrayList<>();
+ for (MediaRouteDescriptor descriptor : lastDescriptor.getRoutes()) {
+ if (TextUtils.equals(descriptor.getId(), routeId)) {
+ routes.add(new MediaRouteDescriptor.Builder(descriptor)
+ .setEnabled(false).build());
+ } else {
+ routes.add(descriptor);
+ }
+ }
+
+ MediaRouteProviderDescriptor providerDescriptor =
+ new MediaRouteProviderDescriptor.Builder(lastDescriptor)
+ .setRoutes(routes).build();
+ sendReply(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
+ createDescriptorBundleForClientVersion(providerDescriptor, client.mVersion),
+ null);
+ }
+
@Override
MediaRouteProviderServiceImplBase.ClientRecord createClientRecord(
Messenger messenger, int version, String packageName) {
@@ -1207,6 +1251,12 @@
public RouteController findControllerByRouteId(String routeId) {
return mRouteIdToControllerMap.get(routeId);
}
+
+ public int findControllerIdByController(RouteController controller) {
+ int index = mControllers.indexOfValue(controller);
+ if (index < 0) return -1;
+ return mControllers.keyAt(index);
+ }
}
}
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 7469b77..39a66a0 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -2808,6 +2808,16 @@
}
}
+ @Override
+ public void releaseProviderController(@NonNull RegisteredMediaRouteProvider provider,
+ @NonNull RouteController controller) {
+ if (mSelectedRouteController == controller) {
+ selectRoute(chooseFallbackRoute(), UNSELECT_REASON_STOPPED);
+ }
+ //TODO: Maybe release a member route controller if the given controller is a member of
+ // the selected route.
+ }
+
void updateProviderDescriptor(MediaRouteProvider providerInstance,
MediaRouteProviderDescriptor descriptor) {
ProviderInfo provider = findProviderInfo(providerInstance);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java
index 4386432..1182537 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java
@@ -50,6 +50,7 @@
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_GENERIC_FAILURE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_GENERIC_SUCCESS;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
+import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_RELEASE_CONTROLLER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_1;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
@@ -95,6 +96,7 @@
private boolean mBound;
private Connection mActiveConnection;
private boolean mConnectionReady;
+ private ControllerCallback mControllerCallback;
public RegisteredMediaRouteProvider(Context context, ComponentName componentName) {
super(context, new ProviderMetadata(componentName));
@@ -212,6 +214,10 @@
}
}
+ public void setControllerCallback(@Nullable ControllerCallback controllerCallback) {
+ mControllerCallback = controllerCallback;
+ }
+
private void updateBinding() {
if (shouldBind()) {
bind();
@@ -377,6 +383,15 @@
}
}
+ void onConnectionRequestReleaseController(Connection connection, int controllerId) {
+ if (mActiveConnection == connection) {
+ ControllerConnection controller = findControllerById(controllerId);
+ if (mControllerCallback != null && controller instanceof RouteController) {
+ mControllerCallback.onRequestReleaseController(((RouteController) controller));
+ }
+ }
+ }
+
private ControllerConnection findControllerById(int id) {
for (ControllerConnection controller: mControllerConnections) {
if (controller.getControllerId() == id) {
@@ -835,6 +850,10 @@
}
}
+ public void onRequestReleaseController(int controllerId) {
+ onConnectionRequestReleaseController(this, controllerId);
+ }
+
@Override
public void binderDied() {
mPrivateHandler.post(new Runnable() {
@@ -1055,8 +1074,16 @@
Log.w(TAG, "No further information on the dynamic group controller");
}
break;
+
+ case SERVICE_MSG_RELEASE_CONTROLLER:
+ connection.onRequestReleaseController(arg /* controllerId */);
+ break;
}
return false;
}
}
+
+ interface ControllerCallback {
+ void onRequestReleaseController(RouteController controller);
+ }
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProviderWatcher.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
index ab121f4..b952d01a 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
@@ -44,7 +44,7 @@
*/
final class RegisteredMediaRouteProviderWatcher {
private final Context mContext;
- private final Callback mCallback;
+ final Callback mCallback;
private final Handler mHandler;
private final PackageManager mPackageManager;
@@ -121,6 +121,8 @@
if (sourceIndex < 0) {
RegisteredMediaRouteProvider provider = new RegisteredMediaRouteProvider(
mContext, new ComponentName(serviceInfo.packageName, serviceInfo.name));
+ provider.setControllerCallback(
+ controller -> mCallback.releaseProviderController(provider, controller));
provider.start();
mProviders.add(targetIndex++, provider);
mCallback.addProvider(provider);
@@ -138,6 +140,7 @@
RegisteredMediaRouteProvider provider = mProviders.get(i);
mCallback.removeProvider(provider);
mProviders.remove(provider);
+ provider.setControllerCallback(null);
provider.stop();
}
}
@@ -192,5 +195,7 @@
public interface Callback {
void addProvider(MediaRouteProvider provider);
void removeProvider(MediaRouteProvider provider);
+ void releaseProviderController(@NonNull RegisteredMediaRouteProvider provider,
+ @NonNull MediaRouteProvider.RouteController controller);
}
}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
index e9aaa25..c671387 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
@@ -142,7 +142,7 @@
mPlayer.updatePresentation();
}
mSessionManager.setPlayer(mPlayer);
- if (reason == MediaRouter.UNSELECT_REASON_STOPPED) {
+ if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
mSessionManager.stop();
} else {
mSessionManager.unsuspend();