MediaRouter.java revision a97f1edf7624785c41ec0bfec6fd12c2388d9234
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; 38import 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 */ 58public 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 * Adds a remote control client to enable remote control of the volume 531 * of the selected route. 532 * <p> 533 * The remote control client must have previously been registered with 534 * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient 535 * AudioManager.registerRemoteControlClient} method. 536 * </p> 537 * 538 * @param remoteControlClient The {@link android.media.RemoteControlClient} to register. 539 */ 540 public void addRemoteControlClient(Object remoteControlClient) { 541 if (remoteControlClient == null) { 542 throw new IllegalArgumentException("remoteControlClient must not be null"); 543 } 544 checkCallingThread(); 545 546 if (DEBUG) { 547 Log.d(TAG, "addRemoteControlClient: " + remoteControlClient); 548 } 549 sGlobal.addRemoteControlClient(remoteControlClient); 550 } 551 552 /** 553 * Removes a remote control client. 554 * 555 * @param remoteControlClient The {@link android.media.RemoteControlClient} to register. 556 */ 557 public void removeRemoteControlClient(Object remoteControlClient) { 558 if (remoteControlClient == null) { 559 throw new IllegalArgumentException("remoteControlClient must not be null"); 560 } 561 562 if (DEBUG) { 563 Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient); 564 } 565 sGlobal.removeRemoteControlClient(remoteControlClient); 566 } 567 568 /** 569 * Ensures that calls into the media router are on the correct thread. 570 * It pays to be a little paranoid when global state invariants are at risk. 571 */ 572 static void checkCallingThread() { 573 if (Looper.myLooper() != Looper.getMainLooper()) { 574 throw new IllegalStateException("The media router service must only be " 575 + "accessed on the application's main thread."); 576 } 577 } 578 579 static <T> boolean equal(T a, T b) { 580 return a == b || (a != null && b != null && a.equals(b)); 581 } 582 583 /** 584 * Provides information about a media route. 585 * <p> 586 * Each media route has a list of {@link MediaControlIntent media control} 587 * {@link #getControlFilters intent filters} that describe the capabilities of the 588 * route and the manner in which it is used and controlled. 589 * </p> 590 */ 591 public static final class RouteInfo { 592 private final ProviderInfo mProvider; 593 private final String mDescriptorId; 594 private final String mUniqueId; 595 private String mName; 596 private String mDescription; 597 private boolean mEnabled; 598 private boolean mConnecting; 599 private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>(); 600 private int mPlaybackType; 601 private int mPlaybackStream; 602 private int mVolumeHandling; 603 private int mVolume; 604 private int mVolumeMax; 605 private Display mPresentationDisplay; 606 private int mPresentationDisplayId = -1; 607 private Bundle mExtras; 608 private MediaRouteDescriptor mDescriptor; 609 610 /** 611 * The default playback type, "local", indicating the presentation of the media 612 * is happening on the same device (e.g. a phone, a tablet) as where it is 613 * controlled from. 614 * 615 * @see #getPlaybackType 616 */ 617 public static final int PLAYBACK_TYPE_LOCAL = 0; 618 619 /** 620 * A playback type indicating the presentation of the media is happening on 621 * a different device (i.e. the remote device) than where it is controlled from. 622 * 623 * @see #getPlaybackType 624 */ 625 public static final int PLAYBACK_TYPE_REMOTE = 1; 626 627 /** 628 * Playback information indicating the playback volume is fixed, i.e. it cannot be 629 * controlled from this object. An example of fixed playback volume is a remote player, 630 * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather 631 * than attenuate at the source. 632 * 633 * @see #getVolumeHandling 634 */ 635 public static final int PLAYBACK_VOLUME_FIXED = 0; 636 637 /** 638 * Playback information indicating the playback volume is variable and can be controlled 639 * from this object. 640 * 641 * @see #getVolumeHandling 642 */ 643 public static final int PLAYBACK_VOLUME_VARIABLE = 1; 644 645 static final int CHANGE_GENERAL = 1 << 0; 646 static final int CHANGE_VOLUME = 1 << 1; 647 static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2; 648 649 RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) { 650 mProvider = provider; 651 mDescriptorId = descriptorId; 652 mUniqueId = uniqueId; 653 } 654 655 /** 656 * Gets information about the provider of this media route. 657 */ 658 public ProviderInfo getProvider() { 659 return mProvider; 660 } 661 662 /** 663 * Gets the unique id of the route. 664 * <p> 665 * The route unique id functions as a stable identifier by which the route is known. 666 * For example, an application can use this id as a token to remember the 667 * selected route across restarts or to communicate its identity to a service. 668 * </p> 669 * 670 * @return The unique id of the route, never null. 671 */ 672 public String getId() { 673 return mUniqueId; 674 } 675 676 /** 677 * Gets the user-visible name of the route. 678 * <p> 679 * The route name identifies the destination represented by the route. 680 * It may be a user-supplied name, an alias, or device serial number. 681 * </p> 682 * 683 * @return The user-visible name of a media route. This is the string presented 684 * to users who may select this as the active route. 685 */ 686 public String getName() { 687 return mName; 688 } 689 690 /** 691 * Gets the user-visible description of the route. 692 * <p> 693 * The route description describes the kind of destination represented by the route. 694 * It may be a user-supplied string, a model number or brand of device. 695 * </p> 696 * 697 * @return The description of the route, or null if none. 698 */ 699 public String getDescription() { 700 return mDescription; 701 } 702 703 /** 704 * Returns true if this route is enabled and may be selected. 705 * 706 * @return True if this route is enabled. 707 */ 708 public boolean isEnabled() { 709 return mEnabled; 710 } 711 712 /** 713 * Returns true if the route is in the process of connecting and is not 714 * yet ready for use. 715 * 716 * @return True if this route is in the process of connecting. 717 */ 718 public boolean isConnecting() { 719 return mConnecting; 720 } 721 722 /** 723 * Returns true if this route is currently selected. 724 * 725 * @return True if this route is currently selected. 726 * 727 * @see MediaRouter#getSelectedRoute 728 */ 729 public boolean isSelected() { 730 checkCallingThread(); 731 return sGlobal.getSelectedRoute() == this; 732 } 733 734 /** 735 * Returns true if this route is the default route. 736 * 737 * @return True if this route is the default route. 738 * 739 * @see MediaRouter#getDefaultRoute 740 */ 741 public boolean isDefault() { 742 checkCallingThread(); 743 return sGlobal.getDefaultRoute() == this; 744 } 745 746 /** 747 * Gets a list of {@link MediaControlIntent media control intent} filters that 748 * describe the capabilities of this route and the media control actions that 749 * it supports. 750 * 751 * @return A list of intent filters that specifies the media control intents that 752 * this route supports. 753 * 754 * @see MediaControlIntent 755 * @see #supportsControlCategory 756 * @see #supportsControlRequest 757 */ 758 public List<IntentFilter> getControlFilters() { 759 return mControlFilters; 760 } 761 762 /** 763 * Returns true if the route supports at least one of the capabilities 764 * described by a media route selector. 765 * 766 * @param selector The selector that specifies the capabilities to check. 767 * @return True if the route supports at least one of the capabilities 768 * described in the media route selector. 769 */ 770 public boolean matchesSelector(MediaRouteSelector selector) { 771 if (selector == null) { 772 throw new IllegalArgumentException("selector must not be null"); 773 } 774 checkCallingThread(); 775 return selector.matchesControlFilters(mControlFilters); 776 } 777 778 /** 779 * Returns true if the route supports the specified 780 * {@link MediaControlIntent media control} category. 781 * <p> 782 * Media control categories describe the capabilities of this route 783 * such as whether it supports live audio streaming or remote playback. 784 * </p> 785 * 786 * @param category A {@link MediaControlIntent media control} category 787 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 788 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 789 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 790 * media control category. 791 * @return True if the route supports the specified intent category. 792 * 793 * @see MediaControlIntent 794 * @see #getControlFilters 795 */ 796 public boolean supportsControlCategory(String category) { 797 if (category == null) { 798 throw new IllegalArgumentException("category must not be null"); 799 } 800 checkCallingThread(); 801 802 int count = mControlFilters.size(); 803 for (int i = 0; i < count; i++) { 804 if (mControlFilters.get(i).hasCategory(category)) { 805 return true; 806 } 807 } 808 return false; 809 } 810 811 /** 812 * Returns true if the route supports the specified 813 * {@link MediaControlIntent media control} category and action. 814 * <p> 815 * Media control actions describe specific requests that an application 816 * can ask a route to perform. 817 * </p> 818 * 819 * @param category A {@link MediaControlIntent media control} category 820 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 821 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 822 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 823 * media control category. 824 * @param action A {@link MediaControlIntent media control} action 825 * such as {@link MediaControlIntent#ACTION_PLAY}. 826 * @return True if the route supports the specified intent action. 827 * 828 * @see MediaControlIntent 829 * @see #getControlFilters 830 */ 831 public boolean supportsControlAction(String category, String action) { 832 if (category == null) { 833 throw new IllegalArgumentException("category must not be null"); 834 } 835 if (action == null) { 836 throw new IllegalArgumentException("action must not be null"); 837 } 838 checkCallingThread(); 839 840 int count = mControlFilters.size(); 841 for (int i = 0; i < count; i++) { 842 IntentFilter filter = mControlFilters.get(i); 843 if (filter.hasCategory(category) && filter.hasAction(action)) { 844 return true; 845 } 846 } 847 return false; 848 } 849 850 /** 851 * Returns true if the route supports the specified 852 * {@link MediaControlIntent media control} request. 853 * <p> 854 * Media control requests are used to request the route to perform 855 * actions such as starting remote playback of a media item. 856 * </p> 857 * 858 * @param intent A {@link MediaControlIntent media control intent}. 859 * @return True if the route can handle the specified intent. 860 * 861 * @see MediaControlIntent 862 * @see #getControlFilters 863 */ 864 public boolean supportsControlRequest(Intent intent) { 865 if (intent == null) { 866 throw new IllegalArgumentException("intent must not be null"); 867 } 868 checkCallingThread(); 869 870 ContentResolver contentResolver = sGlobal.getContentResolver(); 871 int count = mControlFilters.size(); 872 for (int i = 0; i < count; i++) { 873 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) { 874 return true; 875 } 876 } 877 return false; 878 } 879 880 /** 881 * Sends a {@link MediaControlIntent media control} request to be performed 882 * asynchronously by the route's destination. 883 * <p> 884 * Media control requests are used to request the route to perform 885 * actions such as starting remote playback of a media item. 886 * </p><p> 887 * This function may only be called on a selected route. Control requests 888 * sent to unselected routes will fail. 889 * </p> 890 * 891 * @param intent A {@link MediaControlIntent media control intent}. 892 * @param callback A {@link ControlRequestCallback} to invoke with the result 893 * of the request, or null if no result is required. 894 * 895 * @see MediaControlIntent 896 */ 897 public void sendControlRequest(Intent intent, ControlRequestCallback callback) { 898 if (intent == null) { 899 throw new IllegalArgumentException("intent must not be null"); 900 } 901 checkCallingThread(); 902 903 sGlobal.sendControlRequest(this, intent, callback); 904 } 905 906 /** 907 * Gets the type of playback associated with this route. 908 * 909 * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL} 910 * or {@link #PLAYBACK_TYPE_REMOTE}. 911 */ 912 public int getPlaybackType() { 913 return mPlaybackType; 914 } 915 916 /** 917 * Gets the audio stream over which the playback associated with this route is performed. 918 * 919 * @return The stream over which the playback associated with this route is performed. 920 */ 921 public int getPlaybackStream() { 922 return mPlaybackStream; 923 } 924 925 /** 926 * Gets information about how volume is handled on the route. 927 * 928 * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED} 929 * or {@link #PLAYBACK_VOLUME_VARIABLE}. 930 */ 931 public int getVolumeHandling() { 932 return mVolumeHandling; 933 } 934 935 /** 936 * Gets the current volume for this route. Depending on the route, this may only 937 * be valid if the route is currently selected. 938 * 939 * @return The volume at which the playback associated with this route is performed. 940 */ 941 public int getVolume() { 942 return mVolume; 943 } 944 945 /** 946 * Gets the maximum volume at which the playback associated with this route is performed. 947 * 948 * @return The maximum volume at which the playback associated with 949 * this route is performed. 950 */ 951 public int getVolumeMax() { 952 return mVolumeMax; 953 } 954 955 /** 956 * Requests a volume change for this route asynchronously. 957 * <p> 958 * This function may only be called on a selected route. It will have 959 * no effect if the route is currently unselected. 960 * </p> 961 * 962 * @param volume The new volume value between 0 and {@link #getVolumeMax}. 963 */ 964 public void requestSetVolume(int volume) { 965 checkCallingThread(); 966 sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume))); 967 } 968 969 /** 970 * Requests an incremental volume update for this route asynchronously. 971 * <p> 972 * This function may only be called on a selected route. It will have 973 * no effect if the route is currently unselected. 974 * </p> 975 * 976 * @param delta The delta to add to the current volume. 977 */ 978 public void requestUpdateVolume(int delta) { 979 checkCallingThread(); 980 if (delta != 0) { 981 sGlobal.requestUpdateVolume(this, delta); 982 } 983 } 984 985 /** 986 * Gets the {@link Display} that should be used by the application to show 987 * a {@link android.app.Presentation} on an external display when this route is selected. 988 * Depending on the route, this may only be valid if the route is currently 989 * selected. 990 * <p> 991 * The preferred presentation display may change independently of the route 992 * being selected or unselected. For example, the presentation display 993 * of the default system route may change when an external HDMI display is connected 994 * or disconnected even though the route itself has not changed. 995 * </p><p> 996 * This method may return null if there is no external display associated with 997 * the route or if the display is not ready to show UI yet. 998 * </p><p> 999 * The application should listen for changes to the presentation display 1000 * using the {@link Callback#onRoutePresentationDisplayChanged} callback and 1001 * show or dismiss its {@link android.app.Presentation} accordingly when the display 1002 * becomes available or is removed. 1003 * </p><p> 1004 * This method only makes sense for 1005 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes. 1006 * </p> 1007 * 1008 * @return The preferred presentation display to use when this route is 1009 * selected or null if none. 1010 * 1011 * @see MediaControlIntent#CATEGORY_LIVE_VIDEO 1012 * @see android.app.Presentation 1013 */ 1014 public Display getPresentationDisplay() { 1015 checkCallingThread(); 1016 if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) { 1017 mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId); 1018 } 1019 return mPresentationDisplay; 1020 } 1021 1022 /** 1023 * Gets a collection of extra properties about this route that were supplied 1024 * by its media route provider, or null if none. 1025 */ 1026 public Bundle getExtras() { 1027 return mExtras; 1028 } 1029 1030 /** 1031 * Selects this media route. 1032 */ 1033 public void select() { 1034 checkCallingThread(); 1035 sGlobal.selectRoute(this); 1036 } 1037 1038 @Override 1039 public String toString() { 1040 return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId 1041 + ", name=" + mName 1042 + ", description=" + mDescription 1043 + ", enabled=" + mEnabled 1044 + ", connecting=" + mConnecting 1045 + ", playbackType=" + mPlaybackType 1046 + ", playbackStream=" + mPlaybackStream 1047 + ", volumeHandling=" + mVolumeHandling 1048 + ", volume=" + mVolume 1049 + ", volumeMax=" + mVolumeMax 1050 + ", presentationDisplayId=" + mPresentationDisplayId 1051 + ", extras=" + mExtras 1052 + ", providerPackageName=" + mProvider.getPackageName() 1053 + " }"; 1054 } 1055 1056 int updateDescriptor(MediaRouteDescriptor descriptor) { 1057 int changes = 0; 1058 if (mDescriptor != descriptor) { 1059 mDescriptor = descriptor; 1060 if (descriptor != null) { 1061 if (!equal(mName, descriptor.getName())) { 1062 mName = descriptor.getName(); 1063 changes |= CHANGE_GENERAL; 1064 } 1065 if (!equal(mDescription, descriptor.getDescription())) { 1066 mDescription = descriptor.getDescription(); 1067 changes |= CHANGE_GENERAL; 1068 } 1069 if (mEnabled != descriptor.isEnabled()) { 1070 mEnabled = descriptor.isEnabled(); 1071 changes |= CHANGE_GENERAL; 1072 } 1073 if (mConnecting != descriptor.isConnecting()) { 1074 mConnecting = descriptor.isConnecting(); 1075 changes |= CHANGE_GENERAL; 1076 } 1077 if (!mControlFilters.equals(descriptor.getControlFilters())) { 1078 mControlFilters.clear(); 1079 mControlFilters.addAll(descriptor.getControlFilters()); 1080 changes |= CHANGE_GENERAL; 1081 } 1082 if (mPlaybackType != descriptor.getPlaybackType()) { 1083 mPlaybackType = descriptor.getPlaybackType(); 1084 changes |= CHANGE_GENERAL; 1085 } 1086 if (mPlaybackStream != descriptor.getPlaybackStream()) { 1087 mPlaybackStream = descriptor.getPlaybackStream(); 1088 changes |= CHANGE_GENERAL; 1089 } 1090 if (mVolumeHandling != descriptor.getVolumeHandling()) { 1091 mVolumeHandling = descriptor.getVolumeHandling(); 1092 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1093 } 1094 if (mVolume != descriptor.getVolume()) { 1095 mVolume = descriptor.getVolume(); 1096 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1097 } 1098 if (mVolumeMax != descriptor.getVolumeMax()) { 1099 mVolumeMax = descriptor.getVolumeMax(); 1100 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1101 } 1102 if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) { 1103 mPresentationDisplayId = descriptor.getPresentationDisplayId(); 1104 mPresentationDisplay = null; 1105 changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY; 1106 } 1107 if (!equal(mExtras, descriptor.getExtras())) { 1108 mExtras = descriptor.getExtras(); 1109 changes |= CHANGE_GENERAL; 1110 } 1111 } 1112 } 1113 return changes; 1114 } 1115 1116 String getDescriptorId() { 1117 return mDescriptorId; 1118 } 1119 1120 MediaRouteProvider getProviderInstance() { 1121 return mProvider.getProviderInstance(); 1122 } 1123 } 1124 1125 /** 1126 * Provides information about a media route provider. 1127 * <p> 1128 * This object may be used to determine which media route provider has 1129 * published a particular route. 1130 * </p> 1131 */ 1132 public static final class ProviderInfo { 1133 private final MediaRouteProvider mProviderInstance; 1134 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 1135 1136 private final ProviderMetadata mMetadata; 1137 private MediaRouteProviderDescriptor mDescriptor; 1138 private Resources mResources; 1139 private boolean mResourcesNotAvailable; 1140 1141 ProviderInfo(MediaRouteProvider provider) { 1142 mProviderInstance = provider; 1143 mMetadata = provider.getMetadata(); 1144 } 1145 1146 /** 1147 * Gets the provider's underlying {@link MediaRouteProvider} instance. 1148 */ 1149 public MediaRouteProvider getProviderInstance() { 1150 checkCallingThread(); 1151 return mProviderInstance; 1152 } 1153 1154 /** 1155 * Gets the package name of the media route provider service. 1156 */ 1157 public String getPackageName() { 1158 return mMetadata.getPackageName(); 1159 } 1160 1161 /** 1162 * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider. 1163 */ 1164 public List<RouteInfo> getRoutes() { 1165 checkCallingThread(); 1166 return mRoutes; 1167 } 1168 1169 Resources getResources() { 1170 if (mResources == null && !mResourcesNotAvailable) { 1171 String packageName = getPackageName(); 1172 Context context = sGlobal.getProviderContext(packageName); 1173 if (context != null) { 1174 mResources = context.getResources(); 1175 } else { 1176 Log.w(TAG, "Unable to obtain resources for route provider package: " 1177 + packageName); 1178 mResourcesNotAvailable = true; 1179 } 1180 } 1181 return mResources; 1182 } 1183 1184 boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) { 1185 if (mDescriptor != descriptor) { 1186 mDescriptor = descriptor; 1187 return true; 1188 } 1189 return false; 1190 } 1191 1192 int findRouteByDescriptorId(String id) { 1193 final int count = mRoutes.size(); 1194 for (int i = 0; i < count; i++) { 1195 if (mRoutes.get(i).mDescriptorId.equals(id)) { 1196 return i; 1197 } 1198 } 1199 return -1; 1200 } 1201 1202 @Override 1203 public String toString() { 1204 return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName() 1205 + " }"; 1206 } 1207 } 1208 1209 /** 1210 * Interface for receiving events about media routing changes. 1211 * All methods of this interface will be called from the application's main thread. 1212 * <p> 1213 * A Callback will only receive events relevant to routes that the callback 1214 * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS} 1215 * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}. 1216 * </p> 1217 * 1218 * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int) 1219 * @see MediaRouter#removeCallback(Callback) 1220 */ 1221 public static abstract class Callback { 1222 /** 1223 * Called when the supplied media route becomes selected as the active route. 1224 * 1225 * @param router The media router reporting the event. 1226 * @param route The route that has been selected. 1227 */ 1228 public void onRouteSelected(MediaRouter router, RouteInfo route) { 1229 } 1230 1231 /** 1232 * Called when the supplied media route becomes unselected as the active route. 1233 * 1234 * @param router The media router reporting the event. 1235 * @param route The route that has been unselected. 1236 */ 1237 public void onRouteUnselected(MediaRouter router, RouteInfo route) { 1238 } 1239 1240 /** 1241 * Called when a media route has been added. 1242 * 1243 * @param router The media router reporting the event. 1244 * @param route The route that has become available for use. 1245 */ 1246 public void onRouteAdded(MediaRouter router, RouteInfo route) { 1247 } 1248 1249 /** 1250 * Called when a media route has been removed. 1251 * 1252 * @param router The media router reporting the event. 1253 * @param route The route that has been removed from availability. 1254 */ 1255 public void onRouteRemoved(MediaRouter router, RouteInfo route) { 1256 } 1257 1258 /** 1259 * Called when a property of the indicated media route has changed. 1260 * 1261 * @param router The media router reporting the event. 1262 * @param route The route that was changed. 1263 */ 1264 public void onRouteChanged(MediaRouter router, RouteInfo route) { 1265 } 1266 1267 /** 1268 * Called when a media route's volume changes. 1269 * 1270 * @param router The media router reporting the event. 1271 * @param route The route whose volume changed. 1272 */ 1273 public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) { 1274 } 1275 1276 /** 1277 * Called when a media route's presentation display changes. 1278 * <p> 1279 * This method is called whenever the route's presentation display becomes 1280 * available, is removed or has changes to some of its properties (such as its size). 1281 * </p> 1282 * 1283 * @param router The media router reporting the event. 1284 * @param route The route whose presentation display changed. 1285 * 1286 * @see RouteInfo#getPresentationDisplay() 1287 */ 1288 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) { 1289 } 1290 1291 /** 1292 * Called when a media route provider has been added. 1293 * 1294 * @param router The media router reporting the event. 1295 * @param provider The provider that has become available for use. 1296 */ 1297 public void onProviderAdded(MediaRouter router, ProviderInfo provider) { 1298 } 1299 1300 /** 1301 * Called when a media route provider has been removed. 1302 * 1303 * @param router The media router reporting the event. 1304 * @param provider The provider that has been removed from availability. 1305 */ 1306 public void onProviderRemoved(MediaRouter router, ProviderInfo provider) { 1307 } 1308 1309 /** 1310 * Called when a property of the indicated media route provider has changed. 1311 * 1312 * @param router The media router reporting the event. 1313 * @param provider The provider that was changed. 1314 */ 1315 public void onProviderChanged(MediaRouter router, ProviderInfo provider) { 1316 } 1317 } 1318 1319 /** 1320 * Callback which is invoked with the result of a media control request. 1321 * 1322 * @see RouteInfo#sendControlRequest 1323 */ 1324 public static abstract class ControlRequestCallback { 1325 /** 1326 * Called when a media control request succeeds. 1327 * 1328 * @param data Result data, or null if none. 1329 * Contents depend on the {@link MediaControlIntent media control action}. 1330 */ 1331 public void onResult(Bundle data) { 1332 } 1333 1334 /** 1335 * Called when a media control request fails. 1336 * 1337 * @param error A localized error message which may be shown to the user, or null 1338 * if the cause of the error is unclear. 1339 * @param data Error data, or null if none. 1340 * Contents depend on the {@link MediaControlIntent media control action}. 1341 */ 1342 public void onError(String error, Bundle data) { 1343 } 1344 } 1345 1346 private static final class CallbackRecord { 1347 public final MediaRouter mRouter; 1348 public final Callback mCallback; 1349 public MediaRouteSelector mSelector; 1350 public int mFlags; 1351 1352 public CallbackRecord(MediaRouter router, Callback callback) { 1353 mRouter = router; 1354 mCallback = callback; 1355 mSelector = MediaRouteSelector.EMPTY; 1356 } 1357 1358 public boolean filterRouteEvent(RouteInfo route) { 1359 return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0 1360 || route.matchesSelector(mSelector); 1361 } 1362 } 1363 1364 /** 1365 * Global state for the media router. 1366 * <p> 1367 * Media routes and media route providers are global to the process; their 1368 * state and the bulk of the media router implementation lives here. 1369 * </p> 1370 */ 1371 private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback { 1372 private final Context mApplicationContext; 1373 private final MediaRouter mApplicationRouter; 1374 private final ArrayList<WeakReference<MediaRouter>> mRouters = 1375 new ArrayList<WeakReference<MediaRouter>>(); 1376 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 1377 private final ArrayList<ProviderInfo> mProviders = 1378 new ArrayList<ProviderInfo>(); 1379 private final ArrayList<RemoteControlClientRecord> mRemoteControlClients = 1380 new ArrayList<RemoteControlClientRecord>(); 1381 private final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo = 1382 new RemoteControlClientCompat.PlaybackInfo(); 1383 private final ProviderCallback mProviderCallback = new ProviderCallback(); 1384 private final CallbackHandler mCallbackHandler = new CallbackHandler(); 1385 private final DisplayManagerCompat mDisplayManager; 1386 private final SystemMediaRouteProvider mSystemProvider; 1387 1388 private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher; 1389 private RouteInfo mDefaultRoute; 1390 private RouteInfo mSelectedRoute; 1391 private MediaRouteProvider.RouteController mSelectedRouteController; 1392 private MediaRouteDiscoveryRequest mDiscoveryRequest; 1393 1394 GlobalMediaRouter(Context applicationContext) { 1395 mApplicationContext = applicationContext; 1396 mDisplayManager = DisplayManagerCompat.getInstance(applicationContext); 1397 mApplicationRouter = getRouter(applicationContext); 1398 1399 // Add the system media route provider for interoperating with 1400 // the framework media router. This one is special and receives 1401 // synchronization messages from the media router. 1402 mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this); 1403 addProvider(mSystemProvider); 1404 } 1405 1406 public void start() { 1407 // Start watching for routes published by registered media route 1408 // provider services. 1409 mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher( 1410 mApplicationContext, mApplicationRouter); 1411 mRegisteredProviderWatcher.start(); 1412 } 1413 1414 public MediaRouter getRouter(Context context) { 1415 MediaRouter router; 1416 for (int i = mRouters.size(); --i >= 0; ) { 1417 router = mRouters.get(i).get(); 1418 if (router == null) { 1419 mRouters.remove(i); 1420 } else if (router.mContext == context) { 1421 return router; 1422 } 1423 } 1424 router = new MediaRouter(context); 1425 mRouters.add(new WeakReference<MediaRouter>(router)); 1426 return router; 1427 } 1428 1429 public ContentResolver getContentResolver() { 1430 return mApplicationContext.getContentResolver(); 1431 } 1432 1433 public Context getProviderContext(String packageName) { 1434 if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) { 1435 return mApplicationContext; 1436 } 1437 try { 1438 return mApplicationContext.createPackageContext( 1439 packageName, Context.CONTEXT_RESTRICTED); 1440 } catch (NameNotFoundException ex) { 1441 return null; 1442 } 1443 } 1444 1445 public Display getDisplay(int displayId) { 1446 return mDisplayManager.getDisplay(displayId); 1447 } 1448 1449 public void sendControlRequest(RouteInfo route, 1450 Intent intent, ControlRequestCallback callback) { 1451 if (route == mSelectedRoute && mSelectedRouteController != null) { 1452 if (mSelectedRouteController.onControlRequest(intent, callback)) { 1453 return; 1454 } 1455 } 1456 if (callback != null) { 1457 callback.onError(null, null); 1458 } 1459 } 1460 1461 public void requestSetVolume(RouteInfo route, int volume) { 1462 if (route == mSelectedRoute && mSelectedRouteController != null) { 1463 mSelectedRouteController.onSetVolume(volume); 1464 } 1465 } 1466 1467 public void requestUpdateVolume(RouteInfo route, int delta) { 1468 if (route == mSelectedRoute && mSelectedRouteController != null) { 1469 mSelectedRouteController.onUpdateVolume(delta); 1470 } 1471 } 1472 1473 public List<RouteInfo> getRoutes() { 1474 return mRoutes; 1475 } 1476 1477 public List<ProviderInfo> getProviders() { 1478 return mProviders; 1479 } 1480 1481 public RouteInfo getDefaultRoute() { 1482 if (mDefaultRoute == null) { 1483 // This should never happen once the media router has been fully 1484 // initialized but it is good to check for the error in case there 1485 // is a bug in provider initialization. 1486 throw new IllegalStateException("There is no default route. " 1487 + "The media router has not yet been fully initialized."); 1488 } 1489 return mDefaultRoute; 1490 } 1491 1492 public RouteInfo getSelectedRoute() { 1493 if (mSelectedRoute == null) { 1494 // This should never happen once the media router has been fully 1495 // initialized but it is good to check for the error in case there 1496 // is a bug in provider initialization. 1497 throw new IllegalStateException("There is no currently selected route. " 1498 + "The media router has not yet been fully initialized."); 1499 } 1500 return mSelectedRoute; 1501 } 1502 1503 public void selectRoute(RouteInfo route) { 1504 if (!mRoutes.contains(route)) { 1505 Log.w(TAG, "Ignoring attempt to select removed route: " + route); 1506 return; 1507 } 1508 if (!route.mEnabled) { 1509 Log.w(TAG, "Ignoring attempt to select disabled route: " + route); 1510 return; 1511 } 1512 1513 setSelectedRouteInternal(route); 1514 } 1515 1516 public boolean isRouteAvailable(MediaRouteSelector selector, int flags) { 1517 // Check whether any existing routes match the selector. 1518 final int routeCount = mRoutes.size(); 1519 for (int i = 0; i < routeCount; i++) { 1520 RouteInfo route = mRoutes.get(i); 1521 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0 1522 && route.isDefault()) { 1523 continue; 1524 } 1525 if (route.matchesSelector(selector)) { 1526 return true; 1527 } 1528 } 1529 1530 // It doesn't look like we can find a matching route right now. 1531 return false; 1532 } 1533 1534 public void updateDiscoveryRequest() { 1535 // Combine all of the callback selectors and active scan flags. 1536 boolean discover = false; 1537 boolean activeScan = false; 1538 MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); 1539 for (int i = mRouters.size(); --i >= 0; ) { 1540 MediaRouter router = mRouters.get(i).get(); 1541 if (router == null) { 1542 mRouters.remove(i); 1543 } else { 1544 final int count = router.mCallbackRecords.size(); 1545 for (int j = 0; j < count; j++) { 1546 CallbackRecord callback = router.mCallbackRecords.get(j); 1547 builder.addSelector(callback.mSelector); 1548 if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { 1549 activeScan = true; 1550 discover = true; // perform active scan implies request discovery 1551 } 1552 if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) { 1553 discover = true; 1554 } 1555 } 1556 } 1557 } 1558 MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY; 1559 1560 // Create a new discovery request. 1561 if (mDiscoveryRequest != null 1562 && mDiscoveryRequest.getSelector().equals(selector) 1563 && mDiscoveryRequest.isActiveScan() == activeScan) { 1564 return; // no change 1565 } 1566 if (selector.isEmpty() && !activeScan) { 1567 // Discovery is not needed. 1568 if (mDiscoveryRequest == null) { 1569 return; // no change 1570 } 1571 mDiscoveryRequest = null; 1572 } else { 1573 // Discovery is needed. 1574 mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan); 1575 } 1576 if (DEBUG) { 1577 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest); 1578 } 1579 1580 // Notify providers. 1581 final int providerCount = mProviders.size(); 1582 for (int i = 0; i < providerCount; i++) { 1583 mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest); 1584 } 1585 } 1586 1587 public void addProvider(MediaRouteProvider providerInstance) { 1588 int index = findProviderInfo(providerInstance); 1589 if (index < 0) { 1590 // 1. Add the provider to the list. 1591 ProviderInfo provider = new ProviderInfo(providerInstance); 1592 mProviders.add(provider); 1593 if (DEBUG) { 1594 Log.d(TAG, "Provider added: " + provider); 1595 } 1596 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider); 1597 // 2. Create the provider's contents. 1598 updateProviderContents(provider, providerInstance.getDescriptor()); 1599 // 3. Register the provider callback. 1600 providerInstance.setCallback(mProviderCallback); 1601 // 4. Set the discovery request. 1602 providerInstance.setDiscoveryRequest(mDiscoveryRequest); 1603 } 1604 } 1605 1606 public void removeProvider(MediaRouteProvider providerInstance) { 1607 int index = findProviderInfo(providerInstance); 1608 if (index >= 0) { 1609 // 1. Unregister the provider callback. 1610 providerInstance.setCallback(null); 1611 // 2. Clear the discovery request. 1612 providerInstance.setDiscoveryRequest(null); 1613 // 3. Delete the provider's contents. 1614 ProviderInfo provider = mProviders.get(index); 1615 updateProviderContents(provider, null); 1616 // 4. Remove the provider from the list. 1617 if (DEBUG) { 1618 Log.d(TAG, "Provider removed: " + provider); 1619 } 1620 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider); 1621 mProviders.remove(index); 1622 } 1623 } 1624 1625 private void updateProviderDescriptor(MediaRouteProvider providerInstance, 1626 MediaRouteProviderDescriptor descriptor) { 1627 int index = findProviderInfo(providerInstance); 1628 if (index >= 0) { 1629 // Update the provider's contents. 1630 ProviderInfo provider = mProviders.get(index); 1631 updateProviderContents(provider, descriptor); 1632 } 1633 } 1634 1635 private int findProviderInfo(MediaRouteProvider providerInstance) { 1636 final int count = mProviders.size(); 1637 for (int i = 0; i < count; i++) { 1638 if (mProviders.get(i).mProviderInstance == providerInstance) { 1639 return i; 1640 } 1641 } 1642 return -1; 1643 } 1644 1645 private void updateProviderContents(ProviderInfo provider, 1646 MediaRouteProviderDescriptor providerDescriptor) { 1647 if (provider.updateDescriptor(providerDescriptor)) { 1648 // Update all existing routes and reorder them to match 1649 // the order of their descriptors. 1650 int targetIndex = 0; 1651 boolean selectedRouteDescriptorChanged = false; 1652 if (providerDescriptor != null) { 1653 if (providerDescriptor.isValid()) { 1654 final List<MediaRouteDescriptor> routeDescriptors = 1655 providerDescriptor.getRoutes(); 1656 final int routeCount = routeDescriptors.size(); 1657 for (int i = 0; i < routeCount; i++) { 1658 final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i); 1659 final String id = routeDescriptor.getId(); 1660 final int sourceIndex = provider.findRouteByDescriptorId(id); 1661 if (sourceIndex < 0) { 1662 // 1. Add the route to the list. 1663 String uniqueId = assignRouteUniqueId(provider, id); 1664 RouteInfo route = new RouteInfo(provider, id, uniqueId); 1665 provider.mRoutes.add(targetIndex++, route); 1666 mRoutes.add(route); 1667 // 2. Create the route's contents. 1668 route.updateDescriptor(routeDescriptor); 1669 // 3. Notify clients about addition. 1670 if (DEBUG) { 1671 Log.d(TAG, "Route added: " + route); 1672 } 1673 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route); 1674 } else if (sourceIndex < targetIndex) { 1675 Log.w(TAG, "Ignoring route descriptor with duplicate id: " 1676 + routeDescriptor); 1677 } else { 1678 // 1. Reorder the route within the list. 1679 RouteInfo route = provider.mRoutes.get(sourceIndex); 1680 Collections.swap(provider.mRoutes, 1681 sourceIndex, targetIndex++); 1682 // 2. Update the route's contents. 1683 int changes = route.updateDescriptor(routeDescriptor); 1684 // 3. Notify clients about changes. 1685 if (changes != 0) { 1686 if ((changes & RouteInfo.CHANGE_GENERAL) != 0) { 1687 if (DEBUG) { 1688 Log.d(TAG, "Route changed: " + route); 1689 } 1690 mCallbackHandler.post( 1691 CallbackHandler.MSG_ROUTE_CHANGED, route); 1692 } 1693 if ((changes & RouteInfo.CHANGE_VOLUME) != 0) { 1694 if (DEBUG) { 1695 Log.d(TAG, "Route volume changed: " + route); 1696 } 1697 mCallbackHandler.post( 1698 CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route); 1699 } 1700 if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) { 1701 if (DEBUG) { 1702 Log.d(TAG, "Route presentation display changed: " 1703 + route); 1704 } 1705 mCallbackHandler.post(CallbackHandler. 1706 MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route); 1707 } 1708 if (route == mSelectedRoute) { 1709 selectedRouteDescriptorChanged = true; 1710 } 1711 } 1712 } 1713 } 1714 } else { 1715 Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor); 1716 } 1717 } 1718 1719 // Dispose all remaining routes that do not have matching descriptors. 1720 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 1721 // 1. Delete the route's contents. 1722 RouteInfo route = provider.mRoutes.get(i); 1723 route.updateDescriptor(null); 1724 // 2. Remove the route from the list. 1725 mRoutes.remove(route); 1726 } 1727 1728 // Update the selected route if needed. 1729 updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged); 1730 1731 // Now notify clients about routes that were removed. 1732 // We do this after updating the selected route to ensure 1733 // that the framework media router observes the new route 1734 // selection before the removal since removing the currently 1735 // selected route may have side-effects. 1736 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 1737 RouteInfo route = provider.mRoutes.remove(i); 1738 if (DEBUG) { 1739 Log.d(TAG, "Route removed: " + route); 1740 } 1741 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route); 1742 } 1743 1744 // Notify provider changed. 1745 if (DEBUG) { 1746 Log.d(TAG, "Provider changed: " + provider); 1747 } 1748 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider); 1749 } 1750 } 1751 1752 private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) { 1753 // Although route descriptor ids are unique within a provider, it's 1754 // possible for there to be two providers with the same package name. 1755 // Therefore we must dedupe the composite id. 1756 String uniqueId = provider.getPackageName() + ":" + routeDescriptorId; 1757 if (findRouteByUniqueId(uniqueId) < 0) { 1758 return uniqueId; 1759 } 1760 for (int i = 2; ; i++) { 1761 String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i); 1762 if (findRouteByUniqueId(newUniqueId) < 0) { 1763 return newUniqueId; 1764 } 1765 } 1766 } 1767 1768 private int findRouteByUniqueId(String uniqueId) { 1769 final int count = mRoutes.size(); 1770 for (int i = 0; i < count; i++) { 1771 if (mRoutes.get(i).mUniqueId.equals(uniqueId)) { 1772 return i; 1773 } 1774 } 1775 return -1; 1776 } 1777 1778 private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) { 1779 // Update default route. 1780 if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) { 1781 Log.i(TAG, "Clearing the default route because it " 1782 + "is no longer selectable: " + mDefaultRoute); 1783 mDefaultRoute = null; 1784 } 1785 if (mDefaultRoute == null && !mRoutes.isEmpty()) { 1786 for (RouteInfo route : mRoutes) { 1787 if (isSystemDefaultRoute(route) && isRouteSelectable(route)) { 1788 mDefaultRoute = route; 1789 Log.i(TAG, "Found default route: " + mDefaultRoute); 1790 break; 1791 } 1792 } 1793 } 1794 1795 // Update selected route. 1796 if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) { 1797 Log.i(TAG, "Unselecting the current route because it " 1798 + "is no longer selectable: " + mSelectedRoute); 1799 setSelectedRouteInternal(null); 1800 } 1801 if (mSelectedRoute == null) { 1802 // Choose a new route. 1803 // This will have the side-effect of updating the playback info when 1804 // the new route is selected. 1805 setSelectedRouteInternal(chooseFallbackRoute()); 1806 } else if (selectedRouteDescriptorChanged) { 1807 // Update the playback info because the properties of the route have changed. 1808 updatePlaybackInfoFromSelectedRoute(); 1809 } 1810 } 1811 1812 private RouteInfo chooseFallbackRoute() { 1813 // When the current route is removed or no longer selectable, 1814 // we want to revert to a live audio route if there is 1815 // one (usually Bluetooth A2DP). Failing that, use 1816 // the default route. 1817 for (RouteInfo route : mRoutes) { 1818 if (route != mDefaultRoute 1819 && isSystemLiveAudioOnlyRoute(route) 1820 && isRouteSelectable(route)) { 1821 return route; 1822 } 1823 } 1824 return mDefaultRoute; 1825 } 1826 1827 private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) { 1828 return route.getProviderInstance() == mSystemProvider 1829 && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) 1830 && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 1831 } 1832 1833 private boolean isRouteSelectable(RouteInfo route) { 1834 // This tests whether the route is still valid and enabled. 1835 // The route descriptor field is set to null when the route is removed. 1836 return route.mDescriptor != null && route.mEnabled; 1837 } 1838 1839 private boolean isSystemDefaultRoute(RouteInfo route) { 1840 return route.getProviderInstance() == mSystemProvider 1841 && route.mDescriptorId.equals( 1842 SystemMediaRouteProvider.DEFAULT_ROUTE_ID); 1843 } 1844 1845 private void setSelectedRouteInternal(RouteInfo route) { 1846 if (mSelectedRoute != route) { 1847 if (mSelectedRoute != null) { 1848 if (DEBUG) { 1849 Log.d(TAG, "Route unselected: " + mSelectedRoute); 1850 } 1851 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute); 1852 if (mSelectedRouteController != null) { 1853 mSelectedRouteController.onUnselect(); 1854 mSelectedRouteController.onRelease(); 1855 mSelectedRouteController = null; 1856 } 1857 } 1858 1859 mSelectedRoute = route; 1860 1861 if (mSelectedRoute != null) { 1862 mSelectedRouteController = route.getProviderInstance().onCreateRouteController( 1863 route.mDescriptorId); 1864 if (mSelectedRouteController != null) { 1865 mSelectedRouteController.onSelect(); 1866 } 1867 if (DEBUG) { 1868 Log.d(TAG, "Route selected: " + mSelectedRoute); 1869 } 1870 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute); 1871 } 1872 1873 updatePlaybackInfoFromSelectedRoute(); 1874 } 1875 } 1876 1877 @Override 1878 public RouteInfo getSystemRouteByDescriptorId(String id) { 1879 int providerIndex = findProviderInfo(mSystemProvider); 1880 if (providerIndex >= 0) { 1881 ProviderInfo provider = mProviders.get(providerIndex); 1882 int routeIndex = provider.findRouteByDescriptorId(id); 1883 if (routeIndex >= 0) { 1884 return provider.mRoutes.get(routeIndex); 1885 } 1886 } 1887 return null; 1888 } 1889 1890 public void addRemoteControlClient(Object rcc) { 1891 int index = findRemoteControlClientRecord(rcc); 1892 if (index < 0) { 1893 RemoteControlClientRecord record = new RemoteControlClientRecord(rcc); 1894 mRemoteControlClients.add(record); 1895 } 1896 } 1897 1898 public void removeRemoteControlClient(Object rcc) { 1899 int index = findRemoteControlClientRecord(rcc); 1900 if (index >= 0) { 1901 RemoteControlClientRecord record = mRemoteControlClients.remove(index); 1902 record.disconnect(); 1903 } 1904 } 1905 1906 private int findRemoteControlClientRecord(Object rcc) { 1907 final int count = mRemoteControlClients.size(); 1908 for (int i = 0; i < count; i++) { 1909 RemoteControlClientRecord record = mRemoteControlClients.get(i); 1910 if (record.getRemoteControlClient() == rcc) { 1911 return i; 1912 } 1913 } 1914 return -1; 1915 } 1916 1917 private void updatePlaybackInfoFromSelectedRoute() { 1918 if (mSelectedRoute != null) { 1919 mPlaybackInfo.volume = mSelectedRoute.getVolume(); 1920 mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax(); 1921 mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling(); 1922 mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream(); 1923 mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType(); 1924 1925 final int count = mRemoteControlClients.size(); 1926 for (int i = 0; i < count; i++) { 1927 RemoteControlClientRecord record = mRemoteControlClients.get(i); 1928 record.updatePlaybackInfo(); 1929 } 1930 } 1931 } 1932 1933 private final class ProviderCallback extends MediaRouteProvider.Callback { 1934 @Override 1935 public void onDescriptorChanged(MediaRouteProvider provider, 1936 MediaRouteProviderDescriptor descriptor) { 1937 updateProviderDescriptor(provider, descriptor); 1938 } 1939 } 1940 1941 private final class RemoteControlClientRecord 1942 implements RemoteControlClientCompat.VolumeCallback { 1943 private final RemoteControlClientCompat mRccCompat; 1944 private boolean mDisconnected; 1945 1946 public RemoteControlClientRecord(Object rcc) { 1947 mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc); 1948 mRccCompat.setVolumeCallback(this); 1949 updatePlaybackInfo(); 1950 } 1951 1952 public Object getRemoteControlClient() { 1953 return mRccCompat.getRemoteControlClient(); 1954 } 1955 1956 public void disconnect() { 1957 mDisconnected = true; 1958 mRccCompat.setVolumeCallback(null); 1959 } 1960 1961 public void updatePlaybackInfo() { 1962 mRccCompat.setPlaybackInfo(mPlaybackInfo); 1963 } 1964 1965 @Override 1966 public void onVolumeSetRequest(int volume) { 1967 if (!mDisconnected && mSelectedRoute != null) { 1968 mSelectedRoute.requestSetVolume(volume); 1969 } 1970 } 1971 1972 @Override 1973 public void onVolumeUpdateRequest(int direction) { 1974 if (!mDisconnected && mSelectedRoute != null) { 1975 mSelectedRoute.requestUpdateVolume(direction); 1976 } 1977 } 1978 } 1979 1980 private final class CallbackHandler extends Handler { 1981 private final ArrayList<CallbackRecord> mTempCallbackRecords = 1982 new ArrayList<CallbackRecord>(); 1983 1984 private static final int MSG_TYPE_MASK = 0xff00; 1985 private static final int MSG_TYPE_ROUTE = 0x0100; 1986 private static final int MSG_TYPE_PROVIDER = 0x0200; 1987 1988 public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1; 1989 public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2; 1990 public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3; 1991 public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4; 1992 public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5; 1993 public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6; 1994 public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7; 1995 1996 public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1; 1997 public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2; 1998 public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3; 1999 2000 public void post(int msg, Object obj) { 2001 obtainMessage(msg, obj).sendToTarget(); 2002 } 2003 2004 @Override 2005 public void handleMessage(Message msg) { 2006 final int what = msg.what; 2007 final Object obj = msg.obj; 2008 2009 // Synchronize state with the system media router. 2010 syncWithSystemProvider(what, obj); 2011 2012 // Invoke all registered callbacks. 2013 // Build a list of callbacks before invoking them in case callbacks 2014 // are added or removed during dispatch. 2015 try { 2016 for (int i = mRouters.size(); --i >= 0; ) { 2017 MediaRouter router = mRouters.get(i).get(); 2018 if (router == null) { 2019 mRouters.remove(i); 2020 } else { 2021 mTempCallbackRecords.addAll(router.mCallbackRecords); 2022 } 2023 } 2024 2025 final int callbackCount = mTempCallbackRecords.size(); 2026 for (int i = 0; i < callbackCount; i++) { 2027 invokeCallback(mTempCallbackRecords.get(i), what, obj); 2028 } 2029 } finally { 2030 mTempCallbackRecords.clear(); 2031 } 2032 } 2033 2034 private void syncWithSystemProvider(int what, Object obj) { 2035 switch (what) { 2036 case MSG_ROUTE_ADDED: 2037 mSystemProvider.onSyncRouteAdded((RouteInfo)obj); 2038 break; 2039 case MSG_ROUTE_REMOVED: 2040 mSystemProvider.onSyncRouteRemoved((RouteInfo)obj); 2041 break; 2042 case MSG_ROUTE_CHANGED: 2043 mSystemProvider.onSyncRouteChanged((RouteInfo)obj); 2044 break; 2045 case MSG_ROUTE_SELECTED: 2046 mSystemProvider.onSyncRouteSelected((RouteInfo)obj); 2047 break; 2048 } 2049 } 2050 2051 private void invokeCallback(CallbackRecord record, int what, Object obj) { 2052 final MediaRouter router = record.mRouter; 2053 final MediaRouter.Callback callback = record.mCallback; 2054 switch (what & MSG_TYPE_MASK) { 2055 case MSG_TYPE_ROUTE: { 2056 final RouteInfo route = (RouteInfo)obj; 2057 if (!record.filterRouteEvent(route)) { 2058 break; 2059 } 2060 switch (what) { 2061 case MSG_ROUTE_ADDED: 2062 callback.onRouteAdded(router, route); 2063 break; 2064 case MSG_ROUTE_REMOVED: 2065 callback.onRouteRemoved(router, route); 2066 break; 2067 case MSG_ROUTE_CHANGED: 2068 callback.onRouteChanged(router, route); 2069 break; 2070 case MSG_ROUTE_VOLUME_CHANGED: 2071 callback.onRouteVolumeChanged(router, route); 2072 break; 2073 case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED: 2074 callback.onRoutePresentationDisplayChanged(router, route); 2075 break; 2076 case MSG_ROUTE_SELECTED: 2077 callback.onRouteSelected(router, route); 2078 break; 2079 case MSG_ROUTE_UNSELECTED: 2080 callback.onRouteUnselected(router, route); 2081 break; 2082 } 2083 break; 2084 } 2085 case MSG_TYPE_PROVIDER: { 2086 final ProviderInfo provider = (ProviderInfo)obj; 2087 switch (what) { 2088 case MSG_PROVIDER_ADDED: 2089 callback.onProviderAdded(router, provider); 2090 break; 2091 case MSG_PROVIDER_REMOVED: 2092 callback.onProviderRemoved(router, provider); 2093 break; 2094 case MSG_PROVIDER_CHANGED: 2095 callback.onProviderChanged(router, provider); 2096 break; 2097 } 2098 } 2099 } 2100 } 2101 } 2102 } 2103} 2104