RegisteredMediaRouteProvider.java revision e2c6a94b6e4aab502f9b88dd3ff664bd90b25839
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 static final String TAG = "MediaRouteProviderProxy"; // max. 23 chars 48 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 49 50 private final ComponentName mComponentName; 51 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 @Override 95 public void onServiceConnected(ComponentName name, IBinder service) { 96 if (DEBUG) { 97 Log.d(TAG, this + ": Connected"); 98 } 99 100 if (mBound) { 101 disconnect(); 102 103 Messenger messenger = (service != null ? new Messenger(service) : null); 104 if (isValidRemoteMessenger(messenger)) { 105 Connection connection = new Connection(messenger); 106 if (connection.register()) { 107 mActiveConnection = connection; 108 } else { 109 if (DEBUG) { 110 Log.d(TAG, this + ": Registration failed"); 111 } 112 } 113 } else { 114 Log.e(TAG, this + ": Service returned invalid messenger binder"); 115 } 116 } 117 } 118 119 @Override 120 public void onServiceDisconnected(ComponentName name) { 121 if (DEBUG) { 122 Log.d(TAG, this + ": Service disconnected"); 123 } 124 disconnect(); 125 } 126 127 @Override 128 public String toString() { 129 return "Service connection " + mComponentName.flattenToShortString(); 130 } 131 132 public boolean hasComponentName(String packageName, String className) { 133 return mComponentName.getPackageName().equals(packageName) 134 && mComponentName.getClassName().equals(className); 135 } 136 137 public void start() { 138 if (!mStarted) { 139 if (DEBUG) { 140 Log.d(TAG, this + ": Starting"); 141 } 142 143 mStarted = true; 144 updateBinding(); 145 } 146 } 147 148 public void stop() { 149 if (mStarted) { 150 if (DEBUG) { 151 Log.d(TAG, this + ": Stopping"); 152 } 153 154 mStarted = false; 155 updateBinding(); 156 } 157 } 158 159 public void rebindIfDisconnected() { 160 if (mActiveConnection == null && shouldBind()) { 161 unbind(); 162 bind(); 163 } 164 } 165 166 private void updateBinding() { 167 if (shouldBind()) { 168 bind(); 169 } else { 170 unbind(); 171 } 172 } 173 174 private boolean shouldBind() { 175 if (mStarted) { 176 // Bind whenever there is a discovery request. 177 if (getDiscoveryRequest() != null) { 178 return true; 179 } 180 181 // Bind whenever the application has an active route controller. 182 // This means that one of this provider's routes is selected. 183 if (!mControllers.isEmpty()) { 184 return true; 185 } 186 } 187 return false; 188 } 189 190 private void bind() { 191 if (!mBound) { 192 if (DEBUG) { 193 Log.d(TAG, this + ": Binding"); 194 } 195 196 Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE); 197 service.setComponent(mComponentName); 198 try { 199 mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE); 200 if (!mBound && DEBUG) { 201 Log.d(TAG, this + ": Bind failed"); 202 } 203 } catch (SecurityException ex) { 204 if (DEBUG) { 205 Log.d(TAG, this + ": Bind failed", ex); 206 } 207 } 208 } 209 } 210 211 private void unbind() { 212 if (mBound) { 213 if (DEBUG) { 214 Log.d(TAG, this + ": Unbinding"); 215 } 216 217 mBound = false; 218 disconnect(); 219 getContext().unbindService(this); 220 } 221 } 222 223 private RouteController createRouteController(String routeId, String routeGroupId) { 224 MediaRouteProviderDescriptor descriptor = getDescriptor(); 225 if (descriptor != null) { 226 List<MediaRouteDescriptor> routes = descriptor.getRoutes(); 227 final int count = routes.size(); 228 for (int i = 0; i < count; i++) { 229 final MediaRouteDescriptor route = routes.get(i); 230 if (route.getId().equals(routeId)) { 231 Controller controller = new Controller(routeId, routeGroupId); 232 mControllers.add(controller); 233 if (mConnectionReady) { 234 controller.attachConnection(mActiveConnection); 235 } 236 updateBinding(); 237 return controller; 238 } 239 } 240 } 241 return null; 242 } 243 244 void onConnectionReady(Connection connection) { 245 if (mActiveConnection == connection) { 246 mConnectionReady = true; 247 attachControllersToConnection(); 248 249 MediaRouteDiscoveryRequest request = getDiscoveryRequest(); 250 if (request != null) { 251 mActiveConnection.setDiscoveryRequest(request); 252 } 253 } 254 } 255 256 void onConnectionDied(Connection connection) { 257 if (mActiveConnection == connection) { 258 if (DEBUG) { 259 Log.d(TAG, this + ": Service connection died"); 260 } 261 disconnect(); 262 } 263 } 264 265 void onConnectionError(Connection connection, String error) { 266 if (mActiveConnection == connection) { 267 if (DEBUG) { 268 Log.d(TAG, this + ": Service connection error - " + error); 269 } 270 unbind(); 271 } 272 } 273 274 void onConnectionDescriptorChanged(Connection connection, 275 MediaRouteProviderDescriptor descriptor) { 276 if (mActiveConnection == connection) { 277 if (DEBUG) { 278 Log.d(TAG, this + ": Descriptor changed, descriptor=" + descriptor); 279 } 280 setDescriptor(descriptor); 281 } 282 } 283 284 private void disconnect() { 285 if (mActiveConnection != null) { 286 setDescriptor(null); 287 mConnectionReady = false; 288 detachControllersFromConnection(); 289 mActiveConnection.dispose(); 290 mActiveConnection = null; 291 } 292 } 293 294 void onControllerReleased(Controller controller) { 295 mControllers.remove(controller); 296 controller.detachConnection(); 297 updateBinding(); 298 } 299 300 private void attachControllersToConnection() { 301 int count = mControllers.size(); 302 for (int i = 0; i < count; i++) { 303 mControllers.get(i).attachConnection(mActiveConnection); 304 } 305 } 306 307 private void detachControllersFromConnection() { 308 int count = mControllers.size(); 309 for (int i = 0; i < count; i++) { 310 mControllers.get(i).detachConnection(); 311 } 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 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 PrivateHandler() { 619 } 620 } 621 622 /** 623 * Handler that receives messages from the server. 624 * <p> 625 * This inner class is static and only retains a weak reference to the connection 626 * to prevent the client from being leaked in case the service is holding an 627 * active reference to the client's messenger. 628 * </p><p> 629 * This handler should not be used to handle any messages other than those 630 * that come from the service. 631 * </p> 632 */ 633 private static final class ReceiveHandler extends Handler { 634 private final WeakReference<Connection> mConnectionRef; 635 636 public ReceiveHandler(Connection connection) { 637 mConnectionRef = new WeakReference<Connection>(connection); 638 } 639 640 public void dispose() { 641 mConnectionRef.clear(); 642 } 643 644 @Override 645 public void handleMessage(Message msg) { 646 Connection connection = mConnectionRef.get(); 647 if (connection != null) { 648 final int what = msg.what; 649 final int requestId = msg.arg1; 650 final int arg = msg.arg2; 651 final Object obj = msg.obj; 652 final Bundle data = msg.peekData(); 653 if (!processMessage(connection, what, requestId, arg, obj, data)) { 654 if (DEBUG) { 655 Log.d(TAG, "Unhandled message from server: " + msg); 656 } 657 } 658 } 659 } 660 661 private boolean processMessage(Connection connection, 662 int what, int requestId, int arg, Object obj, Bundle data) { 663 switch (what) { 664 case SERVICE_MSG_GENERIC_FAILURE: 665 connection.onGenericFailure(requestId); 666 return true; 667 668 case SERVICE_MSG_GENERIC_SUCCESS: 669 connection.onGenericSuccess(requestId); 670 return true; 671 672 case SERVICE_MSG_REGISTERED: 673 if (obj == null || obj instanceof Bundle) { 674 return connection.onRegistered(requestId, arg, (Bundle)obj); 675 } 676 break; 677 678 case SERVICE_MSG_DESCRIPTOR_CHANGED: 679 if (obj == null || obj instanceof Bundle) { 680 return connection.onDescriptorChanged((Bundle)obj); 681 } 682 break; 683 684 case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED: 685 if (obj == null || obj instanceof Bundle) { 686 return connection.onControlRequestSucceeded( 687 requestId, (Bundle)obj); 688 } 689 break; 690 691 case SERVICE_MSG_CONTROL_REQUEST_FAILED: 692 if (obj == null || obj instanceof Bundle) { 693 String error = (data == null ? null : 694 data.getString(SERVICE_DATA_ERROR)); 695 return connection.onControlRequestFailed( 696 requestId, error, (Bundle)obj); 697 } 698 break; 699 } 700 return false; 701 } 702 } 703} 704