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