| /* |
| * 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_CURRENT; |
| 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_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_1; |
| import static androidx.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| 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.util.Log; |
| import android.util.SparseArray; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController |
| .DynamicRouteDescriptor; |
| import androidx.mediarouter.media.MediaRouter.ControlRequestCallback; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Maintains a connection to a particular media route provider service. |
| */ |
| final class RegisteredMediaRouteProvider extends MediaRouteProvider |
| implements ServiceConnection { |
| static final String TAG = "MediaRouteProviderProxy"; // max. 23 chars |
| static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| private final ComponentName mComponentName; |
| final PrivateHandler mPrivateHandler; |
| private final ArrayList<ControllerConnection> mControllerConnections = |
| new ArrayList<ControllerConnection>(); |
| |
| private boolean mStarted; |
| private boolean mBound; |
| private Connection mActiveConnection; |
| private boolean mConnectionReady; |
| |
| public RegisteredMediaRouteProvider(Context context, ComponentName componentName) { |
| super(context, new ProviderMetadata(componentName)); |
| |
| mComponentName = componentName; |
| mPrivateHandler = new PrivateHandler(); |
| } |
| |
| @Override |
| public RouteController onCreateRouteController(@NonNull String routeId) { |
| if (routeId == null) { |
| throw new IllegalArgumentException("routeId cannot be null"); |
| } |
| return createRouteController(routeId, null); |
| } |
| |
| @Override |
| public RouteController onCreateRouteController( |
| @NonNull String routeId, @NonNull String routeGroupId) { |
| if (routeId == null) { |
| throw new IllegalArgumentException("routeId cannot be null"); |
| } |
| if (routeGroupId == null) { |
| throw new IllegalArgumentException("routeGroupId cannot be null"); |
| } |
| return createRouteController(routeId, routeGroupId); |
| } |
| |
| @Override |
| public DynamicGroupRouteController onCreateDynamicGroupRouteController( |
| @NonNull String initialMemberRouteId) { |
| if (initialMemberRouteId == null) { |
| throw new IllegalArgumentException("initialMemberRouteId cannot be null."); |
| } |
| return createDynamicGroupRouteController(initialMemberRouteId); |
| } |
| |
| @Override |
| public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { |
| if (mConnectionReady) { |
| mActiveConnection.setDiscoveryRequest(request); |
| } |
| updateBinding(); |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Connected"); |
| } |
| |
| if (mBound) { |
| disconnect(); |
| |
| Messenger messenger = (service != null ? new Messenger(service) : null); |
| if (isValidRemoteMessenger(messenger)) { |
| Connection connection = new Connection(messenger); |
| if (connection.register()) { |
| mActiveConnection = connection; |
| } else { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Registration failed"); |
| } |
| } |
| } else { |
| Log.e(TAG, this + ": Service returned invalid messenger binder"); |
| } |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Service disconnected"); |
| } |
| disconnect(); |
| } |
| |
| @Override |
| public String toString() { |
| return "Service connection " + mComponentName.flattenToShortString(); |
| } |
| |
| public boolean hasComponentName(String packageName, String className) { |
| return mComponentName.getPackageName().equals(packageName) |
| && mComponentName.getClassName().equals(className); |
| } |
| |
| public void start() { |
| if (!mStarted) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Starting"); |
| } |
| |
| mStarted = true; |
| updateBinding(); |
| } |
| } |
| |
| public void stop() { |
| if (mStarted) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Stopping"); |
| } |
| |
| mStarted = false; |
| updateBinding(); |
| } |
| } |
| |
| public void rebindIfDisconnected() { |
| if (mActiveConnection == null && shouldBind()) { |
| unbind(); |
| bind(); |
| } |
| } |
| |
| private void updateBinding() { |
| if (shouldBind()) { |
| bind(); |
| } else { |
| unbind(); |
| } |
| } |
| |
| private boolean shouldBind() { |
| if (mStarted) { |
| // Bind whenever there is a discovery request. |
| if (getDiscoveryRequest() != null) { |
| return true; |
| } |
| |
| // Bind whenever the application has an active route controller. |
| // This means that one of this provider's routes is selected. |
| if (!mControllerConnections.isEmpty()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void bind() { |
| if (!mBound) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Binding"); |
| } |
| |
| Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE); |
| service.setComponent(mComponentName); |
| try { |
| mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE); |
| if (!mBound && DEBUG) { |
| Log.d(TAG, this + ": Bind failed"); |
| } |
| } catch (SecurityException ex) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Bind failed", ex); |
| } |
| } |
| } |
| } |
| |
| private void unbind() { |
| if (mBound) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Unbinding"); |
| } |
| |
| mBound = false; |
| disconnect(); |
| try { |
| getContext().unbindService(this); |
| } catch (IllegalArgumentException ex) { |
| Log.e(TAG, this + ": unbindService failed", ex); |
| } |
| } |
| } |
| |
| private RouteController createRouteController(String routeId, String routeGroupId) { |
| MediaRouteProviderDescriptor descriptor = getDescriptor(); |
| if (descriptor != null) { |
| List<MediaRouteDescriptor> routes = descriptor.getRoutes(); |
| final int count = routes.size(); |
| for (int i = 0; i < count; i++) { |
| final MediaRouteDescriptor route = routes.get(i); |
| if (route.getId().equals(routeId)) { |
| RouteController controller = |
| new RegisteredRouteController(routeId, routeGroupId); |
| mControllerConnections.add((ControllerConnection) controller); |
| if (mConnectionReady) { |
| ((ControllerConnection) controller).attachConnection(mActiveConnection); |
| } |
| updateBinding(); |
| return controller; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private DynamicGroupRouteController createDynamicGroupRouteController( |
| String initialMemberRouteId) { |
| MediaRouteProviderDescriptor descriptor = getDescriptor(); |
| if (descriptor != null) { |
| List<MediaRouteDescriptor> routes = descriptor.getRoutes(); |
| final int count = routes.size(); |
| for (int i = 0; i < count; i++) { |
| final MediaRouteDescriptor route = routes.get(i); |
| if (route.getId().equals(initialMemberRouteId)) { |
| DynamicGroupRouteController controller = |
| new RegisteredDynamicController(initialMemberRouteId); |
| mControllerConnections.add((ControllerConnection) controller); |
| if (mConnectionReady) { |
| ((ControllerConnection) controller).attachConnection(mActiveConnection); |
| } |
| updateBinding(); |
| return controller; |
| } |
| } |
| } |
| return null; |
| } |
| |
| void onConnectionReady(Connection connection) { |
| if (mActiveConnection == connection) { |
| mConnectionReady = true; |
| attachControllersToConnection(); |
| |
| MediaRouteDiscoveryRequest request = getDiscoveryRequest(); |
| if (request != null) { |
| mActiveConnection.setDiscoveryRequest(request); |
| } |
| } |
| } |
| |
| void onConnectionDied(Connection connection) { |
| if (mActiveConnection == connection) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Service connection died"); |
| } |
| disconnect(); |
| } |
| } |
| |
| void onConnectionError(Connection connection, String error) { |
| if (mActiveConnection == connection) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Service connection error - " + error); |
| } |
| unbind(); |
| } |
| } |
| |
| void onConnectionDescriptorChanged(Connection connection, |
| MediaRouteProviderDescriptor descriptor) { |
| if (mActiveConnection == connection) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": Descriptor changed, descriptor=" + descriptor); |
| } |
| setDescriptor(descriptor); |
| } |
| } |
| |
| void onDynamicRouteDescriptorChanged(Connection connection, int controllerId, |
| List<DynamicRouteDescriptor> descriptors) { |
| if (mActiveConnection == connection) { |
| if (DEBUG) { |
| Log.d(TAG, this + ": DynamicRouteDescriptors changed, descriptors=" + descriptors); |
| } |
| ControllerConnection controller = findControllerById(controllerId); |
| if (controller instanceof RegisteredDynamicController) { |
| ((RegisteredDynamicController) controller).onDynamicRoutesChanged(descriptors); |
| } |
| } |
| } |
| |
| private ControllerConnection findControllerById(int id) { |
| for (ControllerConnection controller: mControllerConnections) { |
| if (controller.getControllerId() == id) { |
| return controller; |
| } |
| } |
| return null; |
| } |
| |
| private void disconnect() { |
| if (mActiveConnection != null) { |
| setDescriptor(null); |
| mConnectionReady = false; |
| detachControllersFromConnection(); |
| mActiveConnection.dispose(); |
| mActiveConnection = null; |
| } |
| } |
| |
| void onControllerReleased(ControllerConnection controllerConnection) { |
| mControllerConnections.remove(controllerConnection); |
| controllerConnection.detachConnection(); |
| updateBinding(); |
| } |
| |
| private void attachControllersToConnection() { |
| int count = mControllerConnections.size(); |
| for (int i = 0; i < count; i++) { |
| mControllerConnections.get(i).attachConnection(mActiveConnection); |
| } |
| } |
| |
| private void detachControllersFromConnection() { |
| int count = mControllerConnections.size(); |
| for (int i = 0; i < count; i++) { |
| mControllerConnections.get(i).detachConnection(); |
| } |
| } |
| |
| interface ControllerConnection { |
| int getControllerId(); |
| void attachConnection(Connection connection); |
| void detachConnection(); |
| } |
| |
| private final class RegisteredDynamicController extends DynamicGroupRouteController |
| implements ControllerConnection { |
| private final String mInitialMemberRouteId; |
| String mGroupableSectionTitle; |
| String mTransferableSectionTitle; |
| |
| private boolean mSelected; |
| private int mPendingSetVolume = -1; |
| private int mPendingUpdateVolumeDelta; |
| |
| private Connection mConnection; |
| private int mControllerId = -1; |
| |
| RegisteredDynamicController(String initialMemberRouteId) { |
| mInitialMemberRouteId = initialMemberRouteId; |
| } |
| |
| ///////////////////////////////////// |
| // Implements Controller |
| @Override |
| public int getControllerId() { |
| return mControllerId; |
| } |
| |
| @Override |
| public void attachConnection(Connection connection) { |
| ControlRequestCallback callback = new ControlRequestCallback() { |
| @Override |
| public void onResult(Bundle data) { |
| mGroupableSectionTitle = data.getString(DATA_KEY_GROUPABLE_SECION_TITLE); |
| mTransferableSectionTitle = data.getString(DATA_KEY_TRANSFERABLE_SECTION_TITLE); |
| } |
| @Override |
| public void onError(String error, Bundle data) { |
| Log.d(TAG, "Error: " + error + ", data: " + data); |
| } |
| }; |
| mConnection = connection; |
| mControllerId = connection.createDynamicGroupRouteController( |
| mInitialMemberRouteId, callback); |
| if (mSelected) { |
| connection.selectRoute(mControllerId); |
| if (mPendingSetVolume >= 0) { |
| connection.setVolume(mControllerId, mPendingSetVolume); |
| mPendingSetVolume = -1; |
| } |
| if (mPendingUpdateVolumeDelta != 0) { |
| connection.updateVolume(mControllerId, mPendingUpdateVolumeDelta); |
| mPendingUpdateVolumeDelta = 0; |
| } |
| } |
| } |
| |
| @Override |
| public void detachConnection() { |
| if (mConnection != null) { |
| mConnection.releaseRouteController(mControllerId); |
| mConnection = null; |
| mControllerId = 0; |
| } |
| } |
| |
| ///////////////////////////////////// |
| // Overrides RouteController |
| @Override |
| public void onRelease() { |
| onControllerReleased(this); |
| } |
| |
| @Override |
| public void onSelect() { |
| mSelected = true; |
| if (mConnection != null) { |
| mConnection.selectRoute(mControllerId); |
| } |
| } |
| |
| @Override |
| public void onUnselect() { |
| onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN); |
| } |
| |
| @Override |
| public void onUnselect(int reason) { |
| mSelected = false; |
| if (mConnection != null) { |
| mConnection.unselectRoute(mControllerId, reason); |
| } |
| } |
| |
| @Override |
| public void onSetVolume(int volume) { |
| if (mConnection != null) { |
| mConnection.setVolume(mControllerId, volume); |
| } else { |
| mPendingSetVolume = volume; |
| mPendingUpdateVolumeDelta = 0; |
| } |
| } |
| |
| @Override |
| public void onUpdateVolume(int delta) { |
| if (mConnection != null) { |
| mConnection.updateVolume(mControllerId, delta); |
| } else { |
| mPendingUpdateVolumeDelta += delta; |
| } |
| } |
| |
| @Override |
| public boolean onControlRequest(Intent intent, ControlRequestCallback callback) { |
| if (mConnection != null) { |
| return mConnection.sendControlRequest(mControllerId, intent, callback); |
| } |
| return false; |
| } |
| |
| ///////////////////////////////////////// |
| // Overrides DynamicGroupRouteController |
| |
| @Override |
| public String getGroupableSelectionTitle() { |
| return mGroupableSectionTitle; |
| } |
| |
| @Override |
| public String getTransferableSectionTitle() { |
| return mTransferableSectionTitle; |
| } |
| |
| @Override |
| public void onUpdateMemberRoutes(@Nullable List<String> routeIds) { |
| if (mConnection != null) { |
| mConnection.updateMemberRoutes(mControllerId, routeIds); |
| } |
| } |
| |
| @Override |
| public void onAddMemberRoute(@NonNull String routeId) { |
| if (mConnection != null) { |
| mConnection.addMemberRoute(mControllerId, routeId); |
| } |
| } |
| |
| @Override |
| public void onRemoveMemberRoute(@NonNull String routeId) { |
| if (mConnection != null) { |
| mConnection.removeMemberRoute(mControllerId, routeId); |
| } |
| } |
| |
| //////////////////////////////////// |
| // Other methods |
| void onDynamicRoutesChanged( |
| final List<DynamicRouteDescriptor> routes) { |
| notifyDynamicRoutesChanged(routes); |
| } |
| } |
| |
| private final class RegisteredRouteController extends RouteController |
| implements ControllerConnection { |
| private final String mRouteId; |
| private final String mRouteGroupId; |
| |
| private boolean mSelected; |
| private int mPendingSetVolume = -1; |
| private int mPendingUpdateVolumeDelta; |
| |
| private Connection mConnection; |
| private int mControllerId; |
| |
| RegisteredRouteController(String routeId, String routeGroupId) { |
| mRouteId = routeId; |
| mRouteGroupId = routeGroupId; |
| } |
| |
| @Override |
| public int getControllerId() { |
| return mControllerId; |
| } |
| |
| @Override |
| public void attachConnection(Connection connection) { |
| mConnection = connection; |
| mControllerId = connection.createRouteController(mRouteId, mRouteGroupId); |
| if (mSelected) { |
| connection.selectRoute(mControllerId); |
| if (mPendingSetVolume >= 0) { |
| connection.setVolume(mControllerId, mPendingSetVolume); |
| mPendingSetVolume = -1; |
| } |
| if (mPendingUpdateVolumeDelta != 0) { |
| connection.updateVolume(mControllerId, mPendingUpdateVolumeDelta); |
| mPendingUpdateVolumeDelta = 0; |
| } |
| } |
| } |
| |
| @Override |
| public void detachConnection() { |
| if (mConnection != null) { |
| mConnection.releaseRouteController(mControllerId); |
| mConnection = null; |
| mControllerId = 0; |
| } |
| } |
| |
| @Override |
| public void onRelease() { |
| onControllerReleased(this); |
| } |
| |
| @Override |
| public void onSelect() { |
| mSelected = true; |
| if (mConnection != null) { |
| mConnection.selectRoute(mControllerId); |
| } |
| } |
| |
| @Override |
| public void onUnselect() { |
| onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN); |
| } |
| |
| @Override |
| public void onUnselect(int reason) { |
| mSelected = false; |
| if (mConnection != null) { |
| mConnection.unselectRoute(mControllerId, reason); |
| } |
| } |
| |
| @Override |
| public void onSetVolume(int volume) { |
| if (mConnection != null) { |
| mConnection.setVolume(mControllerId, volume); |
| } else { |
| mPendingSetVolume = volume; |
| mPendingUpdateVolumeDelta = 0; |
| } |
| } |
| |
| @Override |
| public void onUpdateVolume(int delta) { |
| if (mConnection != null) { |
| mConnection.updateVolume(mControllerId, delta); |
| } else { |
| mPendingUpdateVolumeDelta += delta; |
| } |
| } |
| |
| @Override |
| public boolean onControlRequest(Intent intent, ControlRequestCallback callback) { |
| if (mConnection != null) { |
| return mConnection.sendControlRequest(mControllerId, intent, callback); |
| } |
| return false; |
| } |
| } |
| |
| private final class Connection implements DeathRecipient { |
| private final Messenger mServiceMessenger; |
| private final ReceiveHandler mReceiveHandler; |
| private final Messenger mReceiveMessenger; |
| |
| private int mNextRequestId = 1; |
| private int mNextControllerId = 1; |
| private int mServiceVersion; // non-zero when registration complete |
| |
| private int mPendingRegisterRequestId; |
| private final SparseArray<ControlRequestCallback> mPendingCallbacks = |
| new SparseArray<ControlRequestCallback>(); |
| |
| public Connection(Messenger serviceMessenger) { |
| mServiceMessenger = serviceMessenger; |
| mReceiveHandler = new ReceiveHandler(this); |
| mReceiveMessenger = new Messenger(mReceiveHandler); |
| } |
| |
| public boolean register() { |
| mPendingRegisterRequestId = mNextRequestId++; |
| if (!sendRequest(CLIENT_MSG_REGISTER, |
| mPendingRegisterRequestId, |
| CLIENT_VERSION_CURRENT, null, null)) { |
| return false; |
| } |
| |
| try { |
| mServiceMessenger.getBinder().linkToDeath(this, 0); |
| return true; |
| } catch (RemoteException ex) { |
| binderDied(); |
| } |
| return false; |
| } |
| |
| public void dispose() { |
| sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null); |
| mReceiveHandler.dispose(); |
| mServiceMessenger.getBinder().unlinkToDeath(this, 0); |
| |
| mPrivateHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| failPendingCallbacks(); |
| } |
| }); |
| } |
| |
| void failPendingCallbacks() { |
| int count = mPendingCallbacks.size(); |
| for (int i = 0; i < count; i++) { |
| mPendingCallbacks.valueAt(i).onError(null, null); |
| } |
| mPendingCallbacks.clear(); |
| } |
| |
| public boolean onGenericFailure(int requestId) { |
| if (requestId == mPendingRegisterRequestId) { |
| mPendingRegisterRequestId = 0; |
| onConnectionError(this, "Registration failed"); |
| } |
| ControlRequestCallback callback = mPendingCallbacks.get(requestId); |
| if (callback != null) { |
| mPendingCallbacks.remove(requestId); |
| callback.onError(null, null); |
| } |
| return true; |
| } |
| |
| public boolean onGenericSuccess(int requestId) { |
| return true; |
| } |
| |
| public boolean onRegistered(int requestId, int serviceVersion, |
| Bundle descriptorBundle) { |
| if (mServiceVersion == 0 |
| && requestId == mPendingRegisterRequestId |
| && serviceVersion >= SERVICE_VERSION_1) { |
| mPendingRegisterRequestId = 0; |
| mServiceVersion = serviceVersion; |
| onConnectionDescriptorChanged(this, |
| MediaRouteProviderDescriptor.fromBundle(descriptorBundle)); |
| onConnectionReady(this); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean onDescriptorChanged(Bundle descriptorBundle) { |
| if (mServiceVersion != 0) { |
| onConnectionDescriptorChanged(this, |
| MediaRouteProviderDescriptor.fromBundle(descriptorBundle)); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean onDynamicRouteDescriptorsChanged( |
| int controllerId, Bundle descriptorsBundle) { |
| if (mServiceVersion != 0) { |
| //descriptorsBundle.setClassLoader(ParcelImpl.class.getClassLoader()); |
| ArrayList<Bundle> bundles = descriptorsBundle.getParcelableArrayList( |
| DATA_KEY_DYNAMIC_ROUTE_DESCRIPTORS); |
| List<DynamicRouteDescriptor> descriptors = new ArrayList<DynamicRouteDescriptor>(); |
| for (Bundle bundle: bundles) { |
| descriptors.add(DynamicRouteDescriptor.fromBundle(bundle)); |
| } |
| onDynamicRouteDescriptorChanged(this, controllerId, descriptors); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean onControlRequestSucceeded(int requestId, Bundle data) { |
| ControlRequestCallback callback = mPendingCallbacks.get(requestId); |
| if (callback != null) { |
| mPendingCallbacks.remove(requestId); |
| callback.onResult(data); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean onControlRequestFailed(int requestId, String error, Bundle data) { |
| ControlRequestCallback callback = mPendingCallbacks.get(requestId); |
| if (callback != null) { |
| mPendingCallbacks.remove(requestId); |
| callback.onError(error, data); |
| return true; |
| } |
| return false; |
| } |
| |
| public void onDynamicGroupRouteControllerCreated(int requestId, Bundle data) { |
| ControlRequestCallback callback = mPendingCallbacks.get(requestId); |
| if (data != null && data.containsKey(CLIENT_DATA_ROUTE_ID)) { |
| mPendingCallbacks.remove(requestId); |
| callback.onResult(data); |
| } else { |
| callback.onError("DynamicGroupRouteController is created without valid route id.", |
| data); |
| } |
| } |
| |
| @Override |
| public void binderDied() { |
| mPrivateHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| onConnectionDied(Connection.this); |
| } |
| }); |
| } |
| |
| public int createRouteController(String routeId, String routeGroupId) { |
| int controllerId = mNextControllerId++; |
| Bundle data = new Bundle(); |
| data.putString(CLIENT_DATA_ROUTE_ID, routeId); |
| data.putString(CLIENT_DATA_ROUTE_LIBRARY_GROUP, routeGroupId); |
| sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER, |
| mNextRequestId++, controllerId, null, data); |
| return controllerId; |
| } |
| |
| public int createDynamicGroupRouteController( |
| String initialMemberRouteId, ControlRequestCallback callback) { |
| int controllerId = mNextControllerId++; |
| int requestId = mNextRequestId++; |
| Bundle data = new Bundle(); |
| data.putString(CLIENT_DATA_MEMBER_ROUTE_ID, initialMemberRouteId); |
| sendRequest(CLIENT_MSG_CREATE_DYNAMIC_GROUP_ROUTE_CONTROLLER, |
| requestId, controllerId, null, data); |
| mPendingCallbacks.put(requestId, callback); |
| return controllerId; |
| } |
| |
| public void releaseRouteController(int controllerId) { |
| sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER, |
| mNextRequestId++, controllerId, null, null); |
| } |
| |
| public void selectRoute(int controllerId) { |
| sendRequest(CLIENT_MSG_SELECT_ROUTE, |
| mNextRequestId++, controllerId, null, null); |
| } |
| |
| public void unselectRoute(int controllerId, int reason) { |
| Bundle extras = new Bundle(); |
| extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason); |
| sendRequest(CLIENT_MSG_UNSELECT_ROUTE, |
| mNextRequestId++, controllerId, null, extras); |
| } |
| |
| public void setVolume(int controllerId, int volume) { |
| Bundle data = new Bundle(); |
| data.putInt(CLIENT_DATA_VOLUME, volume); |
| sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME, |
| mNextRequestId++, controllerId, null, data); |
| } |
| |
| public void updateVolume(int controllerId, int delta) { |
| Bundle data = new Bundle(); |
| data.putInt(CLIENT_DATA_VOLUME, delta); |
| sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME, |
| mNextRequestId++, controllerId, null, data); |
| } |
| |
| public boolean sendControlRequest(int controllerId, Intent intent, |
| ControlRequestCallback callback) { |
| int requestId = mNextRequestId++; |
| if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST, |
| requestId, controllerId, intent, null)) { |
| if (callback != null) { |
| mPendingCallbacks.put(requestId, callback); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| |
| public void updateMemberRoutes(int controllerId, List<String> memberRouteIds) { |
| Bundle data = new Bundle(); |
| data.putStringArrayList(CLIENT_DATA_MEMBER_ROUTE_IDS, new ArrayList<>(memberRouteIds)); |
| sendRequest(CLIENT_MSG_UPDATE_MEMBER_ROUTES, |
| mNextRequestId++, controllerId, null, data); |
| } |
| |
| public void addMemberRoute(int controllerId, String memberRouteId) { |
| Bundle data = new Bundle(); |
| data.putString(CLIENT_DATA_MEMBER_ROUTE_ID, memberRouteId); |
| sendRequest(CLIENT_MSG_ADD_MEMBER_ROUTE, mNextRequestId++, controllerId, null, data); |
| } |
| |
| public void removeMemberRoute(int controllerId, String memberRouteId) { |
| Bundle data = new Bundle(); |
| data.putString(CLIENT_DATA_MEMBER_ROUTE_ID, memberRouteId); |
| sendRequest(CLIENT_MSG_REMOVE_MEMBER_ROUTE, mNextRequestId++, controllerId, null, data); |
| } |
| |
| public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) { |
| sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST, |
| mNextRequestId++, 0, request != null ? request.asBundle() : null, null); |
| } |
| |
| private boolean sendRequest(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); |
| msg.replyTo = mReceiveMessenger; |
| try { |
| mServiceMessenger.send(msg); |
| return true; |
| } catch (DeadObjectException ex) { |
| // The service died. |
| } catch (RemoteException ex) { |
| if (what != CLIENT_MSG_UNREGISTER) { |
| Log.e(TAG, "Could not send message to service.", ex); |
| } |
| } |
| return false; |
| } |
| } |
| |
| private static final class PrivateHandler extends Handler { |
| PrivateHandler() { |
| } |
| } |
| |
| /** |
| * Handler that receives messages from the server. |
| * <p> |
| * This inner class is static and only retains a weak reference to the connection |
| * to prevent the client from being leaked in case the service is holding an |
| * active reference to the client's messenger. |
| * </p><p> |
| * This handler should not be used to handle any messages other than those |
| * that come from the service. |
| * </p> |
| */ |
| private static final class ReceiveHandler extends Handler { |
| private final WeakReference<Connection> mConnectionRef; |
| |
| public ReceiveHandler(Connection connection) { |
| mConnectionRef = new WeakReference<Connection>(connection); |
| } |
| |
| public void dispose() { |
| mConnectionRef.clear(); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| Connection connection = mConnectionRef.get(); |
| if (connection != null) { |
| 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(); |
| if (!processMessage(connection, what, requestId, arg, obj, data)) { |
| if (DEBUG) { |
| Log.d(TAG, "Unhandled message from server: " + msg); |
| } |
| } |
| } |
| } |
| |
| private boolean processMessage(Connection connection, |
| int what, int requestId, int arg, Object obj, Bundle data) { |
| switch (what) { |
| case SERVICE_MSG_GENERIC_FAILURE: |
| connection.onGenericFailure(requestId); |
| return true; |
| |
| case SERVICE_MSG_GENERIC_SUCCESS: |
| connection.onGenericSuccess(requestId); |
| return true; |
| |
| case SERVICE_MSG_REGISTERED: |
| if (obj == null || obj instanceof Bundle) { |
| return connection.onRegistered(requestId, arg, (Bundle) obj); |
| } |
| break; |
| |
| case SERVICE_MSG_DESCRIPTOR_CHANGED: |
| if (obj == null || obj instanceof Bundle) { |
| return connection.onDescriptorChanged((Bundle) obj); |
| } |
| break; |
| |
| case SERVICE_MSG_DYNAMIC_ROUTE_DESCRIPTORS_CHANGED: |
| if (obj == null || obj instanceof Bundle) { |
| return connection.onDynamicRouteDescriptorsChanged( |
| arg /* controllerId */, (Bundle) obj); |
| } |
| break; |
| |
| case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED: |
| if (obj == null || obj instanceof Bundle) { |
| return connection.onControlRequestSucceeded( |
| requestId, (Bundle) obj); |
| } |
| break; |
| |
| case SERVICE_MSG_CONTROL_REQUEST_FAILED: |
| if (obj == null || obj instanceof Bundle) { |
| String error = (data == null ? null : |
| data.getString(SERVICE_DATA_ERROR)); |
| return connection.onControlRequestFailed( |
| requestId, error, (Bundle) obj); |
| } |
| break; |
| |
| case SERVICE_MSG_DYNAMIC_ROUTE_CREATED: |
| if (obj instanceof Bundle) { |
| connection.onDynamicGroupRouteControllerCreated( |
| requestId, (Bundle) obj); |
| } else { |
| Log.w(TAG, "No further information on the dynamic group controller"); |
| } |
| break; |
| } |
| return false; |
| } |
| } |
| } |