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