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