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