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