MediaRouter.java revision b58b8f832d06b0ffa8886eba5a4916578a3b8743
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media; 18 19import android.bluetooth.BluetoothA2dp; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.res.Resources; 25import android.os.Handler; 26import android.util.Log; 27 28import java.util.ArrayList; 29import java.util.HashMap; 30import java.util.List; 31 32/** 33 * MediaRouter allows applications to control the routing of media channels 34 * and streams from the current device to external speakers and destination devices. 35 * 36 * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String) 37 * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE 38 * Context.MEDIA_ROUTER_SERVICE}. 39 * 40 * <p>The media router API is not thread-safe; all interactions with it must be 41 * done from the main thread of the process.</p> 42 */ 43public class MediaRouter { 44 private static final String TAG = "MediaRouter"; 45 46 static class Static { 47 private final Resources mResources; 48 private final AudioManager mAudioManager; 49 private final Handler mHandler; 50 private final ArrayList<CallbackInfo> mCallbacks = new ArrayList<CallbackInfo>(); 51 52 private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 53 private final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>(); 54 55 private final RouteCategory mSystemCategory; 56 private final HeadphoneChangedBroadcastReceiver mHeadphoneChangedReceiver; 57 58 private RouteInfo mDefaultAudio; 59 private RouteInfo mBluetoothA2dpRoute; 60 61 private RouteInfo mSelectedRoute; 62 63 Static(Context appContext) { 64 mResources = Resources.getSystem(); 65 mHandler = new Handler(appContext.getMainLooper()); 66 67 mAudioManager = (AudioManager)appContext.getSystemService(Context.AUDIO_SERVICE); 68 69 // XXX this doesn't deal with locale changes! 70 mSystemCategory = new RouteCategory(mResources.getText( 71 com.android.internal.R.string.default_audio_route_category_name), 72 ROUTE_TYPE_LIVE_AUDIO, false); 73 74 final IntentFilter speakerFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); 75 speakerFilter.addAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG); 76 speakerFilter.addAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG); 77 speakerFilter.addAction(Intent.ACTION_HDMI_AUDIO_PLUG); 78 mHeadphoneChangedReceiver = new HeadphoneChangedBroadcastReceiver(); 79 appContext.registerReceiver(mHeadphoneChangedReceiver, speakerFilter); 80 81 mDefaultAudio = new RouteInfo(mSystemCategory); 82 mDefaultAudio.mName = mResources.getText( 83 com.android.internal.R.string.default_audio_route_name); 84 mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; 85 addRoute(mDefaultAudio); 86 } 87 } 88 89 static Static sStatic; 90 91 /** 92 * Route type flag for live audio. 93 * 94 * <p>A device that supports live audio routing will allow the media audio stream 95 * to be routed to supported destinations. This can include internal speakers or 96 * audio jacks on the device itself, A2DP devices, and more.</p> 97 * 98 * <p>Once initiated this routing is transparent to the application. All audio 99 * played on the media stream will be routed to the selected destination.</p> 100 */ 101 public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1; 102 103 /** 104 * Route type flag for application-specific usage. 105 * 106 * <p>Unlike other media route types, user routes are managed by the application. 107 * The MediaRouter will manage and dispatch events for user routes, but the application 108 * is expected to interpret the meaning of these events and perform the requested 109 * routing tasks.</p> 110 */ 111 public static final int ROUTE_TYPE_USER = 0x00800000; 112 113 // Maps application contexts 114 static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>(); 115 116 static String typesToString(int types) { 117 final StringBuilder result = new StringBuilder(); 118 if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) { 119 result.append("ROUTE_TYPE_LIVE_AUDIO "); 120 } 121 if ((types & ROUTE_TYPE_USER) != 0) { 122 result.append("ROUTE_TYPE_USER "); 123 } 124 return result.toString(); 125 } 126 127 /** @hide */ 128 public MediaRouter(Context context) { 129 synchronized (Static.class) { 130 if (sStatic == null) { 131 sStatic = new Static(context.getApplicationContext()); 132 } 133 } 134 } 135 136 /** 137 * @hide for use by framework routing UI 138 */ 139 public RouteInfo getSystemAudioRoute() { 140 return sStatic.mDefaultAudio; 141 } 142 143 /** 144 * Return the currently selected route for the given types 145 * 146 * @param type route types 147 * @return the selected route 148 */ 149 public RouteInfo getSelectedRoute(int type) { 150 return sStatic.mSelectedRoute; 151 } 152 153 static void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) { 154 sStatic.mDefaultAudio.mName = headphonesPresent 155 ? headphonesName 156 : sStatic.mResources.getText( 157 com.android.internal.R.string.default_audio_route_name); 158 dispatchRouteChanged(sStatic.mDefaultAudio); 159 } 160 161 /** 162 * Add a callback to listen to events about specific kinds of media routes. 163 * If the specified callback is already registered, its registration will be updated for any 164 * additional route types specified. 165 * 166 * @param types Types of routes this callback is interested in 167 * @param cb Callback to add 168 */ 169 public void addCallback(int types, Callback cb) { 170 final int count = sStatic.mCallbacks.size(); 171 for (int i = 0; i < count; i++) { 172 final CallbackInfo info = sStatic.mCallbacks.get(i); 173 if (info.cb == cb) { 174 info.type &= types; 175 return; 176 } 177 } 178 sStatic.mCallbacks.add(new CallbackInfo(cb, types, this)); 179 } 180 181 /** 182 * Remove the specified callback. It will no longer receive events about media routing. 183 * 184 * @param cb Callback to remove 185 */ 186 public void removeCallback(Callback cb) { 187 final int count = sStatic.mCallbacks.size(); 188 for (int i = 0; i < count; i++) { 189 if (sStatic.mCallbacks.get(i).cb == cb) { 190 sStatic.mCallbacks.remove(i); 191 return; 192 } 193 } 194 Log.w(TAG, "removeCallback(" + cb + "): callback not registered"); 195 } 196 197 /** 198 * Select the specified route to use for output of the given media types. 199 * 200 * @param types type flags indicating which types this route should be used for. 201 * The route must support at least a subset. 202 * @param route Route to select 203 */ 204 public void selectRoute(int types, RouteInfo route) { 205 selectRouteStatic(types, route); 206 } 207 208 static void selectRouteStatic(int types, RouteInfo route) { 209 if (sStatic.mSelectedRoute == route) return; 210 211 if (sStatic.mSelectedRoute != null) { 212 // TODO filter types properly 213 dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(), 214 sStatic.mSelectedRoute); 215 } 216 sStatic.mSelectedRoute = route; 217 if (route != null) { 218 // TODO filter types properly 219 dispatchRouteSelected(types & route.getSupportedTypes(), route); 220 } 221 } 222 223 /** 224 * Add an app-specified route for media to the MediaRouter. 225 * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)} 226 * 227 * @param info Definition of the route to add 228 * @see #createUserRoute() 229 * @see #removeUserRoute(UserRouteInfo) 230 */ 231 public void addUserRoute(UserRouteInfo info) { 232 addRoute(info); 233 } 234 235 static void addRoute(RouteInfo info) { 236 final RouteCategory cat = info.getCategory(); 237 if (!sStatic.mCategories.contains(cat)) { 238 sStatic.mCategories.add(cat); 239 } 240 final boolean onlyRoute = sStatic.mRoutes.isEmpty(); 241 if (cat.isGroupable() && !(info instanceof RouteGroup)) { 242 // Enforce that any added route in a groupable category must be in a group. 243 final RouteGroup group = new RouteGroup(info.getCategory()); 244 sStatic.mRoutes.add(group); 245 dispatchRouteAdded(group); 246 247 final int at = group.getRouteCount(); 248 group.addRoute(info); 249 dispatchRouteGrouped(info, group, at); 250 251 info = group; 252 } else { 253 sStatic.mRoutes.add(info); 254 dispatchRouteAdded(info); 255 } 256 257 if (onlyRoute) { 258 selectRouteStatic(info.getSupportedTypes(), info); 259 } 260 } 261 262 /** 263 * Remove an app-specified route for media from the MediaRouter. 264 * 265 * @param info Definition of the route to remove 266 * @see #addUserRoute(UserRouteInfo) 267 */ 268 public void removeUserRoute(UserRouteInfo info) { 269 removeRoute(info); 270 } 271 272 /** 273 * Remove all app-specified routes from the MediaRouter. 274 * 275 * @see #removeUserRoute(UserRouteInfo) 276 */ 277 public void clearUserRoutes() { 278 for (int i = 0; i < sStatic.mRoutes.size(); i++) { 279 final RouteInfo info = sStatic.mRoutes.get(i); 280 if (info instanceof UserRouteInfo) { 281 removeRouteAt(i); 282 i--; 283 } 284 } 285 } 286 287 static void removeRoute(RouteInfo info) { 288 if (sStatic.mRoutes.remove(info)) { 289 final RouteCategory removingCat = info.getCategory(); 290 final int count = sStatic.mRoutes.size(); 291 boolean found = false; 292 for (int i = 0; i < count; i++) { 293 final RouteCategory cat = sStatic.mRoutes.get(i).getCategory(); 294 if (removingCat == cat) { 295 found = true; 296 break; 297 } 298 } 299 if (!found) { 300 sStatic.mCategories.remove(removingCat); 301 } 302 dispatchRouteRemoved(info); 303 } 304 } 305 306 void removeRouteAt(int routeIndex) { 307 if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) { 308 final RouteInfo info = sStatic.mRoutes.remove(routeIndex); 309 final RouteCategory removingCat = info.getCategory(); 310 final int count = sStatic.mRoutes.size(); 311 boolean found = false; 312 for (int i = 0; i < count; i++) { 313 final RouteCategory cat = sStatic.mRoutes.get(i).getCategory(); 314 if (removingCat == cat) { 315 found = true; 316 break; 317 } 318 } 319 if (!found) { 320 sStatic.mCategories.remove(removingCat); 321 } 322 dispatchRouteRemoved(info); 323 } 324 } 325 326 /** 327 * Return the number of {@link MediaRouter.RouteCategory categories} currently 328 * represented by routes known to this MediaRouter. 329 * 330 * @return the number of unique categories represented by this MediaRouter's known routes 331 */ 332 public int getCategoryCount() { 333 return sStatic.mCategories.size(); 334 } 335 336 /** 337 * Return the {@link MediaRouter.RouteCategory category} at the given index. 338 * Valid indices are in the range [0-getCategoryCount). 339 * 340 * @param index which category to return 341 * @return the category at index 342 */ 343 public RouteCategory getCategoryAt(int index) { 344 return sStatic.mCategories.get(index); 345 } 346 347 /** 348 * Return the number of {@link MediaRouter.RouteInfo routes} currently known 349 * to this MediaRouter. 350 * 351 * @return the number of routes tracked by this router 352 */ 353 public int getRouteCount() { 354 return sStatic.mRoutes.size(); 355 } 356 357 /** 358 * Return the route at the specified index. 359 * 360 * @param index index of the route to return 361 * @return the route at index 362 */ 363 public RouteInfo getRouteAt(int index) { 364 return sStatic.mRoutes.get(index); 365 } 366 367 static int getRouteCountStatic() { 368 return sStatic.mRoutes.size(); 369 } 370 371 static RouteInfo getRouteAtStatic(int index) { 372 return sStatic.mRoutes.get(index); 373 } 374 375 /** 376 * Create a new user route that may be modified and registered for use by the application. 377 * 378 * @param category The category the new route will belong to 379 * @return A new UserRouteInfo for use by the application 380 * 381 * @see #addUserRoute(UserRouteInfo) 382 * @see #removeUserRoute(UserRouteInfo) 383 * @see #createRouteCategory(CharSequence) 384 */ 385 public UserRouteInfo createUserRoute(RouteCategory category) { 386 return new UserRouteInfo(category); 387 } 388 389 /** 390 * Create a new route category. Each route must belong to a category. 391 * 392 * @param name Name of the new category 393 * @param isGroupable true if routes in this category may be grouped with one another 394 * @return the new RouteCategory 395 */ 396 public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) { 397 return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable); 398 } 399 400 static void updateRoute(final RouteInfo info) { 401 dispatchRouteChanged(info); 402 } 403 404 static void dispatchRouteSelected(int type, RouteInfo info) { 405 final int count = sStatic.mCallbacks.size(); 406 for (int i = 0; i < count; i++) { 407 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 408 if ((cbi.type & type) != 0) { 409 cbi.cb.onRouteSelected(cbi.router, type, info); 410 } 411 } 412 } 413 414 static void dispatchRouteUnselected(int type, RouteInfo info) { 415 final int count = sStatic.mCallbacks.size(); 416 for (int i = 0; i < count; i++) { 417 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 418 if ((cbi.type & type) != 0) { 419 cbi.cb.onRouteUnselected(cbi.router, type, info); 420 } 421 } 422 } 423 424 static void dispatchRouteChanged(RouteInfo info) { 425 final int count = sStatic.mCallbacks.size(); 426 for (int i = 0; i < count; i++) { 427 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 428 if ((cbi.type & info.mSupportedTypes) != 0) { 429 cbi.cb.onRouteChanged(cbi.router, info); 430 } 431 } 432 } 433 434 static void dispatchRouteAdded(RouteInfo info) { 435 final int count = sStatic.mCallbacks.size(); 436 for (int i = 0; i < count; i++) { 437 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 438 if ((cbi.type & info.mSupportedTypes) != 0) { 439 cbi.cb.onRouteAdded(cbi.router, info); 440 } 441 } 442 } 443 444 static void dispatchRouteRemoved(RouteInfo info) { 445 final int count = sStatic.mCallbacks.size(); 446 for (int i = 0; i < count; i++) { 447 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 448 if ((cbi.type & info.mSupportedTypes) != 0) { 449 cbi.cb.onRouteRemoved(cbi.router, info); 450 } 451 } 452 } 453 454 static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) { 455 final int count = sStatic.mCallbacks.size(); 456 for (int i = 0; i < count; i++) { 457 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 458 if ((cbi.type & group.mSupportedTypes) != 0) { 459 cbi.cb.onRouteGrouped(cbi.router, info, group, index); 460 } 461 } 462 } 463 464 static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) { 465 final int count = sStatic.mCallbacks.size(); 466 for (int i = 0; i < count; i++) { 467 final CallbackInfo cbi = sStatic.mCallbacks.get(i); 468 if ((cbi.type & group.mSupportedTypes) != 0) { 469 cbi.cb.onRouteUngrouped(cbi.router, info, group); 470 } 471 } 472 } 473 474 static void onA2dpDeviceConnected() { 475 final RouteInfo info = new RouteInfo(sStatic.mSystemCategory); 476 info.mName = "Bluetooth"; // TODO Fetch the real name of the device 477 sStatic.mBluetoothA2dpRoute = info; 478 addRoute(sStatic.mBluetoothA2dpRoute); 479 } 480 481 static void onA2dpDeviceDisconnected() { 482 removeRoute(sStatic.mBluetoothA2dpRoute); 483 sStatic.mBluetoothA2dpRoute = null; 484 } 485 486 /** 487 * Information about a media route. 488 */ 489 public static class RouteInfo { 490 CharSequence mName; 491 private CharSequence mStatus; 492 int mSupportedTypes; 493 RouteGroup mGroup; 494 final RouteCategory mCategory; 495 496 RouteInfo(RouteCategory category) { 497 mCategory = category; 498 } 499 500 /** 501 * @return The user-friendly name of a media route. This is the string presented 502 * to users who may select this as the active route. 503 */ 504 public CharSequence getName() { 505 return mName; 506 } 507 508 /** 509 * @return The user-friendly status for a media route. This may include a description 510 * of the currently playing media, if available. 511 */ 512 public CharSequence getStatus() { 513 return mStatus; 514 } 515 516 /** 517 * @return A media type flag set describing which types this route supports. 518 */ 519 public int getSupportedTypes() { 520 return mSupportedTypes; 521 } 522 523 /** 524 * @return The group that this route belongs to. 525 */ 526 public RouteGroup getGroup() { 527 return mGroup; 528 } 529 530 /** 531 * @return the category this route belongs to. 532 */ 533 public RouteCategory getCategory() { 534 return mCategory; 535 } 536 537 void setStatusInt(CharSequence status) { 538 if (!status.equals(mStatus)) { 539 mStatus = status; 540 routeUpdated(); 541 if (mGroup != null) { 542 mGroup.memberStatusChanged(this, status); 543 } 544 routeUpdated(); 545 } 546 } 547 548 void routeUpdated() { 549 updateRoute(this); 550 } 551 552 @Override 553 public String toString() { 554 String supportedTypes = typesToString(mSupportedTypes); 555 return "RouteInfo{ name=" + mName + ", status=" + mStatus + 556 " category=" + mCategory + 557 " supportedTypes=" + supportedTypes + "}"; 558 } 559 } 560 561 /** 562 * Information about a route that the application may define and modify. 563 * 564 * @see MediaRouter.RouteInfo 565 */ 566 public static class UserRouteInfo extends RouteInfo { 567 568 UserRouteInfo(RouteCategory category) { 569 super(category); 570 mSupportedTypes = ROUTE_TYPE_USER; 571 } 572 573 /** 574 * Set the user-visible name of this route. 575 * @param name Name to display to the user to describe this route 576 */ 577 public void setName(CharSequence name) { 578 mName = name; 579 routeUpdated(); 580 } 581 582 /** 583 * Set the current user-visible status for this route. 584 * @param status Status to display to the user to describe what the endpoint 585 * of this route is currently doing 586 */ 587 public void setStatus(CharSequence status) { 588 setStatusInt(status); 589 } 590 } 591 592 /** 593 * Information about a route that consists of multiple other routes in a group. 594 */ 595 public static class RouteGroup extends RouteInfo { 596 final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); 597 private boolean mUpdateName; 598 599 RouteGroup(RouteCategory category) { 600 super(category); 601 mGroup = this; 602 } 603 604 public CharSequence getName() { 605 if (mUpdateName) updateName(); 606 return super.getName(); 607 } 608 609 /** 610 * Add a route to this group. The route must not currently belong to another group. 611 * 612 * @param route route to add to this group 613 */ 614 public void addRoute(RouteInfo route) { 615 if (route.getGroup() != null) { 616 throw new IllegalStateException("Route " + route + " is already part of a group."); 617 } 618 if (route.getCategory() != mCategory) { 619 throw new IllegalArgumentException( 620 "Route cannot be added to a group with a different category. " + 621 "(Route category=" + route.getCategory() + 622 " group category=" + mCategory + ")"); 623 } 624 final int at = mRoutes.size(); 625 mRoutes.add(route); 626 mUpdateName = true; 627 dispatchRouteGrouped(route, this, at); 628 routeUpdated(); 629 } 630 631 /** 632 * Add a route to this group before the specified index. 633 * 634 * @param route route to add 635 * @param insertAt insert the new route before this index 636 */ 637 public void addRoute(RouteInfo route, int insertAt) { 638 if (route.getGroup() != null) { 639 throw new IllegalStateException("Route " + route + " is already part of a group."); 640 } 641 if (route.getCategory() != mCategory) { 642 throw new IllegalArgumentException( 643 "Route cannot be added to a group with a different category. " + 644 "(Route category=" + route.getCategory() + 645 " group category=" + mCategory + ")"); 646 } 647 mRoutes.add(insertAt, route); 648 mUpdateName = true; 649 dispatchRouteGrouped(route, this, insertAt); 650 routeUpdated(); 651 } 652 653 /** 654 * Remove a route from this group. 655 * 656 * @param route route to remove 657 */ 658 public void removeRoute(RouteInfo route) { 659 if (route.getGroup() != this) { 660 throw new IllegalArgumentException("Route " + route + 661 " is not a member of this group."); 662 } 663 mRoutes.remove(route); 664 mUpdateName = true; 665 dispatchRouteUngrouped(route, this); 666 routeUpdated(); 667 } 668 669 /** 670 * Remove the route at the specified index from this group. 671 * 672 * @param index index of the route to remove 673 */ 674 public void removeRoute(int index) { 675 RouteInfo route = mRoutes.remove(index); 676 mUpdateName = true; 677 dispatchRouteUngrouped(route, this); 678 routeUpdated(); 679 } 680 681 /** 682 * @return The number of routes in this group 683 */ 684 public int getRouteCount() { 685 return mRoutes.size(); 686 } 687 688 /** 689 * Return the route in this group at the specified index 690 * 691 * @param index Index to fetch 692 * @return The route at index 693 */ 694 public RouteInfo getRouteAt(int index) { 695 return mRoutes.get(index); 696 } 697 698 void memberNameChanged(RouteInfo info, CharSequence name) { 699 mUpdateName = true; 700 routeUpdated(); 701 } 702 703 void memberStatusChanged(RouteInfo info, CharSequence status) { 704 setStatusInt(status); 705 } 706 707 void updateName() { 708 final StringBuilder sb = new StringBuilder(); 709 final int count = mRoutes.size(); 710 for (int i = 0; i < count; i++) { 711 final RouteInfo info = mRoutes.get(i); 712 if (i > 0) sb.append(", "); 713 sb.append(info.mName); 714 } 715 mName = sb.toString(); 716 mUpdateName = false; 717 } 718 } 719 720 /** 721 * Definition of a category of routes. All routes belong to a category. 722 */ 723 public static class RouteCategory { 724 CharSequence mName; 725 int mTypes; 726 final boolean mGroupable; 727 728 RouteCategory(CharSequence name, int types, boolean groupable) { 729 mName = name; 730 mTypes = types; 731 mGroupable = groupable; 732 } 733 734 /** 735 * @return the name of this route category 736 */ 737 public CharSequence getName() { 738 return mName; 739 } 740 741 /** 742 * Return the current list of routes in this category that have been added 743 * to the MediaRouter. 744 * 745 * <p>This list will not include routes that are nested within RouteGroups. 746 * A RouteGroup is treated as a single route within its category.</p> 747 * 748 * @param out a List to fill with the routes in this category. If this parameter is 749 * non-null, it will be cleared, filled with the current routes with this 750 * category, and returned. If this parameter is null, a new List will be 751 * allocated to report the category's current routes. 752 * @return A list with the routes in this category that have been added to the MediaRouter. 753 */ 754 public List<RouteInfo> getRoutes(List<RouteInfo> out) { 755 if (out == null) { 756 out = new ArrayList<RouteInfo>(); 757 } else { 758 out.clear(); 759 } 760 761 final int count = getRouteCountStatic(); 762 for (int i = 0; i < count; i++) { 763 final RouteInfo route = getRouteAtStatic(i); 764 if (route.mCategory == this) { 765 out.add(route); 766 } 767 } 768 return out; 769 } 770 771 /** 772 * @return Flag set describing the route types supported by this category 773 */ 774 public int getSupportedTypes() { 775 return mTypes; 776 } 777 778 /** 779 * Return whether or not this category supports grouping. 780 * 781 * <p>If this method returns true, all routes obtained from this category 782 * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p> 783 * 784 * @return true if this category supports 785 */ 786 public boolean isGroupable() { 787 return mGroupable; 788 } 789 790 public String toString() { 791 return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) + 792 " groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }"; 793 } 794 } 795 796 static class CallbackInfo { 797 public int type; 798 public final Callback cb; 799 public final MediaRouter router; 800 801 public CallbackInfo(Callback cb, int type, MediaRouter router) { 802 this.cb = cb; 803 this.type = type; 804 this.router = router; 805 } 806 } 807 808 /** 809 * Interface for receiving events about media routing changes. 810 * All methods of this interface will be called from the application's main thread. 811 * 812 * <p>A Callback will only receive events relevant to routes that the callback 813 * was registered for.</p> 814 * 815 * @see MediaRouter#addCallback(int, Callback) 816 * @see MediaRouter#removeCallback(Callback) 817 */ 818 public interface Callback { 819 /** 820 * Called when the supplied route becomes selected as the active route 821 * for the given route type. 822 * 823 * @param router the MediaRouter reporting the event 824 * @param type Type flag set indicating the routes that have been selected 825 * @param info Route that has been selected for the given route types 826 */ 827 public void onRouteSelected(MediaRouter router, int type, RouteInfo info); 828 829 /** 830 * Called when the supplied route becomes unselected as the active route 831 * for the given route type. 832 * 833 * @param router the MediaRouter reporting the event 834 * @param type Type flag set indicating the routes that have been unselected 835 * @param info Route that has been unselected for the given route types 836 */ 837 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info); 838 839 /** 840 * Called when a route for the specified type was added. 841 * 842 * @param router the MediaRouter reporting the event 843 * @param info Route that has become available for use 844 */ 845 public void onRouteAdded(MediaRouter router, RouteInfo info); 846 847 /** 848 * Called when a route for the specified type was removed. 849 * 850 * @param router the MediaRouter reporting the event 851 * @param info Route that has been removed from availability 852 */ 853 public void onRouteRemoved(MediaRouter router, RouteInfo info); 854 855 /** 856 * Called when an aspect of the indicated route has changed. 857 * 858 * <p>This will not indicate that the types supported by this route have 859 * changed, only that cosmetic info such as name or status have been updated.</p> 860 * 861 * @param router the MediaRouter reporting the event 862 * @param info The route that was changed 863 */ 864 public void onRouteChanged(MediaRouter router, RouteInfo info); 865 866 /** 867 * Called when a route is added to a group. 868 * 869 * @param router the MediaRouter reporting the event 870 * @param info The route that was added 871 * @param group The group the route was added to 872 * @param index The route index within group that info was added at 873 */ 874 public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index); 875 876 /** 877 * Called when a route is removed from a group. 878 * 879 * @param router the MediaRouter reporting the event 880 * @param info The route that was removed 881 * @param group The group the route was removed from 882 */ 883 public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group); 884 } 885 886 /** 887 * Stub implementation of the {@link MediaRouter.Callback} interface. 888 * Each interface method is defined as a no-op. Override just the ones 889 * you need. 890 */ 891 public static class SimpleCallback implements Callback { 892 893 @Override 894 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 895 } 896 897 @Override 898 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 899 } 900 901 @Override 902 public void onRouteAdded(MediaRouter router, RouteInfo info) { 903 } 904 905 @Override 906 public void onRouteRemoved(MediaRouter router, RouteInfo info) { 907 } 908 909 @Override 910 public void onRouteChanged(MediaRouter router, RouteInfo info) { 911 } 912 913 @Override 914 public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, 915 int index) { 916 } 917 918 @Override 919 public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { 920 } 921 922 } 923 924 class BtChangedBroadcastReceiver extends BroadcastReceiver { 925 @Override 926 public void onReceive(Context context, Intent intent) { 927 final String action = intent.getAction(); 928 if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 929 final int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1); 930 if (state == BluetoothA2dp.STATE_CONNECTED) { 931 onA2dpDeviceConnected(); 932 } else if (state == BluetoothA2dp.STATE_DISCONNECTING || 933 state == BluetoothA2dp.STATE_DISCONNECTED) { 934 onA2dpDeviceDisconnected(); 935 } 936 } 937 } 938 } 939 940 static class HeadphoneChangedBroadcastReceiver extends BroadcastReceiver { 941 @Override 942 public void onReceive(Context context, Intent intent) { 943 final String action = intent.getAction(); 944 if (Intent.ACTION_HEADSET_PLUG.equals(action)) { 945 final boolean plugged = intent.getIntExtra("state", 0) != 0; 946 final String name = sStatic.mResources.getString( 947 com.android.internal.R.string.default_audio_route_name_headphones); 948 onHeadphonesPlugged(plugged, name); 949 } else if (Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG.equals(action) || 950 Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG.equals(action)) { 951 final boolean plugged = intent.getIntExtra("state", 0) != 0; 952 final String name = sStatic.mResources.getString( 953 com.android.internal.R.string.default_audio_route_name_dock_speakers); 954 onHeadphonesPlugged(plugged, name); 955 } else if (Intent.ACTION_HDMI_AUDIO_PLUG.equals(action)) { 956 final boolean plugged = intent.getIntExtra("state", 0) != 0; 957 final String name = sStatic.mResources.getString( 958 com.android.internal.R.string.default_audio_route_name_hdmi); 959 onHeadphonesPlugged(plugged, name); 960 } 961 } 962 } 963} 964