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