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