MediaRouter.java revision 77367b4a1871417198d0399d9ad074314c758567
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.app.ActivityManager; 20import android.content.ComponentName; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.IntentSender; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.content.res.Resources; 28import android.net.Uri; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Looper; 32import android.os.Message; 33import android.support.annotation.IntDef; 34import android.support.annotation.NonNull; 35import android.support.annotation.Nullable; 36import android.support.v4.app.ActivityManagerCompat; 37import android.support.v4.hardware.display.DisplayManagerCompat; 38import android.support.v4.media.VolumeProviderCompat; 39import android.support.v4.media.session.MediaSessionCompat; 40import android.support.v4.util.Pair; 41import android.support.v7.media.MediaRouteProvider.ProviderMetadata; 42import android.support.v7.media.MediaRouteProvider.RouteController; 43import android.util.Log; 44import android.view.Display; 45 46import java.lang.annotation.Retention; 47import java.lang.annotation.RetentionPolicy; 48import java.lang.ref.WeakReference; 49import java.util.ArrayList; 50import java.util.Collections; 51import java.util.HashMap; 52import java.util.List; 53import java.util.Locale; 54import java.util.Map; 55 56/** 57 * MediaRouter allows applications to control the routing of media channels 58 * and streams from the current device to external speakers and destination devices. 59 * <p> 60 * A MediaRouter instance is retrieved through {@link #getInstance}. Applications 61 * can query the media router about the currently selected route and its capabilities 62 * to determine how to send content to the route's destination. Applications can 63 * also {@link RouteInfo#sendControlRequest send control requests} to the route 64 * to ask the route's destination to perform certain remote control functions 65 * such as playing media. 66 * </p><p> 67 * See also {@link MediaRouteProvider} for information on how an application 68 * can publish new media routes to the media router. 69 * </p><p> 70 * The media router API is not thread-safe; all interactions with it must be 71 * done from the main thread of the process. 72 * </p> 73 */ 74public final class MediaRouter { 75 private static final String TAG = "MediaRouter"; 76 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 77 78 /** 79 * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)} 80 * when the reason the route was unselected is unknown. 81 */ 82 public static final int UNSELECT_REASON_UNKNOWN = 0; 83 /** 84 * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)} 85 * when the user pressed the disconnect button to disconnect and keep playing. 86 * <p> 87 * 88 * @see {@link MediaRouteDescriptor#canDisconnectAndKeepPlaying()}. 89 */ 90 public static final int UNSELECT_REASON_DISCONNECTED = 1; 91 /** 92 * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)} 93 * when the user pressed the stop casting button. 94 */ 95 public static final int UNSELECT_REASON_STOPPED = 2; 96 /** 97 * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)} 98 * when the user selected a different route. 99 */ 100 public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; 101 102 // Maintains global media router state for the process. 103 // This field is initialized in MediaRouter.getInstance() before any 104 // MediaRouter objects are instantiated so it is guaranteed to be 105 // valid whenever any instance method is invoked. 106 static GlobalMediaRouter sGlobal; 107 108 // Context-bound state of the media router. 109 final Context mContext; 110 final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>(); 111 112 /** @hide */ 113 @IntDef(flag = true, 114 value = { 115 CALLBACK_FLAG_PERFORM_ACTIVE_SCAN, 116 CALLBACK_FLAG_REQUEST_DISCOVERY, 117 CALLBACK_FLAG_UNFILTERED_EVENTS 118 } 119 ) 120 @Retention(RetentionPolicy.SOURCE) 121 private @interface CallbackFlags {} 122 123 /** 124 * Flag for {@link #addCallback}: Actively scan for routes while this callback 125 * is registered. 126 * <p> 127 * When this flag is specified, the media router will actively scan for new 128 * routes. Certain routes, such as wifi display routes, may not be discoverable 129 * except when actively scanning. This flag is typically used when the route picker 130 * dialog has been opened by the user to ensure that the route information is 131 * up to date. 132 * </p><p> 133 * Active scanning may consume a significant amount of power and may have intrusive 134 * effects on wireless connectivity. Therefore it is important that active scanning 135 * only be requested when it is actually needed to satisfy a user request to 136 * discover and select a new route. 137 * </p><p> 138 * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing 139 * active scans is much more expensive than a normal discovery request. 140 * </p> 141 * 142 * @see #CALLBACK_FLAG_REQUEST_DISCOVERY 143 */ 144 public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0; 145 146 /** 147 * Flag for {@link #addCallback}: Do not filter route events. 148 * <p> 149 * When this flag is specified, the callback will be invoked for events that affect any 150 * route even if they do not match the callback's filter. 151 * </p> 152 */ 153 public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1; 154 155 /** 156 * Flag for {@link #addCallback}: Request passive route discovery while this 157 * callback is registered, except on {@link ActivityManager#isLowRamDevice low-RAM devices}. 158 * <p> 159 * When this flag is specified, the media router will try to discover routes. 160 * Although route discovery is intended to be efficient, checking for new routes may 161 * result in some network activity and could slowly drain the battery. Therefore 162 * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when 163 * they are running in the foreground and would like to provide the user with the 164 * option of connecting to new routes. 165 * </p><p> 166 * Applications should typically add a callback using this flag in the 167 * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart} 168 * method and remove it in the {@link android.app.Activity#onStop onStop} method. 169 * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may 170 * also be used for this purpose. 171 * </p><p class="note"> 172 * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag 173 * will be ignored. Refer to 174 * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details. 175 * </p> 176 * 177 * @see android.support.v7.app.MediaRouteDiscoveryFragment 178 */ 179 public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2; 180 181 /** 182 * Flag for {@link #addCallback}: Request passive route discovery while this 183 * callback is registered, even on {@link ActivityManager#isLowRamDevice low-RAM devices}. 184 * <p class="note"> 185 * This flag has a significant performance impact on low-RAM devices 186 * since it may cause many media route providers to be started simultaneously. 187 * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid 188 * performing passive discovery on these devices altogether. Refer to 189 * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details. 190 * </p> 191 * 192 * @see android.support.v7.app.MediaRouteDiscoveryFragment 193 */ 194 public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 1 << 3; 195 196 /** 197 * Flag for {@link #isRouteAvailable}: Ignore the default route. 198 * <p> 199 * This flag is used to determine whether a matching non-default route is available. 200 * This constraint may be used to decide whether to offer the route chooser dialog 201 * to the user. There is no point offering the chooser if there are no 202 * non-default choices. 203 * </p> 204 */ 205 public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0; 206 207 /** 208 * Flag for {@link #isRouteAvailable}: Require an actual route to be matched. 209 * <p> 210 * If this flag is not set, then {@link #isRouteAvailable} will return true 211 * if it is possible to discover a matching route even if discovery is not in 212 * progress or if no matching route has yet been found. This feature is used to 213 * save resources by removing the need to perform passive route discovery on 214 * {@link ActivityManager#isLowRamDevice low-RAM devices}. 215 * </p><p> 216 * If this flag is set, then {@link #isRouteAvailable} will only return true if 217 * a matching route has actually been discovered. 218 * </p> 219 */ 220 public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1; 221 222 MediaRouter(Context context) { 223 mContext = context; 224 } 225 226 /** 227 * Gets an instance of the media router service associated with the context. 228 * <p> 229 * The application is responsible for holding a strong reference to the returned 230 * {@link MediaRouter} instance, such as by storing the instance in a field of 231 * the {@link android.app.Activity}, to ensure that the media router remains alive 232 * as long as the application is using its features. 233 * </p><p> 234 * In other words, the support library only holds a {@link WeakReference weak reference} 235 * to each media router instance. When there are no remaining strong references to the 236 * media router instance, all of its callbacks will be removed and route discovery 237 * will no longer be performed on its behalf. 238 * </p> 239 * 240 * @return The media router instance for the context. The application must hold 241 * a strong reference to this object as long as it is in use. 242 */ 243 public static MediaRouter getInstance(@NonNull Context context) { 244 if (context == null) { 245 throw new IllegalArgumentException("context must not be null"); 246 } 247 checkCallingThread(); 248 249 if (sGlobal == null) { 250 sGlobal = new GlobalMediaRouter(context.getApplicationContext()); 251 sGlobal.start(); 252 } 253 return sGlobal.getRouter(context); 254 } 255 256 /** 257 * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to 258 * this media router. 259 */ 260 public List<RouteInfo> getRoutes() { 261 checkCallingThread(); 262 return sGlobal.getRoutes(); 263 } 264 265 /** 266 * Gets information about the {@link MediaRouter.ProviderInfo route providers} 267 * currently known to this media router. 268 */ 269 public List<ProviderInfo> getProviders() { 270 checkCallingThread(); 271 return sGlobal.getProviders(); 272 } 273 274 /** 275 * Gets the default route for playing media content on the system. 276 * <p> 277 * The system always provides a default route. 278 * </p> 279 * 280 * @return The default route, which is guaranteed to never be null. 281 */ 282 @NonNull 283 public RouteInfo getDefaultRoute() { 284 checkCallingThread(); 285 return sGlobal.getDefaultRoute(); 286 } 287 288 /** 289 * Gets the currently selected route. 290 * <p> 291 * The application should examine the route's 292 * {@link RouteInfo#getControlFilters media control intent filters} to assess the 293 * capabilities of the route before attempting to use it. 294 * </p> 295 * 296 * <h3>Example</h3> 297 * <pre> 298 * public boolean playMovie() { 299 * MediaRouter mediaRouter = MediaRouter.getInstance(context); 300 * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute(); 301 * 302 * // First try using the remote playback interface, if supported. 303 * if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 304 * // The route supports remote playback. 305 * // Try to send it the Uri of the movie to play. 306 * Intent intent = new Intent(MediaControlIntent.ACTION_PLAY); 307 * intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 308 * intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4"); 309 * if (route.supportsControlRequest(intent)) { 310 * route.sendControlRequest(intent, null); 311 * return true; // sent the request to play the movie 312 * } 313 * } 314 * 315 * // If remote playback was not possible, then play locally. 316 * if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) { 317 * // The route supports live video streaming. 318 * // Prepare to play content locally in a window or in a presentation. 319 * return playMovieInWindow(); 320 * } 321 * 322 * // Neither interface is supported, so we can't play the movie to this route. 323 * return false; 324 * } 325 * </pre> 326 * 327 * @return The selected route, which is guaranteed to never be null. 328 * 329 * @see RouteInfo#getControlFilters 330 * @see RouteInfo#supportsControlCategory 331 * @see RouteInfo#supportsControlRequest 332 */ 333 @NonNull 334 public RouteInfo getSelectedRoute() { 335 checkCallingThread(); 336 return sGlobal.getSelectedRoute(); 337 } 338 339 /** 340 * Returns the selected route if it matches the specified selector, otherwise 341 * selects the default route and returns it. 342 * 343 * @param selector The selector to match. 344 * @return The previously selected route if it matched the selector, otherwise the 345 * newly selected default route which is guaranteed to never be null. 346 * 347 * @see MediaRouteSelector 348 * @see RouteInfo#matchesSelector 349 * @see RouteInfo#isDefault 350 */ 351 @NonNull 352 public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) { 353 if (selector == null) { 354 throw new IllegalArgumentException("selector must not be null"); 355 } 356 checkCallingThread(); 357 358 if (DEBUG) { 359 Log.d(TAG, "updateSelectedRoute: " + selector); 360 } 361 RouteInfo route = sGlobal.getSelectedRoute(); 362 if (!route.isDefault() && !route.matchesSelector(selector)) { 363 route = sGlobal.getDefaultRoute(); 364 sGlobal.selectRoute(route); 365 } 366 return route; 367 } 368 369 /** 370 * Selects the specified route. 371 * 372 * @param route The route to select. 373 */ 374 public void selectRoute(@NonNull RouteInfo route) { 375 if (route == null) { 376 throw new IllegalArgumentException("route must not be null"); 377 } 378 checkCallingThread(); 379 380 if (DEBUG) { 381 Log.d(TAG, "selectRoute: " + route); 382 } 383 sGlobal.selectRoute(route); 384 } 385 386 /** 387 * Unselects the current round and selects the default route instead. 388 * <p> 389 * The reason given must be one of: 390 * <ul> 391 * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li> 392 * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li> 393 * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li> 394 * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li> 395 * </ul> 396 * 397 * @param reason The reason for disconnecting the current route. 398 */ 399 public void unselect(int reason) { 400 if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN || 401 reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) { 402 throw new IllegalArgumentException("Unsupported reason to unselect route"); 403 } 404 checkCallingThread(); 405 406 sGlobal.selectRoute(getDefaultRoute(), reason); 407 } 408 409 /** 410 * Returns true if there is a route that matches the specified selector. 411 * <p> 412 * This method returns true if there are any available routes that match the 413 * selector regardless of whether they are enabled or disabled. If the 414 * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then 415 * the method will only consider non-default routes. 416 * </p> 417 * <p class="note"> 418 * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method 419 * will return true if it is possible to discover a matching route even if 420 * discovery is not in progress or if no matching route has yet been found. 421 * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match. 422 * </p> 423 * 424 * @param selector The selector to match. 425 * @param flags Flags to control the determination of whether a route may be 426 * available. May be zero or some combination of 427 * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and 428 * {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}. 429 * @return True if a matching route may be available. 430 */ 431 public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) { 432 if (selector == null) { 433 throw new IllegalArgumentException("selector must not be null"); 434 } 435 checkCallingThread(); 436 437 return sGlobal.isRouteAvailable(selector, flags); 438 } 439 440 /** 441 * Registers a callback to discover routes that match the selector and to receive 442 * events when they change. 443 * <p> 444 * This is a convenience method that has the same effect as calling 445 * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags. 446 * </p> 447 * 448 * @param selector A route selector that indicates the kinds of routes that the 449 * callback would like to discover. 450 * @param callback The callback to add. 451 * @see #removeCallback 452 */ 453 public void addCallback(MediaRouteSelector selector, Callback callback) { 454 addCallback(selector, callback, 0); 455 } 456 457 /** 458 * Registers a callback to discover routes that match the selector and to receive 459 * events when they change. 460 * <p> 461 * The selector describes the kinds of routes that the application wants to 462 * discover. For example, if the application wants to use 463 * live audio routes then it should include the 464 * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category} 465 * in its selector when it adds a callback to the media router. 466 * The selector may include any number of categories. 467 * </p><p> 468 * If the callback has already been registered, then the selector is added to 469 * the set of selectors being monitored by the callback. 470 * </p><p> 471 * By default, the callback will only be invoked for events that affect routes 472 * that match the specified selector. Event filtering may be disabled by specifying 473 * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered. 474 * </p><p> 475 * Applications should use the {@link #isRouteAvailable} method to determine 476 * whether is it possible to discover a route with the desired capabilities 477 * and therefore whether the media route button should be shown to the user. 478 * </p><p> 479 * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application 480 * is in the foreground to request that passive discovery be performed if there are 481 * sufficient resources to allow continuous passive discovery. 482 * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be 483 * ignored to conserve resources. 484 * </p><p> 485 * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when 486 * passive discovery absolutely must be performed, even on low-RAM devices. 487 * This flag has a significant performance impact on low-RAM devices 488 * since it may cause many media route providers to be started simultaneously. 489 * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid 490 * performing passive discovery on these devices altogether. 491 * </p><p> 492 * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the 493 * media route chooser dialog is showing to confirm the presence of available 494 * routes that the user may connect to. This flag may use substantially more 495 * power. 496 * </p> 497 * 498 * <h3>Example</h3> 499 * <pre> 500 * public class MyActivity extends Activity { 501 * private MediaRouter mRouter; 502 * private MediaRouter.Callback mCallback; 503 * private MediaRouteSelector mSelector; 504 * 505 * protected void onCreate(Bundle savedInstanceState) { 506 * super.onCreate(savedInstanceState); 507 * 508 * mRouter = Mediarouter.getInstance(this); 509 * mCallback = new MyCallback(); 510 * mSelector = new MediaRouteSelector.Builder() 511 * .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) 512 * .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) 513 * .build(); 514 * } 515 * 516 * // Add the callback on start to tell the media router what kinds of routes 517 * // the application is interested in so that it can try to discover suitable ones. 518 * public void onStart() { 519 * super.onStart(); 520 * 521 * mediaRouter.addCallback(mSelector, mCallback, 522 * MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); 523 * 524 * MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector); 525 * // do something with the route... 526 * } 527 * 528 * // Remove the selector on stop to tell the media router that it no longer 529 * // needs to invest effort trying to discover routes of these kinds for now. 530 * public void onStop() { 531 * super.onStop(); 532 * 533 * mediaRouter.removeCallback(mCallback); 534 * } 535 * 536 * private final class MyCallback extends MediaRouter.Callback { 537 * // Implement callback methods as needed. 538 * } 539 * } 540 * </pre> 541 * 542 * @param selector A route selector that indicates the kinds of routes that the 543 * callback would like to discover. 544 * @param callback The callback to add. 545 * @param flags Flags to control the behavior of the callback. 546 * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and 547 * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}. 548 * @see #removeCallback 549 */ 550 public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback, 551 @CallbackFlags int flags) { 552 if (selector == null) { 553 throw new IllegalArgumentException("selector must not be null"); 554 } 555 if (callback == null) { 556 throw new IllegalArgumentException("callback must not be null"); 557 } 558 checkCallingThread(); 559 560 if (DEBUG) { 561 Log.d(TAG, "addCallback: selector=" + selector 562 + ", callback=" + callback + ", flags=" + Integer.toHexString(flags)); 563 } 564 565 CallbackRecord record; 566 int index = findCallbackRecord(callback); 567 if (index < 0) { 568 record = new CallbackRecord(this, callback); 569 mCallbackRecords.add(record); 570 } else { 571 record = mCallbackRecords.get(index); 572 } 573 boolean updateNeeded = false; 574 if ((flags & ~record.mFlags) != 0) { 575 record.mFlags |= flags; 576 updateNeeded = true; 577 } 578 if (!record.mSelector.contains(selector)) { 579 record.mSelector = new MediaRouteSelector.Builder(record.mSelector) 580 .addSelector(selector) 581 .build(); 582 updateNeeded = true; 583 } 584 if (updateNeeded) { 585 sGlobal.updateDiscoveryRequest(); 586 } 587 } 588 589 /** 590 * Removes the specified callback. It will no longer receive events about 591 * changes to media routes. 592 * 593 * @param callback The callback to remove. 594 * @see #addCallback 595 */ 596 public void removeCallback(@NonNull Callback callback) { 597 if (callback == null) { 598 throw new IllegalArgumentException("callback must not be null"); 599 } 600 checkCallingThread(); 601 602 if (DEBUG) { 603 Log.d(TAG, "removeCallback: callback=" + callback); 604 } 605 606 int index = findCallbackRecord(callback); 607 if (index >= 0) { 608 mCallbackRecords.remove(index); 609 sGlobal.updateDiscoveryRequest(); 610 } 611 } 612 613 private int findCallbackRecord(Callback callback) { 614 final int count = mCallbackRecords.size(); 615 for (int i = 0; i < count; i++) { 616 if (mCallbackRecords.get(i).mCallback == callback) { 617 return i; 618 } 619 } 620 return -1; 621 } 622 623 /** 624 * Registers a media route provider within this application process. 625 * <p> 626 * The provider will be added to the list of providers that all {@link MediaRouter} 627 * instances within this process can use to discover routes. 628 * </p> 629 * 630 * @param providerInstance The media route provider instance to add. 631 * 632 * @see MediaRouteProvider 633 * @see #removeCallback 634 */ 635 public void addProvider(@NonNull MediaRouteProvider providerInstance) { 636 if (providerInstance == null) { 637 throw new IllegalArgumentException("providerInstance must not be null"); 638 } 639 checkCallingThread(); 640 641 if (DEBUG) { 642 Log.d(TAG, "addProvider: " + providerInstance); 643 } 644 sGlobal.addProvider(providerInstance); 645 } 646 647 /** 648 * Unregisters a media route provider within this application process. 649 * <p> 650 * The provider will be removed from the list of providers that all {@link MediaRouter} 651 * instances within this process can use to discover routes. 652 * </p> 653 * 654 * @param providerInstance The media route provider instance to remove. 655 * 656 * @see MediaRouteProvider 657 * @see #addCallback 658 */ 659 public void removeProvider(@NonNull MediaRouteProvider providerInstance) { 660 if (providerInstance == null) { 661 throw new IllegalArgumentException("providerInstance must not be null"); 662 } 663 checkCallingThread(); 664 665 if (DEBUG) { 666 Log.d(TAG, "removeProvider: " + providerInstance); 667 } 668 sGlobal.removeProvider(providerInstance); 669 } 670 671 /** 672 * Adds a remote control client to enable remote control of the volume 673 * of the selected route. 674 * <p> 675 * The remote control client must have previously been registered with 676 * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient 677 * AudioManager.registerRemoteControlClient} method. 678 * </p> 679 * 680 * @param remoteControlClient The {@link android.media.RemoteControlClient} to register. 681 */ 682 public void addRemoteControlClient(@NonNull Object remoteControlClient) { 683 if (remoteControlClient == null) { 684 throw new IllegalArgumentException("remoteControlClient must not be null"); 685 } 686 checkCallingThread(); 687 688 if (DEBUG) { 689 Log.d(TAG, "addRemoteControlClient: " + remoteControlClient); 690 } 691 sGlobal.addRemoteControlClient(remoteControlClient); 692 } 693 694 /** 695 * Removes a remote control client. 696 * 697 * @param remoteControlClient The {@link android.media.RemoteControlClient} 698 * to unregister. 699 */ 700 public void removeRemoteControlClient(@NonNull Object remoteControlClient) { 701 if (remoteControlClient == null) { 702 throw new IllegalArgumentException("remoteControlClient must not be null"); 703 } 704 705 if (DEBUG) { 706 Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient); 707 } 708 sGlobal.removeRemoteControlClient(remoteControlClient); 709 } 710 711 /** 712 * Sets the media session to enable remote control of the volume of the 713 * selected route. This should be used instead of 714 * {@link #addRemoteControlClient} when using media sessions. Set the 715 * session to null to clear it. 716 * 717 * @param mediaSession The {@link android.media.session.MediaSession} to 718 * use. 719 */ 720 public void setMediaSession(Object mediaSession) { 721 if (DEBUG) { 722 Log.d(TAG, "addMediaSession: " + mediaSession); 723 } 724 sGlobal.setMediaSession(mediaSession); 725 } 726 727 /** 728 * Sets a compat media session to enable remote control of the volume of the 729 * selected route. This should be used instead of 730 * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}. 731 * Set the session to null to clear it. 732 * 733 * @param mediaSession 734 */ 735 public void setMediaSessionCompat(MediaSessionCompat mediaSession) { 736 if (DEBUG) { 737 Log.d(TAG, "addMediaSessionCompat: " + mediaSession); 738 } 739 sGlobal.setMediaSessionCompat(mediaSession); 740 } 741 742 public MediaSessionCompat.Token getMediaSessionToken() { 743 return sGlobal.getMediaSessionToken(); 744 } 745 746 /** 747 * Ensures that calls into the media router are on the correct thread. 748 * It pays to be a little paranoid when global state invariants are at risk. 749 */ 750 static void checkCallingThread() { 751 if (Looper.myLooper() != Looper.getMainLooper()) { 752 throw new IllegalStateException("The media router service must only be " 753 + "accessed on the application's main thread."); 754 } 755 } 756 757 static <T> boolean equal(T a, T b) { 758 return a == b || (a != null && b != null && a.equals(b)); 759 } 760 761 /** 762 * Provides information about a media route. 763 * <p> 764 * Each media route has a list of {@link MediaControlIntent media control} 765 * {@link #getControlFilters intent filters} that describe the capabilities of the 766 * route and the manner in which it is used and controlled. 767 * </p> 768 */ 769 public static class RouteInfo { 770 private final ProviderInfo mProvider; 771 private final String mDescriptorId; 772 private final String mUniqueId; 773 private String mName; 774 private String mDescription; 775 private Uri mIconUri; 776 private boolean mEnabled; 777 private boolean mConnecting; 778 private int mConnectionState; 779 private boolean mCanDisconnect; 780 private final ArrayList<IntentFilter> mControlFilters = new ArrayList<>(); 781 private int mPlaybackType; 782 private int mPlaybackStream; 783 private int mVolumeHandling; 784 private int mVolume; 785 private int mVolumeMax; 786 private Display mPresentationDisplay; 787 private int mPresentationDisplayId = PRESENTATION_DISPLAY_ID_NONE; 788 private Bundle mExtras; 789 private IntentSender mSettingsIntent; 790 MediaRouteDescriptor mDescriptor; 791 792 /** @hide */ 793 @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING, 794 CONNECTION_STATE_CONNECTED}) 795 @Retention(RetentionPolicy.SOURCE) 796 private @interface ConnectionState {} 797 798 /** 799 * The default connection state indicating the route is disconnected. 800 * 801 * @see #getConnectionState 802 * @hide 803 * STOPSHIP: Unhide or remove. 804 */ 805 public static final int CONNECTION_STATE_DISCONNECTED = 0; 806 807 /** 808 * A connection state indicating the route is in the process of connecting and is not yet 809 * ready for use. 810 * 811 * @see #getConnectionState 812 * @hide 813 * STOPSHIP: Unhide or remove. 814 */ 815 public static final int CONNECTION_STATE_CONNECTING = 1; 816 817 /** 818 * A connection state indicating the route is connected. 819 * 820 * @see #getConnectionState 821 * @hide 822 * STOPSHIP: Unhide or remove. 823 */ 824 public static final int CONNECTION_STATE_CONNECTED = 2; 825 826 /** @hide */ 827 @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE}) 828 @Retention(RetentionPolicy.SOURCE) 829 private @interface PlaybackType {} 830 831 /** 832 * The default playback type, "local", indicating the presentation of the media 833 * is happening on the same device (e.g. a phone, a tablet) as where it is 834 * controlled from. 835 * 836 * @see #getPlaybackType 837 */ 838 public static final int PLAYBACK_TYPE_LOCAL = 0; 839 840 /** 841 * A playback type indicating the presentation of the media is happening on 842 * a different device (i.e. the remote device) than where it is controlled from. 843 * 844 * @see #getPlaybackType 845 */ 846 public static final int PLAYBACK_TYPE_REMOTE = 1; 847 848 /** @hide */ 849 @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE}) 850 @Retention(RetentionPolicy.SOURCE) 851 private @interface PlaybackVolume {} 852 853 /** 854 * Playback information indicating the playback volume is fixed, i.e. it cannot be 855 * controlled from this object. An example of fixed playback volume is a remote player, 856 * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather 857 * than attenuate at the source. 858 * 859 * @see #getVolumeHandling 860 */ 861 public static final int PLAYBACK_VOLUME_FIXED = 0; 862 863 /** 864 * Playback information indicating the playback volume is variable and can be controlled 865 * from this object. 866 * 867 * @see #getVolumeHandling 868 */ 869 public static final int PLAYBACK_VOLUME_VARIABLE = 1; 870 871 /** 872 * The default presentation display id indicating no presentation display is associated 873 * with the route. 874 * @hide 875 */ 876 public static final int PRESENTATION_DISPLAY_ID_NONE = -1; 877 878 static final int CHANGE_GENERAL = 1 << 0; 879 static final int CHANGE_VOLUME = 1 << 1; 880 static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2; 881 882 RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) { 883 mProvider = provider; 884 mDescriptorId = descriptorId; 885 mUniqueId = uniqueId; 886 } 887 888 /** 889 * Gets information about the provider of this media route. 890 */ 891 public ProviderInfo getProvider() { 892 return mProvider; 893 } 894 895 /** 896 * Gets the unique id of the route. 897 * <p> 898 * The route unique id functions as a stable identifier by which the route is known. 899 * For example, an application can use this id as a token to remember the 900 * selected route across restarts or to communicate its identity to a service. 901 * </p> 902 * 903 * @return The unique id of the route, never null. 904 */ 905 @NonNull 906 public String getId() { 907 return mUniqueId; 908 } 909 910 /** 911 * Gets the user-visible name of the route. 912 * <p> 913 * The route name identifies the destination represented by the route. 914 * It may be a user-supplied name, an alias, or device serial number. 915 * </p> 916 * 917 * @return The user-visible name of a media route. This is the string presented 918 * to users who may select this as the active route. 919 */ 920 public String getName() { 921 return mName; 922 } 923 924 /** 925 * Gets the user-visible description of the route. 926 * <p> 927 * The route description describes the kind of destination represented by the route. 928 * It may be a user-supplied string, a model number or brand of device. 929 * </p> 930 * 931 * @return The description of the route, or null if none. 932 */ 933 @Nullable 934 public String getDescription() { 935 return mDescription; 936 } 937 938 /** 939 * Gets the URI of the icon representing this route. 940 * <p> 941 * This icon will be used in picker UIs if available. 942 * </p> 943 * 944 * @return The URI of the icon representing this route, or null if none. 945 * @hide 946 * STOPSHIP: Unhide or remove. 947 */ 948 public Uri getIconUri() { 949 return mIconUri; 950 } 951 952 /** 953 * Returns true if this route is enabled and may be selected. 954 * 955 * @return True if this route is enabled. 956 */ 957 public boolean isEnabled() { 958 return mEnabled; 959 } 960 961 /** 962 * Returns true if the route is in the process of connecting and is not 963 * yet ready for use. 964 * 965 * @return True if this route is in the process of connecting. 966 * STOPSHIP: Deprecate or keep. 967 */ 968 public boolean isConnecting() { 969 return mConnecting; 970 } 971 972 /** 973 * Gets the connection state of the route. 974 * 975 * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED}, 976 * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}. 977 * @hide 978 * STOPSHIP: Unhide or remove. 979 */ 980 @ConnectionState 981 public int getConnectionState() { 982 return mConnectionState; 983 } 984 985 /** 986 * Returns true if this route is currently selected. 987 * 988 * @return True if this route is currently selected. 989 * 990 * @see MediaRouter#getSelectedRoute 991 */ 992 public boolean isSelected() { 993 checkCallingThread(); 994 return sGlobal.getSelectedRoute() == this; 995 } 996 997 /** 998 * Returns true if this route is the default route. 999 * 1000 * @return True if this route is the default route. 1001 * 1002 * @see MediaRouter#getDefaultRoute 1003 */ 1004 public boolean isDefault() { 1005 checkCallingThread(); 1006 return sGlobal.getDefaultRoute() == this; 1007 } 1008 1009 /** 1010 * Gets a list of {@link MediaControlIntent media control intent} filters that 1011 * describe the capabilities of this route and the media control actions that 1012 * it supports. 1013 * 1014 * @return A list of intent filters that specifies the media control intents that 1015 * this route supports. 1016 * 1017 * @see MediaControlIntent 1018 * @see #supportsControlCategory 1019 * @see #supportsControlRequest 1020 */ 1021 public List<IntentFilter> getControlFilters() { 1022 return mControlFilters; 1023 } 1024 1025 /** 1026 * Returns true if the route supports at least one of the capabilities 1027 * described by a media route selector. 1028 * 1029 * @param selector The selector that specifies the capabilities to check. 1030 * @return True if the route supports at least one of the capabilities 1031 * described in the media route selector. 1032 */ 1033 public boolean matchesSelector(@NonNull MediaRouteSelector selector) { 1034 if (selector == null) { 1035 throw new IllegalArgumentException("selector must not be null"); 1036 } 1037 checkCallingThread(); 1038 return selector.matchesControlFilters(mControlFilters); 1039 } 1040 1041 /** 1042 * Returns true if the route supports the specified 1043 * {@link MediaControlIntent media control} category. 1044 * <p> 1045 * Media control categories describe the capabilities of this route 1046 * such as whether it supports live audio streaming or remote playback. 1047 * </p> 1048 * 1049 * @param category A {@link MediaControlIntent media control} category 1050 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 1051 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 1052 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 1053 * media control category. 1054 * @return True if the route supports the specified intent category. 1055 * 1056 * @see MediaControlIntent 1057 * @see #getControlFilters 1058 */ 1059 public boolean supportsControlCategory(@NonNull String category) { 1060 if (category == null) { 1061 throw new IllegalArgumentException("category must not be null"); 1062 } 1063 checkCallingThread(); 1064 1065 int count = mControlFilters.size(); 1066 for (int i = 0; i < count; i++) { 1067 if (mControlFilters.get(i).hasCategory(category)) { 1068 return true; 1069 } 1070 } 1071 return false; 1072 } 1073 1074 /** 1075 * Returns true if the route supports the specified 1076 * {@link MediaControlIntent media control} category and action. 1077 * <p> 1078 * Media control actions describe specific requests that an application 1079 * can ask a route to perform. 1080 * </p> 1081 * 1082 * @param category A {@link MediaControlIntent media control} category 1083 * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}, 1084 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO}, 1085 * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined 1086 * media control category. 1087 * @param action A {@link MediaControlIntent media control} action 1088 * such as {@link MediaControlIntent#ACTION_PLAY}. 1089 * @return True if the route supports the specified intent action. 1090 * 1091 * @see MediaControlIntent 1092 * @see #getControlFilters 1093 */ 1094 public boolean supportsControlAction(@NonNull String category, @NonNull String action) { 1095 if (category == null) { 1096 throw new IllegalArgumentException("category must not be null"); 1097 } 1098 if (action == null) { 1099 throw new IllegalArgumentException("action must not be null"); 1100 } 1101 checkCallingThread(); 1102 1103 int count = mControlFilters.size(); 1104 for (int i = 0; i < count; i++) { 1105 IntentFilter filter = mControlFilters.get(i); 1106 if (filter.hasCategory(category) && filter.hasAction(action)) { 1107 return true; 1108 } 1109 } 1110 return false; 1111 } 1112 1113 /** 1114 * Returns true if the route supports the specified 1115 * {@link MediaControlIntent media control} request. 1116 * <p> 1117 * Media control requests are used to request the route to perform 1118 * actions such as starting remote playback of a media item. 1119 * </p> 1120 * 1121 * @param intent A {@link MediaControlIntent media control intent}. 1122 * @return True if the route can handle the specified intent. 1123 * 1124 * @see MediaControlIntent 1125 * @see #getControlFilters 1126 */ 1127 public boolean supportsControlRequest(@NonNull Intent intent) { 1128 if (intent == null) { 1129 throw new IllegalArgumentException("intent must not be null"); 1130 } 1131 checkCallingThread(); 1132 1133 ContentResolver contentResolver = sGlobal.getContentResolver(); 1134 int count = mControlFilters.size(); 1135 for (int i = 0; i < count; i++) { 1136 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) { 1137 return true; 1138 } 1139 } 1140 return false; 1141 } 1142 1143 /** 1144 * Sends a {@link MediaControlIntent media control} request to be performed 1145 * asynchronously by the route's destination. 1146 * <p> 1147 * Media control requests are used to request the route to perform 1148 * actions such as starting remote playback of a media item. 1149 * </p><p> 1150 * This function may only be called on a selected route. Control requests 1151 * sent to unselected routes will fail. 1152 * </p> 1153 * 1154 * @param intent A {@link MediaControlIntent media control intent}. 1155 * @param callback A {@link ControlRequestCallback} to invoke with the result 1156 * of the request, or null if no result is required. 1157 * 1158 * @see MediaControlIntent 1159 */ 1160 public void sendControlRequest(@NonNull Intent intent, 1161 @Nullable ControlRequestCallback callback) { 1162 if (intent == null) { 1163 throw new IllegalArgumentException("intent must not be null"); 1164 } 1165 checkCallingThread(); 1166 1167 sGlobal.sendControlRequest(this, intent, callback); 1168 } 1169 1170 /** 1171 * Gets the type of playback associated with this route. 1172 * 1173 * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL} 1174 * or {@link #PLAYBACK_TYPE_REMOTE}. 1175 */ 1176 @PlaybackType 1177 public int getPlaybackType() { 1178 return mPlaybackType; 1179 } 1180 1181 /** 1182 * Gets the audio stream over which the playback associated with this route is performed. 1183 * 1184 * @return The stream over which the playback associated with this route is performed. 1185 */ 1186 public int getPlaybackStream() { 1187 return mPlaybackStream; 1188 } 1189 1190 /** 1191 * Gets information about how volume is handled on the route. 1192 * 1193 * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED} 1194 * or {@link #PLAYBACK_VOLUME_VARIABLE}. 1195 */ 1196 @PlaybackVolume 1197 public int getVolumeHandling() { 1198 return mVolumeHandling; 1199 } 1200 1201 /** 1202 * Gets the current volume for this route. Depending on the route, this may only 1203 * be valid if the route is currently selected. 1204 * 1205 * @return The volume at which the playback associated with this route is performed. 1206 */ 1207 public int getVolume() { 1208 return mVolume; 1209 } 1210 1211 /** 1212 * Gets the maximum volume at which the playback associated with this route is performed. 1213 * 1214 * @return The maximum volume at which the playback associated with 1215 * this route is performed. 1216 */ 1217 public int getVolumeMax() { 1218 return mVolumeMax; 1219 } 1220 1221 /** 1222 * Gets whether this route supports disconnecting without interrupting 1223 * playback. 1224 * 1225 * @return True if this route can disconnect without stopping playback, 1226 * false otherwise. 1227 */ 1228 public boolean canDisconnect() { 1229 return mCanDisconnect; 1230 } 1231 1232 /** 1233 * Requests a volume change for this route asynchronously. 1234 * <p> 1235 * This function may only be called on a selected route. It will have 1236 * no effect if the route is currently unselected. 1237 * </p> 1238 * 1239 * @param volume The new volume value between 0 and {@link #getVolumeMax}. 1240 */ 1241 public void requestSetVolume(int volume) { 1242 checkCallingThread(); 1243 sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume))); 1244 } 1245 1246 /** 1247 * Requests an incremental volume update for this route asynchronously. 1248 * <p> 1249 * This function may only be called on a selected route. It will have 1250 * no effect if the route is currently unselected. 1251 * </p> 1252 * 1253 * @param delta The delta to add to the current volume. 1254 */ 1255 public void requestUpdateVolume(int delta) { 1256 checkCallingThread(); 1257 if (delta != 0) { 1258 sGlobal.requestUpdateVolume(this, delta); 1259 } 1260 } 1261 1262 /** 1263 * Gets the {@link Display} that should be used by the application to show 1264 * a {@link android.app.Presentation} on an external display when this route is selected. 1265 * Depending on the route, this may only be valid if the route is currently 1266 * selected. 1267 * <p> 1268 * The preferred presentation display may change independently of the route 1269 * being selected or unselected. For example, the presentation display 1270 * of the default system route may change when an external HDMI display is connected 1271 * or disconnected even though the route itself has not changed. 1272 * </p><p> 1273 * This method may return null if there is no external display associated with 1274 * the route or if the display is not ready to show UI yet. 1275 * </p><p> 1276 * The application should listen for changes to the presentation display 1277 * using the {@link Callback#onRoutePresentationDisplayChanged} callback and 1278 * show or dismiss its {@link android.app.Presentation} accordingly when the display 1279 * becomes available or is removed. 1280 * </p><p> 1281 * This method only makes sense for 1282 * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes. 1283 * </p> 1284 * 1285 * @return The preferred presentation display to use when this route is 1286 * selected or null if none. 1287 * 1288 * @see MediaControlIntent#CATEGORY_LIVE_VIDEO 1289 * @see android.app.Presentation 1290 */ 1291 @Nullable 1292 public Display getPresentationDisplay() { 1293 checkCallingThread(); 1294 if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) { 1295 mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId); 1296 } 1297 return mPresentationDisplay; 1298 } 1299 1300 /** 1301 * Gets the route's presentation display id, or -1 if none. 1302 * @hide 1303 */ 1304 public int getPresentationDisplayId() { 1305 return mPresentationDisplayId; 1306 } 1307 1308 /** 1309 * Gets a collection of extra properties about this route that were supplied 1310 * by its media route provider, or null if none. 1311 */ 1312 @Nullable 1313 public Bundle getExtras() { 1314 return mExtras; 1315 } 1316 1317 /** 1318 * Gets an intent sender for launching a settings activity for this 1319 * route. 1320 */ 1321 @Nullable 1322 public IntentSender getSettingsIntent() { 1323 return mSettingsIntent; 1324 } 1325 1326 /** 1327 * Selects this media route. 1328 */ 1329 public void select() { 1330 checkCallingThread(); 1331 sGlobal.selectRoute(this); 1332 } 1333 1334 @Override 1335 public String toString() { 1336 return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId 1337 + ", name=" + mName 1338 + ", description=" + mDescription 1339 + ", iconUri=" + mIconUri 1340 + ", enabled=" + mEnabled 1341 + ", connecting=" + mConnecting 1342 + ", connectionState=" + mConnectionState 1343 + ", canDisconnect=" + mCanDisconnect 1344 + ", playbackType=" + mPlaybackType 1345 + ", playbackStream=" + mPlaybackStream 1346 + ", volumeHandling=" + mVolumeHandling 1347 + ", volume=" + mVolume 1348 + ", volumeMax=" + mVolumeMax 1349 + ", presentationDisplayId=" + mPresentationDisplayId 1350 + ", extras=" + mExtras 1351 + ", settingsIntent=" + mSettingsIntent 1352 + ", providerPackageName=" + mProvider.getPackageName() 1353 + " }"; 1354 } 1355 1356 int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) { 1357 int changes = 0; 1358 if (mDescriptor != descriptor) { 1359 changes = updateDescriptor(descriptor); 1360 } 1361 return changes; 1362 } 1363 1364 int updateDescriptor(MediaRouteDescriptor descriptor) { 1365 int changes = 0; 1366 mDescriptor = descriptor; 1367 if (descriptor != null) { 1368 if (!equal(mName, descriptor.getName())) { 1369 mName = descriptor.getName(); 1370 changes |= CHANGE_GENERAL; 1371 } 1372 if (!equal(mDescription, descriptor.getDescription())) { 1373 mDescription = descriptor.getDescription(); 1374 changes |= CHANGE_GENERAL; 1375 } 1376 if (!equal(mIconUri, descriptor.getIconUri())) { 1377 mIconUri = descriptor.getIconUri(); 1378 changes |= CHANGE_GENERAL; 1379 } 1380 if (mEnabled != descriptor.isEnabled()) { 1381 mEnabled = descriptor.isEnabled(); 1382 changes |= CHANGE_GENERAL; 1383 } 1384 if (mConnecting != descriptor.isConnecting()) { 1385 mConnecting = descriptor.isConnecting(); 1386 changes |= CHANGE_GENERAL; 1387 } 1388 if (mConnectionState != descriptor.getConnectionState()) { 1389 mConnectionState = descriptor.getConnectionState(); 1390 changes |= CHANGE_GENERAL; 1391 } 1392 if (!mControlFilters.equals(descriptor.getControlFilters())) { 1393 mControlFilters.clear(); 1394 mControlFilters.addAll(descriptor.getControlFilters()); 1395 changes |= CHANGE_GENERAL; 1396 } 1397 if (mPlaybackType != descriptor.getPlaybackType()) { 1398 mPlaybackType = descriptor.getPlaybackType(); 1399 changes |= CHANGE_GENERAL; 1400 } 1401 if (mPlaybackStream != descriptor.getPlaybackStream()) { 1402 mPlaybackStream = descriptor.getPlaybackStream(); 1403 changes |= CHANGE_GENERAL; 1404 } 1405 if (mVolumeHandling != descriptor.getVolumeHandling()) { 1406 mVolumeHandling = descriptor.getVolumeHandling(); 1407 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1408 } 1409 if (mVolume != descriptor.getVolume()) { 1410 mVolume = descriptor.getVolume(); 1411 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1412 } 1413 if (mVolumeMax != descriptor.getVolumeMax()) { 1414 mVolumeMax = descriptor.getVolumeMax(); 1415 changes |= CHANGE_GENERAL | CHANGE_VOLUME; 1416 } 1417 if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) { 1418 mPresentationDisplayId = descriptor.getPresentationDisplayId(); 1419 mPresentationDisplay = null; 1420 changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY; 1421 } 1422 if (!equal(mExtras, descriptor.getExtras())) { 1423 mExtras = descriptor.getExtras(); 1424 changes |= CHANGE_GENERAL; 1425 } 1426 if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) { 1427 mSettingsIntent = descriptor.getSettingsActivity(); 1428 changes |= CHANGE_GENERAL; 1429 } 1430 if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) { 1431 mCanDisconnect = descriptor.canDisconnectAndKeepPlaying(); 1432 changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY; 1433 } 1434 } 1435 return changes; 1436 } 1437 1438 String getDescriptorId() { 1439 return mDescriptorId; 1440 } 1441 1442 /** @hide */ 1443 public MediaRouteProvider getProviderInstance() { 1444 return mProvider.getProviderInstance(); 1445 } 1446 } 1447 1448 /** 1449 * Information about a route that consists of multiple other routes in a group. 1450 * @hide 1451 * STOPSHIP: Unhide or remove. 1452 */ 1453 public static class RouteGroup extends RouteInfo { 1454 private List<RouteInfo> mRoutes = new ArrayList<>(); 1455 1456 RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId) { 1457 super(provider, descriptorId, uniqueId); 1458 } 1459 1460 /** 1461 * @return The number of routes in this group 1462 */ 1463 public int getRouteCount() { 1464 return mRoutes.size(); 1465 } 1466 1467 /** 1468 * Returns the route in this group at the specified index 1469 * 1470 * @param index Index to fetch 1471 * @return The route at index 1472 */ 1473 public RouteInfo getRouteAt(int index) { 1474 return mRoutes.get(index); 1475 } 1476 1477 /** 1478 * Returns the routes in this group 1479 * 1480 * @return The list of the routes in this group 1481 */ 1482 public List<RouteInfo> getRoutes() { 1483 return mRoutes; 1484 } 1485 1486 @Override 1487 public String toString() { 1488 StringBuilder sb = new StringBuilder(super.toString()); 1489 sb.append('['); 1490 final int count = mRoutes.size(); 1491 for (int i = 0; i < count; i++) { 1492 if (i > 0) sb.append(", "); 1493 sb.append(mRoutes.get(i)); 1494 } 1495 sb.append(']'); 1496 return sb.toString(); 1497 } 1498 1499 @Override 1500 int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) { 1501 boolean changed = false; 1502 if (mDescriptor != descriptor) { 1503 mDescriptor = descriptor; 1504 if (descriptor != null) { 1505 List<String> groupMemberIds = descriptor.getGroupMemberIds(); 1506 List<RouteInfo> routes = new ArrayList<>(); 1507 changed = groupMemberIds.size() != mRoutes.size(); 1508 for (String groupMemberId : groupMemberIds) { 1509 String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId); 1510 RouteInfo groupMember = sGlobal.getRoute(uniqueId); 1511 if (groupMember != null) { 1512 routes.add(groupMember); 1513 if (!changed && !mRoutes.contains(groupMember)) { 1514 changed = true; 1515 } 1516 } 1517 } 1518 if (changed) { 1519 mRoutes = routes; 1520 } 1521 } 1522 } 1523 return (changed ? CHANGE_GENERAL : 0) | super.updateDescriptor(descriptor); 1524 } 1525 } 1526 1527 /** 1528 * Provides information about a media route provider. 1529 * <p> 1530 * This object may be used to determine which media route provider has 1531 * published a particular route. 1532 * </p> 1533 */ 1534 public static final class ProviderInfo { 1535 private final MediaRouteProvider mProviderInstance; 1536 private final List<RouteInfo> mRoutes = new ArrayList<>(); 1537 1538 private final ProviderMetadata mMetadata; 1539 private MediaRouteProviderDescriptor mDescriptor; 1540 private Resources mResources; 1541 private boolean mResourcesNotAvailable; 1542 1543 ProviderInfo(MediaRouteProvider provider) { 1544 mProviderInstance = provider; 1545 mMetadata = provider.getMetadata(); 1546 } 1547 1548 /** 1549 * Gets the provider's underlying {@link MediaRouteProvider} instance. 1550 */ 1551 public MediaRouteProvider getProviderInstance() { 1552 checkCallingThread(); 1553 return mProviderInstance; 1554 } 1555 1556 /** 1557 * Gets the package name of the media route provider. 1558 */ 1559 public String getPackageName() { 1560 return mMetadata.getPackageName(); 1561 } 1562 1563 /** 1564 * Gets the component name of the media route provider. 1565 */ 1566 public ComponentName getComponentName() { 1567 return mMetadata.getComponentName(); 1568 } 1569 1570 /** 1571 * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider. 1572 */ 1573 public List<RouteInfo> getRoutes() { 1574 checkCallingThread(); 1575 return mRoutes; 1576 } 1577 1578 Resources getResources() { 1579 if (mResources == null && !mResourcesNotAvailable) { 1580 String packageName = getPackageName(); 1581 Context context = sGlobal.getProviderContext(packageName); 1582 if (context != null) { 1583 mResources = context.getResources(); 1584 } else { 1585 Log.w(TAG, "Unable to obtain resources for route provider package: " 1586 + packageName); 1587 mResourcesNotAvailable = true; 1588 } 1589 } 1590 return mResources; 1591 } 1592 1593 boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) { 1594 if (mDescriptor != descriptor) { 1595 mDescriptor = descriptor; 1596 return true; 1597 } 1598 return false; 1599 } 1600 1601 int findRouteByDescriptorId(String id) { 1602 final int count = mRoutes.size(); 1603 for (int i = 0; i < count; i++) { 1604 if (mRoutes.get(i).mDescriptorId.equals(id)) { 1605 return i; 1606 } 1607 } 1608 return -1; 1609 } 1610 1611 @Override 1612 public String toString() { 1613 return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName() 1614 + " }"; 1615 } 1616 } 1617 1618 /** 1619 * Interface for receiving events about media routing changes. 1620 * All methods of this interface will be called from the application's main thread. 1621 * <p> 1622 * A Callback will only receive events relevant to routes that the callback 1623 * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS} 1624 * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}. 1625 * </p> 1626 * 1627 * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int) 1628 * @see MediaRouter#removeCallback(Callback) 1629 */ 1630 public static abstract class Callback { 1631 /** 1632 * Called when the supplied media route becomes selected as the active route. 1633 * 1634 * @param router The media router reporting the event. 1635 * @param route The route that has been selected. 1636 */ 1637 public void onRouteSelected(MediaRouter router, RouteInfo route) { 1638 } 1639 1640 /** 1641 * Called when the supplied media route becomes unselected as the active route. 1642 * 1643 * @param router The media router reporting the event. 1644 * @param route The route that has been unselected. 1645 */ 1646 public void onRouteUnselected(MediaRouter router, RouteInfo route) { 1647 } 1648 1649 /** 1650 * Called when a media route has been added. 1651 * 1652 * @param router The media router reporting the event. 1653 * @param route The route that has become available for use. 1654 */ 1655 public void onRouteAdded(MediaRouter router, RouteInfo route) { 1656 } 1657 1658 /** 1659 * Called when a media route has been removed. 1660 * 1661 * @param router The media router reporting the event. 1662 * @param route The route that has been removed from availability. 1663 */ 1664 public void onRouteRemoved(MediaRouter router, RouteInfo route) { 1665 } 1666 1667 /** 1668 * Called when a property of the indicated media route has changed. 1669 * 1670 * @param router The media router reporting the event. 1671 * @param route The route that was changed. 1672 */ 1673 public void onRouteChanged(MediaRouter router, RouteInfo route) { 1674 } 1675 1676 /** 1677 * Called when a media route's volume changes. 1678 * 1679 * @param router The media router reporting the event. 1680 * @param route The route whose volume changed. 1681 */ 1682 public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) { 1683 } 1684 1685 /** 1686 * Called when a media route's presentation display changes. 1687 * <p> 1688 * This method is called whenever the route's presentation display becomes 1689 * available, is removed or has changes to some of its properties (such as its size). 1690 * </p> 1691 * 1692 * @param router The media router reporting the event. 1693 * @param route The route whose presentation display changed. 1694 * 1695 * @see RouteInfo#getPresentationDisplay() 1696 */ 1697 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) { 1698 } 1699 1700 /** 1701 * Called when a media route provider has been added. 1702 * 1703 * @param router The media router reporting the event. 1704 * @param provider The provider that has become available for use. 1705 */ 1706 public void onProviderAdded(MediaRouter router, ProviderInfo provider) { 1707 } 1708 1709 /** 1710 * Called when a media route provider has been removed. 1711 * 1712 * @param router The media router reporting the event. 1713 * @param provider The provider that has been removed from availability. 1714 */ 1715 public void onProviderRemoved(MediaRouter router, ProviderInfo provider) { 1716 } 1717 1718 /** 1719 * Called when a property of the indicated media route provider has changed. 1720 * 1721 * @param router The media router reporting the event. 1722 * @param provider The provider that was changed. 1723 */ 1724 public void onProviderChanged(MediaRouter router, ProviderInfo provider) { 1725 } 1726 } 1727 1728 /** 1729 * Callback which is invoked with the result of a media control request. 1730 * 1731 * @see RouteInfo#sendControlRequest 1732 */ 1733 public static abstract class ControlRequestCallback { 1734 /** 1735 * Called when a media control request succeeds. 1736 * 1737 * @param data Result data, or null if none. 1738 * Contents depend on the {@link MediaControlIntent media control action}. 1739 */ 1740 public void onResult(Bundle data) { 1741 } 1742 1743 /** 1744 * Called when a media control request fails. 1745 * 1746 * @param error A localized error message which may be shown to the user, or null 1747 * if the cause of the error is unclear. 1748 * @param data Error data, or null if none. 1749 * Contents depend on the {@link MediaControlIntent media control action}. 1750 */ 1751 public void onError(String error, Bundle data) { 1752 } 1753 } 1754 1755 private static final class CallbackRecord { 1756 public final MediaRouter mRouter; 1757 public final Callback mCallback; 1758 public MediaRouteSelector mSelector; 1759 public int mFlags; 1760 1761 public CallbackRecord(MediaRouter router, Callback callback) { 1762 mRouter = router; 1763 mCallback = callback; 1764 mSelector = MediaRouteSelector.EMPTY; 1765 } 1766 1767 public boolean filterRouteEvent(RouteInfo route) { 1768 return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0 1769 || route.matchesSelector(mSelector); 1770 } 1771 } 1772 1773 /** 1774 * Global state for the media router. 1775 * <p> 1776 * Media routes and media route providers are global to the process; their 1777 * state and the bulk of the media router implementation lives here. 1778 * </p> 1779 */ 1780 private static final class GlobalMediaRouter 1781 implements SystemMediaRouteProvider.SyncCallback, 1782 RegisteredMediaRouteProviderWatcher.Callback { 1783 private final Context mApplicationContext; 1784 private final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>(); 1785 private final ArrayList<RouteInfo> mRoutes = new ArrayList<>(); 1786 private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>(); 1787 private final ArrayList<ProviderInfo> mProviders = new ArrayList<>(); 1788 private final ArrayList<RemoteControlClientRecord> mRemoteControlClients = 1789 new ArrayList<>(); 1790 private final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo = 1791 new RemoteControlClientCompat.PlaybackInfo(); 1792 private final ProviderCallback mProviderCallback = new ProviderCallback(); 1793 private final CallbackHandler mCallbackHandler = new CallbackHandler(); 1794 private final DisplayManagerCompat mDisplayManager; 1795 private final SystemMediaRouteProvider mSystemProvider; 1796 private final boolean mLowRam; 1797 1798 private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher; 1799 private RouteInfo mDefaultRoute; 1800 private RouteInfo mSelectedRoute; 1801 private RouteController mSelectedRouteController; 1802 private Map<String, RouteController> mGroupMemberControllers; 1803 private MediaRouteDiscoveryRequest mDiscoveryRequest; 1804 private MediaSessionRecord mMediaSession; 1805 private MediaSessionCompat mRccMediaSession; 1806 private MediaSessionCompat mCompatSession; 1807 private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener = 1808 new MediaSessionCompat.OnActiveChangeListener() { 1809 @Override 1810 public void onActiveChanged() { 1811 if(mRccMediaSession != null) { 1812 if (mRccMediaSession.isActive()) { 1813 addRemoteControlClient(mRccMediaSession.getRemoteControlClient()); 1814 } else { 1815 removeRemoteControlClient(mRccMediaSession.getRemoteControlClient()); 1816 } 1817 } 1818 } 1819 }; 1820 1821 GlobalMediaRouter(Context applicationContext) { 1822 mApplicationContext = applicationContext; 1823 mDisplayManager = DisplayManagerCompat.getInstance(applicationContext); 1824 mLowRam = ActivityManagerCompat.isLowRamDevice( 1825 (ActivityManager)applicationContext.getSystemService( 1826 Context.ACTIVITY_SERVICE)); 1827 1828 // Add the system media route provider for interoperating with 1829 // the framework media router. This one is special and receives 1830 // synchronization messages from the media router. 1831 mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this); 1832 addProvider(mSystemProvider); 1833 } 1834 1835 public void start() { 1836 // Start watching for routes published by registered media route 1837 // provider services. 1838 mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher( 1839 mApplicationContext, this); 1840 mRegisteredProviderWatcher.start(); 1841 } 1842 1843 public MediaRouter getRouter(Context context) { 1844 MediaRouter router; 1845 for (int i = mRouters.size(); --i >= 0; ) { 1846 router = mRouters.get(i).get(); 1847 if (router == null) { 1848 mRouters.remove(i); 1849 } else if (router.mContext == context) { 1850 return router; 1851 } 1852 } 1853 router = new MediaRouter(context); 1854 mRouters.add(new WeakReference<MediaRouter>(router)); 1855 return router; 1856 } 1857 1858 public ContentResolver getContentResolver() { 1859 return mApplicationContext.getContentResolver(); 1860 } 1861 1862 public Context getProviderContext(String packageName) { 1863 if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) { 1864 return mApplicationContext; 1865 } 1866 try { 1867 return mApplicationContext.createPackageContext( 1868 packageName, Context.CONTEXT_RESTRICTED); 1869 } catch (NameNotFoundException ex) { 1870 return null; 1871 } 1872 } 1873 1874 public Display getDisplay(int displayId) { 1875 return mDisplayManager.getDisplay(displayId); 1876 } 1877 1878 public void sendControlRequest(RouteInfo route, 1879 Intent intent, ControlRequestCallback callback) { 1880 if (route == mSelectedRoute && mSelectedRouteController != null) { 1881 if (mSelectedRouteController.onControlRequest(intent, callback)) { 1882 return; 1883 } 1884 } 1885 if (callback != null) { 1886 callback.onError(null, null); 1887 } 1888 } 1889 1890 public void requestSetVolume(RouteInfo route, int volume) { 1891 if (route == mSelectedRoute && mSelectedRouteController != null) { 1892 mSelectedRouteController.onSetVolume(volume); 1893 } else if (mGroupMemberControllers != null) { 1894 RouteController controller = mGroupMemberControllers.get(route.mDescriptorId); 1895 if (controller != null) { 1896 controller.onSetVolume(volume); 1897 } 1898 } 1899 } 1900 1901 public void requestUpdateVolume(RouteInfo route, int delta) { 1902 if (route == mSelectedRoute && mSelectedRouteController != null) { 1903 mSelectedRouteController.onUpdateVolume(delta); 1904 } 1905 } 1906 1907 public RouteInfo getRoute(String uniqueId) { 1908 for (RouteInfo info : mRoutes) { 1909 if (info.mUniqueId.equals(uniqueId)) { 1910 return info; 1911 } 1912 } 1913 return null; 1914 } 1915 1916 public List<RouteInfo> getRoutes() { 1917 return mRoutes; 1918 } 1919 1920 public List<ProviderInfo> getProviders() { 1921 return mProviders; 1922 } 1923 1924 public RouteInfo getDefaultRoute() { 1925 if (mDefaultRoute == null) { 1926 // This should never happen once the media router has been fully 1927 // initialized but it is good to check for the error in case there 1928 // is a bug in provider initialization. 1929 throw new IllegalStateException("There is no default route. " 1930 + "The media router has not yet been fully initialized."); 1931 } 1932 return mDefaultRoute; 1933 } 1934 1935 public RouteInfo getSelectedRoute() { 1936 if (mSelectedRoute == null) { 1937 // This should never happen once the media router has been fully 1938 // initialized but it is good to check for the error in case there 1939 // is a bug in provider initialization. 1940 throw new IllegalStateException("There is no currently selected route. " 1941 + "The media router has not yet been fully initialized."); 1942 } 1943 return mSelectedRoute; 1944 } 1945 1946 public void selectRoute(RouteInfo route) { 1947 selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED); 1948 } 1949 1950 public void selectRoute(RouteInfo route, int unselectReason) { 1951 if (!mRoutes.contains(route)) { 1952 Log.w(TAG, "Ignoring attempt to select removed route: " + route); 1953 return; 1954 } 1955 if (!route.mEnabled) { 1956 Log.w(TAG, "Ignoring attempt to select disabled route: " + route); 1957 return; 1958 } 1959 1960 setSelectedRouteInternal(route, unselectReason); 1961 } 1962 1963 public boolean isRouteAvailable(MediaRouteSelector selector, int flags) { 1964 if (selector.isEmpty()) { 1965 return false; 1966 } 1967 1968 // On low-RAM devices, do not rely on actual discovery results unless asked to. 1969 if ((flags & AVAILABILITY_FLAG_REQUIRE_MATCH) == 0 && mLowRam) { 1970 return true; 1971 } 1972 1973 // Check whether any existing routes match the selector. 1974 final int routeCount = mRoutes.size(); 1975 for (int i = 0; i < routeCount; i++) { 1976 RouteInfo route = mRoutes.get(i); 1977 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0 1978 && route.isDefault()) { 1979 continue; 1980 } 1981 if (route.matchesSelector(selector)) { 1982 return true; 1983 } 1984 } 1985 1986 // It doesn't look like we can find a matching route right now. 1987 return false; 1988 } 1989 1990 public void updateDiscoveryRequest() { 1991 // Combine all of the callback selectors and active scan flags. 1992 boolean discover = false; 1993 boolean activeScan = false; 1994 MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); 1995 for (int i = mRouters.size(); --i >= 0; ) { 1996 MediaRouter router = mRouters.get(i).get(); 1997 if (router == null) { 1998 mRouters.remove(i); 1999 } else { 2000 final int count = router.mCallbackRecords.size(); 2001 for (int j = 0; j < count; j++) { 2002 CallbackRecord callback = router.mCallbackRecords.get(j); 2003 builder.addSelector(callback.mSelector); 2004 if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { 2005 activeScan = true; 2006 discover = true; // perform active scan implies request discovery 2007 } 2008 if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) { 2009 if (!mLowRam) { 2010 discover = true; 2011 } 2012 } 2013 if ((callback.mFlags & CALLBACK_FLAG_FORCE_DISCOVERY) != 0) { 2014 discover = true; 2015 } 2016 } 2017 } 2018 } 2019 MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY; 2020 2021 // Create a new discovery request. 2022 if (mDiscoveryRequest != null 2023 && mDiscoveryRequest.getSelector().equals(selector) 2024 && mDiscoveryRequest.isActiveScan() == activeScan) { 2025 return; // no change 2026 } 2027 if (selector.isEmpty() && !activeScan) { 2028 // Discovery is not needed. 2029 if (mDiscoveryRequest == null) { 2030 return; // no change 2031 } 2032 mDiscoveryRequest = null; 2033 } else { 2034 // Discovery is needed. 2035 mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan); 2036 } 2037 if (DEBUG) { 2038 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest); 2039 } 2040 if (discover && !activeScan && mLowRam) { 2041 Log.i(TAG, "Forcing passive route discovery on a low-RAM device, " 2042 + "system performance may be affected. Please consider using " 2043 + "CALLBACK_FLAG_REQUEST_DISCOVERY instead of " 2044 + "CALLBACK_FLAG_FORCE_DISCOVERY."); 2045 } 2046 2047 // Notify providers. 2048 final int providerCount = mProviders.size(); 2049 for (int i = 0; i < providerCount; i++) { 2050 mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest); 2051 } 2052 } 2053 2054 @Override 2055 public void addProvider(MediaRouteProvider providerInstance) { 2056 int index = findProviderInfo(providerInstance); 2057 if (index < 0) { 2058 // 1. Add the provider to the list. 2059 ProviderInfo provider = new ProviderInfo(providerInstance); 2060 mProviders.add(provider); 2061 if (DEBUG) { 2062 Log.d(TAG, "Provider added: " + provider); 2063 } 2064 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider); 2065 // 2. Create the provider's contents. 2066 updateProviderContents(provider, providerInstance.getDescriptor()); 2067 // 3. Register the provider callback. 2068 providerInstance.setCallback(mProviderCallback); 2069 // 4. Set the discovery request. 2070 providerInstance.setDiscoveryRequest(mDiscoveryRequest); 2071 } 2072 } 2073 2074 @Override 2075 public void removeProvider(MediaRouteProvider providerInstance) { 2076 int index = findProviderInfo(providerInstance); 2077 if (index >= 0) { 2078 // 1. Unregister the provider callback. 2079 providerInstance.setCallback(null); 2080 // 2. Clear the discovery request. 2081 providerInstance.setDiscoveryRequest(null); 2082 // 3. Delete the provider's contents. 2083 ProviderInfo provider = mProviders.get(index); 2084 updateProviderContents(provider, null); 2085 // 4. Remove the provider from the list. 2086 if (DEBUG) { 2087 Log.d(TAG, "Provider removed: " + provider); 2088 } 2089 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider); 2090 mProviders.remove(index); 2091 } 2092 } 2093 2094 private void updateProviderDescriptor(MediaRouteProvider providerInstance, 2095 MediaRouteProviderDescriptor descriptor) { 2096 int index = findProviderInfo(providerInstance); 2097 if (index >= 0) { 2098 // Update the provider's contents. 2099 ProviderInfo provider = mProviders.get(index); 2100 updateProviderContents(provider, descriptor); 2101 } 2102 } 2103 2104 private int findProviderInfo(MediaRouteProvider providerInstance) { 2105 final int count = mProviders.size(); 2106 for (int i = 0; i < count; i++) { 2107 if (mProviders.get(i).mProviderInstance == providerInstance) { 2108 return i; 2109 } 2110 } 2111 return -1; 2112 } 2113 2114 private void updateProviderContents(ProviderInfo provider, 2115 MediaRouteProviderDescriptor providerDescriptor) { 2116 if (provider.updateDescriptor(providerDescriptor)) { 2117 // Update all existing routes and reorder them to match 2118 // the order of their descriptors. 2119 int targetIndex = 0; 2120 boolean selectedRouteDescriptorChanged = false; 2121 if (providerDescriptor != null) { 2122 if (providerDescriptor.isValid()) { 2123 final List<MediaRouteDescriptor> routeDescriptors = 2124 providerDescriptor.getRoutes(); 2125 final int routeCount = routeDescriptors.size(); 2126 // Updating route group's contents requires all member routes' information. 2127 // Add the groups to the lists and update them later. 2128 List<Pair<RouteInfo, MediaRouteDescriptor>> addedGroups = new ArrayList<>(); 2129 List<Pair<RouteInfo, MediaRouteDescriptor>> updatedGroups = 2130 new ArrayList<>(); 2131 for (int i = 0; i < routeCount; i++) { 2132 final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i); 2133 final String id = routeDescriptor.getId(); 2134 final int sourceIndex = provider.findRouteByDescriptorId(id); 2135 if (sourceIndex < 0) { 2136 // 1. Add the route to the list. 2137 String uniqueId = assignRouteUniqueId(provider, id); 2138 boolean isGroup = routeDescriptor.getGroupMemberIds() != null; 2139 RouteInfo route = isGroup ? new RouteGroup(provider, id, uniqueId) : 2140 new RouteInfo(provider, id, uniqueId); 2141 provider.mRoutes.add(targetIndex++, route); 2142 mRoutes.add(route); 2143 // 2. Create the route's contents. 2144 if (isGroup) { 2145 addedGroups.add(new Pair(route, routeDescriptor)); 2146 } else { 2147 route.maybeUpdateDescriptor(routeDescriptor); 2148 // 3. Notify clients about addition. 2149 if (DEBUG) { 2150 Log.d(TAG, "Route added: " + route); 2151 } 2152 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route); 2153 } 2154 2155 } else if (sourceIndex < targetIndex) { 2156 Log.w(TAG, "Ignoring route descriptor with duplicate id: " 2157 + routeDescriptor); 2158 } else { 2159 // 1. Reorder the route within the list. 2160 RouteInfo route = provider.mRoutes.get(sourceIndex); 2161 Collections.swap(provider.mRoutes, 2162 sourceIndex, targetIndex++); 2163 // 2. Update the route's contents. 2164 if (route instanceof RouteGroup) { 2165 updatedGroups.add(new Pair(route, routeDescriptor)); 2166 } else { 2167 // 3. Notify clients about changes. 2168 if (updateRouteDescriptorAndNotify(route, routeDescriptor) 2169 != 0) { 2170 if (route == mSelectedRoute) { 2171 selectedRouteDescriptorChanged = true; 2172 } 2173 } 2174 } 2175 } 2176 } 2177 // Update the new and/or existing groups. 2178 for (Pair<RouteInfo, MediaRouteDescriptor> pair : addedGroups) { 2179 RouteInfo route = pair.first; 2180 route.maybeUpdateDescriptor(pair.second); 2181 if (DEBUG) { 2182 Log.d(TAG, "Route added: " + route); 2183 } 2184 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route); 2185 } 2186 for (Pair<RouteInfo, MediaRouteDescriptor> pair : updatedGroups) { 2187 RouteInfo route = pair.first; 2188 if (updateRouteDescriptorAndNotify(route, pair.second) != 0) { 2189 if (route == mSelectedRoute) { 2190 selectedRouteDescriptorChanged = true; 2191 } 2192 } 2193 } 2194 } else { 2195 Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor); 2196 } 2197 } 2198 2199 // Dispose all remaining routes that do not have matching descriptors. 2200 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 2201 // 1. Delete the route's contents. 2202 RouteInfo route = provider.mRoutes.get(i); 2203 route.maybeUpdateDescriptor(null); 2204 // 2. Remove the route from the list. 2205 mRoutes.remove(route); 2206 } 2207 2208 // Update the selected route if needed. 2209 updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged); 2210 2211 // Now notify clients about routes that were removed. 2212 // We do this after updating the selected route to ensure 2213 // that the framework media router observes the new route 2214 // selection before the removal since removing the currently 2215 // selected route may have side-effects. 2216 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) { 2217 RouteInfo route = provider.mRoutes.remove(i); 2218 if (DEBUG) { 2219 Log.d(TAG, "Route removed: " + route); 2220 } 2221 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route); 2222 } 2223 2224 // Notify provider changed. 2225 if (DEBUG) { 2226 Log.d(TAG, "Provider changed: " + provider); 2227 } 2228 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider); 2229 } 2230 } 2231 2232 private int updateRouteDescriptorAndNotify(RouteInfo route, 2233 MediaRouteDescriptor routeDescriptor) { 2234 int changes = route.maybeUpdateDescriptor(routeDescriptor); 2235 if (changes != 0) { 2236 if ((changes & RouteInfo.CHANGE_GENERAL) != 0) { 2237 if (DEBUG) { 2238 Log.d(TAG, "Route changed: " + route); 2239 } 2240 mCallbackHandler.post( 2241 CallbackHandler.MSG_ROUTE_CHANGED, route); 2242 } 2243 if ((changes & RouteInfo.CHANGE_VOLUME) != 0) { 2244 if (DEBUG) { 2245 Log.d(TAG, "Route volume changed: " + route); 2246 } 2247 mCallbackHandler.post( 2248 CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route); 2249 } 2250 if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) { 2251 if (DEBUG) { 2252 Log.d(TAG, "Route presentation display changed: " 2253 + route); 2254 } 2255 mCallbackHandler.post(CallbackHandler. 2256 MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route); 2257 } 2258 } 2259 return changes; 2260 } 2261 2262 private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) { 2263 // Although route descriptor ids are unique within a provider, it's 2264 // possible for there to be two providers with the same package name. 2265 // Therefore we must dedupe the composite id. 2266 String componentName = provider.getComponentName().flattenToShortString(); 2267 String uniqueId = componentName + ":" + routeDescriptorId; 2268 if (findRouteByUniqueId(uniqueId) < 0) { 2269 mUniqueIdMap.put(new Pair(componentName, routeDescriptorId), uniqueId); 2270 return uniqueId; 2271 } 2272 Log.w(TAG, "Either " + routeDescriptorId + " isn't unique in " + componentName 2273 + " or we're trying to assign a unique ID for an already added route"); 2274 for (int i = 2; ; i++) { 2275 String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i); 2276 if (findRouteByUniqueId(newUniqueId) < 0) { 2277 mUniqueIdMap.put(new Pair(componentName, routeDescriptorId), newUniqueId); 2278 return newUniqueId; 2279 } 2280 } 2281 } 2282 2283 private int findRouteByUniqueId(String uniqueId) { 2284 final int count = mRoutes.size(); 2285 for (int i = 0; i < count; i++) { 2286 if (mRoutes.get(i).mUniqueId.equals(uniqueId)) { 2287 return i; 2288 } 2289 } 2290 return -1; 2291 } 2292 2293 private String getUniqueId(ProviderInfo provider, String routeDescriptorId) { 2294 String componentName = provider.getComponentName().flattenToShortString(); 2295 return mUniqueIdMap.get(new Pair(componentName, routeDescriptorId)); 2296 } 2297 2298 private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) { 2299 // Update default route. 2300 if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) { 2301 Log.i(TAG, "Clearing the default route because it " 2302 + "is no longer selectable: " + mDefaultRoute); 2303 mDefaultRoute = null; 2304 } 2305 if (mDefaultRoute == null && !mRoutes.isEmpty()) { 2306 for (RouteInfo route : mRoutes) { 2307 if (isSystemDefaultRoute(route) && isRouteSelectable(route)) { 2308 mDefaultRoute = route; 2309 Log.i(TAG, "Found default route: " + mDefaultRoute); 2310 break; 2311 } 2312 } 2313 } 2314 2315 // Update selected route. 2316 if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) { 2317 Log.i(TAG, "Unselecting the current route because it " 2318 + "is no longer selectable: " + mSelectedRoute); 2319 setSelectedRouteInternal(null, 2320 MediaRouter.UNSELECT_REASON_UNKNOWN); 2321 } 2322 if (mSelectedRoute == null) { 2323 // Choose a new route. 2324 // This will have the side-effect of updating the playback info when 2325 // the new route is selected. 2326 setSelectedRouteInternal(chooseFallbackRoute(), 2327 MediaRouter.UNSELECT_REASON_UNKNOWN); 2328 } else if (selectedRouteDescriptorChanged) { 2329 // Update the playback info because the properties of the route have changed. 2330 updatePlaybackInfoFromSelectedRoute(); 2331 } 2332 } 2333 2334 private RouteInfo chooseFallbackRoute() { 2335 // When the current route is removed or no longer selectable, 2336 // we want to revert to a live audio route if there is 2337 // one (usually Bluetooth A2DP). Failing that, use 2338 // the default route. 2339 for (RouteInfo route : mRoutes) { 2340 if (route != mDefaultRoute 2341 && isSystemLiveAudioOnlyRoute(route) 2342 && isRouteSelectable(route)) { 2343 return route; 2344 } 2345 } 2346 return mDefaultRoute; 2347 } 2348 2349 private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) { 2350 return route.getProviderInstance() == mSystemProvider 2351 && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) 2352 && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 2353 } 2354 2355 private boolean isRouteSelectable(RouteInfo route) { 2356 // This tests whether the route is still valid and enabled. 2357 // The route descriptor field is set to null when the route is removed. 2358 return route.mDescriptor != null && route.mEnabled; 2359 } 2360 2361 private boolean isSystemDefaultRoute(RouteInfo route) { 2362 return route.getProviderInstance() == mSystemProvider 2363 && route.mDescriptorId.equals( 2364 SystemMediaRouteProvider.DEFAULT_ROUTE_ID); 2365 } 2366 2367 private void setSelectedRouteInternal(RouteInfo route, int unselectReason) { 2368 if (mSelectedRoute != route) { 2369 if (mSelectedRoute != null) { 2370 if (DEBUG) { 2371 Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: " 2372 + unselectReason); 2373 } 2374 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute); 2375 if (mSelectedRouteController != null) { 2376 mSelectedRouteController.onUnselect(unselectReason); 2377 mSelectedRouteController.onRelease(); 2378 mSelectedRouteController = null; 2379 } 2380 if (mGroupMemberControllers != null) { 2381 for (RouteController controller : mGroupMemberControllers.values()) { 2382 controller.onUnselect(); 2383 controller.onRelease(); 2384 } 2385 mGroupMemberControllers = null; 2386 } 2387 } 2388 2389 mSelectedRoute = route; 2390 2391 if (mSelectedRoute != null) { 2392 mSelectedRouteController = route.getProviderInstance().onCreateRouteController( 2393 route.mDescriptorId); 2394 if (mSelectedRouteController != null) { 2395 mSelectedRouteController.onSelect(); 2396 } 2397 if (DEBUG) { 2398 Log.d(TAG, "Route selected: " + mSelectedRoute); 2399 } 2400 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute); 2401 2402 if (mSelectedRoute instanceof RouteGroup) { 2403 mGroupMemberControllers = new HashMap<>(); 2404 RouteGroup group = (RouteGroup) mSelectedRoute; 2405 for (RouteInfo groupMember : group.getRoutes()) { 2406 RouteController controller = groupMember.getProviderInstance() 2407 .onCreateRouteController(groupMember.mDescriptorId); 2408 controller.onSelect(); 2409 mGroupMemberControllers.put(groupMember.mDescriptorId, controller); 2410 } 2411 } 2412 } 2413 2414 updatePlaybackInfoFromSelectedRoute(); 2415 } 2416 } 2417 2418 @Override 2419 public RouteInfo getSystemRouteByDescriptorId(String id) { 2420 int providerIndex = findProviderInfo(mSystemProvider); 2421 if (providerIndex >= 0) { 2422 ProviderInfo provider = mProviders.get(providerIndex); 2423 int routeIndex = provider.findRouteByDescriptorId(id); 2424 if (routeIndex >= 0) { 2425 return provider.mRoutes.get(routeIndex); 2426 } 2427 } 2428 return null; 2429 } 2430 2431 public void addRemoteControlClient(Object rcc) { 2432 int index = findRemoteControlClientRecord(rcc); 2433 if (index < 0) { 2434 RemoteControlClientRecord record = new RemoteControlClientRecord(rcc); 2435 mRemoteControlClients.add(record); 2436 } 2437 } 2438 2439 public void removeRemoteControlClient(Object rcc) { 2440 int index = findRemoteControlClientRecord(rcc); 2441 if (index >= 0) { 2442 RemoteControlClientRecord record = mRemoteControlClients.remove(index); 2443 record.disconnect(); 2444 } 2445 } 2446 2447 public void setMediaSession(Object session) { 2448 if (mMediaSession != null) { 2449 mMediaSession.clearVolumeHandling(); 2450 } 2451 if (session == null) { 2452 mMediaSession = null; 2453 } else { 2454 mMediaSession = new MediaSessionRecord(session); 2455 updatePlaybackInfoFromSelectedRoute(); 2456 } 2457 } 2458 2459 public void setMediaSessionCompat(final MediaSessionCompat session) { 2460 mCompatSession = session; 2461 if (android.os.Build.VERSION.SDK_INT >= 21) { 2462 setMediaSession(session != null ? session.getMediaSession() : null); 2463 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 2464 if (mRccMediaSession != null) { 2465 removeRemoteControlClient(mRccMediaSession.getRemoteControlClient()); 2466 mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener); 2467 } 2468 mRccMediaSession = session; 2469 if (session != null) { 2470 session.addOnActiveChangeListener(mSessionActiveListener); 2471 if (session.isActive()) { 2472 addRemoteControlClient(session.getRemoteControlClient()); 2473 } 2474 } 2475 } 2476 } 2477 2478 public MediaSessionCompat.Token getMediaSessionToken() { 2479 if (mMediaSession != null) { 2480 return mMediaSession.getToken(); 2481 } else if (mCompatSession != null) { 2482 return mCompatSession.getSessionToken(); 2483 } 2484 return null; 2485 } 2486 2487 private int findRemoteControlClientRecord(Object rcc) { 2488 final int count = mRemoteControlClients.size(); 2489 for (int i = 0; i < count; i++) { 2490 RemoteControlClientRecord record = mRemoteControlClients.get(i); 2491 if (record.getRemoteControlClient() == rcc) { 2492 return i; 2493 } 2494 } 2495 return -1; 2496 } 2497 2498 private void updatePlaybackInfoFromSelectedRoute() { 2499 if (mSelectedRoute != null) { 2500 mPlaybackInfo.volume = mSelectedRoute.getVolume(); 2501 mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax(); 2502 mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling(); 2503 mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream(); 2504 mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType(); 2505 2506 final int count = mRemoteControlClients.size(); 2507 for (int i = 0; i < count; i++) { 2508 RemoteControlClientRecord record = mRemoteControlClients.get(i); 2509 record.updatePlaybackInfo(); 2510 } 2511 if (mMediaSession != null) { 2512 if (mSelectedRoute == getDefaultRoute()) { 2513 // Local route 2514 mMediaSession.clearVolumeHandling(); 2515 } else { 2516 @VolumeProviderCompat.ControlType int controlType = 2517 VolumeProviderCompat.VOLUME_CONTROL_FIXED; 2518 if (mPlaybackInfo.volumeHandling 2519 == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) { 2520 controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; 2521 } 2522 mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax, 2523 mPlaybackInfo.volume); 2524 } 2525 } 2526 } else { 2527 if (mMediaSession != null) { 2528 mMediaSession.clearVolumeHandling(); 2529 } 2530 } 2531 } 2532 2533 private final class ProviderCallback extends MediaRouteProvider.Callback { 2534 @Override 2535 public void onDescriptorChanged(MediaRouteProvider provider, 2536 MediaRouteProviderDescriptor descriptor) { 2537 updateProviderDescriptor(provider, descriptor); 2538 } 2539 } 2540 2541 private final class MediaSessionRecord { 2542 private final MediaSessionCompat mMsCompat; 2543 2544 private @VolumeProviderCompat.ControlType int mControlType; 2545 private int mMaxVolume; 2546 private VolumeProviderCompat mVpCompat; 2547 2548 public MediaSessionRecord(Object mediaSession) { 2549 mMsCompat = MediaSessionCompat.obtain(mApplicationContext, mediaSession); 2550 } 2551 2552 public void configureVolume(@VolumeProviderCompat.ControlType int controlType, 2553 int max, int current) { 2554 if (mVpCompat != null && controlType == mControlType && max == mMaxVolume) { 2555 // If we haven't changed control type or max just set the 2556 // new current volume 2557 mVpCompat.setCurrentVolume(current); 2558 } else { 2559 // Otherwise create a new provider and update 2560 mVpCompat = new VolumeProviderCompat(controlType, max, current) { 2561 @Override 2562 public void onSetVolumeTo(final int volume) { 2563 mCallbackHandler.post(new Runnable() { 2564 @Override 2565 public void run() { 2566 if (mSelectedRoute != null) { 2567 mSelectedRoute.requestSetVolume(volume); 2568 } 2569 } 2570 }); 2571 } 2572 2573 @Override 2574 public void onAdjustVolume(final int direction) { 2575 mCallbackHandler.post(new Runnable() { 2576 @Override 2577 public void run() { 2578 if (mSelectedRoute != null) { 2579 mSelectedRoute.requestUpdateVolume(direction); 2580 } 2581 } 2582 }); 2583 } 2584 }; 2585 mMsCompat.setPlaybackToRemote(mVpCompat); 2586 } 2587 } 2588 2589 public void clearVolumeHandling() { 2590 mMsCompat.setPlaybackToLocal(mPlaybackInfo.playbackStream); 2591 mVpCompat = null; 2592 } 2593 2594 public MediaSessionCompat.Token getToken() { 2595 return mMsCompat.getSessionToken(); 2596 } 2597 2598 } 2599 2600 private final class RemoteControlClientRecord 2601 implements RemoteControlClientCompat.VolumeCallback { 2602 private final RemoteControlClientCompat mRccCompat; 2603 private boolean mDisconnected; 2604 2605 public RemoteControlClientRecord(Object rcc) { 2606 mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc); 2607 mRccCompat.setVolumeCallback(this); 2608 updatePlaybackInfo(); 2609 } 2610 2611 public Object getRemoteControlClient() { 2612 return mRccCompat.getRemoteControlClient(); 2613 } 2614 2615 public void disconnect() { 2616 mDisconnected = true; 2617 mRccCompat.setVolumeCallback(null); 2618 } 2619 2620 public void updatePlaybackInfo() { 2621 mRccCompat.setPlaybackInfo(mPlaybackInfo); 2622 } 2623 2624 @Override 2625 public void onVolumeSetRequest(int volume) { 2626 if (!mDisconnected && mSelectedRoute != null) { 2627 mSelectedRoute.requestSetVolume(volume); 2628 } 2629 } 2630 2631 @Override 2632 public void onVolumeUpdateRequest(int direction) { 2633 if (!mDisconnected && mSelectedRoute != null) { 2634 mSelectedRoute.requestUpdateVolume(direction); 2635 } 2636 } 2637 } 2638 2639 private final class CallbackHandler extends Handler { 2640 private final ArrayList<CallbackRecord> mTempCallbackRecords = 2641 new ArrayList<CallbackRecord>(); 2642 2643 private static final int MSG_TYPE_MASK = 0xff00; 2644 private static final int MSG_TYPE_ROUTE = 0x0100; 2645 private static final int MSG_TYPE_PROVIDER = 0x0200; 2646 2647 public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1; 2648 public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2; 2649 public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3; 2650 public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4; 2651 public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5; 2652 public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6; 2653 public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7; 2654 2655 public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1; 2656 public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2; 2657 public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3; 2658 2659 public void post(int msg, Object obj) { 2660 obtainMessage(msg, obj).sendToTarget(); 2661 } 2662 2663 @Override 2664 public void handleMessage(Message msg) { 2665 final int what = msg.what; 2666 final Object obj = msg.obj; 2667 2668 // Synchronize state with the system media router. 2669 syncWithSystemProvider(what, obj); 2670 2671 // Invoke all registered callbacks. 2672 // Build a list of callbacks before invoking them in case callbacks 2673 // are added or removed during dispatch. 2674 try { 2675 for (int i = mRouters.size(); --i >= 0; ) { 2676 MediaRouter router = mRouters.get(i).get(); 2677 if (router == null) { 2678 mRouters.remove(i); 2679 } else { 2680 mTempCallbackRecords.addAll(router.mCallbackRecords); 2681 } 2682 } 2683 2684 final int callbackCount = mTempCallbackRecords.size(); 2685 for (int i = 0; i < callbackCount; i++) { 2686 invokeCallback(mTempCallbackRecords.get(i), what, obj); 2687 } 2688 } finally { 2689 mTempCallbackRecords.clear(); 2690 } 2691 } 2692 2693 private void syncWithSystemProvider(int what, Object obj) { 2694 switch (what) { 2695 case MSG_ROUTE_ADDED: 2696 mSystemProvider.onSyncRouteAdded((RouteInfo)obj); 2697 break; 2698 case MSG_ROUTE_REMOVED: 2699 mSystemProvider.onSyncRouteRemoved((RouteInfo)obj); 2700 break; 2701 case MSG_ROUTE_CHANGED: 2702 mSystemProvider.onSyncRouteChanged((RouteInfo)obj); 2703 break; 2704 case MSG_ROUTE_SELECTED: 2705 mSystemProvider.onSyncRouteSelected((RouteInfo)obj); 2706 break; 2707 } 2708 } 2709 2710 private void invokeCallback(CallbackRecord record, int what, Object obj) { 2711 final MediaRouter router = record.mRouter; 2712 final MediaRouter.Callback callback = record.mCallback; 2713 switch (what & MSG_TYPE_MASK) { 2714 case MSG_TYPE_ROUTE: { 2715 final RouteInfo route = (RouteInfo)obj; 2716 if (!record.filterRouteEvent(route)) { 2717 break; 2718 } 2719 switch (what) { 2720 case MSG_ROUTE_ADDED: 2721 callback.onRouteAdded(router, route); 2722 break; 2723 case MSG_ROUTE_REMOVED: 2724 callback.onRouteRemoved(router, route); 2725 break; 2726 case MSG_ROUTE_CHANGED: 2727 callback.onRouteChanged(router, route); 2728 break; 2729 case MSG_ROUTE_VOLUME_CHANGED: 2730 callback.onRouteVolumeChanged(router, route); 2731 break; 2732 case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED: 2733 callback.onRoutePresentationDisplayChanged(router, route); 2734 break; 2735 case MSG_ROUTE_SELECTED: 2736 callback.onRouteSelected(router, route); 2737 break; 2738 case MSG_ROUTE_UNSELECTED: 2739 callback.onRouteUnselected(router, route); 2740 break; 2741 } 2742 break; 2743 } 2744 case MSG_TYPE_PROVIDER: { 2745 final ProviderInfo provider = (ProviderInfo)obj; 2746 switch (what) { 2747 case MSG_PROVIDER_ADDED: 2748 callback.onProviderAdded(router, provider); 2749 break; 2750 case MSG_PROVIDER_REMOVED: 2751 callback.onProviderRemoved(router, provider); 2752 break; 2753 case MSG_PROVIDER_CHANGED: 2754 callback.onProviderChanged(router, provider); 2755 break; 2756 } 2757 } 2758 } 2759 } 2760 } 2761 } 2762} 2763