| /* |
| * 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_CONTROLLER_ID; |
| import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_CONTROLLER_INFO; |
| 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_PROVIDER_DESCRIPTOR; |
| 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.DATA_KEY_DYNAMIC_ROUTE_DESCRIPTORS; |
| import static androidx.mediarouter.media.MediaRouteProviderProtocol.DATA_KEY_GROUPABLE_SECION_TITLE; |
| 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_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_CREATED_WITHOUT_REQUEST; |
| 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 android.annotation.SuppressLint; |
| 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.Message; |
| import android.os.Messenger; |
| import android.os.RemoteException; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.SparseArray; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.RequiresApi; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.core.content.ContextCompat; |
| import androidx.core.os.BuildCompat; |
| import androidx.core.util.ObjectsCompat; |
| import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController; |
| import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor; |
| import androidx.mediarouter.media.MediaRouteProvider.RouteController; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| 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> |
| * <service android:name=".MyMediaRouteProviderService" |
| * android:label="@string/my_media_route_provider_service"> |
| * <intent-filter> |
| * <action android:name="android.media.MediaRouteProviderService" /> |
| * </intent-filter> |
| * </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; |
| |
| //TODO: move implementations to the impl class (WIP). |
| 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, 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. |
| */ |
| //TODO: Remove SuppressLint when R version is finalized. |
| @SuppressLint("NewApi") |
| public MediaRouteProviderService() { |
| mReceiveHandler = new ReceiveHandler(this); |
| mReceiveMessenger = new Messenger(mReceiveHandler); |
| mPrivateHandler = new PrivateHandler(); |
| |
| if (BuildCompat.isAtLeastR()) { |
| 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. |
| */ |
| public abstract MediaRouteProvider onCreateMediaRouteProvider(); |
| |
| @Override |
| public IBinder onBind(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() |
| */ |
| 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); |
| 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) { |
| sendReply(messenger, SERVICE_MSG_GENERIC_FAILURE, requestId, 0, null, null); |
| } |
| } |
| |
| static void sendGenericSuccess(Messenger messenger, int requestId) { |
| if (requestId != 0) { |
| sendReply(messenger, SERVICE_MSG_GENERIC_SUCCESS, requestId, 0, null, null); |
| } |
| } |
| |
| static void sendReply(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 ? |
| MediaRouter.UNSELECT_REASON_UNKNOWN |
| : data.getInt(CLIENT_DATA_UNSELECT_REASON, |
| MediaRouter.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; |
| |
| 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(); |
| sendReply(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); |
| } |
| sendReply(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, 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) { |
| sendReply(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); |
| sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED, |
| requestId, 0, data, bundle); |
| } else { |
| sendReply(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); |
| sendReply(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0, |
| createDescriptorBundleForClientVersion(descriptor, client.mVersion), null); |
| if (DEBUG) { |
| Log.d(TAG, client + ": Sent descriptor change event, descriptor=" + descriptor); |
| } |
| } |
| } |
| |
| boolean setBaseDiscoveryRequest(MediaRouteDiscoveryRequest request) { |
| if (!ObjectsCompat.equals(mBaseDiscoveryRequest, request)) { |
| mBaseDiscoveryRequest = request; |
| return updateCompositeDiscoveryRequest(); |
| } |
| return false; |
| } |
| |
| boolean updateCompositeDiscoveryRequest() { |
| MediaRouteDiscoveryRequest composite = null; |
| MediaRouteSelector.Builder selectorBuilder = null; |
| boolean activeScan = false; |
| |
| if (mBaseDiscoveryRequest != null) { |
| activeScan = mBaseDiscoveryRequest.isActiveScan(); |
| selectorBuilder = new MediaRouteSelector.Builder( |
| mBaseDiscoveryRequest.getSelector()); |
| } |
| |
| final int count = mClients.size(); |
| for (int i = 0; i < count; i++) { |
| MediaRouteDiscoveryRequest request = mClients.get(i).mDiscoveryRequest; |
| if (request != null |
| && (!request.getSelector().isEmpty() || request.isActiveScan())) { |
| activeScan |= request.isActiveScan(); |
| if (composite == null) { |
| composite = request; |
| } else { |
| if (selectorBuilder == null) { |
| selectorBuilder = new MediaRouteSelector.Builder( |
| composite.getSelector()); |
| } |
| selectorBuilder.addSelector(request.getSelector()); |
| } |
| } |
| } |
| if (selectorBuilder != null) { |
| composite = 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; |
| } |
| |
| ClientRecord getClientForPackageName(String packageName) { |
| for (ClientRecord client : mClients) { |
| if (TextUtils.equals(client.mPackageName, packageName)) { |
| return client; |
| } |
| } |
| return null; |
| } |
| |
| //TODO: Reconsider "ClientRecord" structure |
| class ClientRecord implements DeathRecipient { |
| public final Messenger mMessenger; |
| public final int mVersion; |
| public final String mPackageName; |
| public MediaRouteDiscoveryRequest mDiscoveryRequest; |
| |
| final SparseArray<RouteController> mControllers = new SparseArray<>(); |
| |
| final DynamicGroupRouteController.OnDynamicRoutesChangedListener |
| mDynamicRoutesChangedListener = |
| new DynamicGroupRouteController.OnDynamicRoutesChangedListener() { |
| @Override |
| public void onRoutesChanged( |
| @NonNull DynamicGroupRouteController controller, |
| @NonNull Collection<DynamicRouteDescriptor> routes) { |
| sendDynamicRouteDescriptors(controller, 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(CLIENT_DATA_ROUTE_ID, initialMemberRouteId); |
| 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) { |
| if (!ObjectsCompat.equals(mDiscoveryRequest, request)) { |
| mDiscoveryRequest = request; |
| return updateCompositeDiscoveryRequest(); |
| } |
| return false; |
| } |
| |
| // Runs on a binder thread. |
| @Override |
| public void binderDied() { |
| mService.mPrivateHandler |
| .obtainMessage(PRIVATE_MSG_CLIENT_DIED, mMessenger).sendToTarget(); |
| } |
| |
| @Override |
| public String toString() { |
| return getClientId(mMessenger); |
| } |
| |
| void sendDynamicRouteDescriptors( |
| DynamicGroupRouteController controller, |
| 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(); |
| bundle.putParcelableArrayList(DATA_KEY_DYNAMIC_ROUTE_DESCRIPTORS, |
| dynamicRouteBundles); |
| sendReply(mMessenger, SERVICE_MSG_DYNAMIC_ROUTE_DESCRIPTORS_CHANGED, |
| 0, controllerId, bundle, null); |
| } |
| |
| void sendDynamicRouteControllerCreatedWithoutRequest( |
| MediaRouteProviderDescriptor descriptor, |
| DynamicGroupRouteController controller, |
| int controllerId, String routeId) { |
| |
| mControllers.put(controllerId, controller); |
| controller.setOnDynamicRoutesChangedListener( |
| ContextCompat.getMainExecutor(mService.getApplicationContext()), |
| mDynamicRoutesChangedListener); |
| |
| Bundle bundle = new Bundle(); |
| bundle.putBundle(CLIENT_DATA_PROVIDER_DESCRIPTOR, |
| createDescriptorBundleForClientVersion(descriptor, mVersion)); |
| |
| Bundle controllerInfo = new Bundle(); |
| controllerInfo.putString(CLIENT_DATA_ROUTE_ID, routeId); |
| controllerInfo.putString(DATA_KEY_GROUPABLE_SECION_TITLE, |
| controller.getGroupableSelectionTitle()); |
| controllerInfo.putString(DATA_KEY_TRANSFERABLE_SECTION_TITLE, |
| controller.getTransferableSectionTitle()); |
| controllerInfo.putInt(CLIENT_DATA_CONTROLLER_ID, controllerId); |
| bundle.putBundle(CLIENT_DATA_CONTROLLER_INFO, controllerInfo); |
| |
| sendReply(mMessenger, SERVICE_MSG_DYNAMIC_ROUTE_CREATED_WITHOUT_REQUEST, |
| 0, 0, 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); |
| } |
| } |
| } |
| |
| //TODO: We may need to change version number |
| @RequiresApi(api = Build.VERSION_CODES.R) |
| static class MediaRouteProviderServiceImplApi30 extends MediaRouteProviderServiceImplBase { |
| MediaRoute2ProviderServiceAdapter mMR2ProviderServiceAdapter; |
| // Maps the route ID to route controller. |
| final Map<String, RouteController> mRouteIdToControllerMap = new ArrayMap<>(); |
| |
| 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); |
| } |
| @Override |
| ClientRecord getClientForPackageName(String packageName) { |
| return (ClientRecord) super.getClientForPackageName(packageName); |
| } |
| |
| RouteController getControllerForRouteId(String routeId) { |
| return mRouteIdToControllerMap.get(routeId); |
| } |
| |
| class ClientRecord extends MediaRouteProviderServiceImplBase.ClientRecord { |
| ClientRecord(Messenger messenger, int version, String packageName) { |
| super(messenger, version, packageName); |
| } |
| |
| public boolean saveRouteController(String routeId, RouteController controller, |
| int controllerId) { |
| mRouteIdToControllerMap.put(routeId, controller); |
| |
| if (mControllers.indexOfKey(controllerId) < 0) { |
| mControllers.put(controllerId, controller); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean createRouteController(String routeId, String routeGroupId, |
| int controllerId) { |
| boolean result = super.createRouteController(routeId, routeGroupId, |
| controllerId); |
| // Don't add route controllers of member routes. |
| if (routeGroupId == null && result && mPackageName != null) { |
| mMR2ProviderServiceAdapter.addRouteController(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.addRouteController(mControllers.get(controllerId), |
| controllerId, mPackageName, initialMemberRouteId); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean releaseRouteController(int controllerId) { |
| mMR2ProviderServiceAdapter.removeRouteController(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; |
| } |
| } |
| } |
| return super.releaseRouteController(controllerId); |
| } |
| |
| @Override |
| void sendDynamicRouteDescriptors( |
| DynamicGroupRouteController controller, |
| Collection<DynamicRouteDescriptor> descriptors) { |
| super.sendDynamicRouteDescriptors(controller, descriptors); |
| if (mMR2ProviderServiceAdapter != null) { |
| mMR2ProviderServiceAdapter.setDynamicRouteDescriptor(controller, descriptors); |
| } |
| } |
| } |
| } |
| } |