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