RegisteredMediaRouteProvider.java revision 2f829125aef3796ca674d0ca5fccf9bd37b8417b
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.media; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.os.Bundle; 24import android.os.DeadObjectException; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.RemoteException; 28import android.os.IBinder.DeathRecipient; 29import android.os.Message; 30import android.os.Messenger; 31import android.support.annotation.NonNull; 32import android.support.v7.media.MediaRouter.ControlRequestCallback; 33import android.util.Log; 34import android.util.SparseArray; 35 36import java.lang.ref.WeakReference; 37import java.util.ArrayList; 38import java.util.List; 39 40import static android.support.v7.media.MediaRouteProviderProtocol.*; 41 42/** 43 * Maintains a connection to a particular media route provider service. 44 */ 45final class RegisteredMediaRouteProvider extends MediaRouteProvider 46 implements ServiceConnection { 47 private static final String TAG = "MediaRouteProviderProxy"; // max. 23 chars 48 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 49 50 private final ComponentName mComponentName; 51 private final PrivateHandler mPrivateHandler; 52 private final ArrayList<Controller> mControllers = new ArrayList<Controller>(); 53 54 private boolean mStarted; 55 private boolean mBound; 56 private Connection mActiveConnection; 57 private boolean mConnectionReady; 58 59 public RegisteredMediaRouteProvider(Context context, ComponentName componentName) { 60 super(context, new ProviderMetadata(componentName)); 61 62 mComponentName = componentName; 63 mPrivateHandler = new PrivateHandler(); 64 } 65 66 @Override 67 public RouteController onCreateRouteController(@NonNull String routeId) { 68 if (routeId == null) { 69 throw new IllegalArgumentException("routeId cannot be null"); 70 } 71 return createRouteController(routeId, null); 72 } 73 74 @Override 75 public RouteController onCreateRouteController( 76 @NonNull String routeId, @NonNull String routeGroupId) { 77 if (routeId == null) { 78 throw new IllegalArgumentException("routeId cannot be null"); 79 } 80 if (routeGroupId == null) { 81 throw new IllegalArgumentException("routeGroupId cannot be null"); 82 } 83 return createRouteController(routeId, routeGroupId); 84 } 85 86 @Override 87 public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { 88 if (mConnectionReady) { 89 mActiveConnection.setDiscoveryRequest(request); 90 } 91 updateBinding(); 92 } 93 94 public boolean hasComponentName(String packageName, String className) { 95 return mComponentName.getPackageName().equals(packageName) 96 && mComponentName.getClassName().equals(className); 97 } 98 99 public void start() { 100 if (!mStarted) { 101 if (DEBUG) { 102 Log.d(TAG, this + ": Starting"); 103 } 104 105 mStarted = true; 106 updateBinding(); 107 } 108 } 109 110 public void stop() { 111 if (mStarted) { 112 if (DEBUG) { 113 Log.d(TAG, this + ": Stopping"); 114 } 115 116 mStarted = false; 117 updateBinding(); 118 } 119 } 120 121 public void rebindIfDisconnected() { 122 if (mActiveConnection == null && shouldBind()) { 123 unbind(); 124 bind(); 125 } 126 } 127 128 private void updateBinding() { 129 if (shouldBind()) { 130 bind(); 131 } else { 132 unbind(); 133 } 134 } 135 136 private boolean shouldBind() { 137 if (mStarted) { 138 // Bind whenever there is a discovery request. 139 if (getDiscoveryRequest() != null) { 140 return true; 141 } 142 143 // Bind whenever the application has an active route controller. 144 // This means that one of this provider's routes is selected. 145 if (!mControllers.isEmpty()) { 146 return true; 147 } 148 } 149 return false; 150 } 151 152 private void bind() { 153 if (!mBound) { 154 if (DEBUG) { 155 Log.d(TAG, this + ": Binding"); 156 } 157 158 Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE); 159 service.setComponent(mComponentName); 160 try { 161 mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE); 162 if (!mBound && DEBUG) { 163 Log.d(TAG, this + ": Bind failed"); 164 } 165 } catch (SecurityException ex) { 166 if (DEBUG) { 167 Log.d(TAG, this + ": Bind failed", ex); 168 } 169 } 170 } 171 } 172 173 private void unbind() { 174 if (mBound) { 175 if (DEBUG) { 176 Log.d(TAG, this + ": Unbinding"); 177 } 178 179 mBound = false; 180 disconnect(); 181 getContext().unbindService(this); 182 } 183 } 184 185 @Override 186 public void onServiceConnected(ComponentName name, IBinder service) { 187 if (DEBUG) { 188 Log.d(TAG, this + ": Connected"); 189 } 190 191 if (mBound) { 192 disconnect(); 193 194 Messenger messenger = (service != null ? new Messenger(service) : null); 195 if (isValidRemoteMessenger(messenger)) { 196 Connection connection = new Connection(messenger); 197 if (connection.register()) { 198 mActiveConnection = connection; 199 } else { 200 if (DEBUG) { 201 Log.d(TAG, this + ": Registration failed"); 202 } 203 } 204 } else { 205 Log.e(TAG, this + ": Service returned invalid messenger binder"); 206 } 207 } 208 } 209 210 @Override 211 public void onServiceDisconnected(ComponentName name) { 212 if (DEBUG) { 213 Log.d(TAG, this + ": Service disconnected"); 214 } 215 disconnect(); 216 } 217 218 private RouteController createRouteController(String routeId, String routeGroupId) { 219 MediaRouteProviderDescriptor descriptor = getDescriptor(); 220 if (descriptor != null) { 221 List<MediaRouteDescriptor> routes = descriptor.getRoutes(); 222 final int count = routes.size(); 223 for (int i = 0; i < count; i++) { 224 final MediaRouteDescriptor route = routes.get(i); 225 if (route.getId().equals(routeId)) { 226 Controller controller = new Controller(routeId, routeGroupId); 227 mControllers.add(controller); 228 if (mConnectionReady) { 229 controller.attachConnection(mActiveConnection); 230 } 231 updateBinding(); 232 return controller; 233 } 234 } 235 } 236 return null; 237 } 238 239 private void onConnectionReady(Connection connection) { 240 if (mActiveConnection == connection) { 241 mConnectionReady = true; 242 attachControllersToConnection(); 243 244 MediaRouteDiscoveryRequest request = getDiscoveryRequest(); 245 if (request != null) { 246 mActiveConnection.setDiscoveryRequest(request); 247 } 248 } 249 } 250 251 private void onConnectionDied(Connection connection) { 252 if (mActiveConnection == connection) { 253 if (DEBUG) { 254 Log.d(TAG, this + ": Service connection died"); 255 } 256 disconnect(); 257 } 258 } 259 260 private void onConnectionError(Connection connection, String error) { 261 if (mActiveConnection == connection) { 262 if (DEBUG) { 263 Log.d(TAG, this + ": Service connection error - " + error); 264 } 265 unbind(); 266 } 267 } 268 269 private void onConnectionDescriptorChanged(Connection connection, 270 MediaRouteProviderDescriptor descriptor) { 271 if (mActiveConnection == connection) { 272 if (DEBUG) { 273 Log.d(TAG, this + ": Descriptor changed, descriptor=" + descriptor); 274 } 275 setDescriptor(descriptor); 276 } 277 } 278 279 private void disconnect() { 280 if (mActiveConnection != null) { 281 setDescriptor(null); 282 mConnectionReady = false; 283 detachControllersFromConnection(); 284 mActiveConnection.dispose(); 285 mActiveConnection = null; 286 } 287 } 288 289 private void onControllerReleased(Controller controller) { 290 mControllers.remove(controller); 291 controller.detachConnection(); 292 updateBinding(); 293 } 294 295 private void attachControllersToConnection() { 296 int count = mControllers.size(); 297 for (int i = 0; i < count; i++) { 298 mControllers.get(i).attachConnection(mActiveConnection); 299 } 300 } 301 302 private void detachControllersFromConnection() { 303 int count = mControllers.size(); 304 for (int i = 0; i < count; i++) { 305 mControllers.get(i).detachConnection(); 306 } 307 } 308 309 @Override 310 public String toString() { 311 return "Service connection " + mComponentName.flattenToShortString(); 312 } 313 314 private final class Controller extends RouteController { 315 private final String mRouteId; 316 private final String mRouteGroupId; 317 318 private boolean mSelected; 319 private int mPendingSetVolume = -1; 320 private int mPendingUpdateVolumeDelta; 321 322 private Connection mConnection; 323 private int mControllerId; 324 325 public Controller(String routeId, String routeGroupId) { 326 mRouteId = routeId; 327 mRouteGroupId = routeGroupId; 328 } 329 330 public void attachConnection(Connection connection) { 331 mConnection = connection; 332 mControllerId = connection.createRouteController(mRouteId, mRouteGroupId); 333 if (mSelected) { 334 connection.selectRoute(mControllerId); 335 if (mPendingSetVolume >= 0) { 336 connection.setVolume(mControllerId, mPendingSetVolume); 337 mPendingSetVolume = -1; 338 } 339 if (mPendingUpdateVolumeDelta != 0) { 340 connection.updateVolume(mControllerId, mPendingUpdateVolumeDelta); 341 mPendingUpdateVolumeDelta = 0; 342 } 343 } 344 } 345 346 public void detachConnection() { 347 if (mConnection != null) { 348 mConnection.releaseRouteController(mControllerId); 349 mConnection = null; 350 mControllerId = 0; 351 } 352 } 353 354 @Override 355 public void onRelease() { 356 onControllerReleased(this); 357 } 358 359 @Override 360 public void onSelect() { 361 mSelected = true; 362 if (mConnection != null) { 363 mConnection.selectRoute(mControllerId); 364 } 365 } 366 367 @Override 368 public void onUnselect() { 369 onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN); 370 } 371 372 @Override 373 public void onUnselect(int reason) { 374 mSelected = false; 375 if (mConnection != null) { 376 mConnection.unselectRoute(mControllerId, reason); 377 } 378 } 379 380 @Override 381 public void onSetVolume(int volume) { 382 if (mConnection != null) { 383 mConnection.setVolume(mControllerId, volume); 384 } else { 385 mPendingSetVolume = volume; 386 mPendingUpdateVolumeDelta = 0; 387 } 388 } 389 390 @Override 391 public void onUpdateVolume(int delta) { 392 if (mConnection != null) { 393 mConnection.updateVolume(mControllerId, delta); 394 } else { 395 mPendingUpdateVolumeDelta += delta; 396 } 397 } 398 399 @Override 400 public boolean onControlRequest(Intent intent, ControlRequestCallback callback) { 401 if (mConnection != null) { 402 return mConnection.sendControlRequest(mControllerId, intent, callback); 403 } 404 return false; 405 } 406 } 407 408 private final class Connection implements DeathRecipient { 409 private final Messenger mServiceMessenger; 410 private final ReceiveHandler mReceiveHandler; 411 private final Messenger mReceiveMessenger; 412 413 private int mNextRequestId = 1; 414 private int mNextControllerId = 1; 415 private int mServiceVersion; // non-zero when registration complete 416 417 private int mPendingRegisterRequestId; 418 private final SparseArray<ControlRequestCallback> mPendingCallbacks = 419 new SparseArray<ControlRequestCallback>(); 420 421 public Connection(Messenger serviceMessenger) { 422 mServiceMessenger = serviceMessenger; 423 mReceiveHandler = new ReceiveHandler(this); 424 mReceiveMessenger = new Messenger(mReceiveHandler); 425 } 426 427 public boolean register() { 428 mPendingRegisterRequestId = mNextRequestId++; 429 if (!sendRequest(CLIENT_MSG_REGISTER, 430 mPendingRegisterRequestId, 431 CLIENT_VERSION_CURRENT, null, null)) { 432 return false; 433 } 434 435 try { 436 mServiceMessenger.getBinder().linkToDeath(this, 0); 437 return true; 438 } catch (RemoteException ex) { 439 binderDied(); 440 } 441 return false; 442 } 443 444 public void dispose() { 445 sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null); 446 mReceiveHandler.dispose(); 447 mServiceMessenger.getBinder().unlinkToDeath(this, 0); 448 449 mPrivateHandler.post(new Runnable() { 450 @Override 451 public void run() { 452 failPendingCallbacks(); 453 } 454 }); 455 } 456 457 private void failPendingCallbacks() { 458 int count = 0; 459 for (int i = 0; i < mPendingCallbacks.size(); i++) { 460 mPendingCallbacks.valueAt(i).onError(null, null); 461 } 462 mPendingCallbacks.clear(); 463 } 464 465 public boolean onGenericFailure(int requestId) { 466 if (requestId == mPendingRegisterRequestId) { 467 mPendingRegisterRequestId = 0; 468 onConnectionError(this, "Registration failed"); 469 } 470 ControlRequestCallback callback = mPendingCallbacks.get(requestId); 471 if (callback != null) { 472 mPendingCallbacks.remove(requestId); 473 callback.onError(null, null); 474 } 475 return true; 476 } 477 478 public boolean onGenericSuccess(int requestId) { 479 return true; 480 } 481 482 public boolean onRegistered(int requestId, int serviceVersion, 483 Bundle descriptorBundle) { 484 if (mServiceVersion == 0 485 && requestId == mPendingRegisterRequestId 486 && serviceVersion >= SERVICE_VERSION_1) { 487 mPendingRegisterRequestId = 0; 488 mServiceVersion = serviceVersion; 489 onConnectionDescriptorChanged(this, 490 MediaRouteProviderDescriptor.fromBundle(descriptorBundle)); 491 onConnectionReady(this); 492 return true; 493 } 494 return false; 495 } 496 497 public boolean onDescriptorChanged(Bundle descriptorBundle) { 498 if (mServiceVersion != 0) { 499 onConnectionDescriptorChanged(this, 500 MediaRouteProviderDescriptor.fromBundle(descriptorBundle)); 501 return true; 502 } 503 return false; 504 } 505 506 public boolean onControlRequestSucceeded(int requestId, Bundle data) { 507 ControlRequestCallback callback = mPendingCallbacks.get(requestId); 508 if (callback != null) { 509 mPendingCallbacks.remove(requestId); 510 callback.onResult(data); 511 return true; 512 } 513 return false; 514 } 515 516 public boolean onControlRequestFailed(int requestId, String error, Bundle data) { 517 ControlRequestCallback callback = mPendingCallbacks.get(requestId); 518 if (callback != null) { 519 mPendingCallbacks.remove(requestId); 520 callback.onError(error, data); 521 return true; 522 } 523 return false; 524 } 525 526 @Override 527 public void binderDied() { 528 mPrivateHandler.post(new Runnable() { 529 @Override 530 public void run() { 531 onConnectionDied(Connection.this); 532 } 533 }); 534 } 535 536 public int createRouteController(String routeId, String routeGroupId) { 537 int controllerId = mNextControllerId++; 538 Bundle data = new Bundle(); 539 data.putString(CLIENT_DATA_ROUTE_ID, routeId); 540 data.putString(CLIENT_DATA_ROUTE_GROUP_ID, routeGroupId); 541 sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER, 542 mNextRequestId++, controllerId, null, data); 543 return controllerId; 544 } 545 546 public void releaseRouteController(int controllerId) { 547 sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER, 548 mNextRequestId++, controllerId, null, null); 549 } 550 551 public void selectRoute(int controllerId) { 552 sendRequest(CLIENT_MSG_SELECT_ROUTE, 553 mNextRequestId++, controllerId, null, null); 554 } 555 556 public void unselectRoute(int controllerId, int reason) { 557 Bundle extras = new Bundle(); 558 extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason); 559 sendRequest(CLIENT_MSG_UNSELECT_ROUTE, 560 mNextRequestId++, controllerId, null, extras); 561 } 562 563 public void setVolume(int controllerId, int volume) { 564 Bundle data = new Bundle(); 565 data.putInt(CLIENT_DATA_VOLUME, volume); 566 sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME, 567 mNextRequestId++, controllerId, null, data); 568 } 569 570 public void updateVolume(int controllerId, int delta) { 571 Bundle data = new Bundle(); 572 data.putInt(CLIENT_DATA_VOLUME, delta); 573 sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME, 574 mNextRequestId++, controllerId, null, data); 575 } 576 577 public boolean sendControlRequest(int controllerId, Intent intent, 578 ControlRequestCallback callback) { 579 int requestId = mNextRequestId++; 580 if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST, 581 requestId, controllerId, intent, null)) { 582 if (callback != null) { 583 mPendingCallbacks.put(requestId, callback); 584 } 585 return true; 586 } 587 return false; 588 } 589 590 public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) { 591 sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST, 592 mNextRequestId++, 0, request != null ? request.asBundle() : null, null); 593 } 594 595 private boolean sendRequest(int what, int requestId, int arg, Object obj, Bundle data) { 596 Message msg = Message.obtain(); 597 msg.what = what; 598 msg.arg1 = requestId; 599 msg.arg2 = arg; 600 msg.obj = obj; 601 msg.setData(data); 602 msg.replyTo = mReceiveMessenger; 603 try { 604 mServiceMessenger.send(msg); 605 return true; 606 } catch (DeadObjectException ex) { 607 // The service died. 608 } catch (RemoteException ex) { 609 if (what != CLIENT_MSG_UNREGISTER) { 610 Log.e(TAG, "Could not send message to service.", ex); 611 } 612 } 613 return false; 614 } 615 } 616 617 private final class PrivateHandler extends Handler { 618 } 619 620 /** 621 * Handler that receives messages from the server. 622 * <p> 623 * This inner class is static and only retains a weak reference to the connection 624 * to prevent the client from being leaked in case the service is holding an 625 * active reference to the client's messenger. 626 * </p><p> 627 * This handler should not be used to handle any messages other than those 628 * that come from the service. 629 * </p> 630 */ 631 private static final class ReceiveHandler extends Handler { 632 private final WeakReference<Connection> mConnectionRef; 633 634 public ReceiveHandler(Connection connection) { 635 mConnectionRef = new WeakReference<Connection>(connection); 636 } 637 638 public void dispose() { 639 mConnectionRef.clear(); 640 } 641 642 @Override 643 public void handleMessage(Message msg) { 644 Connection connection = mConnectionRef.get(); 645 if (connection != null) { 646 final int what = msg.what; 647 final int requestId = msg.arg1; 648 final int arg = msg.arg2; 649 final Object obj = msg.obj; 650 final Bundle data = msg.peekData(); 651 if (!processMessage(connection, what, requestId, arg, obj, data)) { 652 if (DEBUG) { 653 Log.d(TAG, "Unhandled message from server: " + msg); 654 } 655 } 656 } 657 } 658 659 private boolean processMessage(Connection connection, 660 int what, int requestId, int arg, Object obj, Bundle data) { 661 switch (what) { 662 case SERVICE_MSG_GENERIC_FAILURE: 663 connection.onGenericFailure(requestId); 664 return true; 665 666 case SERVICE_MSG_GENERIC_SUCCESS: 667 connection.onGenericSuccess(requestId); 668 return true; 669 670 case SERVICE_MSG_REGISTERED: 671 if (obj == null || obj instanceof Bundle) { 672 return connection.onRegistered(requestId, arg, (Bundle)obj); 673 } 674 break; 675 676 case SERVICE_MSG_DESCRIPTOR_CHANGED: 677 if (obj == null || obj instanceof Bundle) { 678 return connection.onDescriptorChanged((Bundle)obj); 679 } 680 break; 681 682 case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED: 683 if (obj == null || obj instanceof Bundle) { 684 return connection.onControlRequestSucceeded( 685 requestId, (Bundle)obj); 686 } 687 break; 688 689 case SERVICE_MSG_CONTROL_REQUEST_FAILED: 690 if (obj == null || obj instanceof Bundle) { 691 String error = (data == null ? null : 692 data.getString(SERVICE_DATA_ERROR)); 693 return connection.onControlRequestFailed( 694 requestId, error, (Bundle)obj); 695 } 696 break; 697 } 698 return false; 699 } 700 } 701} 702