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