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