MediaRouter.java revision adf0f4a217e14894af07dfa9f46cad7d98b8a7f4
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.ContentResolver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.pm.PackageManager.NameNotFoundException; 25import android.content.res.Resources; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.Message; 30import android.support.v4.hardware.display.DisplayManagerCompat; 31import android.support.v7.media.MediaRouteProvider.ProviderMetadata; 32import android.util.Log; 33import android.view.Display; 34 35import java.lang.ref.WeakReference; 36import java.util.ArrayList; 37import java.util.Collections; 38import java.util.List; 39import java.util.Locale; 40 41/** 42 * MediaRouter allows applications to control the routing of media channels 43 * and streams from the current device to external speakers and destination devices. 44 * <p> 45 * A MediaRouter instance is retrieved through {@link #getInstance}. Applications 46 * can query the media router about the currently selected route and its capabilities 47 * to determine how to send content to the route's destination. Applications can 48 * also {@link RouteInfo#sendControlRequest send control requests} to the route 49 * to ask the route's destination to perform certain remote control functions 50 * such as playing media. 51 * </p><p> 52 * See also {@link MediaRouteProvider} for information on how an application 53 * can publish new media routes to the media router. 54 * </p><p> 55 * The media router API is not thread-safe; all interactions with it must be 56 * done from the main thread of the process. 57 * </p> 58 */ 59public final class MediaRouter { 60 private static final String TAG = "MediaRouter"; 61 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 62 63 // Maintains global media router state for the process. 64 // This field is initialized in MediaRouter.getInstance() before any 65 // MediaRouter objects are instantiated so it is guaranteed to be 66 // valid whenever any instance method is invoked. 67 static GlobalMediaRouter sGlobal; 68 69 // Context-bound state of the media router. 70 final Context mContext; 71 final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>(); 72 73 /** 74 * Flag for {@link #addCallback}: Actively scan for routes while this callback 75 * is registered. 76 * <p> 77 * When this flag is specified, the media router will actively scan for new 78 * routes. Certain routes, such as wifi display routes, may not be discoverable 79 * except when actively scanning. This flag is typically used when the route picker 80 * dialog has been opened by the user to ensure that the route information is 81 * up to date. 82 * </p><p> 83 * Active scanning may consume a significant amount of power and may have intrusive 84 * effects on wireless connectivity. Therefore it is important that active scanning 85 * only be requested when it is actually needed to satisfy a user request to 86 * discover and select a new route. 87 * </p><p> 88 * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing 89 * active scans is much more expensive than a normal discovery request. 90 * </p> 91 * 92 * @see #CALLBACK_FLAG_REQUEST_DISCOVERY 93 */ 94 public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0; 95 96 /** 97 * Flag for {@link #addCallback}: Do not filter route events. 98 * <p> 99 * When this flag is specified, the callback will be invoked for events that affect any 100 * route even if they do not match the callback's filter. 101 * </p> 102 */ 103 public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1; 104 105 /** 106 * Flag for {@link #addCallback}: Request that route discovery be performed while this 107 * callback is registered. 108 * <p> 109 * When this flag is specified, the media router will try to discover routes. 110 * Although route discovery is intended to be efficient, checking for new routes may 111 * result in some network activity and could slowly drain the battery. Therefore 112 * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when 113 * they are running in the foreground and would like to provide the user with the 114 * option of connecting to new routes. 115 * </p><p> 116 * Applications should typically add a callback using this flag in the 117 * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart} 118 * method and remove it in the {@link android.app.Activity#onStop onStop} method. 119 * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may 120 * also be used for this purpose. 121 * </p> 122 * 123 * @see android.support.v7.app.MediaRouteDiscoveryFragment 124 */ 125 public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2; 126 127 /** 128 * Flag for {@link #isRouteAvailable}: Ignore the default route. 129 * <p> 130 * This flag is used to determine whether a matching non-default route is available. 131 * This constraint may be used to decide whether to offer the route chooser dialog 132 * to the user. There is no point offering the chooser if there are no 133 * non-default choices. 134 * </p> 135 */ 136 public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0; 137 138 MediaRouter(Context context) { 139 mContext = context; 140 } 141 142 /** 143 * Gets an instance of the media router service associated with the context. 144 * <p> 145 * The application is responsible for holding a strong reference to the returned 146 * {@link MediaRouter} instance, such as by storing the instance in a field of 147 * the {@link android.app.Activity}, to ensure that the media router remains alive 148 * as long as the application is using its features. 149 * </p><p> 150 * In other words, the support library only holds a {@link WeakReference weak reference} 151 * to each media router instance. When there are no remaining strong references to the 152 * media router instance, all of its callbacks will be removed and route discovery 153 * will no longer be performed on its behalf. 154 * </p> 155 * 156 * @return The media router instance for the context. The application must hold 157 * a strong reference to this object as long as it is in use. 158 */ 159 public static MediaRouter getInstance(Context context) { 160 if (context == null) { 161 throw new IllegalArgumentException("context must not be null"); 162 } 163 checkCallingThread(); 164 165 if (sGlobal == null) { 166 sGlobal = new GlobalMediaRouter(context.getApplicationContext()); 167 sGlobal.start(); 168 } 169 return sGlobal.getRouter(context); 170 } 171 172 /** 173 * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to 174 * this media router. 175 */ 176 public List<RouteInfo> getRoutes() { 177 checkCallingThread(); 178 return sGlobal.getRoutes(); 179 } 180 181 /** 182 * Gets information about the {@link MediaRouter.ProviderInfo route providers} 183 * currently known to this media router. 184 */ 185 public List<ProviderInfo> getProviders() { 186 checkCallingThread(); 187 return sGlobal.getProviders(); 188 } 189 190 /** 191 * Gets the default route for playing media content on the system. 192 * <p> 193 * The system always provides a default route. 194 * </p> 195 * 196 * @return The default route, which is guaranteed to never be null. 197 */ 198 public RouteInfo getDefaultRoute() { 199 checkCallingThread(); 200 return sGlobal.getDefaultRoute(); 201 } 202 203 /** 204 * Gets the currently selected route. 205 * <p> 206 * The application should examine the route's 207 * {@link RouteInfo#getControlFilters media control intent filters} to assess the 208 * capabilities of the route before attempting to use it. 209 * </p> 210 * 211 * <h3>Example</h3> 212 * <pre> 213 * public boolean playMovie() { 214 * MediaRouter mediaRouter = MediaRouter.getInstance(context); 215 * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute(); 216 * 217 * // First try using the remote playback interface, if supported. 218 * if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 219 * // The route supports remote playback. 220 * // Try to send it the Uri of the movie to play. 221 * Intent intent = new Intent(MediaControlIntent.ACTION_PLAY); 222 * intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 223 * intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4"); 224 * if (route.supportsControlRequest(intent)) { 225 * route.sendControlRequest(intent, null); 226 * return true; // sent the request to play the movie 227 * } 228 * } 229 * 230 * // If remote playback was not possible, then play locally. 231 * if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) { 232 * // The route supports live video streaming. 233 * // Prepare to play content locally in a window or in a presentation. 234 * return playMovieInWindow(); 235 * } 236 * 237 * // Neither interface is supported, so we can't play the movie to this route. 238 * return false; 239 * } 240 * </pre> 241 * 242 * @return The selected route, which is guaranteed to never be null. 243 * 244 * @see RouteInfo#getControlFilters 245 * @see RouteInfo#supportsControlCategory 246 * @see RouteInfo#supportsControlRequest 247 */ 248 public RouteInfo getSelectedRoute() { 249 checkCallingThread(); 250 return sGlobal.getSelectedRoute(); 251 } 252 253 /** 254 * Returns the selected route if it matches the specified selector, otherwise 255 * selects the default route and returns it. 256 * 257 * @param selector The selector to match. 258 * @return The previously selected route if it matched the selector, otherwise the 259 * newly selected default route which is guaranteed to never be null. 260 * 261 * @see MediaRouteSelector 262 * @see RouteInfo#matchesSelector 263 * @see RouteInfo#isDefault 264 */ 265 public RouteInfo updateSelectedRoute(MediaRouteSelector selector) { 266 if (selector == null) { 267 throw new IllegalArgumentException("selector must not be null"); 268 } 269 checkCallingThread(); 270 271 if (DEBUG) { 272 Log.d(TAG, "updateSelectedRoute: " + selector); 273 } 274 RouteInfo route = sGlobal.getSelectedRoute(); 275 if (!route.isDefault() && !route.matchesSelector(selector)) { 276 route = sGlobal.getDefaultRoute(); 277 sGlobal.selectRoute(route); 278 } 279 return route; 280 } 281 282 /** 283 * Selects the specified route. 284 * 285 * @param route The route to select. 286 */ 287 public void selectRoute(RouteInfo route) { 288 if (route == null) { 289 throw new IllegalArgumentException("route must not be null"); 290 } 291 checkCallingThread(); 292 293 if (DEBUG) { 294 Log.d(TAG, "selectRoute: " + route); 295 } 296 sGlobal.selectRoute(route); 297 } 298 299 /** 300 * Returns true if there is a route that matches the specified selector. 301 * <p> 302 * This method returns true if there are any available routes that match the selector 303 * regardless of whether they are enabled or disabled. If the 304 * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then 305 * the method will only consider non-default routes. 306 * </p> 307 * 308 * @param selector The selector to match. 309 * @param flags Flags to control the determination of whether a route may be available. 310 * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}. 311 * @return True if a matching route may be available. 312 */ 313 public boolean isRouteAvailable(MediaRouteSelector selector, int flags) { 314 if (selector == null) { 315 throw new IllegalArgumentException("selector must not be null"); 316 } 317 checkCallingThread(); 318 319 return sGlobal.isRouteAvailable(selector, flags); 320 } 321 322 /** 323 * Registers a callback to discover routes that match the selector and to receive 324 * events when they change. 325 * <p> 326 * This is a convenience method that has the same effect as calling 327 * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags. 328 * </p> 329 * 330 * @param selector A route selector that indicates the kinds of routes that the 331 * callback would like to discover. 332 * @param callback The callback to add. 333 * @see #removeCallback 334 */ 335 public void addCallback(MediaRouteSelector selector, Callback callback) { 336 addCallback(selector, callback, 0); 337 } 338 339 /** 340 * Registers a callback to discover routes that match the selector and to receive 341 * events when they change. 342 * <p> 343 * The selector describes the kinds of routes that the application wants to 344 * discover. For example, if the application wants to use 345 * live audio routes then it should include the 346 * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category} 347 * in its selector when it adds a callback to the media router. 348 * The selector may include any number of categories. 349 * </p><p> 350 * If the callback has already been registered, then the selector is added to 351 * the set of selectors being monitored by the callback. 352 * </p><p> 353 * By default, the callback will only be invoked for events that affect routes 354 * that match the specified selector. Event filtering may be disabled by specifying 355 * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered. 356 * </p> 357 * 358 * <h3>Example</h3> 359 * <pre> 360 * public class MyActivity extends Activity { 361 * private MediaRouter mRouter; 362 * private MediaRouter.Callback mCallback; 363 * private MediaRouteSelector mSelector; 364 * 365 * protected void onCreate(Bundle savedInstanceState) { 366 * super.onCreate(savedInstanceState); 367 * 368 * mRouter = Mediarouter.getInstance(this); 369 * mCallback = new MyCallback(); 370 * mSelector = new MediaRouteSelector.Builder() 371 * .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) 372 * .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) 373 * .build(); 374 * } 375 * 376 * // Add the callback on start to tell the media router what kinds of routes 377 * // the application is interested in so that it can try to discover suitable ones. 378 * public void onStart() { 379 * super.onStart(); 380 * 381 * mediaRouter.addCallback(mSelector, mCallback, 382 * MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); 383 * 384 * MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector); 385 * // do something with the route... 386 * } 387 * 388 * // Remove the selector on stop to tell the media router that it no longer 389 * // needs to invest effort trying to discover routes of these kinds for now. 390 * public void onStop() { 391 * super.onStop(); 392 * 393 * mediaRouter.removeCallback(mCallback); 394 * } 395 * 396 * private final class MyCallback extends MediaRouter.Callback { 397 * // Implement callback methods as needed. 398 * } 399 * } 400 * </pre> 401 * 402 * @param selector A route selector that indicates the kinds of routes that the 403 * callback would like to discover. 404 * @param callback The callback to add. 405 * @param flags Flags to control the behavior of the callback. 406 * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and 407 * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}. 408 * @see #removeCallback 409 */ 410 public void addCallback(MediaRouteSelector selector, Callback callback, int flags) { 411 if (selector == null) { 412 throw new IllegalArgumentException("selector must not be null"); 413 } 414 if (callback == null) { 415 throw new IllegalArgumentException("callback must not be null"); 416 } 417 checkCallingThread(); 418 419 if (DEBUG) { 420 Log.d(TAG, "addCallback: selector=" + selector 421 + ", callback=" + callback + ", flags=" + Integer.toHexString(flags)); 422 } 423 424 CallbackRecord record; 425 int index = findCallbackRecord(callback); 426 if (index < 0) { 427 record = new CallbackRecord(this, callback); 428 mCallbackRecords.add(record); 429 } else { 430 record = mCallbackRecords.get(index); 431 } 432 boolean updateNeeded = false; 433 if ((flags & ~record.mFlags) != 0) { 434 record.mFlags |= flags; 435 updateNeeded = true; 436 } 437 if (!record.mSelector.contains(selector)) { 438 record.mSelector = new MediaRouteSelector.Builder(record.mSelector) 439 .addSelector(selector) 440 .build(); 441 updateNeeded = true; 442 } 443 if (updateNeeded) { 444 sGlobal.updateDiscoveryRequest(); 445 } 446 } 447 448 /** 449 * Removes the specified callback. It will no longer receive events about 450 * changes to media routes. 451 * 452 * @param callback The callback to remove. 453 * @see #addCallback 454 */ 455 public void removeCallback(Callback callback) { 456 if (callback == null) { 457 throw new IllegalArgumentException("callback must not be null"); 458 } 459 checkCallingThread(); 460 461 if (DEBUG) { 462 Log.d(TAG, "removeCallback: callback=" + callback); 463 } 464 465 int index = findCallbackRecord(callback); 466 if (index >= 0) { 467 mCallbackRecords.remove(index); 468 sGlobal.updateDiscoveryRequest(); 469 } 470 } 471 472 private int findCallbackRecord(Callback callback) { 473 final int count = mCallbackRecords.size(); 474 for (int i = 0; i < count; i++) { 475 if (mCallbackRecords.get(i).mCallback == callback) { 476 return i; 477 } 478 } 479 return -1; 480 } 481 482 /** 483 * Registers a media route provider within this application process. 484 * <p> 485 * The provider will be added to the list of providers that all {@link MediaRouter} 486 * instances within this process can use to discover routes. 487 * </p> 488 * 489 * @param providerInstance The media route provider instance to add. 490 * 491 * @see MediaRouteProvider 492 * @see #removeCallback 493 */ 494 public void addProvider(MediaRouteProvider providerInstance) { 495 if (providerInstance == null) { 496 throw new IllegalArgumentException("providerInstance must not be null"); 497 } 498 checkCallingThread(); 499 500 if (DEBUG) { 501 Log.d(TAG, "addProvider: " + providerInstance); 502 } 503 sGlobal.addProvider(providerInstance); 504 } 505 506 /** 507 * Unregisters a media route provider within this application process. 508 * <p> 509 * The provider will be removed from the list of providers that all {@link MediaRouter} 510 * instances within this process can use to discover routes. 511 * </p> 512 * 513 * @param providerInstance The media route provider instance to remove. 514 * 515 * @see MediaRouteProvider 516 * @see #addCallback 517 */ 518 public void removeProvider(MediaRouteProvider providerInstance) { 519 if (providerInstance == null) { 520 throw new IllegalArgumentException("providerInstance must not be null"); 521 } 522 checkCallingThread(); 523 524 if (DEBUG) { 525 Log.d(TAG, "removeProvider: " + providerInstance); 526 } 527 sGlobal.removeProvider(providerInstance); 528 } 529 530 /** 531 * Adds a remote control client to enable remote control of the volume 532 * of the selected route. 533 * <p> 534 * The remote control client must have previously been registered with 535 * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient 536 * AudioManager.registerRemoteControlClient} method. 537 * </p> 538 * 539 * @param remoteControlClient The {@link android.media.RemoteControlClient} to register. 540 */ 541 public void addRemoteControlClient(Object remoteControlClient) { 542 if (remoteControlClient == null) { 543 throw new IllegalArgumentException("remoteControlClient must not be null"); 544 } 545 checkCallingThread(); 546 547 if (DEBUG) { 548 Log.d(TAG, "addRemoteControlClient: " + remoteControlClient); 549 } 550 sGlobal.addRemoteControlClient(remoteControlClient); 551 } 552 553 /** 554 * Removes a remote control client. 555 * 556 * @param remoteControlClient The {@link android.media.RemoteControlClient} to register. 557 */ 558 public void removeRemoteControlClient(Object remoteControlClient) { 559 if (remoteControlClient == null) { 560 throw new IllegalArgumentException("remoteControlClient must not be null"); 561 } 562 563 if (DEBUG) { 564 Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient); 565 } 566 sGlobal.removeRemoteControlClient(remoteControlClient); 567 } 568 569 /** 570 * Ensures that calls into the media router are on the correct thread. 571 * It pays to be a little paranoid when global state invariants are at risk. 572 */ 573 static void checkCallingThread() { 574 if (Looper.myLooper() != Looper.getMainLooper()) { 575 throw new IllegalStateException("The media router service must only be " 576 + "accessed on the application's main thread."); 577 } 578 } 579 580 static <T> boolean equal(T a, T b) { 581 return a == b || (a != null && b != null && a.equals(b)); 582 } 583 584 /** 585 * Provides information about a media route. 586 * <p> 587 * Each media route has a list of {@link MediaControlIntent media control} 588 * {@link #getControlFilters intent filters} that describe the capabilities of the 589 * route and the manner in which it is used and controlled. 590 * </p> 591 */ 592 public static final class RouteInfo { 593 private final ProviderInfo mProvider; 594 private final String mDescriptorId; 595 private final String mUniqueId; 596 private String mName; 597 private String mDescription; 598 private boolean mEnabled; 599 private boolean mConnecting; 600 private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>(); 601 private int mPlaybackType; 602 private int mPlaybackStream; 603 private int mVolumeHandling; 604 private int mVolume; 605 private int mVolumeMax; 606 private Display mPresentationDisplay; 607 private int mPresentationDisplayId = -1; 608 private Bundle mExtras; 609 private MediaRouteDescriptor mDescriptor; 610 611 /** 612 * The default playback type, "local", indicating the presentation of the media 613 * is happening on the same device (e.g. a phone, a tablet) as where it is 614 * controlled from. 615 * 616 * @see #getPlaybackType 617 */ 618 public static final int PLAYBACK_TYPE_LOCAL = 0; 619 620 /** 621 * A playback type indicating the presentation of the media is happening on 622 * a different device (i.e. the remote device) than where it is controlled from. 623 * 624 * @see #getPlaybackType 625 */ 626 public static final int PLAYBACK_TYPE_REMOTE = 1; 627 628 /** 629 * Playback information indicating the playback volume is fixed, i.e. it cannot be 630 * controlled from this object. An example of fixed playback volume is a remote player, 631 * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather 632 * than attenuate at the source. 633 * 634 * @see #getVolumeHandling 635 */ 636 public static final int PLAYBACK_VOLUME_FIXED = 0; 637 638 /** 639 * Playback information indicating the playback volume is variable and can be controlled 640 * from this object. 641 * 642 * @see #getVolumeHandling 643 */ 644 public static final int PLAYBACK_VOLUME_VARIABLE = 1; 645 646 static final int CHANGE_GENERAL = 1 << 0; 647 static final int CHANGE_VOLUME = 1 << 1; 648 static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2; 649 650 RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) { 651 mProvider = provider; 652 mDescriptorId = descriptorId; 653 mUniqueId = uniqueId; 654 } 655 656 /** 657 * Gets information about the provider of this media route. 658 */ 659 public ProviderInfo getProvider() { 660 return mProvider; 661 } 662 663 /** 664 * Gets the unique id of the route. 665 * <p> 666 * The route unique id functions as a stable identifier by which the route is known. 667 * For example, an application can use this id as a token to remember the 668 * selected route across restarts or to communicate its identity to a service. 669 * </p> 670 * 671 * @return The unique id of the route, never null. 672 */ 673 public String getId() { 674 return mUniqueId; 675 } 676 677 /** 678 * Gets the user-visible name of the route. 679 * <p> 680 * The route name identifies the destination represented by the route. 681 * It may be a user-supplied name, an alias, or device serial number. 682 * </p> 683 * 684 * @return The user-visible name of a media route. This is the string presented 685 * to users who may select this as the active route. 686 */ 687 public String getName() { 688 return mName; 689 } 690 691 /** 692 * Gets the user-visible description of the route. 693 * <p> 694 * The route description describes the kind of destination represented by the route. 695 * It may be a user-supplied string, a model number or brand of device. 696 * </p> 697 * 698 * @return The description of the route, or null if none. 699 */ 700 public String getDescription() { 701 return mDescription; 702 } 703 704 /** 705 * Returns true if this route is enabled and may be selected. 706 * 707 * @return True if this route is enabled. 708 */ 709 public boolean isEnabled() { 710 return mEnabled; 711 } 712 713 /** 714 * Returns true if the route is in the process of connecting and is not 715 * yet ready for use. 716 * 717 * @return True if this route is in the process of connecting. 718 */ 719 public boolean isConnecting() { 720 return mConnecting; 721 } 722 723 /** 724 * Returns true if this route is currently selected. 725 * 726 * @return True if this route is currently selected. 727 * 728 * @see MediaRouter#getSelectedRoute 729 */ 730 public boolean isSelected() { 731 checkCallingThread(); 732 return sGlobal.getSelectedRoute() == this; 733 } 734 735 /** 736 * Returns true if this route is the default route. 737 * 738 * @return True if this route is the default route. 739 * 740 * @see MediaRouter#getDefaultRoute 741 */ 742 public boolean isDefault() { 743 checkCallingThread(); 744 return sGlobal.getDefaultRoute() == this; 745 } 746 747 /** 748 * Gets a list of {@link MediaControlIntent media control intent} filters that 749 * describe the capabilities of this route and the media control actions that 750 * it supports. 751 * 752 * @return A list of intent filters that specifies the media control intents that 753 * this route supports. 754 * 755 * @see MediaControlIntent 756 * @see #supportsControlCategory 757 * @see #supportsControlRequest 758 */ 759 public List<IntentFilter> getControlFilters() { 760 return mControlFilters; 761 } 762 763 /** 764 * Returns true if the route supports at least one of the capabilities 765 * described by a media route selector. 766 * 767 * @param selector The selector that specifies the capabilities to check. 768 * @return True if the route supports at least one of the capabilities 769 * described in the media route selector. 770 */ 771 public boolean matchesSelector(MediaRouteSelector selector) { 772 if (selector == null) { 773 throw new IllegalArgumentException("selector must not be null"); 774 } 775 checkCallingThread(); 776 return selector.matchesControlFilters(mControlFilters); 777 } 778 779 /** 780 * Returns true if the route supports the specified 781 * {@link MediaControlIntent media control} category. 782 * <p> 783 * Media control categories describe the capabilities of this route 784 * such as whether it supports live audio streaming or remote playback. 785 * </p> 786 * 787 * @param category A {@link MediaControlIntent media control} category 788 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 789 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 790 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 791 * media control category. 792 * @return True if the route supports the specified intent category. 793 * 794 * @see MediaControlIntent 795 * @see #getControlFilters 796 */ 797 public boolean supportsControlCategory(String category) { 798 if (category == null) { 799 throw new IllegalArgumentException("category must not be null"); 800 } 801 checkCallingThread(); 802 803 int count = mControlFilters.size(); 804 for (int i = 0; i < count; i++) { 805 if (mControlFilters.get(i).hasCategory(category)) { 806 return true; 807 } 808 } 809 return false; 810 } 811 812 /** 813 * Returns true if the route supports the specified 814 * {@link MediaControlIntent media control} category and action. 815 * <p> 816 * Media control actions describe specific requests that an application 817 * can ask a route to perform. 818 * </p> 819 * 820 * @param category A {@link MediaControlIntent media control} category 821 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 822 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 823 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 824 * media control category. 825 * @param action A {@link MediaControlIntent media control} action 826 * such as {@link MediaControlIntent#ACTION_PLAY}. 827 * @return True if the route supports the specified intent action. 828 * 829 * @see MediaControlIntent 830 * @see #getControlFilters 831 */ 832 public boolean supportsControlAction(String category, String action) { 833 if (category == null) { 834 throw new IllegalArgumentException("category must not be null"); 835 } 836 if (action == null) { 837 throw new IllegalArgumentException("action must not be null"); 838 } 839 checkCallingThread(); 840 841 int count = mControlFilters.size(); 842 for (int i = 0; i < count; i++) { 843 IntentFilter filter = mControlFilters.get(i); 844 if (filter.hasCategory(category) && filter.hasAction(action)) { 845 return true; 846 } 847 } 848 return false; 849 } 850 851 /** 852 * Returns true if the route supports the specified 853 * {@link MediaControlIntent media control} request. 854 * <p> 855 * Media control requests are used to request the route to perform 856 * actions such as starting remote playback of a media item. 857 * </p> 858 * 859 * @param intent A {@link MediaControlIntent media control intent}. 860 * @return True if the route can handle the specified intent. 861 * 862 * @see MediaControlIntent 863 * @see #getControlFilters 864 */ 865 public boolean supportsControlRequest(Intent intent) { 866 if (intent == null) { 867 throw new IllegalArgumentException("intent must not be null"); 868 } 869 checkCallingThread(); 870 871 ContentResolver contentResolver = sGlobal.getContentResolver(); 872 int count = mControlFilters.size(); 873 for (int i = 0; i < count; i++) { 874 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) { 875 return true; 876 } 877 } 878 return false; 879 } 880 881 /** 882 * Sends a {@link MediaControlIntent media control} request to be performed 883 * asynchronously by the route's destination. 884 * <p> 885 * Media control requests are used to request the route to perform 886 * actions such as starting remote playback of a media item. 887 * </p><p> 888 * This function may only be called on a selected route. Control requests 889 * sent to unselected routes will fail. 890 * </p> 891 * 892 * @param intent A {@link MediaControlIntent media control intent}. 893 * @param callback A {@link ControlRequestCallback} to invoke with the result 894 * of the request, or null if no result is required. 895 * 896 * @see MediaControlIntent 897 */ 898 public void sendControlRequest(Intent intent, ControlRequestCallback callback) { 899 if (intent == null) { 900 throw new IllegalArgumentException("intent must not be null"); 901 } 902 checkCallingThread(); 903 904 sGlobal.sendControlRequest(this, intent, callback); 905 } 906 907 /** 908 * Gets the type of playback associated with this route. 909 * 910 * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL} 911 * or {@link #PLAYBACK_TYPE_REMOTE}. 912 */ 913 public int getPlaybackType() { 914 return mPlaybackType; 915 } 916 917 /** 918 * Gets the audio stream over which the playback associated with this route is performed. 919 * 920 * @return The stream over which the playback associated with this route is performed. 921 */ 922 public int getPlaybackStream() { 923 return mPlaybackStream; 924 } 925 926 /** 927 * Gets information about how volume is handled on the route. 928 * 929 * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED} 930 * or {@link #PLAYBACK_VOLUME_VARIABLE}. 931 */ 932 public int getVolumeHandling() { 933 return mVolumeHandling; 934 } 935 936 /** 937 * Gets the current volume for this route. Depending on the route, this may only 938 * be valid if the route is currently selected. 939 * 940 * @return The volume at which the playback associated with this route is performed. 941 */ 942 public int getVolume() { 943 return mVolume; 944 } 945 946 /** 947 * Gets the maximum volume at which the playback associated with this route is performed. 948 * 949 * @return The maximum volume at which the playback associated with 950 * this route is performed. 951 */ 952 public int getVolumeMax() { 953 return mVolumeMax; 954 } 955 956 /** 957 * Requests a volume change for this route asynchronously. 958 * <p> 959 * This function may only be called on a selected route. It will have 960 * no effect if the route is currently unselected. 961 * </p> 962 * 963 * @param volume The new volume value between 0 and {@link #getVolumeMax}. 964 */ 965 public void requestSetVolume(int volume) { 966 checkCallingThread(); 967 sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume))); 968 } 969 970 /** 971 * Requests an incremental volume update for this route asynchronously. 972 * <p> 973 * This function may only be called on a selected route. It will have 974 * no effect if the route is currently unselected. 975 * </p> 976 * 977 * @param delta The delta to add to the current volume. 978 */ 979 public void requestUpdateVolume(int delta) { 980 checkCallingThread(); 981 if (delta != 0) { 982 sGlobal.requestUpdateVolume(this, delta); 983 } 984 } 985 986 /** 987 * Gets the {@link Display} that should be used by the application to show 988 * a {@link android.app.Presentation} on an external display when this route is selected. 989 * Depending on the route, this may only be valid if the route is currently 990 * selected. 991 * <p> 992 * The preferred presentation display may change independently of the route 993 * being selected or unselected. For example, the presentation display 994 * of the default system route may change when an external HDMI display is connected 995 * or disconnected even though the route itself has not changed. 996 * </p><p> 997 * This method may return null if there is no external display associated with 998 * the route or if the display is not ready to show UI yet. 999 * </p><p> 1000 * The application should listen for changes to the presentation display 1001 * using the {@link Callback#onRoutePresentationDisplayChanged} callback and 1002 * show or dismiss its {@link android.app.Presentation} accordingly when the display 1003 * becomes available or is removed. 1004 * </p><p> 1005 * This method only makes sense for 1006 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes. 1007 * </p> 1008 * 1009 * @return The preferred presentation display to use when this route is 1010 * selected or null if none. 1011 * 1012 * @see MediaControlIntent#CATEGORY_LIVE_VIDEO 1013 * @see android.app.Presentation 1014 */ 1015 public Display getPresentationDisplay() { 1016 checkCallingThread(); 1017 if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) { 1018 mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId); 1019 } 1020 return mPresentationDisplay; 1021 } 1022 1023 /** 1024 * Gets a collection of extra properties about this route that were supplied 1025 * by its media route provider, or null if none. 1026 */ 1027 public Bundle getExtras() { 1028 return mExtras; 1029 } 1030 1031 /** 1032 * Selects this media route. 1033 */ 1034 public void select() { 1035 checkCallingThread(); 1036 sGlobal.selectRoute(this); 1037 } 1038 1039 @Override 1040 public String toString() { 1041 return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId 1042 + ", name=" + mName 1043 + ", description=" + mDescription 1044 + ", enabled=" + mEnabled 1045 + ", connecting=" + mConnecting 1046 + ", playbackType=" + mPlaybackType 1047 + ", playbackStream=" + mPlaybackStream 1048 + ", volumeHandling=" + mVolumeHandling 1049 + ", volume=" + mVolume 1050 + ", volumeMax=" + mVolumeMax 1051 + ", presentationDisplayId=" + mPresentationDisplayId 1052 + ", extras=" + mExtras 1053 + ", providerPackageName=" + mProvider.getPackageName() 1054 + " }"; 1055 } 1056 1057 int updateDescriptor(MediaRouteDescriptor descriptor) { 1058 int changes = 0; 1059 if (mDescriptor != descriptor) { 1060 mDescriptor = descriptor; 1061 if (descriptor != null) { 1062 if (!equal(mName, descriptor.getName())) { 1063 mName = descriptor.getName(); 1064 changes |= CHANGE_GENERAL; 1065 } 1066 if (!equal(mDescription, descriptor.getDescription())) { 1067 mDescription = descriptor.getDescription(); 1068 changes |= CHANGE_GENERAL; 1069 } 1070 if (mEnabled != descriptor.isEnabled()) { 1071 mEnabled = descriptor.isEnabled(); 1072 changes |= CHANGE_GENERAL; 1073 } 1074 if (mConnecting != descriptor.isConnecting()) { 1075 mConnecting = descriptor.isConnecting(); 1076 changes |= CHANGE_GENERAL; 1077 } 1078 if (!mControlFilters.equals(descriptor.getControlFilters())) { 1079 mControlFilters.clear(); 1080 mControlFilters.addAll(descriptor.getControlFilters()); 1081 changes |= CHANGE_GENERAL; 1082 } 1083 if (mPlaybackType != descriptor.getPlaybackType()) { 1084 mPlaybackType = descriptor.getPlaybackType(); 1085 changes |= CHANGE_GENERAL; 1086 } 1087 if (mPlaybackStream != descriptor.getPlaybackStream()) { 1088 mPlaybackStream = descriptor.getPlaybackStream(); 1089 changes |= CHANGE_GENERAL; 1090 } 1091 if (mVolumeHandling != descriptor.getVolumeHandling()) { 1092 mVolumeHandling = descriptor.getVolumeHandling(); 1093 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1094 } 1095 if (mVolume != descriptor.getVolume()) { 1096 mVolume = descriptor.getVolume(); 1097 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1098 } 1099 if (mVolumeMax != descriptor.getVolumeMax()) { 1100 mVolumeMax = descriptor.getVolumeMax(); 1101 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1102 } 1103 if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) { 1104 mPresentationDisplayId = descriptor.getPresentationDisplayId(); 1105 mPresentationDisplay = null; 1106 changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY; 1107 } 1108 if (!equal(mExtras, descriptor.getExtras())) { 1109 mExtras = descriptor.getExtras(); 1110 changes |= CHANGE_GENERAL; 1111 } 1112 } 1113 } 1114 return changes; 1115 } 1116 1117 String getDescriptorId() { 1118 return mDescriptorId; 1119 } 1120 1121 MediaRouteProvider getProviderInstance() { 1122 return mProvider.getProviderInstance(); 1123 } 1124 } 1125 1126 /** 1127 * Provides information about a media route provider. 1128 * <p> 1129 * This object may be used to determine which media route provider has 1130 * published a particular route. 1131 * </p> 1132 */ 1133 public static final class ProviderInfo { 1134 private final MediaRouteProvider mProviderInstance; 1135 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 1136 1137 private final ProviderMetadata mMetadata; 1138 private MediaRouteProviderDescriptor mDescriptor; 1139 private Resources mResources; 1140 private boolean mResourcesNotAvailable; 1141 1142 ProviderInfo(MediaRouteProvider provider) { 1143 mProviderInstance = provider; 1144 mMetadata = provider.getMetadata(); 1145 } 1146 1147 /** 1148 * Gets the provider's underlying {@link MediaRouteProvider} instance. 1149 */ 1150 public MediaRouteProvider getProviderInstance() { 1151 checkCallingThread(); 1152 return mProviderInstance; 1153 } 1154 1155 /** 1156 * Gets the package name of the media route provider. 1157 */ 1158 public String getPackageName() { 1159 return mMetadata.getPackageName(); 1160 } 1161 1162 /** 1163 * Gets the component name of the media route provider. 1164 */ 1165 public ComponentName getComponentName() { 1166 return mMetadata.getComponentName(); 1167 } 1168 1169 /** 1170 * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider. 1171 */ 1172 public List<RouteInfo> getRoutes() { 1173 checkCallingThread(); 1174 return mRoutes; 1175 } 1176 1177 Resources getResources() { 1178 if (mResources == null && !mResourcesNotAvailable) { 1179 String packageName = getPackageName(); 1180 Context context = sGlobal.getProviderContext(packageName); 1181 if (context != null) { 1182 mResources = context.getResources(); 1183 } else { 1184 Log.w(TAG, "Unable to obtain resources for route provider package: " 1185 + packageName); 1186 mResourcesNotAvailable = true; 1187 } 1188 } 1189 return mResources; 1190 } 1191 1192 boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) { 1193 if (mDescriptor != descriptor) { 1194 mDescriptor = descriptor; 1195 return true; 1196 } 1197 return false; 1198 } 1199 1200 int findRouteByDescriptorId(String id) { 1201 final int count = mRoutes.size(); 1202 for (int i = 0; i < count; i++) { 1203 if (mRoutes.get(i).mDescriptorId.equals(id)) { 1204 return i; 1205 } 1206 } 1207 return -1; 1208 } 1209 1210 @Override 1211 public String toString() { 1212 return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName() 1213 + " }"; 1214 } 1215 } 1216 1217 /** 1218 * Interface for receiving events about media routing changes. 1219 * All methods of this interface will be called from the application's main thread. 1220 * <p> 1221 * A Callback will only receive events relevant to routes that the callback 1222 * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS} 1223 * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}. 1224 * </p> 1225 * 1226 * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int) 1227 * @see MediaRouter#removeCallback(Callback) 1228 */ 1229 public static abstract class Callback { 1230 /** 1231 * Called when the supplied media route becomes selected as the active route. 1232 * 1233 * @param router The media router reporting the event. 1234 * @param route The route that has been selected. 1235 */ 1236 public void onRouteSelected(MediaRouter router, RouteInfo route) { 1237 } 1238 1239 /** 1240 * Called when the supplied media route becomes unselected as the active route. 1241 * 1242 * @param router The media router reporting the event. 1243 * @param route The route that has been unselected. 1244 */ 1245 public void onRouteUnselected(MediaRouter router, RouteInfo route) { 1246 } 1247 1248 /** 1249 * Called when a media route has been added. 1250 * 1251 * @param router The media router reporting the event. 1252 * @param route The route that has become available for use. 1253 */ 1254 public void onRouteAdded(MediaRouter router, RouteInfo route) { 1255 } 1256 1257 /** 1258 * Called when a media route has been removed. 1259 * 1260 * @param router The media router reporting the event. 1261 * @param route The route that has been removed from availability. 1262 */ 1263 public void onRouteRemoved(MediaRouter router, RouteInfo route) { 1264 } 1265 1266 /** 1267 * Called when a property of the indicated media route has changed. 1268 * 1269 * @param router The media router reporting the event. 1270 * @param route The route that was changed. 1271 */ 1272 public void onRouteChanged(MediaRouter router, RouteInfo route) { 1273 } 1274 1275 /** 1276 * Called when a media route's volume changes. 1277 * 1278 * @param router The media router reporting the event. 1279 * @param route The route whose volume changed. 1280 */ 1281 public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) { 1282 } 1283 1284 /** 1285 * Called when a media route's presentation display changes. 1286 * <p> 1287 * This method is called whenever the route's presentation display becomes 1288 * available, is removed or has changes to some of its properties (such as its size). 1289 * </p> 1290 * 1291 * @param router The media router reporting the event. 1292 * @param route The route whose presentation display changed. 1293 * 1294 * @see RouteInfo#getPresentationDisplay() 1295 */ 1296 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) { 1297 } 1298 1299 /** 1300 * Called when a media route provider has been added. 1301 * 1302 * @param router The media router reporting the event. 1303 * @param provider The provider that has become available for use. 1304 */ 1305 public void onProviderAdded(MediaRouter router, ProviderInfo provider) { 1306 } 1307 1308 /** 1309 * Called when a media route provider has been removed. 1310 * 1311 * @param router The media router reporting the event. 1312 * @param provider The provider that has been removed from availability. 1313 */ 1314 public void onProviderRemoved(MediaRouter router, ProviderInfo provider) { 1315 } 1316 1317 /** 1318 * Called when a property of the indicated media route provider has changed. 1319 * 1320 * @param router The media router reporting the event. 1321 * @param provider The provider that was changed. 1322 */ 1323 public void onProviderChanged(MediaRouter router, ProviderInfo provider) { 1324 } 1325 } 1326 1327 /** 1328 * Callback which is invoked with the result of a media control request. 1329 * 1330 * @see RouteInfo#sendControlRequest 1331 */ 1332 public static abstract class ControlRequestCallback { 1333 /** 1334 * Called when a media control request succeeds. 1335 * 1336 * @param data Result data, or null if none. 1337 * Contents depend on the {@link MediaControlIntent media control action}. 1338 */ 1339 public void onResult(Bundle data) { 1340 } 1341 1342 /** 1343 * Called when a media control request fails. 1344 * 1345 * @param error A localized error message which may be shown to the user, or null 1346 * if the cause of the error is unclear. 1347 * @param data Error data, or null if none. 1348 * Contents depend on the {@link MediaControlIntent media control action}. 1349 */ 1350 public void onError(String error, Bundle data) { 1351 } 1352 } 1353 1354 private static final class CallbackRecord { 1355 public final MediaRouter mRouter; 1356 public final Callback mCallback; 1357 public MediaRouteSelector mSelector; 1358 public int mFlags; 1359 1360 public CallbackRecord(MediaRouter router, Callback callback) { 1361 mRouter = router; 1362 mCallback = callback; 1363 mSelector = MediaRouteSelector.EMPTY; 1364 } 1365 1366 public boolean filterRouteEvent(RouteInfo route) { 1367 return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0 1368 || route.matchesSelector(mSelector); 1369 } 1370 } 1371 1372 /** 1373 * Global state for the media router. 1374 * <p> 1375 * Media routes and media route providers are global to the process; their 1376 * state and the bulk of the media router implementation lives here. 1377 * </p> 1378 */ 1379 private static final class GlobalMediaRouter 1380 implements SystemMediaRouteProvider.SyncCallback, 1381 RegisteredMediaRouteProviderWatcher.Callback { 1382 private final Context mApplicationContext; 1383 private final ArrayList<WeakReference<MediaRouter>> mRouters = 1384 new ArrayList<WeakReference<MediaRouter>>(); 1385 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 1386 private final ArrayList<ProviderInfo> mProviders = 1387 new ArrayList<ProviderInfo>(); 1388 private final ArrayList<RemoteControlClientRecord> mRemoteControlClients = 1389 new ArrayList<RemoteControlClientRecord>(); 1390 private final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo = 1391 new RemoteControlClientCompat.PlaybackInfo(); 1392 private final ProviderCallback mProviderCallback = new ProviderCallback(); 1393 private final CallbackHandler mCallbackHandler = new CallbackHandler(); 1394 private final DisplayManagerCompat mDisplayManager; 1395 private final SystemMediaRouteProvider mSystemProvider; 1396 1397 private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher; 1398 private RouteInfo mDefaultRoute; 1399 private RouteInfo mSelectedRoute; 1400 private MediaRouteProvider.RouteController mSelectedRouteController; 1401 private MediaRouteDiscoveryRequest mDiscoveryRequest; 1402 1403 GlobalMediaRouter(Context applicationContext) { 1404 mApplicationContext = applicationContext; 1405 mDisplayManager = DisplayManagerCompat.getInstance(applicationContext); 1406 1407 // Add the system media route provider for interoperating with 1408 // the framework media router. This one is special and receives 1409 // synchronization messages from the media router. 1410 mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this); 1411 addProvider(mSystemProvider); 1412 } 1413 1414 public void start() { 1415 // Start watching for routes published by registered media route 1416 // provider services. 1417 mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher( 1418 mApplicationContext, this); 1419 mRegisteredProviderWatcher.start(); 1420 } 1421 1422 public MediaRouter getRouter(Context context) { 1423 MediaRouter router; 1424 for (int i = mRouters.size(); --i >= 0; ) { 1425 router = mRouters.get(i).get(); 1426 if (router == null) { 1427 mRouters.remove(i); 1428 } else if (router.mContext == context) { 1429 return router; 1430 } 1431 } 1432 router = new MediaRouter(context); 1433 mRouters.add(new WeakReference<MediaRouter>(router)); 1434 return router; 1435 } 1436 1437 public ContentResolver getContentResolver() { 1438 return mApplicationContext.getContentResolver(); 1439 } 1440 1441 public Context getProviderContext(String packageName) { 1442 if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) { 1443 return mApplicationContext; 1444 } 1445 try { 1446 return mApplicationContext.createPackageContext( 1447 packageName, Context.CONTEXT_RESTRICTED); 1448 } catch (NameNotFoundException ex) { 1449 return null; 1450 } 1451 } 1452 1453 public Display getDisplay(int displayId) { 1454 return mDisplayManager.getDisplay(displayId); 1455 } 1456 1457 public void sendControlRequest(RouteInfo route, 1458 Intent intent, ControlRequestCallback callback) { 1459 if (route == mSelectedRoute && mSelectedRouteController != null) { 1460 if (mSelectedRouteController.onControlRequest(intent, callback)) { 1461 return; 1462 } 1463 } 1464 if (callback != null) { 1465 callback.onError(null, null); 1466 } 1467 } 1468 1469 public void requestSetVolume(RouteInfo route, int volume) { 1470 if (route == mSelectedRoute && mSelectedRouteController != null) { 1471 mSelectedRouteController.onSetVolume(volume); 1472 } 1473 } 1474 1475 public void requestUpdateVolume(RouteInfo route, int delta) { 1476 if (route == mSelectedRoute && mSelectedRouteController != null) { 1477 mSelectedRouteController.onUpdateVolume(delta); 1478 } 1479 } 1480 1481 public List<RouteInfo> getRoutes() { 1482 return mRoutes; 1483 } 1484 1485 public List<ProviderInfo> getProviders() { 1486 return mProviders; 1487 } 1488 1489 public RouteInfo getDefaultRoute() { 1490 if (mDefaultRoute == null) { 1491 // This should never happen once the media router has been fully 1492 // initialized but it is good to check for the error in case there 1493 // is a bug in provider initialization. 1494 throw new IllegalStateException("There is no default route. " 1495 + "The media router has not yet been fully initialized."); 1496 } 1497 return mDefaultRoute; 1498 } 1499 1500 public RouteInfo getSelectedRoute() { 1501 if (mSelectedRoute == null) { 1502 // This should never happen once the media router has been fully 1503 // initialized but it is good to check for the error in case there 1504 // is a bug in provider initialization. 1505 throw new IllegalStateException("There is no currently selected route. " 1506 + "The media router has not yet been fully initialized."); 1507 } 1508 return mSelectedRoute; 1509 } 1510 1511 public void selectRoute(RouteInfo route) { 1512 if (!mRoutes.contains(route)) { 1513 Log.w(TAG, "Ignoring attempt to select removed route: " + route); 1514 return; 1515 } 1516 if (!route.mEnabled) { 1517 Log.w(TAG, "Ignoring attempt to select disabled route: " + route); 1518 return; 1519 } 1520 1521 setSelectedRouteInternal(route); 1522 } 1523 1524 public boolean isRouteAvailable(MediaRouteSelector selector, int flags) { 1525 // Check whether any existing routes match the selector. 1526 final int routeCount = mRoutes.size(); 1527 for (int i = 0; i < routeCount; i++) { 1528 RouteInfo route = mRoutes.get(i); 1529 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0 1530 && route.isDefault()) { 1531 continue; 1532 } 1533 if (route.matchesSelector(selector)) { 1534 return true; 1535 } 1536 } 1537 1538 // It doesn't look like we can find a matching route right now. 1539 return false; 1540 } 1541 1542 public void updateDiscoveryRequest() { 1543 // Combine all of the callback selectors and active scan flags. 1544 boolean discover = false; 1545 boolean activeScan = false; 1546 MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); 1547 for (int i = mRouters.size(); --i >= 0; ) { 1548 MediaRouter router = mRouters.get(i).get(); 1549 if (router == null) { 1550 mRouters.remove(i); 1551 } else { 1552 final int count = router.mCallbackRecords.size(); 1553 for (int j = 0; j < count; j++) { 1554 CallbackRecord callback = router.mCallbackRecords.get(j); 1555 builder.addSelector(callback.mSelector); 1556 if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { 1557 activeScan = true; 1558 discover = true; // perform active scan implies request discovery 1559 } 1560 if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) { 1561 discover = true; 1562 } 1563 } 1564 } 1565 } 1566 MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY; 1567 1568 // Create a new discovery request. 1569 if (mDiscoveryRequest != null 1570 && mDiscoveryRequest.getSelector().equals(selector) 1571 && mDiscoveryRequest.isActiveScan() == activeScan) { 1572 return; // no change 1573 } 1574 if (selector.isEmpty() && !activeScan) { 1575 // Discovery is not needed. 1576 if (mDiscoveryRequest == null) { 1577 return; // no change 1578 } 1579 mDiscoveryRequest = null; 1580 } else { 1581 // Discovery is needed. 1582 mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan); 1583 } 1584 if (DEBUG) { 1585 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest); 1586 } 1587 1588 // Notify providers. 1589 final int providerCount = mProviders.size(); 1590 for (int i = 0; i < providerCount; i++) { 1591 mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest); 1592 } 1593 } 1594 1595 @Override 1596 public void addProvider(MediaRouteProvider providerInstance) { 1597 int index = findProviderInfo(providerInstance); 1598 if (index < 0) { 1599 // 1. Add the provider to the list. 1600 ProviderInfo provider = new ProviderInfo(providerInstance); 1601 mProviders.add(provider); 1602 if (DEBUG) { 1603 Log.d(TAG, "Provider added: " + provider); 1604 } 1605 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider); 1606 // 2. Create the provider's contents. 1607 updateProviderContents(provider, providerInstance.getDescriptor()); 1608 // 3. Register the provider callback. 1609 providerInstance.setCallback(mProviderCallback); 1610 // 4. Set the discovery request. 1611 providerInstance.setDiscoveryRequest(mDiscoveryRequest); 1612 } 1613 } 1614 1615 @Override 1616 public void removeProvider(MediaRouteProvider providerInstance) { 1617 int index = findProviderInfo(providerInstance); 1618 if (index >= 0) { 1619 // 1. Unregister the provider callback. 1620 providerInstance.setCallback(null); 1621 // 2. Clear the discovery request. 1622 providerInstance.setDiscoveryRequest(null); 1623 // 3. Delete the provider's contents. 1624 ProviderInfo provider = mProviders.get(index); 1625 updateProviderContents(provider, null); 1626 // 4. Remove the provider from the list. 1627 if (DEBUG) { 1628 Log.d(TAG, "Provider removed: " + provider); 1629 } 1630 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider); 1631 mProviders.remove(index); 1632 } 1633 } 1634 1635 private void updateProviderDescriptor(MediaRouteProvider providerInstance, 1636 MediaRouteProviderDescriptor descriptor) { 1637 int index = findProviderInfo(providerInstance); 1638 if (index >= 0) { 1639 // Update the provider's contents. 1640 ProviderInfo provider = mProviders.get(index); 1641 updateProviderContents(provider, descriptor); 1642 } 1643 } 1644 1645 private int findProviderInfo(MediaRouteProvider providerInstance) { 1646 final int count = mProviders.size(); 1647 for (int i = 0; i < count; i++) { 1648 if (mProviders.get(i).mProviderInstance == providerInstance) { 1649 return i; 1650 } 1651 } 1652 return -1; 1653 } 1654 1655 private void updateProviderContents(ProviderInfo provider, 1656 MediaRouteProviderDescriptor providerDescriptor) { 1657 if (provider.updateDescriptor(providerDescriptor)) { 1658 // Update all existing routes and reorder them to match 1659 // the order of their descriptors. 1660 int targetIndex = 0; 1661 boolean selectedRouteDescriptorChanged = false; 1662 if (providerDescriptor != null) { 1663 if (providerDescriptor.isValid()) { 1664 final List<MediaRouteDescriptor> routeDescriptors = 1665 providerDescriptor.getRoutes(); 1666 final int routeCount = routeDescriptors.size(); 1667 for (int i = 0; i < routeCount; i++) { 1668 final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i); 1669 final String id = routeDescriptor.getId(); 1670 final int sourceIndex = provider.findRouteByDescriptorId(id); 1671 if (sourceIndex < 0) { 1672 // 1. Add the route to the list. 1673 String uniqueId = assignRouteUniqueId(provider, id); 1674 RouteInfo route = new RouteInfo(provider, id, uniqueId); 1675 provider.mRoutes.add(targetIndex++, route); 1676 mRoutes.add(route); 1677 // 2. Create the route's contents. 1678 route.updateDescriptor(routeDescriptor); 1679 // 3. Notify clients about addition. 1680 if (DEBUG) { 1681 Log.d(TAG, "Route added: " + route); 1682 } 1683 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route); 1684 } else if (sourceIndex < targetIndex) { 1685 Log.w(TAG, "Ignoring route descriptor with duplicate id: " 1686 + routeDescriptor); 1687 } else { 1688 // 1. Reorder the route within the list. 1689 RouteInfo route = provider.mRoutes.get(sourceIndex); 1690 Collections.swap(provider.mRoutes, 1691 sourceIndex, targetIndex++); 1692 // 2. Update the route's contents. 1693 int changes = route.updateDescriptor(routeDescriptor); 1694 // 3. Notify clients about changes. 1695 if (changes != 0) { 1696 if ((changes & RouteInfo.CHANGE_GENERAL) != 0) { 1697 if (DEBUG) { 1698 Log.d(TAG, "Route changed: " + route); 1699 } 1700 mCallbackHandler.post( 1701 CallbackHandler.MSG_ROUTE_CHANGED, route); 1702 } 1703 if ((changes & RouteInfo.CHANGE_VOLUME) != 0) { 1704 if (DEBUG) { 1705 Log.d(TAG, "Route volume changed: " + route); 1706 } 1707 mCallbackHandler.post( 1708 CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route); 1709 } 1710 if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) { 1711 if (DEBUG) { 1712 Log.d(TAG, "Route presentation display changed: " 1713 + route); 1714 } 1715 mCallbackHandler.post(CallbackHandler. 1716 MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route); 1717 } 1718 if (route == mSelectedRoute) { 1719 selectedRouteDescriptorChanged = true; 1720 } 1721 } 1722 } 1723 } 1724 } else { 1725 Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor); 1726 } 1727 } 1728 1729 // Dispose all remaining routes that do not have matching descriptors. 1730 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 1731 // 1. Delete the route's contents. 1732 RouteInfo route = provider.mRoutes.get(i); 1733 route.updateDescriptor(null); 1734 // 2. Remove the route from the list. 1735 mRoutes.remove(route); 1736 } 1737 1738 // Update the selected route if needed. 1739 updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged); 1740 1741 // Now notify clients about routes that were removed. 1742 // We do this after updating the selected route to ensure 1743 // that the framework media router observes the new route 1744 // selection before the removal since removing the currently 1745 // selected route may have side-effects. 1746 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 1747 RouteInfo route = provider.mRoutes.remove(i); 1748 if (DEBUG) { 1749 Log.d(TAG, "Route removed: " + route); 1750 } 1751 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route); 1752 } 1753 1754 // Notify provider changed. 1755 if (DEBUG) { 1756 Log.d(TAG, "Provider changed: " + provider); 1757 } 1758 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider); 1759 } 1760 } 1761 1762 private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) { 1763 // Although route descriptor ids are unique within a provider, it's 1764 // possible for there to be two providers with the same package name. 1765 // Therefore we must dedupe the composite id. 1766 String uniqueId = provider.getComponentName().flattenToShortString() 1767 + ":" + routeDescriptorId; 1768 if (findRouteByUniqueId(uniqueId) < 0) { 1769 return uniqueId; 1770 } 1771 for (int i = 2; ; i++) { 1772 String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i); 1773 if (findRouteByUniqueId(newUniqueId) < 0) { 1774 return newUniqueId; 1775 } 1776 } 1777 } 1778 1779 private int findRouteByUniqueId(String uniqueId) { 1780 final int count = mRoutes.size(); 1781 for (int i = 0; i < count; i++) { 1782 if (mRoutes.get(i).mUniqueId.equals(uniqueId)) { 1783 return i; 1784 } 1785 } 1786 return -1; 1787 } 1788 1789 private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) { 1790 // Update default route. 1791 if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) { 1792 Log.i(TAG, "Clearing the default route because it " 1793 + "is no longer selectable: " + mDefaultRoute); 1794 mDefaultRoute = null; 1795 } 1796 if (mDefaultRoute == null && !mRoutes.isEmpty()) { 1797 for (RouteInfo route : mRoutes) { 1798 if (isSystemDefaultRoute(route) && isRouteSelectable(route)) { 1799 mDefaultRoute = route; 1800 Log.i(TAG, "Found default route: " + mDefaultRoute); 1801 break; 1802 } 1803 } 1804 } 1805 1806 // Update selected route. 1807 if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) { 1808 Log.i(TAG, "Unselecting the current route because it " 1809 + "is no longer selectable: " + mSelectedRoute); 1810 setSelectedRouteInternal(null); 1811 } 1812 if (mSelectedRoute == null) { 1813 // Choose a new route. 1814 // This will have the side-effect of updating the playback info when 1815 // the new route is selected. 1816 setSelectedRouteInternal(chooseFallbackRoute()); 1817 } else if (selectedRouteDescriptorChanged) { 1818 // Update the playback info because the properties of the route have changed. 1819 updatePlaybackInfoFromSelectedRoute(); 1820 } 1821 } 1822 1823 private RouteInfo chooseFallbackRoute() { 1824 // When the current route is removed or no longer selectable, 1825 // we want to revert to a live audio route if there is 1826 // one (usually Bluetooth A2DP). Failing that, use 1827 // the default route. 1828 for (RouteInfo route : mRoutes) { 1829 if (route != mDefaultRoute 1830 && isSystemLiveAudioOnlyRoute(route) 1831 && isRouteSelectable(route)) { 1832 return route; 1833 } 1834 } 1835 return mDefaultRoute; 1836 } 1837 1838 private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) { 1839 return route.getProviderInstance() == mSystemProvider 1840 && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) 1841 && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 1842 } 1843 1844 private boolean isRouteSelectable(RouteInfo route) { 1845 // This tests whether the route is still valid and enabled. 1846 // The route descriptor field is set to null when the route is removed. 1847 return route.mDescriptor != null && route.mEnabled; 1848 } 1849 1850 private boolean isSystemDefaultRoute(RouteInfo route) { 1851 return route.getProviderInstance() == mSystemProvider 1852 && route.mDescriptorId.equals( 1853 SystemMediaRouteProvider.DEFAULT_ROUTE_ID); 1854 } 1855 1856 private void setSelectedRouteInternal(RouteInfo route) { 1857 if (mSelectedRoute != route) { 1858 if (mSelectedRoute != null) { 1859 if (DEBUG) { 1860 Log.d(TAG, "Route unselected: " + mSelectedRoute); 1861 } 1862 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute); 1863 if (mSelectedRouteController != null) { 1864 mSelectedRouteController.onUnselect(); 1865 mSelectedRouteController.onRelease(); 1866 mSelectedRouteController = null; 1867 } 1868 } 1869 1870 mSelectedRoute = route; 1871 1872 if (mSelectedRoute != null) { 1873 mSelectedRouteController = route.getProviderInstance().onCreateRouteController( 1874 route.mDescriptorId); 1875 if (mSelectedRouteController != null) { 1876 mSelectedRouteController.onSelect(); 1877 } 1878 if (DEBUG) { 1879 Log.d(TAG, "Route selected: " + mSelectedRoute); 1880 } 1881 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute); 1882 } 1883 1884 updatePlaybackInfoFromSelectedRoute(); 1885 } 1886 } 1887 1888 @Override 1889 public RouteInfo getSystemRouteByDescriptorId(String id) { 1890 int providerIndex = findProviderInfo(mSystemProvider); 1891 if (providerIndex >= 0) { 1892 ProviderInfo provider = mProviders.get(providerIndex); 1893 int routeIndex = provider.findRouteByDescriptorId(id); 1894 if (routeIndex >= 0) { 1895 return provider.mRoutes.get(routeIndex); 1896 } 1897 } 1898 return null; 1899 } 1900 1901 public void addRemoteControlClient(Object rcc) { 1902 int index = findRemoteControlClientRecord(rcc); 1903 if (index < 0) { 1904 RemoteControlClientRecord record = new RemoteControlClientRecord(rcc); 1905 mRemoteControlClients.add(record); 1906 } 1907 } 1908 1909 public void removeRemoteControlClient(Object rcc) { 1910 int index = findRemoteControlClientRecord(rcc); 1911 if (index >= 0) { 1912 RemoteControlClientRecord record = mRemoteControlClients.remove(index); 1913 record.disconnect(); 1914 } 1915 } 1916 1917 private int findRemoteControlClientRecord(Object rcc) { 1918 final int count = mRemoteControlClients.size(); 1919 for (int i = 0; i < count; i++) { 1920 RemoteControlClientRecord record = mRemoteControlClients.get(i); 1921 if (record.getRemoteControlClient() == rcc) { 1922 return i; 1923 } 1924 } 1925 return -1; 1926 } 1927 1928 private void updatePlaybackInfoFromSelectedRoute() { 1929 if (mSelectedRoute != null) { 1930 mPlaybackInfo.volume = mSelectedRoute.getVolume(); 1931 mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax(); 1932 mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling(); 1933 mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream(); 1934 mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType(); 1935 1936 final int count = mRemoteControlClients.size(); 1937 for (int i = 0; i < count; i++) { 1938 RemoteControlClientRecord record = mRemoteControlClients.get(i); 1939 record.updatePlaybackInfo(); 1940 } 1941 } 1942 } 1943 1944 private final class ProviderCallback extends MediaRouteProvider.Callback { 1945 @Override 1946 public void onDescriptorChanged(MediaRouteProvider provider, 1947 MediaRouteProviderDescriptor descriptor) { 1948 updateProviderDescriptor(provider, descriptor); 1949 } 1950 } 1951 1952 private final class RemoteControlClientRecord 1953 implements RemoteControlClientCompat.VolumeCallback { 1954 private final RemoteControlClientCompat mRccCompat; 1955 private boolean mDisconnected; 1956 1957 public RemoteControlClientRecord(Object rcc) { 1958 mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc); 1959 mRccCompat.setVolumeCallback(this); 1960 updatePlaybackInfo(); 1961 } 1962 1963 public Object getRemoteControlClient() { 1964 return mRccCompat.getRemoteControlClient(); 1965 } 1966 1967 public void disconnect() { 1968 mDisconnected = true; 1969 mRccCompat.setVolumeCallback(null); 1970 } 1971 1972 public void updatePlaybackInfo() { 1973 mRccCompat.setPlaybackInfo(mPlaybackInfo); 1974 } 1975 1976 @Override 1977 public void onVolumeSetRequest(int volume) { 1978 if (!mDisconnected && mSelectedRoute != null) { 1979 mSelectedRoute.requestSetVolume(volume); 1980 } 1981 } 1982 1983 @Override 1984 public void onVolumeUpdateRequest(int direction) { 1985 if (!mDisconnected && mSelectedRoute != null) { 1986 mSelectedRoute.requestUpdateVolume(direction); 1987 } 1988 } 1989 } 1990 1991 private final class CallbackHandler extends Handler { 1992 private final ArrayList<CallbackRecord> mTempCallbackRecords = 1993 new ArrayList<CallbackRecord>(); 1994 1995 private static final int MSG_TYPE_MASK = 0xff00; 1996 private static final int MSG_TYPE_ROUTE = 0x0100; 1997 private static final int MSG_TYPE_PROVIDER = 0x0200; 1998 1999 public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1; 2000 public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2; 2001 public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3; 2002 public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4; 2003 public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5; 2004 public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6; 2005 public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7; 2006 2007 public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1; 2008 public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2; 2009 public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3; 2010 2011 public void post(int msg, Object obj) { 2012 obtainMessage(msg, obj).sendToTarget(); 2013 } 2014 2015 @Override 2016 public void handleMessage(Message msg) { 2017 final int what = msg.what; 2018 final Object obj = msg.obj; 2019 2020 // Synchronize state with the system media router. 2021 syncWithSystemProvider(what, obj); 2022 2023 // Invoke all registered callbacks. 2024 // Build a list of callbacks before invoking them in case callbacks 2025 // are added or removed during dispatch. 2026 try { 2027 for (int i = mRouters.size(); --i >= 0; ) { 2028 MediaRouter router = mRouters.get(i).get(); 2029 if (router == null) { 2030 mRouters.remove(i); 2031 } else { 2032 mTempCallbackRecords.addAll(router.mCallbackRecords); 2033 } 2034 } 2035 2036 final int callbackCount = mTempCallbackRecords.size(); 2037 for (int i = 0; i < callbackCount; i++) { 2038 invokeCallback(mTempCallbackRecords.get(i), what, obj); 2039 } 2040 } finally { 2041 mTempCallbackRecords.clear(); 2042 } 2043 } 2044 2045 private void syncWithSystemProvider(int what, Object obj) { 2046 switch (what) { 2047 case MSG_ROUTE_ADDED: 2048 mSystemProvider.onSyncRouteAdded((RouteInfo)obj); 2049 break; 2050 case MSG_ROUTE_REMOVED: 2051 mSystemProvider.onSyncRouteRemoved((RouteInfo)obj); 2052 break; 2053 case MSG_ROUTE_CHANGED: 2054 mSystemProvider.onSyncRouteChanged((RouteInfo)obj); 2055 break; 2056 case MSG_ROUTE_SELECTED: 2057 mSystemProvider.onSyncRouteSelected((RouteInfo)obj); 2058 break; 2059 } 2060 } 2061 2062 private void invokeCallback(CallbackRecord record, int what, Object obj) { 2063 final MediaRouter router = record.mRouter; 2064 final MediaRouter.Callback callback = record.mCallback; 2065 switch (what & MSG_TYPE_MASK) { 2066 case MSG_TYPE_ROUTE: { 2067 final RouteInfo route = (RouteInfo)obj; 2068 if (!record.filterRouteEvent(route)) { 2069 break; 2070 } 2071 switch (what) { 2072 case MSG_ROUTE_ADDED: 2073 callback.onRouteAdded(router, route); 2074 break; 2075 case MSG_ROUTE_REMOVED: 2076 callback.onRouteRemoved(router, route); 2077 break; 2078 case MSG_ROUTE_CHANGED: 2079 callback.onRouteChanged(router, route); 2080 break; 2081 case MSG_ROUTE_VOLUME_CHANGED: 2082 callback.onRouteVolumeChanged(router, route); 2083 break; 2084 case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED: 2085 callback.onRoutePresentationDisplayChanged(router, route); 2086 break; 2087 case MSG_ROUTE_SELECTED: 2088 callback.onRouteSelected(router, route); 2089 break; 2090 case MSG_ROUTE_UNSELECTED: 2091 callback.onRouteUnselected(router, route); 2092 break; 2093 } 2094 break; 2095 } 2096 case MSG_TYPE_PROVIDER: { 2097 final ProviderInfo provider = (ProviderInfo)obj; 2098 switch (what) { 2099 case MSG_PROVIDER_ADDED: 2100 callback.onProviderAdded(router, provider); 2101 break; 2102 case MSG_PROVIDER_REMOVED: 2103 callback.onProviderRemoved(router, provider); 2104 break; 2105 case MSG_PROVIDER_CHANGED: 2106 callback.onProviderChanged(router, provider); 2107 break; 2108 } 2109 } 2110 } 2111 } 2112 } 2113 } 2114} 2115