[go: nahoru, domu]

blob: 596de172b1c1bdaa0e27d3438d638ed32e1e2b36 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.mediarouter.media;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_MEMBER_ROUTE_ID;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_MEMBER_ROUTE_IDS;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_LIBRARY_GROUP;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_UNSELECT_REASON;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_VOLUME;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_ADD_MEMBER_ROUTE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_CREATE_DYNAMIC_GROUP_ROUTE_CONTROLLER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_CREATE_ROUTE_CONTROLLER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_RELEASE_ROUTE_CONTROLLER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REMOVE_MEMBER_ROUTE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_ROUTE_CONTROL_REQUEST;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SELECT_ROUTE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SET_DISCOVERY_REQUEST;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SET_ROUTE_VOLUME;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNREGISTER;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNSELECT_ROUTE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UPDATE_MEMBER_ROUTES;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UPDATE_ROUTE_VOLUME;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_1;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_4;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.DATA_KEY_DYNAMIC_ROUTE_DESCRIPTORS;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.DATA_KEY_GROUPABLE_SECION_TITLE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.DATA_KEY_GROUP_ROUTE_DESCRIPTOR;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.DATA_KEY_TRANSFERABLE_SECTION_TITLE;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_CONTROLLER_RELEASED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_CONTROL_REQUEST_FAILED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_DESCRIPTOR_CHANGED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_DYNAMIC_ROUTE_CREATED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_DYNAMIC_ROUTE_DESCRIPTORS_CHANGED;
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_VERSION_CURRENT;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
import static androidx.mediarouter.media.MediaRouter.UNSELECT_REASON_UNKNOWN;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
import androidx.core.content.ContextCompat;
import androidx.core.util.ObjectsCompat;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.OnDynamicRoutesChangedListener;
import androidx.mediarouter.media.MediaRouteProvider.RouteController;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Base class for media route provider services.
* <p>
* A media router will bind to media route provider services when a callback is added via
* {@link MediaRouter#addCallback(MediaRouteSelector, MediaRouter.Callback, int)} with a discovery
* flag: {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY},
* {@link MediaRouter#CALLBACK_FLAG_FORCE_DISCOVERY}, or
* {@link MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN}, and will unbind when the callback
* is removed via {@link MediaRouter#removeCallback(MediaRouter.Callback)}.
* </p><p>
* To implement your own media route provider service, extend this class and
* override the {@link #onCreateMediaRouteProvider} method to return an
* instance of your {@link MediaRouteProvider}.
* </p><p>
* Declare your media route provider service in your application manifest
* like this:
* </p>
* <pre>
* &lt;service android:name=".MyMediaRouteProviderService"
* android:label="@string/my_media_route_provider_service">
* &lt;intent-filter>
* &lt;action android:name="android.media.MediaRouteProviderService" />
* &lt;/intent-filter>
* &lt;/service>
* </pre>
*/
public abstract class MediaRouteProviderService extends Service {
static final String TAG = "MediaRouteProviderSrv"; // max. 23 chars
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ReceiveHandler mReceiveHandler;
final Messenger mReceiveMessenger;
final PrivateHandler mPrivateHandler;
private final MediaRouteProvider.Callback mProviderCallback;
MediaRouteProvider mProvider;
final MediaRouteProviderServiceImpl mImpl;
/**
* The {@link Intent} that must be declared as handled by the service.
* Put this in your manifest.
*/
public static final String SERVICE_INTERFACE = MediaRouteProviderProtocol.SERVICE_INTERFACE;
/*
* Private messages used internally. (Yes, you can renumber these.)
*/
static final int PRIVATE_MSG_CLIENT_DIED = 1;
interface MediaRouteProviderServiceImpl {
IBinder onBind(Intent intent);
void attachBaseContext(Context context);
void onBinderDied(Messenger messenger);
boolean onRegisterClient(Messenger messenger, int requestId, int version,
String packageName);
boolean onUnregisterClient(Messenger messenger, int requestId);
boolean onCreateRouteController(Messenger messenger, int requestId,
int controllerId, String routeId, String routeGroupId);
boolean onCreateDynamicGroupRouteController(Messenger messenger, int requestId,
int controllerId, String initialMemberRouteId);
boolean onAddMemberRoute(Messenger messenger, int requestId, int controllerId,
String memberId);
boolean onRemoveMemberRoute(Messenger messenger, int requestId, int controllerId,
String memberId);
boolean onUpdateMemberRoutes(Messenger messenger, int requestId, int controllerId,
List<String> memberIds);
boolean onReleaseRouteController(Messenger messenger, int requestId,
int controllerId);
boolean onSelectRoute(Messenger messenger, int requestId,
int controllerId);
boolean onUnselectRoute(Messenger messenger, int requestId,
int controllerId, @MediaRouter.UnselectReason int reason);
boolean onSetRouteVolume(Messenger messenger, int requestId,
int controllerId, int volume);
boolean onUpdateRouteVolume(Messenger messenger, int requestId,
int controllerId, int delta);
boolean onRouteControlRequest(Messenger messenger, int requestId,
int controllerId, Intent intent);
boolean onSetDiscoveryRequest(Messenger messenger, int requestId,
MediaRouteDiscoveryRequest request);
MediaRouteProvider.Callback getProviderCallback();
}
/**
* Creates a media route provider service.
*/
public MediaRouteProviderService() {
mReceiveHandler = new ReceiveHandler(this);
mReceiveMessenger = new Messenger(mReceiveHandler);
mPrivateHandler = new PrivateHandler();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
mImpl = new MediaRouteProviderServiceImplApi30(this);
} else {
mImpl = new MediaRouteProviderServiceImplBase(this);
}
mProviderCallback = mImpl.getProviderCallback();
}
/**
* Called by the system when it is time to create the media route provider.
*
* @return The media route provider offered by this service, or null if
* this service has decided not to offer a media route provider.
*/
@Nullable
public abstract MediaRouteProvider onCreateMediaRouteProvider();
@Override
@Nullable
public IBinder onBind(@NonNull Intent intent) {
return mImpl.onBind(intent);
}
@Override
protected void attachBaseContext(@NonNull Context context) {
super.attachBaseContext(context);
mImpl.attachBaseContext(context);
}
/**
* Gets the media route provider offered by this service.
*
* @return The media route provider offered by this service, or null if
* it has not yet been created.
*
* @see #onCreateMediaRouteProvider()
*/
@Nullable
public MediaRouteProvider getMediaRouteProvider() {
return mProvider;
}
void ensureProvider() {
if (mProvider == null) {
MediaRouteProvider provider = onCreateMediaRouteProvider();
if (provider != null) {
String providerPackage = provider.getMetadata().getPackageName();
if (!providerPackage.equals(getPackageName())) {
throw new IllegalStateException("onCreateMediaRouteProvider() returned "
+ "a provider whose package name does not match the package "
+ "name of the service. A media route provider service can "
+ "only export its own media route providers. "
+ "Provider package name: " + providerPackage
+ ". Service package name: " + getPackageName() + ".");
}
mProvider = provider;
mProvider.setCallback(mProviderCallback);
}
}
}
@Override
public void onDestroy() {
if (mProvider != null) {
mProvider.setCallback(null);
}
super.onDestroy();
}
@VisibleForTesting
static Bundle createDescriptorBundleForClientVersion(MediaRouteProviderDescriptor descriptor,
int clientVersion) {
if (descriptor == null) {
return null;
}
MediaRouteProviderDescriptor.Builder builder =
new MediaRouteProviderDescriptor.Builder(descriptor);
builder.setRoutes(null);
// Disable dynamic grouping for old clients
if (clientVersion < CLIENT_VERSION_4) {
builder.setSupportsDynamicGroupRoute(false);
}
for (MediaRouteDescriptor route : descriptor.getRoutes()) {
if (clientVersion >= route.getMinClientVersion()
&& clientVersion <= route.getMaxClientVersion()) {
builder.addRoute(route);
}
}
return builder.build().asBundle();
}
static void sendGenericFailure(Messenger messenger, int requestId) {
if (requestId != 0) {
sendMessage(messenger, SERVICE_MSG_GENERIC_FAILURE, requestId, 0, null, null);
}
}
static void sendGenericSuccess(Messenger messenger, int requestId) {
if (requestId != 0) {
sendMessage(messenger, SERVICE_MSG_GENERIC_SUCCESS, requestId, 0, null, null);
}
}
static void sendMessage(Messenger messenger, int what,
int requestId, int arg, Object obj, Bundle data) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = requestId;
msg.arg2 = arg;
msg.obj = obj;
msg.setData(data);
try {
messenger.send(msg);
} catch (DeadObjectException ex) {
// The client died.
} catch (RemoteException ex) {
Log.e(TAG, "Could not send message to " + getClientId(messenger), ex);
}
}
static String getClientId(Messenger messenger) {
return "Client connection " + messenger.getBinder().toString();
}
private final class PrivateHandler extends Handler {
PrivateHandler() {
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PRIVATE_MSG_CLIENT_DIED:
mImpl.onBinderDied((Messenger) msg.obj);
break;
}
}
}
/**
* Handler that receives messages from clients.
* <p>
* This inner class is static and only retains a weak reference to the service
* to prevent the service from being leaked in case one of the clients is holding an
* active reference to the server's messenger.
* </p><p>
* This handler should not be used to handle any messages other than those
* that come from the client.
* </p>
*/
private static final class ReceiveHandler extends Handler {
private final WeakReference<MediaRouteProviderService> mServiceRef;
public ReceiveHandler(MediaRouteProviderService service) {
mServiceRef = new WeakReference<MediaRouteProviderService>(service);
}
@Override
public void handleMessage(Message msg) {
final Messenger messenger = msg.replyTo;
if (isValidRemoteMessenger(messenger)) {
final int what = msg.what;
final int requestId = msg.arg1;
final int arg = msg.arg2;
final Object obj = msg.obj;
final Bundle data = msg.peekData();
String packageName = null;
if (what == CLIENT_MSG_REGISTER && Build.VERSION.SDK_INT
>= Build.VERSION_CODES.LOLLIPOP) {
String[] packages = mServiceRef.get().getPackageManager()
.getPackagesForUid(msg.sendingUid);
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
}
if (!processMessage(what, messenger, requestId, arg, obj, data, packageName)) {
if (DEBUG) {
Log.d(TAG, getClientId(messenger) + ": Message failed, what=" + what
+ ", requestId=" + requestId + ", arg=" + arg
+ ", obj=" + obj + ", data=" + data);
}
sendGenericFailure(messenger, requestId);
}
} else {
if (DEBUG) {
Log.d(TAG, "Ignoring message without valid reply messenger.");
}
}
}
private boolean processMessage(int what, Messenger messenger,
int requestId, int arg, Object obj, Bundle data, String packageName) {
MediaRouteProviderService service = mServiceRef.get();
if (service != null) {
switch (what) {
case CLIENT_MSG_REGISTER:
return service.mImpl.onRegisterClient(messenger, requestId, arg,
packageName);
case CLIENT_MSG_UNREGISTER:
return service.mImpl.onUnregisterClient(messenger, requestId);
case CLIENT_MSG_CREATE_ROUTE_CONTROLLER: {
String routeId = data.getString(CLIENT_DATA_ROUTE_ID);
String routeGroupId =
data.getString(CLIENT_DATA_ROUTE_LIBRARY_GROUP);
if (routeId != null) {
return service.mImpl.onCreateRouteController(
messenger, requestId, arg, routeId, routeGroupId);
}
break;
}
case CLIENT_MSG_CREATE_DYNAMIC_GROUP_ROUTE_CONTROLLER: {
String initialMemberId = data.getString(CLIENT_DATA_MEMBER_ROUTE_ID);
if (initialMemberId != null) {
return service.mImpl.onCreateDynamicGroupRouteController(
messenger, requestId, arg, initialMemberId);
}
break;
}
case CLIENT_MSG_ADD_MEMBER_ROUTE: {
String memberId = data.getString(CLIENT_DATA_MEMBER_ROUTE_ID);
if (memberId != null) {
return service.mImpl.onAddMemberRoute(messenger, requestId, arg,
memberId);
}
break;
}
case CLIENT_MSG_REMOVE_MEMBER_ROUTE: {
String memberId = data.getString(CLIENT_DATA_MEMBER_ROUTE_ID);
if (memberId != null) {
return service.mImpl.onRemoveMemberRoute(messenger, requestId, arg,
memberId);
}
break;
}
case CLIENT_MSG_UPDATE_MEMBER_ROUTES: {
ArrayList<String> memberIds =
data.getStringArrayList(CLIENT_DATA_MEMBER_ROUTE_IDS);
if (memberIds != null) {
return service.mImpl.onUpdateMemberRoutes(
messenger, requestId, arg, memberIds);
}
break;
}
case CLIENT_MSG_RELEASE_ROUTE_CONTROLLER:
return service.mImpl.onReleaseRouteController(messenger, requestId, arg);
case CLIENT_MSG_SELECT_ROUTE:
return service.mImpl.onSelectRoute(messenger, requestId, arg);
case CLIENT_MSG_UNSELECT_ROUTE:
int reason = data == null ?
UNSELECT_REASON_UNKNOWN
: data.getInt(CLIENT_DATA_UNSELECT_REASON,
UNSELECT_REASON_UNKNOWN);
return service.mImpl.onUnselectRoute(messenger, requestId, arg, reason);
case CLIENT_MSG_SET_ROUTE_VOLUME: {
int volume = data.getInt(CLIENT_DATA_VOLUME, -1);
if (volume >= 0) {
return service.mImpl.onSetRouteVolume(
messenger, requestId, arg, volume);
}
break;
}
case CLIENT_MSG_UPDATE_ROUTE_VOLUME: {
int delta = data.getInt(CLIENT_DATA_VOLUME, 0);
if (delta != 0) {
return service.mImpl.onUpdateRouteVolume(
messenger, requestId, arg, delta);
}
break;
}
case CLIENT_MSG_ROUTE_CONTROL_REQUEST:
if (obj instanceof Intent) {
return service.mImpl.onRouteControlRequest(
messenger, requestId, arg, (Intent)obj);
}
break;
case CLIENT_MSG_SET_DISCOVERY_REQUEST: {
if (obj == null || obj instanceof Bundle) {
MediaRouteDiscoveryRequest request =
MediaRouteDiscoveryRequest.fromBundle((Bundle)obj);
return service.mImpl.onSetDiscoveryRequest(
messenger, requestId,
request != null && request.isValid() ? request : null);
}
}
}
}
return false;
}
}
static class MediaRouteProviderServiceImplBase implements MediaRouteProviderServiceImpl {
final MediaRouteProviderService mService;
final ArrayList<ClientRecord> mClients = new ArrayList<ClientRecord>();
MediaRouteDiscoveryRequest mCompositeDiscoveryRequest;
MediaRouteDiscoveryRequest mBaseDiscoveryRequest;
long mBaseDiscoveryRequestTimestamp;
private final MediaRouterActiveScanThrottlingHelper mActiveScanThrottlingHelper =
new MediaRouterActiveScanThrottlingHelper(new Runnable() {
@Override
public void run() {
updateCompositeDiscoveryRequest();
}
});
MediaRouteProviderServiceImplBase(MediaRouteProviderService service) {
mService = service;
}
public MediaRouteProviderService getService() {
return mService;
}
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals(SERVICE_INTERFACE)) {
mService.ensureProvider();
if (mService.getMediaRouteProvider() != null) {
return mService.mReceiveMessenger.getBinder();
}
}
return null;
}
@Override
public void attachBaseContext(Context context) {}
@Override
public MediaRouteProvider.Callback getProviderCallback() {
return new ProviderCallbackBase();
}
@Override
public boolean onRegisterClient(Messenger messenger, int requestId, int version,
String packageName) {
if (version >= CLIENT_VERSION_1) {
int index = findClient(messenger);
if (index < 0) {
ClientRecord client = createClientRecord(messenger, version, packageName);
if (client.register()) {
mClients.add(client);
if (DEBUG) {
Log.d(TAG, client + ": Registered, version=" + version);
}
if (requestId != 0) {
MediaRouteProviderDescriptor descriptor =
mService.getMediaRouteProvider().getDescriptor();
sendMessage(messenger, SERVICE_MSG_REGISTERED,
requestId, SERVICE_VERSION_CURRENT,
createDescriptorBundleForClientVersion(descriptor,
client.mVersion), null);
}
return true;
}
}
}
return false;
}
@Override
public boolean onUnregisterClient(Messenger messenger, int requestId) {
int index = findClient(messenger);
if (index >= 0) {
ClientRecord client = mClients.remove(index);
if (DEBUG) {
Log.d(TAG, client + ": Unregistered");
}
client.dispose();
sendGenericSuccess(messenger, requestId);
return true;
}
return false;
}
@Override
public void onBinderDied(Messenger messenger) {
int index = findClient(messenger);
if (index >= 0) {
ClientRecord client = mClients.remove(index);
if (DEBUG) {
Log.d(TAG, client + ": Binder died");
}
client.dispose();
}
}
@Override
public boolean onCreateRouteController(Messenger messenger, int requestId,
int controllerId, String routeId, String routeGroupId) {
ClientRecord client = getClient(messenger);
if (client != null) {
if (client.createRouteController(routeId, routeGroupId, controllerId)) {
if (DEBUG) {
Log.d(TAG, client + ": Route controller created, controllerId="
+ controllerId + ", routeId=" + routeId
+ ", routeGroupId=" + routeGroupId);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onCreateDynamicGroupRouteController(Messenger messenger, int requestId,
int controllerId, String initialMemberRouteId) {
ClientRecord client = getClient(messenger);
if (client != null) {
Bundle bundle = client.createDynamicGroupRouteController(
initialMemberRouteId, controllerId);
if (bundle != null) {
if (DEBUG) {
Log.d(TAG, client + ": Route controller created, controllerId="
+ controllerId
+ ", initialMemberRouteId=" + initialMemberRouteId);
}
sendMessage(messenger, SERVICE_MSG_DYNAMIC_ROUTE_CREATED,
requestId, SERVICE_VERSION_CURRENT,
bundle, null);
return true;
}
}
return false;
}
@Override
public boolean onAddMemberRoute(Messenger messenger, int requestId, int controllerId,
String memberId) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller instanceof MediaRouteProvider.DynamicGroupRouteController) {
((MediaRouteProvider.DynamicGroupRouteController) controller)
.onAddMemberRoute(memberId);
if (DEBUG) {
Log.d(TAG, client + ": Added a member route"
+ ", controllerId=" + controllerId + ", memberId=" + memberId);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onRemoveMemberRoute(Messenger messenger, int requestId, int controllerId,
String memberId) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller instanceof MediaRouteProvider.DynamicGroupRouteController) {
((MediaRouteProvider.DynamicGroupRouteController) controller)
.onRemoveMemberRoute(memberId);
if (DEBUG) {
Log.d(TAG, client + ": Removed a member route"
+ ", controllerId=" + controllerId + ", memberId=" + memberId);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onUpdateMemberRoutes(Messenger messenger, int requestId, int controllerId,
List<String> memberIds) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller instanceof MediaRouteProvider.DynamicGroupRouteController) {
((MediaRouteProvider.DynamicGroupRouteController) controller)
.onUpdateMemberRoutes(memberIds);
if (DEBUG) {
Log.d(TAG, client + ": Updated list of member routes"
+ ", controllerId=" + controllerId + ", memberIds=" + memberIds);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onReleaseRouteController(Messenger messenger, int requestId,
int controllerId) {
ClientRecord client = getClient(messenger);
if (client != null) {
if (client.releaseRouteController(controllerId)) {
if (DEBUG) {
Log.d(TAG, client + ": Route controller released"
+ ", controllerId=" + controllerId);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onSelectRoute(Messenger messenger, int requestId,
int controllerId) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller != null) {
controller.onSelect();
if (DEBUG) {
Log.d(TAG, client + ": Route selected"
+ ", controllerId=" + controllerId);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onUnselectRoute(Messenger messenger, int requestId,
int controllerId, @MediaRouter.UnselectReason int reason) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller != null) {
controller.onUnselect(reason);
if (DEBUG) {
Log.d(TAG, client + ": Route unselected"
+ ", controllerId=" + controllerId);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onSetRouteVolume(Messenger messenger, int requestId,
int controllerId, int volume) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller != null) {
controller.onSetVolume(volume);
if (DEBUG) {
Log.d(TAG, client + ": Route volume changed"
+ ", controllerId=" + controllerId + ", volume=" + volume);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onUpdateRouteVolume(Messenger messenger, int requestId,
int controllerId, int delta) {
ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller != null) {
controller.onUpdateVolume(delta);
if (DEBUG) {
Log.d(TAG, client + ": Route volume updated"
+ ", controllerId=" + controllerId + ", delta=" + delta);
}
sendGenericSuccess(messenger, requestId);
return true;
}
}
return false;
}
@Override
public boolean onRouteControlRequest(final Messenger messenger, final int requestId,
final int controllerId, final Intent intent) {
final ClientRecord client = getClient(messenger);
if (client != null) {
RouteController controller =
client.getRouteController(controllerId);
if (controller != null) {
MediaRouter.ControlRequestCallback callback = null;
if (requestId != 0) {
callback = new MediaRouter.ControlRequestCallback() {
@Override
public void onResult(Bundle data) {
if (DEBUG) {
Log.d(TAG, client + ": Route control request succeeded"
+ ", controllerId=" + controllerId
+ ", intent=" + intent
+ ", data=" + data);
}
if (findClient(messenger) >= 0) {
sendMessage(messenger, SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED,
requestId, 0, data, null);
}
}
@Override
public void onError(String error, Bundle data) {
if (DEBUG) {
Log.d(TAG, client + ": Route control request failed"
+ ", controllerId=" + controllerId
+ ", intent=" + intent
+ ", error=" + error + ", data=" + data);
}
if (findClient(messenger) >= 0) {
if (error != null) {
Bundle bundle = new Bundle();
bundle.putString(SERVICE_DATA_ERROR, error);
sendMessage(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
requestId, 0, data, bundle);
} else {
sendMessage(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
requestId, 0, data, null);
}
}
}
};
}
if (controller.onControlRequest(intent, callback)) {
if (DEBUG) {
Log.d(TAG, client + ": Route control request delivered"
+ ", controllerId=" + controllerId + ", intent=" + intent);
}
return true;
}
}
}
return false;
}
@Override
public boolean onSetDiscoveryRequest(Messenger messenger, int requestId,
MediaRouteDiscoveryRequest request) {
ClientRecord client = getClient(messenger);
if (client != null) {
boolean actuallyChanged = client.setDiscoveryRequest(request);
if (DEBUG) {
Log.d(TAG, client + ": Set discovery request, request=" + request
+ ", actuallyChanged=" + actuallyChanged
+ ", compositeDiscoveryRequest=" + mCompositeDiscoveryRequest);
}
sendGenericSuccess(messenger, requestId);
return true;
}
return false;
}
void sendDescriptorChanged(MediaRouteProviderDescriptor descriptor) {
final int count = mClients.size();
for (int i = 0; i < count; i++) {
ClientRecord client = mClients.get(i);
sendMessage(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
client.createDescriptorBundle(descriptor), null);
if (DEBUG) {
Log.d(TAG, client + ": Sent descriptor change event, descriptor=" + descriptor);
}
}
}
boolean setBaseDiscoveryRequest(MediaRouteDiscoveryRequest request) {
long timestamp = SystemClock.elapsedRealtime();
if (!ObjectsCompat.equals(mBaseDiscoveryRequest, request) || request.isActiveScan()) {
mBaseDiscoveryRequest = request;
mBaseDiscoveryRequestTimestamp = timestamp;
return updateCompositeDiscoveryRequest();
}
return false;
}
boolean updateCompositeDiscoveryRequest() {
MediaRouteSelector.Builder selectorBuilder = null;
mActiveScanThrottlingHelper.reset();
if (mBaseDiscoveryRequest != null) {
mActiveScanThrottlingHelper.requestActiveScan(
mBaseDiscoveryRequest.isActiveScan(),
mBaseDiscoveryRequestTimestamp);
selectorBuilder = new MediaRouteSelector.Builder(
mBaseDiscoveryRequest.getSelector());
}
final int count = mClients.size();
for (int i = 0; i < count; i++) {
ClientRecord clientRecord = mClients.get(i);
MediaRouteDiscoveryRequest request = clientRecord.mDiscoveryRequest;
if (request != null
&& (!request.getSelector().isEmpty() || request.isActiveScan())) {
mActiveScanThrottlingHelper.requestActiveScan(request.isActiveScan(),
clientRecord.mDiscoveryRequestTimestamp);
if (selectorBuilder == null) {
selectorBuilder = new MediaRouteSelector.Builder(request.getSelector());
} else {
selectorBuilder.addSelector(request.getSelector());
}
}
}
boolean activeScan =
mActiveScanThrottlingHelper
.finalizeActiveScanAndScheduleSuppressActiveScanRunnable();
MediaRouteDiscoveryRequest composite = (selectorBuilder == null) ? null
: new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
if (!ObjectsCompat.equals(mCompositeDiscoveryRequest, composite)) {
mCompositeDiscoveryRequest = composite;
mService.getMediaRouteProvider().setDiscoveryRequest(composite);
return true;
}
return false;
}
private ClientRecord getClient(Messenger messenger) {
int index = findClient(messenger);
return index >= 0 ? mClients.get(index) : null;
}
int findClient(Messenger messenger) {
final int count = mClients.size();
for (int i = 0; i < count; i++) {
ClientRecord client = mClients.get(i);
if (client.hasMessenger(messenger)) {
return i;
}
}
return -1;
}
class ClientRecord implements DeathRecipient {
public final Messenger mMessenger;
public final int mVersion;
public final String mPackageName;
public MediaRouteDiscoveryRequest mDiscoveryRequest;
public long mDiscoveryRequestTimestamp;
final SparseArray<RouteController> mControllers = new SparseArray<>();
final OnDynamicRoutesChangedListener mDynamicRoutesChangedListener =
new OnDynamicRoutesChangedListener() {
@Override
public void onRoutesChanged(
@NonNull DynamicGroupRouteController controller,
@NonNull MediaRouteDescriptor groupRoute,
@NonNull Collection<DynamicRouteDescriptor> routes) {
sendDynamicRouteDescriptors(controller, groupRoute, routes);
}
};
ClientRecord(Messenger messenger, int version, String packageName) {
mMessenger = messenger;
mVersion = version;
mPackageName = packageName;
}
public boolean register() {
try {
mMessenger.getBinder().linkToDeath(this, 0);
return true;
} catch (RemoteException ex) {
binderDied();
}
return false;
}
public void dispose() {
int count = mControllers.size();
for (int i = 0; i < count; i++) {
mControllers.valueAt(i).onRelease();
}
mControllers.clear();
mMessenger.getBinder().unlinkToDeath(this, 0);
setDiscoveryRequest(null);
}
public boolean hasMessenger(Messenger other) {
return mMessenger.getBinder() == other.getBinder();
}
public boolean createRouteController(String routeId, String routeGroupId,
int controllerId) {
if (mControllers.indexOfKey(controllerId) < 0) {
RouteController controller = routeGroupId == null
? mService.getMediaRouteProvider().onCreateRouteController(routeId)
: mService.getMediaRouteProvider()
.onCreateRouteController(routeId, routeGroupId);
if (controller != null) {
mControllers.put(controllerId, controller);
return true;
}
}
return false;
}
public Bundle createDynamicGroupRouteController(
String initialMemberRouteId, int controllerId) {
if (mControllers.indexOfKey(controllerId) < 0) {
MediaRouteProvider.DynamicGroupRouteController controller =
mService.getMediaRouteProvider()
.onCreateDynamicGroupRouteController(initialMemberRouteId);
if (controller != null) {
controller.setOnDynamicRoutesChangedListener(
ContextCompat.getMainExecutor(mService.getApplicationContext()),
mDynamicRoutesChangedListener);
mControllers.put(controllerId, controller);
Bundle bundle = new Bundle();
bundle.putString(DATA_KEY_GROUPABLE_SECION_TITLE,
controller.getGroupableSelectionTitle());
bundle.putString(DATA_KEY_TRANSFERABLE_SECTION_TITLE,
controller.getTransferableSectionTitle());
return bundle;
}
}
return null;
}
public boolean releaseRouteController(int controllerId) {
RouteController controller = mControllers.get(controllerId);
if (controller != null) {
mControllers.remove(controllerId);
controller.onRelease();
return true;
}
return false;
}
public RouteController getRouteController(int controllerId) {
return mControllers.get(controllerId);
}
public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
long timestamp = SystemClock.elapsedRealtime();
if (!ObjectsCompat.equals(mDiscoveryRequest, request)) {
mDiscoveryRequest = request;
mDiscoveryRequestTimestamp = timestamp;
return updateCompositeDiscoveryRequest();
}
return false;
}
/**
* Creates a bundle of the given provider descriptor for this client.
*/
public Bundle createDescriptorBundle(MediaRouteProviderDescriptor descriptor) {
return createDescriptorBundleForClientVersion(descriptor, mVersion);
}
// Runs on a binder thread.
@Override
public void binderDied() {
mService.mPrivateHandler
.obtainMessage(PRIVATE_MSG_CLIENT_DIED, mMessenger).sendToTarget();
}
@NonNull
@Override
public String toString() {
return getClientId(mMessenger);
}
void sendDynamicRouteDescriptors(
DynamicGroupRouteController controller,
MediaRouteDescriptor groupRoute,
Collection<DynamicRouteDescriptor> descriptors) {
int index = mControllers.indexOfValue(controller);
if (index < 0) {
Log.w(TAG, "Ignoring unknown dynamic group route controller: " + controller);
return;
}
int controllerId = mControllers.keyAt(index);
ArrayList<Bundle> dynamicRouteBundles = new ArrayList<Bundle>();
for (DynamicRouteDescriptor descriptor: descriptors) {
dynamicRouteBundles.add(descriptor.toBundle());
}
Bundle bundle = new Bundle();
if (groupRoute != null) {
bundle.putParcelable(DATA_KEY_GROUP_ROUTE_DESCRIPTOR, groupRoute.asBundle());
}
bundle.putParcelableArrayList(DATA_KEY_DYNAMIC_ROUTE_DESCRIPTORS,
dynamicRouteBundles);
sendMessage(mMessenger, SERVICE_MSG_DYNAMIC_ROUTE_DESCRIPTORS_CHANGED,
0, controllerId, bundle, null);
}
}
ClientRecord createClientRecord(Messenger messenger, int version, String packageName) {
return new ClientRecord(messenger, version, packageName);
}
class ProviderCallbackBase extends MediaRouteProvider.Callback {
@Override
public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
MediaRouteProviderDescriptor descriptor) {
sendDescriptorChanged(descriptor);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.R)
static class MediaRouteProviderServiceImplApi30 extends MediaRouteProviderServiceImplBase {
MediaRoute2ProviderServiceAdapter mMR2ProviderServiceAdapter;
final OnDynamicRoutesChangedListener mDynamicRoutesChangedListener =
(controller, groupRoute, routes) -> mMR2ProviderServiceAdapter
.setDynamicRouteDescriptor(controller, groupRoute, routes);
MediaRouteProviderServiceImplApi30(MediaRouteProviderService instance) {
super(instance);
}
@Override
public IBinder onBind(Intent intent) {
mService.ensureProvider();
if (mMR2ProviderServiceAdapter == null) {
mMR2ProviderServiceAdapter = new MediaRoute2ProviderServiceAdapter(this);
if (mService.getBaseContext() != null) {
mMR2ProviderServiceAdapter.attachBaseContext(mService);
}
}
IBinder binder = super.onBind(intent);
if (binder != null) {
return binder;
}
return mMR2ProviderServiceAdapter.onBind(intent);
}
@Override
public void attachBaseContext(Context context) {
if (mMR2ProviderServiceAdapter != null) {
mMR2ProviderServiceAdapter.attachBaseContext(context);
}
}
@Override
void sendDescriptorChanged(MediaRouteProviderDescriptor descriptor) {
super.sendDescriptorChanged(descriptor);
mMR2ProviderServiceAdapter.setProviderDescriptor(descriptor);
}
@Override
MediaRouteProviderServiceImplBase.ClientRecord createClientRecord(
Messenger messenger, int version, String packageName) {
return new ClientRecord(messenger, version, packageName);
}
void setDynamicRoutesChangedListener(DynamicGroupRouteController controller) {
controller.setOnDynamicRoutesChangedListener(
ContextCompat.getMainExecutor(mService.getApplicationContext()),
mDynamicRoutesChangedListener);
}
class ClientRecord extends MediaRouteProviderServiceImplBase.ClientRecord {
// Maps the route ID to member route controller.
private final Map<String, RouteController> mRouteIdToControllerMap = new ArrayMap<>();
private final Handler mClientHandler = new Handler(Looper.getMainLooper());
// Maps disabled route ID to ID of controllers released by provider.
private final Map<String, Integer> mReleasedControllerIds;
private static final long DISABLE_ROUTE_FOR_RELEASED_CONTROLLER_TIMEOUT_MS = 5_000;
ClientRecord(Messenger messenger, int version, String packageName) {
super(messenger, version, packageName);
// Only required by clients that don't support SERVICE_MSG_CONTROLLER_RELEASED.
if (version < CLIENT_VERSION_4) {
mReleasedControllerIds = new ArrayMap<>();
} else {
mReleasedControllerIds = Collections.emptyMap();
}
}
@Override
public void dispose() {
int count = mControllers.size();
for (int i = 0; i < count; i++) {
int controllerId = mControllers.keyAt(i);
mMR2ProviderServiceAdapter.notifyRouteControllerRemoved(controllerId);
}
mRouteIdToControllerMap.clear();
super.dispose();
}
@Override
public boolean createRouteController(String routeId, String routeGroupId,
int controllerId) {
RouteController controller = mRouteIdToControllerMap.get(routeId);
if (controller != null) {
mControllers.put(controllerId, controller);
return true;
}
boolean result = super.createRouteController(routeId, routeGroupId,
controllerId);
// Don't add route controllers of member routes.
if (routeGroupId == null && result && mPackageName != null) {
mMR2ProviderServiceAdapter.notifyRouteControllerAdded(
this, mControllers.get(controllerId),
controllerId, mPackageName, routeId);
}
if (result) {
mRouteIdToControllerMap.put(routeId, mControllers.get(controllerId));
}
return result;
}
@Override
public Bundle createDynamicGroupRouteController(
String initialMemberRouteId, int controllerId) {
Bundle result =
super.createDynamicGroupRouteController(initialMemberRouteId, controllerId);
if (result != null && mPackageName != null) {
mMR2ProviderServiceAdapter.notifyRouteControllerAdded(
this, mControllers.get(controllerId),
controllerId, mPackageName, initialMemberRouteId);
}
return result;
}
@Override
public boolean releaseRouteController(int controllerId) {
mMR2ProviderServiceAdapter.notifyRouteControllerRemoved(controllerId);
RouteController controller = mControllers.get(controllerId);
if (controller != null) {
for (Map.Entry<String, RouteController> entry :
mRouteIdToControllerMap.entrySet()) {
if (entry.getValue() == controller) {
mRouteIdToControllerMap.remove(entry.getKey());
break;
}
}
}
for (Map.Entry<String, Integer> entry : mReleasedControllerIds.entrySet()) {
if (entry.getValue() == controllerId) {
enableRouteForReleasedRouteController(/*routeId=*/entry.getKey());
break;
}
}
return super.releaseRouteController(controllerId);
}
@Override
void sendDynamicRouteDescriptors(
DynamicGroupRouteController controller,
MediaRouteDescriptor groupRoute,
Collection<DynamicRouteDescriptor> descriptors) {
super.sendDynamicRouteDescriptors(controller, groupRoute, descriptors);
if (mMR2ProviderServiceAdapter != null) {
mMR2ProviderServiceAdapter.setDynamicRouteDescriptor(controller,
groupRoute, descriptors);
}
}
@Override
public Bundle createDescriptorBundle(MediaRouteProviderDescriptor descriptor) {
if (mReleasedControllerIds.isEmpty()) {
return super.createDescriptorBundle(descriptor);
}
List<MediaRouteDescriptor> routes = new ArrayList<>();
for (MediaRouteDescriptor routeDescriptor : descriptor.getRoutes()) {
if (mReleasedControllerIds.containsKey(routeDescriptor.getId())) {
routes.add(new MediaRouteDescriptor.Builder(routeDescriptor)
.setEnabled(false).build());
} else {
routes.add(routeDescriptor);
}
}
MediaRouteProviderDescriptor newDescriptor =
new MediaRouteProviderDescriptor.Builder(descriptor)
.setRoutes(routes).build();
return super.createDescriptorBundle(newDescriptor);
}
void releaseControllerByProvider(RouteController controller, String routeId) {
int controllerId = findControllerIdByController(controller);
releaseRouteController(controllerId);
// Let the client unselect the corresponding route.
if (mVersion >= CLIENT_VERSION_4) {
if (controllerId < 0) {
Log.w(TAG, "releaseControllerByProvider: Can't find the controller."
+ " route ID=" + routeId);
return;
}
sendMessage(mMessenger, SERVICE_MSG_CONTROLLER_RELEASED, 0, controllerId,
null, null);
} else {
// Use a workaround for old versions.
disableRouteForReleasedRouteController(routeId, controllerId);
}
}
/**
* Sets the route that corresponds to the given released route controller as disabled
* so that media router client unselects the route and releases the route controller
* on the client side.
* The route becomes enabled when the route controller on the client side is released
* or it times out.
*/
private void disableRouteForReleasedRouteController(String routeId, int controllerId) {
mReleasedControllerIds.put(routeId, controllerId);
mClientHandler.postDelayed(() -> enableRouteForReleasedRouteController(routeId),
DISABLE_ROUTE_FOR_RELEASED_CONTROLLER_TIMEOUT_MS);
sendDescriptor();
}
private void enableRouteForReleasedRouteController(String routeId) {
if (mReleasedControllerIds.remove(routeId) == null) {
return;
}
sendDescriptor();
}
/**
* Sends the lastly known provider descriptor to the client.
*/
void sendDescriptor() {
MediaRouteProviderDescriptor providerDescriptor =
getService().getMediaRouteProvider().getDescriptor();
if (providerDescriptor != null) {
sendMessage(mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
createDescriptorBundle(providerDescriptor), null);
}
}
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);
}
}
}
}