MediaRouter.java revision 55b361aea868e53e848bc45af3a55ae43e7871c3
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 mStatus; 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 name of this route. 593 * 594 * @return The user-friendly name of a media route. This is the string presented 595 * to users who may select this as the active route. 596 */ 597 public String getName() { 598 return mName; 599 } 600 601 /** 602 * Gets the status of this route. 603 * 604 * @return The user-friendly status for a media route. This may include a description 605 * of the currently playing media, if available. 606 */ 607 public String getStatus() { 608 return mStatus; 609 } 610 611 /** 612 * Returns true if this route is enabled and may be selected. 613 * 614 * @return True if this route is enabled. 615 */ 616 public boolean isEnabled() { 617 return mEnabled; 618 } 619 620 /** 621 * Returns true if the route is in the process of connecting and is not 622 * yet ready for use. 623 * 624 * @return True if this route is in the process of connecting. 625 */ 626 public boolean isConnecting() { 627 return mConnecting; 628 } 629 630 /** 631 * Returns true if this route is currently selected. 632 * 633 * @return True if this route is currently selected. 634 * 635 * @see MediaRouter#getSelectedRoute 636 */ 637 public boolean isSelected() { 638 checkCallingThread(); 639 return sGlobal.getSelectedRoute() == this; 640 } 641 642 /** 643 * Returns true if this route is the default route. 644 * 645 * @return True if this route is the default route. 646 * 647 * @see MediaRouter#getDefaultRoute 648 */ 649 public boolean isDefault() { 650 checkCallingThread(); 651 return sGlobal.getDefaultRoute() == this; 652 } 653 654 /** 655 * Gets a list of {@link MediaControlIntent media control intent} filters that 656 * describe the capabilities of this route and the media control actions that 657 * it supports. 658 * 659 * @return A list of intent filters that specifies the media control intents that 660 * this route supports. 661 * 662 * @see MediaControlIntent 663 * @see #supportsControlCategory 664 * @see #supportsControlRequest 665 */ 666 public List<IntentFilter> getControlFilters() { 667 return mControlFilters; 668 } 669 670 /** 671 * Returns true if the route supports at least one of the capabilities 672 * described by a media route selector. 673 * 674 * @param selector The selector that specifies the capabilities to check. 675 * @return True if the route supports at least one of the capabilities 676 * described in the media route selector. 677 */ 678 public boolean matchesSelector(MediaRouteSelector selector) { 679 if (selector == null) { 680 throw new IllegalArgumentException("selector must not be null"); 681 } 682 checkCallingThread(); 683 return selector.matchesControlFilters(mControlFilters); 684 } 685 686 /** 687 * Returns true if the route supports the specified 688 * {@link MediaControlIntent media control} category. 689 * <p> 690 * Media control categories describe the capabilities of this route 691 * such as whether it supports live audio streaming or remote playback. 692 * </p> 693 * 694 * @param category A {@link MediaControlIntent media control} category 695 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 696 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 697 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 698 * media control category. 699 * @return True if the route supports the specified intent category. 700 * 701 * @see MediaControlIntent 702 * @see #getControlFilters 703 */ 704 public boolean supportsControlCategory(String category) { 705 if (category == null) { 706 throw new IllegalArgumentException("category must not be null"); 707 } 708 checkCallingThread(); 709 710 int count = mControlFilters.size(); 711 for (int i = 0; i < count; i++) { 712 if (mControlFilters.get(i).hasCategory(category)) { 713 return true; 714 } 715 } 716 return false; 717 } 718 719 /** 720 * Returns true if the route supports the specified 721 * {@link MediaControlIntent media control} request. 722 * <p> 723 * Media control requests are used to request the route to perform 724 * actions such as starting remote playback of a media item. 725 * </p> 726 * 727 * @param intent A {@link MediaControlIntent media control intent}. 728 * @return True if the route can handle the specified intent. 729 * 730 * @see MediaControlIntent 731 * @see #getControlFilters 732 */ 733 public boolean supportsControlRequest(Intent intent) { 734 if (intent == null) { 735 throw new IllegalArgumentException("intent must not be null"); 736 } 737 checkCallingThread(); 738 739 ContentResolver contentResolver = sGlobal.getContentResolver(); 740 int count = mControlFilters.size(); 741 for (int i = 0; i < count; i++) { 742 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) { 743 return true; 744 } 745 } 746 return false; 747 } 748 749 /** 750 * Sends a {@link MediaControlIntent media control} request to be performed 751 * asynchronously by the route's destination. 752 * <p> 753 * Media control requests are used to request the route to perform 754 * actions such as starting remote playback of a media item. 755 * </p><p> 756 * This function may only be called on a selected route. Control requests 757 * sent to unselected routes will fail. 758 * </p> 759 * 760 * @param intent A {@link MediaControlIntent media control intent}. 761 * @param callback A {@link ControlRequestCallback} to invoke with the result 762 * of the request, or null if no result is required. 763 * 764 * @see MediaControlIntent 765 */ 766 public void sendControlRequest(Intent intent, ControlRequestCallback callback) { 767 if (intent == null) { 768 throw new IllegalArgumentException("intent must not be null"); 769 } 770 checkCallingThread(); 771 772 sGlobal.sendControlRequest(this, intent, callback); 773 } 774 775 /** 776 * Gets the type of playback associated with this route. 777 * 778 * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL} 779 * or {@link #PLAYBACK_TYPE_REMOTE}. 780 */ 781 public int getPlaybackType() { 782 return mPlaybackType; 783 } 784 785 /** 786 * Gets the audio stream over which the playback associated with this route is performed. 787 * 788 * @return The stream over which the playback associated with this route is performed. 789 */ 790 public int getPlaybackStream() { 791 return mPlaybackStream; 792 } 793 794 /** 795 * Gets information about how volume is handled on the route. 796 * 797 * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED} 798 * or {@link #PLAYBACK_VOLUME_VARIABLE}. 799 */ 800 public int getVolumeHandling() { 801 return mVolumeHandling; 802 } 803 804 /** 805 * Gets the current volume for this route. Depending on the route, this may only 806 * be valid if the route is currently selected. 807 * 808 * @return The volume at which the playback associated with this route is performed. 809 */ 810 public int getVolume() { 811 return mVolume; 812 } 813 814 /** 815 * Gets the maximum volume at which the playback associated with this route is performed. 816 * 817 * @return The maximum volume at which the playback associated with 818 * this route is performed. 819 */ 820 public int getVolumeMax() { 821 return mVolumeMax; 822 } 823 824 /** 825 * Requests a volume change for this route asynchronously. 826 * <p> 827 * This function may only be called on a selected route. It will have 828 * no effect if the route is currently unselected. 829 * </p> 830 * 831 * @param volume The new volume value between 0 and {@link #getVolumeMax}. 832 */ 833 public void requestSetVolume(int volume) { 834 checkCallingThread(); 835 sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume))); 836 } 837 838 /** 839 * Requests an incremental volume update for this route asynchronously. 840 * <p> 841 * This function may only be called on a selected route. It will have 842 * no effect if the route is currently unselected. 843 * </p> 844 * 845 * @param delta The delta to add to the current volume. 846 */ 847 public void requestUpdateVolume(int delta) { 848 checkCallingThread(); 849 if (delta != 0) { 850 sGlobal.requestUpdateVolume(this, delta); 851 } 852 } 853 854 /** 855 * Gets the {@link Display} that should be used by the application to show 856 * a {@link android.app.Presentation} on an external display when this route is selected. 857 * Depending on the route, this may only be valid if the route is currently 858 * selected. 859 * <p> 860 * The preferred presentation display may change independently of the route 861 * being selected or unselected. For example, the presentation display 862 * of the default system route may change when an external HDMI display is connected 863 * or disconnected even though the route itself has not changed. 864 * </p><p> 865 * This method may return null if there is no external display associated with 866 * the route or if the display is not ready to show UI yet. 867 * </p><p> 868 * The application should listen for changes to the presentation display 869 * using the {@link Callback#onRoutePresentationDisplayChanged} callback and 870 * show or dismiss its {@link android.app.Presentation} accordingly when the display 871 * becomes available or is removed. 872 * </p><p> 873 * This method only makes sense for 874 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes. 875 * </p> 876 * 877 * @return The preferred presentation display to use when this route is 878 * selected or null if none. 879 * 880 * @see MediaControlIntent#CATEGORY_LIVE_VIDEO 881 * @see android.app.Presentation 882 */ 883 public Display getPresentationDisplay() { 884 checkCallingThread(); 885 if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) { 886 mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId); 887 } 888 return mPresentationDisplay; 889 } 890 891 /** 892 * Gets a collection of extra properties about this route that were supplied 893 * by its media route provider, or null if none. 894 */ 895 public Bundle getExtras() { 896 return mExtras; 897 } 898 899 /** 900 * Selects this media route. 901 */ 902 public void select() { 903 checkCallingThread(); 904 sGlobal.selectRoute(this); 905 } 906 907 @Override 908 public String toString() { 909 return "MediaRouter.RouteInfo{ name=" + mName 910 + ", status=" + mStatus 911 + ", enabled=" + mEnabled 912 + ", connecting=" + mConnecting 913 + ", playbackType=" + mPlaybackType 914 + ", playbackStream=" + mPlaybackStream 915 + ", volumeHandling=" + mVolumeHandling 916 + ", volume=" + mVolume 917 + ", volumeMax=" + mVolumeMax 918 + ", presentationDisplayId=" + mPresentationDisplayId 919 + ", extras=" + mExtras 920 + ", providerPackageName=" + mProvider.getPackageName() 921 + " }"; 922 } 923 924 int updateDescriptor(MediaRouteDescriptor descriptor) { 925 int changes = 0; 926 if (mDescriptor != descriptor) { 927 mDescriptor = descriptor; 928 if (descriptor != null) { 929 if (!equal(mName, descriptor.getName())) { 930 mName = descriptor.getName(); 931 changes |= CHANGE_GENERAL; 932 } 933 if (!equal(mStatus, descriptor.getStatus())) { 934 mStatus = descriptor.getStatus(); 935 changes |= CHANGE_GENERAL; 936 } 937 if (mEnabled != descriptor.isEnabled()) { 938 mEnabled = descriptor.isEnabled(); 939 changes |= CHANGE_GENERAL; 940 } 941 if (mConnecting != descriptor.isConnecting()) { 942 mConnecting = descriptor.isConnecting(); 943 changes |= CHANGE_GENERAL; 944 } 945 if (!mControlFilters.equals(descriptor.getControlFilters())) { 946 mControlFilters.clear(); 947 mControlFilters.addAll(descriptor.getControlFilters()); 948 changes |= CHANGE_GENERAL; 949 } 950 if (mPlaybackType != descriptor.getPlaybackType()) { 951 mPlaybackType = descriptor.getPlaybackType(); 952 changes |= CHANGE_GENERAL; 953 } 954 if (mPlaybackStream != descriptor.getPlaybackStream()) { 955 mPlaybackStream = descriptor.getPlaybackStream(); 956 changes |= CHANGE_GENERAL; 957 } 958 if (mVolumeHandling != descriptor.getVolumeHandling()) { 959 mVolumeHandling = descriptor.getVolumeHandling(); 960 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 961 } 962 if (mVolume != descriptor.getVolume()) { 963 mVolume = descriptor.getVolume(); 964 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 965 } 966 if (mVolumeMax != descriptor.getVolumeMax()) { 967 mVolumeMax = descriptor.getVolumeMax(); 968 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 969 } 970 if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) { 971 mPresentationDisplayId = descriptor.getPresentationDisplayId(); 972 mPresentationDisplay = null; 973 changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY; 974 } 975 if (!equal(mExtras, descriptor.getExtras())) { 976 mExtras = descriptor.getExtras(); 977 changes |= CHANGE_GENERAL; 978 } 979 } 980 } 981 return changes; 982 } 983 984 String getDescriptorId() { 985 return mDescriptorId; 986 } 987 988 MediaRouteProvider getProviderInstance() { 989 return mProvider.getProviderInstance(); 990 } 991 } 992 993 /** 994 * Provides information about a media route provider. 995 * <p> 996 * This object may be used to determine which media route provider has 997 * published a particular route. 998 * </p> 999 */ 1000 public static final class ProviderInfo { 1001 private final MediaRouteProvider mProviderInstance; 1002 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 1003 private final ArrayList<IntentFilter> mDiscoverableControlFilters = 1004 new ArrayList<IntentFilter>(); 1005 1006 private final ProviderMetadata mMetadata; 1007 private MediaRouteProviderDescriptor mDescriptor; 1008 private Resources mResources; 1009 private boolean mResourcesNotAvailable; 1010 1011 ProviderInfo(MediaRouteProvider provider) { 1012 mProviderInstance = provider; 1013 mMetadata = provider.getMetadata(); 1014 } 1015 1016 /** 1017 * Gets the provider's underlying {@link MediaRouteProvider} instance. 1018 */ 1019 public MediaRouteProvider getProviderInstance() { 1020 checkCallingThread(); 1021 return mProviderInstance; 1022 } 1023 1024 /** 1025 * Gets the package name of the media route provider service. 1026 */ 1027 public String getPackageName() { 1028 return mMetadata.getPackageName(); 1029 } 1030 1031 /** 1032 * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider. 1033 */ 1034 public List<RouteInfo> getRoutes() { 1035 checkCallingThread(); 1036 return mRoutes; 1037 } 1038 1039 /** 1040 * Returns true if the provider requires active scans to discover routes. 1041 * <p> 1042 * To provide the best user experience, a media route provider should passively 1043 * discover and publish changes to route descriptors in the background. 1044 * However, for some providers, scanning for routes may use a significant 1045 * amount of power or may interfere with wireless network connectivity. 1046 * If this is the case, then the provider will indicate that it requires 1047 * active scans to discover routes by setting this flag. Active scans 1048 * will be performed when the user opens the route chooser dialog. 1049 * </p> 1050 */ 1051 public boolean isActiveScanRequired() { 1052 checkCallingThread(); 1053 return mDescriptor != null && mDescriptor.isActiveScanRequired(); 1054 } 1055 1056 /** 1057 * Gets a list of {@link MediaControlIntent media route control filters} that 1058 * describe the union of capabilities of all routes that this provider can 1059 * possibly discover. 1060 * <p> 1061 * Because a route provider may not know what to look for until an 1062 * application actually asks for it, the contents of the discoverable control 1063 * filter list may change depending on the route selectors that applications have 1064 * actually specified when {@link MediaRouter#addCallback registering callbacks} 1065 * on the media router to discover routes. 1066 * </p> 1067 */ 1068 public List<IntentFilter> getDiscoverableControlFilters() { 1069 checkCallingThread(); 1070 return mDiscoverableControlFilters; 1071 } 1072 1073 Resources getResources() { 1074 if (mResources == null && !mResourcesNotAvailable) { 1075 String packageName = getPackageName(); 1076 Context context = sGlobal.getProviderContext(packageName); 1077 if (context != null) { 1078 mResources = context.getResources(); 1079 } else { 1080 Log.w(TAG, "Unable to obtain resources for route provider package: " 1081 + packageName); 1082 mResourcesNotAvailable = true; 1083 } 1084 } 1085 return mResources; 1086 } 1087 1088 boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) { 1089 if (mDescriptor != descriptor) { 1090 mDescriptor = descriptor; 1091 1092 if (!mDiscoverableControlFilters.equals( 1093 descriptor.getDiscoverableControlFilters())) { 1094 mDiscoverableControlFilters.clear(); 1095 mDiscoverableControlFilters.addAll(descriptor.getDiscoverableControlFilters()); 1096 } 1097 return true; 1098 } 1099 return false; 1100 } 1101 1102 int findRouteByDescriptorId(String id) { 1103 final int count = mRoutes.size(); 1104 for (int i = 0; i < count; i++) { 1105 if (mRoutes.get(i).mDescriptorId.equals(id)) { 1106 return i; 1107 } 1108 } 1109 return -1; 1110 } 1111 1112 @Override 1113 public String toString() { 1114 return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName() 1115 + ", isActiveScanRequired=" + isActiveScanRequired() 1116 + " }"; 1117 } 1118 } 1119 1120 /** 1121 * Interface for receiving events about media routing changes. 1122 * All methods of this interface will be called from the application's main thread. 1123 * <p> 1124 * A Callback will only receive events relevant to routes that the callback 1125 * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS} 1126 * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}. 1127 * </p> 1128 * 1129 * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int) 1130 * @see MediaRouter#removeCallback(Callback) 1131 */ 1132 public static abstract class Callback { 1133 /** 1134 * Called when the supplied media route becomes selected as the active route. 1135 * 1136 * @param router The media router reporting the event. 1137 * @param route The route that has been selected. 1138 */ 1139 public void onRouteSelected(MediaRouter router, RouteInfo route) { 1140 } 1141 1142 /** 1143 * Called when the supplied media route becomes unselected as the active route. 1144 * 1145 * @param router The media router reporting the event. 1146 * @param route The route that has been unselected. 1147 */ 1148 public void onRouteUnselected(MediaRouter router, RouteInfo route) { 1149 } 1150 1151 /** 1152 * Called when a media route has been added. 1153 * 1154 * @param router The media router reporting the event. 1155 * @param route The route that has become available for use. 1156 */ 1157 public void onRouteAdded(MediaRouter router, RouteInfo route) { 1158 } 1159 1160 /** 1161 * Called when a media route has been removed. 1162 * 1163 * @param router The media router reporting the event. 1164 * @param route The route that has been removed from availability. 1165 */ 1166 public void onRouteRemoved(MediaRouter router, RouteInfo route) { 1167 } 1168 1169 /** 1170 * Called when a property of the indicated media route has changed. 1171 * 1172 * @param router The media router reporting the event. 1173 * @param route The route that was changed. 1174 */ 1175 public void onRouteChanged(MediaRouter router, RouteInfo route) { 1176 } 1177 1178 /** 1179 * Called when a media route's volume changes. 1180 * 1181 * @param router The media router reporting the event. 1182 * @param route The route whose volume changed. 1183 */ 1184 public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) { 1185 } 1186 1187 /** 1188 * Called when a media route's presentation display changes. 1189 * <p> 1190 * This method is called whenever the route's presentation display becomes 1191 * available, is removed or has changes to some of its properties (such as its size). 1192 * </p> 1193 * 1194 * @param router The media router reporting the event. 1195 * @param route The route whose presentation display changed. 1196 * 1197 * @see RouteInfo#getPresentationDisplay() 1198 */ 1199 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) { 1200 } 1201 1202 /** 1203 * Called when a media route provider has been added. 1204 * 1205 * @param router The media router reporting the event. 1206 * @param provider The provider that has become available for use. 1207 */ 1208 public void onProviderAdded(MediaRouter router, ProviderInfo provider) { 1209 } 1210 1211 /** 1212 * Called when a media route provider has been removed. 1213 * 1214 * @param router The media router reporting the event. 1215 * @param provider The provider that has been removed from availability. 1216 */ 1217 public void onProviderRemoved(MediaRouter router, ProviderInfo provider) { 1218 } 1219 1220 /** 1221 * Called when a property of the indicated media route provider has changed. 1222 * 1223 * @param router The media router reporting the event. 1224 * @param provider The provider that was changed. 1225 */ 1226 public void onProviderChanged(MediaRouter router, ProviderInfo provider) { 1227 } 1228 } 1229 1230 /** 1231 * Callback which is invoked with the result of a media control request. 1232 * 1233 * @see RouteInfo#sendControlRequest 1234 */ 1235 public static abstract class ControlRequestCallback { 1236 /** 1237 * Result code: The media control action succeeded. 1238 */ 1239 public static final int REQUEST_SUCCEEDED = 0; 1240 1241 /** 1242 * Result code: The media control action failed. 1243 */ 1244 public static final int REQUEST_FAILED = -1; 1245 1246 /** 1247 * Called with the result of the media control request. 1248 * 1249 * @param result The result code: {@link #REQUEST_SUCCEEDED}, or {@link #REQUEST_FAILED}. 1250 * @param data Additional result data. Contents depend on the media control action. 1251 */ 1252 public void onResult(int result, Bundle data) { 1253 } 1254 } 1255 1256 private static final class CallbackRecord { 1257 public final Callback mCallback; 1258 public MediaRouteSelector mSelector; 1259 public int mFlags; 1260 1261 public CallbackRecord(Callback callback) { 1262 mCallback = callback; 1263 mSelector = MediaRouteSelector.EMPTY; 1264 } 1265 1266 public boolean filterRouteEvent(RouteInfo route) { 1267 return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0 1268 || route.matchesSelector(mSelector); 1269 } 1270 } 1271 1272 /** 1273 * Global state for the media router. 1274 * <p> 1275 * Media routes and media route providers are global to the process; their 1276 * state and the bulk of the media router implementation lives here. 1277 * </p> 1278 */ 1279 private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback { 1280 private final Context mApplicationContext; 1281 private final MediaRouter mApplicationRouter; 1282 private final WeakHashMap<Context, MediaRouter> mRouters = 1283 new WeakHashMap<Context, MediaRouter>(); 1284 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 1285 private final ArrayList<ProviderInfo> mProviders = 1286 new ArrayList<ProviderInfo>(); 1287 private final ProviderCallback mProviderCallback = new ProviderCallback(); 1288 private final CallbackHandler mCallbackHandler = new CallbackHandler(); 1289 private final DisplayManagerCompat mDisplayManager; 1290 private final SystemMediaRouteProvider mSystemProvider; 1291 1292 private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher; 1293 private RouteInfo mDefaultRoute; 1294 private RouteInfo mSelectedRoute; 1295 private MediaRouteProvider.RouteController mSelectedRouteController; 1296 private MediaRouteDiscoveryRequest mDiscoveryRequest; 1297 1298 GlobalMediaRouter(Context applicationContext) { 1299 mApplicationContext = applicationContext; 1300 mDisplayManager = DisplayManagerCompat.getInstance(applicationContext); 1301 mApplicationRouter = getRouter(applicationContext); 1302 1303 // Add the system media route provider for interoperating with 1304 // the framework media router. This one is special and receives 1305 // synchronization messages from the media router. 1306 mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this); 1307 addProvider(mSystemProvider); 1308 } 1309 1310 public void start() { 1311 // Start watching for routes published by registered media route 1312 // provider services. 1313 mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher( 1314 mApplicationContext, mApplicationRouter); 1315 mRegisteredProviderWatcher.start(); 1316 } 1317 1318 public MediaRouter getRouter(Context context) { 1319 MediaRouter router = mRouters.get(context); 1320 if (router == null) { 1321 router = new MediaRouter(context); 1322 mRouters.put(context, router); 1323 } 1324 return router; 1325 } 1326 1327 public ContentResolver getContentResolver() { 1328 return mApplicationContext.getContentResolver(); 1329 } 1330 1331 public Context getProviderContext(String packageName) { 1332 if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) { 1333 return mApplicationContext; 1334 } 1335 try { 1336 return mApplicationContext.createPackageContext( 1337 packageName, Context.CONTEXT_RESTRICTED); 1338 } catch (NameNotFoundException ex) { 1339 return null; 1340 } 1341 } 1342 1343 public Display getDisplay(int displayId) { 1344 return mDisplayManager.getDisplay(displayId); 1345 } 1346 1347 public void sendControlRequest(RouteInfo route, 1348 Intent intent, ControlRequestCallback callback) { 1349 if (route == mSelectedRoute && mSelectedRouteController != null) { 1350 if (mSelectedRouteController.onControlRequest(intent, callback)) { 1351 return; 1352 } 1353 } 1354 if (callback != null) { 1355 callback.onResult(ControlRequestCallback.REQUEST_FAILED, null); 1356 } 1357 } 1358 1359 public void requestSetVolume(RouteInfo route, int volume) { 1360 if (route == mSelectedRoute && mSelectedRouteController != null) { 1361 mSelectedRouteController.onSetVolume(volume); 1362 } 1363 } 1364 1365 public void requestUpdateVolume(RouteInfo route, int delta) { 1366 if (route == mSelectedRoute && mSelectedRouteController != null) { 1367 mSelectedRouteController.onUpdateVolume(delta); 1368 } 1369 } 1370 1371 public List<RouteInfo> getRoutes() { 1372 return mRoutes; 1373 } 1374 1375 public List<ProviderInfo> getProviders() { 1376 return mProviders; 1377 } 1378 1379 public RouteInfo getDefaultRoute() { 1380 if (mDefaultRoute == null) { 1381 // This should never happen once the media router has been fully 1382 // initialized but it is good to check for the error in case there 1383 // is a bug in provider initialization. 1384 throw new IllegalStateException("There is no default route. " 1385 + "The media router has not yet been fully initialized."); 1386 } 1387 return mDefaultRoute; 1388 } 1389 1390 public RouteInfo getSelectedRoute() { 1391 if (mSelectedRoute == null) { 1392 // This should never happen once the media router has been fully 1393 // initialized but it is good to check for the error in case there 1394 // is a bug in provider initialization. 1395 throw new IllegalStateException("There is no currently selected route. " 1396 + "The media router has not yet been fully initialized."); 1397 } 1398 return mSelectedRoute; 1399 } 1400 1401 public void selectRoute(RouteInfo route) { 1402 if (!mRoutes.contains(route)) { 1403 Log.w(TAG, "Ignoring attempt to select removed route: " + route); 1404 return; 1405 } 1406 if (!route.mEnabled) { 1407 Log.w(TAG, "Ignoring attempt to select disabled route: " + route); 1408 return; 1409 } 1410 1411 setSelectedRouteInternal(route); 1412 } 1413 1414 public boolean isRouteAvailable(MediaRouteSelector selector, int flags) { 1415 // Check whether any existing routes match the selector. 1416 final int routeCount = mRoutes.size(); 1417 for (int i = 0; i < routeCount; i++) { 1418 RouteInfo route = mRoutes.get(i); 1419 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0 1420 && route.isDefault()) { 1421 continue; 1422 } 1423 if (route.matchesSelector(selector)) { 1424 return true; 1425 } 1426 } 1427 1428 // Check whether any provider could possibly discover a matching route 1429 // if a required active scan were performed. 1430 if ((flags & AVAILABILITY_FLAG_CONSIDER_ACTIVE_SCAN) != 0) { 1431 final int providerCount = mProviders.size(); 1432 for (int i = 0; i < providerCount; i++) { 1433 ProviderInfo provider = mProviders.get(i); 1434 if (provider.isActiveScanRequired() && selector.matchesControlFilters( 1435 provider.getDiscoverableControlFilters())) { 1436 return true; 1437 } 1438 } 1439 } 1440 1441 // It doesn't look like we can find a matching route right now. 1442 return false; 1443 } 1444 1445 public void updateDiscoveryRequest() { 1446 // Combine all of the callback selectors and active scan flags. 1447 boolean activeScan = false; 1448 MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); 1449 for (MediaRouter router : mRouters.values()) { 1450 final int count = router.mCallbackRecords.size(); 1451 for (int i = 0; i < count; i++) { 1452 CallbackRecord callback = router.mCallbackRecords.get(i); 1453 builder.addSelector(callback.mSelector); 1454 if ((callback.mFlags & CALLBACK_FLAG_ACTIVE_SCAN) != 0) { 1455 activeScan = true; 1456 } 1457 } 1458 } 1459 MediaRouteSelector selector = builder.build(); 1460 1461 // Create a new discovery request. 1462 if (mDiscoveryRequest != null 1463 && mDiscoveryRequest.getSelector().equals(selector) 1464 && mDiscoveryRequest.isActiveScan() == activeScan) { 1465 return; // no change 1466 } 1467 if (selector.isEmpty() && !activeScan) { 1468 // Discovery is not needed. 1469 if (mDiscoveryRequest == null) { 1470 return; // no change 1471 } 1472 mDiscoveryRequest = null; 1473 } else { 1474 // Discovery is needed. 1475 mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan); 1476 } 1477 if (DEBUG) { 1478 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest); 1479 } 1480 1481 // Notify providers. 1482 final int providerCount = mProviders.size(); 1483 for (int i = 0; i < providerCount; i++) { 1484 mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest); 1485 } 1486 } 1487 1488 public void addProvider(MediaRouteProvider providerInstance) { 1489 int index = findProviderInfo(providerInstance); 1490 if (index < 0) { 1491 // 1. Add the provider to the list. 1492 ProviderInfo provider = new ProviderInfo(providerInstance); 1493 mProviders.add(provider); 1494 if (DEBUG) { 1495 Log.d(TAG, "Provider added: " + provider); 1496 } 1497 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider); 1498 // 2. Create the provider's contents. 1499 updateProviderContents(provider, providerInstance.getDescriptor()); 1500 // 3. Register the provider callback. 1501 providerInstance.setCallback(mProviderCallback); 1502 // 4. Set the discovery request. 1503 providerInstance.setDiscoveryRequest(mDiscoveryRequest); 1504 } 1505 } 1506 1507 public void removeProvider(MediaRouteProvider providerInstance) { 1508 int index = findProviderInfo(providerInstance); 1509 if (index >= 0) { 1510 // 1. Unregister the provider callback. 1511 providerInstance.setCallback(null); 1512 // 2. Clear the discovery request. 1513 providerInstance.setDiscoveryRequest(null); 1514 // 3. Delete the provider's contents. 1515 ProviderInfo provider = mProviders.get(index); 1516 updateProviderContents(provider, null); 1517 // 4. Remove the provider from the list. 1518 if (DEBUG) { 1519 Log.d(TAG, "Provider removed: " + provider); 1520 } 1521 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider); 1522 mProviders.remove(index); 1523 } 1524 } 1525 1526 private void updateProviderDescriptor(MediaRouteProvider providerInstance, 1527 MediaRouteProviderDescriptor descriptor) { 1528 int index = findProviderInfo(providerInstance); 1529 if (index >= 0) { 1530 // Update the provider's contents. 1531 ProviderInfo provider = mProviders.get(index); 1532 updateProviderContents(provider, descriptor); 1533 } 1534 } 1535 1536 private int findProviderInfo(MediaRouteProvider providerInstance) { 1537 final int count = mProviders.size(); 1538 for (int i = 0; i < count; i++) { 1539 if (mProviders.get(i).mProviderInstance == providerInstance) { 1540 return i; 1541 } 1542 } 1543 return -1; 1544 } 1545 1546 private void updateProviderContents(ProviderInfo provider, 1547 MediaRouteProviderDescriptor providerDescriptor) { 1548 if (provider.updateDescriptor(providerDescriptor)) { 1549 // Update all existing routes and reorder them to match 1550 // the order of their descriptors. 1551 int targetIndex = 0; 1552 if (providerDescriptor != null) { 1553 if (providerDescriptor.isValid()) { 1554 final List<MediaRouteDescriptor> routeDescriptors = 1555 providerDescriptor.getRoutes(); 1556 final int routeCount = routeDescriptors.size(); 1557 for (int i = 0; i < routeCount; i++) { 1558 final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i); 1559 final String id = routeDescriptor.getId(); 1560 final int sourceIndex = provider.findRouteByDescriptorId(id); 1561 if (sourceIndex < 0) { 1562 // 1. Add the route to the list. 1563 RouteInfo route = new RouteInfo(provider, id); 1564 provider.mRoutes.add(targetIndex++, route); 1565 mRoutes.add(route); 1566 // 2. Create the route's contents. 1567 route.updateDescriptor(routeDescriptor); 1568 // 3. Notify clients about addition. 1569 if (DEBUG) { 1570 Log.d(TAG, "Route added: " + route); 1571 } 1572 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route); 1573 } else if (sourceIndex < targetIndex) { 1574 Log.w(TAG, "Ignoring route descriptor with duplicate id: " 1575 + routeDescriptor); 1576 } else { 1577 // 1. Reorder the route within the list. 1578 RouteInfo route = provider.mRoutes.get(sourceIndex); 1579 Collections.swap(provider.mRoutes, 1580 sourceIndex, targetIndex++); 1581 // 2. Update the route's contents. 1582 int changes = route.updateDescriptor(routeDescriptor); 1583 // 3. Unselect route if needed before notifying about changes. 1584 unselectRouteIfNeeded(route); 1585 // 4. Notify clients about changes. 1586 if ((changes & RouteInfo.CHANGE_GENERAL) != 0) { 1587 if (DEBUG) { 1588 Log.d(TAG, "Route changed: " + route); 1589 } 1590 mCallbackHandler.post( 1591 CallbackHandler.MSG_ROUTE_CHANGED, route); 1592 } 1593 if ((changes & RouteInfo.CHANGE_VOLUME) != 0) { 1594 if (DEBUG) { 1595 Log.d(TAG, "Route volume changed: " + route); 1596 } 1597 mCallbackHandler.post( 1598 CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route); 1599 } 1600 if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) { 1601 if (DEBUG) { 1602 Log.d(TAG, "Route presentation display changed: " 1603 + route); 1604 } 1605 mCallbackHandler.post(CallbackHandler. 1606 MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route); 1607 } 1608 } 1609 } 1610 } else { 1611 Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor); 1612 } 1613 } 1614 1615 // Dispose all remaining routes that do not have matching descriptors. 1616 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 1617 // 1. Delete the route's contents. 1618 RouteInfo route = provider.mRoutes.get(i); 1619 route.updateDescriptor(null); 1620 // 2. Remove the route from the list. 1621 mRoutes.remove(route); 1622 provider.mRoutes.remove(i); 1623 // 3. Unselect route if needed before notifying about removal. 1624 unselectRouteIfNeeded(route); 1625 // 4. Notify clients about removal. 1626 if (DEBUG) { 1627 Log.d(TAG, "Route removed: " + route); 1628 } 1629 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route); 1630 } 1631 1632 // Notify provider changed. 1633 if (DEBUG) { 1634 Log.d(TAG, "Provider changed: " + provider); 1635 } 1636 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider); 1637 1638 // Choose a new selected route if needed. 1639 selectRouteIfNeeded(); 1640 } 1641 } 1642 1643 private void unselectRouteIfNeeded(RouteInfo route) { 1644 if (mDefaultRoute == route && !isRouteSelectable(route)) { 1645 Log.i(TAG, "Choosing a new default route because the current one " 1646 + "is no longer selectable: " + route); 1647 mDefaultRoute = null; 1648 } 1649 if (mSelectedRoute == route && !isRouteSelectable(route)) { 1650 Log.i(TAG, "Choosing a new selected route because the current one " 1651 + "is no longer selectable: " + route); 1652 setSelectedRouteInternal(null); 1653 } 1654 } 1655 1656 private void selectRouteIfNeeded() { 1657 if (mDefaultRoute == null && !mRoutes.isEmpty()) { 1658 for (RouteInfo route : mRoutes) { 1659 if (isSystemDefaultRoute(route) && isRouteSelectable(route)) { 1660 mDefaultRoute = route; 1661 break; 1662 } 1663 } 1664 } 1665 if (mSelectedRoute == null) { 1666 setSelectedRouteInternal(mDefaultRoute); 1667 } 1668 } 1669 1670 private boolean isRouteSelectable(RouteInfo route) { 1671 // This tests whether the route is still valid and enabled. 1672 // The route descriptor field is set to null when the route is removed. 1673 return route.mDescriptor != null && route.mEnabled; 1674 } 1675 1676 private boolean isSystemDefaultRoute(RouteInfo route) { 1677 return route.getProviderInstance() == mSystemProvider 1678 && route.mDescriptorId.equals( 1679 SystemMediaRouteProvider.DEFAULT_ROUTE_ID); 1680 } 1681 1682 private void setSelectedRouteInternal(RouteInfo route) { 1683 if (mSelectedRoute != route) { 1684 if (mSelectedRoute != null) { 1685 if (DEBUG) { 1686 Log.d(TAG, "Route unselected: " + mSelectedRoute); 1687 } 1688 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute); 1689 if (mSelectedRouteController != null) { 1690 mSelectedRouteController.onUnselect(); 1691 mSelectedRouteController.onRelease(); 1692 mSelectedRouteController = null; 1693 } 1694 } 1695 1696 mSelectedRoute = route; 1697 1698 if (mSelectedRoute != null) { 1699 mSelectedRouteController = route.getProviderInstance().onCreateRouteController( 1700 route.mDescriptorId); 1701 if (mSelectedRouteController != null) { 1702 mSelectedRouteController.onSelect(); 1703 } 1704 if (DEBUG) { 1705 Log.d(TAG, "Route selected: " + mSelectedRoute); 1706 } 1707 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute); 1708 } 1709 } 1710 } 1711 1712 @Override 1713 public RouteInfo getSystemRouteByDescriptorId(String id) { 1714 int providerIndex = findProviderInfo(mSystemProvider); 1715 if (providerIndex >= 0) { 1716 ProviderInfo provider = mProviders.get(providerIndex); 1717 int routeIndex = provider.findRouteByDescriptorId(id); 1718 if (routeIndex >= 0) { 1719 return provider.mRoutes.get(routeIndex); 1720 } 1721 } 1722 return null; 1723 } 1724 1725 private final class ProviderCallback extends MediaRouteProvider.Callback { 1726 @Override 1727 public void onDescriptorChanged(MediaRouteProvider provider, 1728 MediaRouteProviderDescriptor descriptor) { 1729 updateProviderDescriptor(provider, descriptor); 1730 } 1731 } 1732 1733 private final class CallbackHandler extends Handler { 1734 private final ArrayList<MediaRouter> mTempMediaRouters = 1735 new ArrayList<MediaRouter>(); 1736 1737 private static final int MSG_TYPE_MASK = 0xff00; 1738 private static final int MSG_TYPE_ROUTE = 0x0100; 1739 private static final int MSG_TYPE_PROVIDER = 0x0200; 1740 1741 public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1; 1742 public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2; 1743 public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3; 1744 public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4; 1745 public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5; 1746 public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6; 1747 public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7; 1748 1749 public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1; 1750 public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2; 1751 public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3; 1752 1753 public void post(int msg, Object obj) { 1754 obtainMessage(msg, obj).sendToTarget(); 1755 } 1756 1757 @Override 1758 public void handleMessage(Message msg) { 1759 final int what = msg.what; 1760 final Object obj = msg.obj; 1761 1762 // Synchronize state with the system media router. 1763 syncWithSystemProvider(what, obj); 1764 1765 // Invoke all registered callbacks. 1766 mTempMediaRouters.addAll(mRouters.values()); 1767 try { 1768 final int routerCount = mTempMediaRouters.size(); 1769 for (int i = 0; i < routerCount; i++) { 1770 final MediaRouter router = mTempMediaRouters.get(i); 1771 if (!router.mCallbackRecords.isEmpty()) { 1772 for (CallbackRecord record : router.mCallbackRecords) { 1773 invokeCallback(router, record, what, obj); 1774 } 1775 } 1776 } 1777 } finally { 1778 mTempMediaRouters.clear(); 1779 } 1780 } 1781 1782 private void syncWithSystemProvider(int what, Object obj) { 1783 switch (what) { 1784 case MSG_ROUTE_ADDED: 1785 mSystemProvider.onSyncRouteAdded((RouteInfo)obj); 1786 break; 1787 case MSG_ROUTE_REMOVED: 1788 mSystemProvider.onSyncRouteRemoved((RouteInfo)obj); 1789 break; 1790 case MSG_ROUTE_CHANGED: 1791 mSystemProvider.onSyncRouteChanged((RouteInfo)obj); 1792 break; 1793 case MSG_ROUTE_SELECTED: 1794 mSystemProvider.onSyncRouteSelected((RouteInfo)obj); 1795 break; 1796 } 1797 } 1798 1799 private void invokeCallback(MediaRouter router, CallbackRecord record, 1800 int what, Object obj) { 1801 final MediaRouter.Callback callback = record.mCallback; 1802 switch (what & MSG_TYPE_MASK) { 1803 case MSG_TYPE_ROUTE: { 1804 final RouteInfo route = (RouteInfo)obj; 1805 if (!record.filterRouteEvent(route)) { 1806 break; 1807 } 1808 switch (what) { 1809 case MSG_ROUTE_ADDED: 1810 callback.onRouteAdded(router, route); 1811 break; 1812 case MSG_ROUTE_REMOVED: 1813 callback.onRouteRemoved(router, route); 1814 break; 1815 case MSG_ROUTE_CHANGED: 1816 callback.onRouteChanged(router, route); 1817 break; 1818 case MSG_ROUTE_VOLUME_CHANGED: 1819 callback.onRouteVolumeChanged(router, route); 1820 break; 1821 case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED: 1822 callback.onRoutePresentationDisplayChanged(router, route); 1823 break; 1824 case MSG_ROUTE_SELECTED: 1825 callback.onRouteSelected(router, route); 1826 break; 1827 case MSG_ROUTE_UNSELECTED: 1828 callback.onRouteUnselected(router, route); 1829 break; 1830 } 1831 break; 1832 } 1833 case MSG_TYPE_PROVIDER: { 1834 final ProviderInfo provider = (ProviderInfo)obj; 1835 switch (what) { 1836 case MSG_PROVIDER_ADDED: 1837 callback.onProviderAdded(router, provider); 1838 break; 1839 case MSG_PROVIDER_REMOVED: 1840 callback.onProviderRemoved(router, provider); 1841 break; 1842 case MSG_PROVIDER_CHANGED: 1843 callback.onProviderChanged(router, provider); 1844 break; 1845 } 1846 } 1847 } 1848 } 1849 } 1850 } 1851} 1852