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