SystemMediaRouteProvider.java revision 19e77645aa6ce65b466d570375b36e2428f170b3
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.media; 18 19import android.content.BroadcastReceiver; 20import android.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.content.res.Resources; 24import android.media.AudioManager; 25import android.os.Build; 26import android.support.v7.mediarouter.R; 27import android.view.Display; 28 29import java.util.ArrayList; 30import java.util.List; 31import java.util.Locale; 32 33/** 34 * Provides routes for built-in system destinations such as the local display 35 * and speaker. On Jellybean and newer platform releases, queries the framework 36 * MediaRouter for framework-provided routes and registers non-framework-provided 37 * routes as user routes. 38 */ 39abstract class SystemMediaRouteProvider extends MediaRouteProvider { 40 private static final String TAG = "SystemMediaRouteProvider"; 41 42 public static final String PACKAGE_NAME = "android"; 43 public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE"; 44 45 protected SystemMediaRouteProvider(Context context) { 46 super(context, new ProviderMetadata(PACKAGE_NAME)); 47 } 48 49 public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) { 50 if (Build.VERSION.SDK_INT >= 18) { 51 return new JellybeanMr2Impl(context, syncCallback); 52 } 53 if (Build.VERSION.SDK_INT >= 17) { 54 return new JellybeanMr1Impl(context, syncCallback); 55 } 56 if (Build.VERSION.SDK_INT >= 16) { 57 return new JellybeanImpl(context, syncCallback); 58 } 59 return new LegacyImpl(context); 60 } 61 62 /** 63 * Called by the media router when a route is added to synchronize state with 64 * the framework media router. 65 */ 66 public void onSyncRouteAdded(MediaRouter.RouteInfo route) { 67 } 68 69 /** 70 * Called by the media router when a route is removed to synchronize state with 71 * the framework media router. 72 */ 73 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) { 74 } 75 76 /** 77 * Called by the media router when a route is changed to synchronize state with 78 * the framework media router. 79 */ 80 public void onSyncRouteChanged(MediaRouter.RouteInfo route) { 81 } 82 83 /** 84 * Called by the media router when a route is selected to synchronize state with 85 * the framework media router. 86 */ 87 public void onSyncRouteSelected(MediaRouter.RouteInfo route) { 88 } 89 90 /** 91 * Callbacks into the media router to synchronize state with the framework media router. 92 */ 93 public interface SyncCallback { 94 public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id); 95 } 96 97 /** 98 * Legacy implementation for platform versions prior to Jellybean. 99 */ 100 static class LegacyImpl extends SystemMediaRouteProvider { 101 private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC; 102 103 private static final ArrayList<IntentFilter> CONTROL_FILTERS; 104 static { 105 IntentFilter f = new IntentFilter(); 106 f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 107 f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 108 109 CONTROL_FILTERS = new ArrayList<IntentFilter>(); 110 CONTROL_FILTERS.add(f); 111 } 112 113 private final AudioManager mAudioManager; 114 private final VolumeChangeReceiver mVolumeChangeReceiver; 115 private int mLastReportedVolume = -1; 116 117 public LegacyImpl(Context context) { 118 super(context); 119 mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 120 mVolumeChangeReceiver = new VolumeChangeReceiver(); 121 122 context.registerReceiver(mVolumeChangeReceiver, 123 new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION)); 124 publishRoutes(); 125 } 126 127 private void publishRoutes() { 128 Resources r = getContext().getResources(); 129 int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM); 130 mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM); 131 MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder( 132 DEFAULT_ROUTE_ID, r.getString(R.string.mr_system_route_name)) 133 .addControlFilters(CONTROL_FILTERS) 134 .setPlaybackStream(PLAYBACK_STREAM) 135 .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL) 136 .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) 137 .setVolumeMax(maxVolume) 138 .setVolume(mLastReportedVolume) 139 .build(); 140 141 MediaRouteProviderDescriptor providerDescriptor = 142 new MediaRouteProviderDescriptor.Builder() 143 .addRoute(defaultRoute) 144 .build(); 145 setDescriptor(providerDescriptor); 146 } 147 148 @Override 149 public RouteController onCreateRouteController(String routeId) { 150 if (routeId.equals(DEFAULT_ROUTE_ID)) { 151 return new DefaultRouteController(); 152 } 153 return null; 154 } 155 156 final class DefaultRouteController extends RouteController { 157 @Override 158 public void onSetVolume(int volume) { 159 mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0); 160 publishRoutes(); 161 } 162 163 @Override 164 public void onUpdateVolume(int delta) { 165 int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM); 166 int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM); 167 int newVolume = Math.min(maxVolume, Math.max(0, volume + delta)); 168 if (newVolume != volume) { 169 mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0); 170 } 171 publishRoutes(); 172 } 173 } 174 175 final class VolumeChangeReceiver extends BroadcastReceiver { 176 // These constants come from AudioManager. 177 public static final String VOLUME_CHANGED_ACTION = 178 "android.media.VOLUME_CHANGED_ACTION"; 179 public static final String EXTRA_VOLUME_STREAM_TYPE = 180 "android.media.EXTRA_VOLUME_STREAM_TYPE"; 181 public static final String EXTRA_VOLUME_STREAM_VALUE = 182 "android.media.EXTRA_VOLUME_STREAM_VALUE"; 183 184 @Override 185 public void onReceive(Context context, Intent intent) { 186 if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) { 187 final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1); 188 if (streamType == PLAYBACK_STREAM) { 189 final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1); 190 if (volume >= 0 && volume != mLastReportedVolume) { 191 publishRoutes(); 192 } 193 } 194 } 195 } 196 } 197 } 198 199 /** 200 * Jellybean implementation. 201 */ 202 static class JellybeanImpl extends SystemMediaRouteProvider 203 implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback { 204 private static final ArrayList<IntentFilter> LIVE_AUDIO_CONTROL_FILTERS; 205 static { 206 IntentFilter f = new IntentFilter(); 207 f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 208 209 LIVE_AUDIO_CONTROL_FILTERS = new ArrayList<IntentFilter>(); 210 LIVE_AUDIO_CONTROL_FILTERS.add(f); 211 } 212 213 private static final ArrayList<IntentFilter> LIVE_VIDEO_CONTROL_FILTERS; 214 static { 215 IntentFilter f = new IntentFilter(); 216 f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 217 218 LIVE_VIDEO_CONTROL_FILTERS = new ArrayList<IntentFilter>(); 219 LIVE_VIDEO_CONTROL_FILTERS.add(f); 220 } 221 222 private final SyncCallback mSyncCallback; 223 224 protected final Object mRouterObj; 225 protected final Object mCallbackObj; 226 protected final Object mVolumeCallbackObj; 227 protected final Object mUserRouteCategoryObj; 228 protected int mRouteTypes; 229 protected boolean mActiveScan; 230 protected boolean mCallbackRegistered; 231 232 // Maintains an association from framework routes to support library routes. 233 // Note that we cannot use the tag field for this because an application may 234 // have published its own user routes to the framework media router and already 235 // used the tag for its own purposes. 236 protected final ArrayList<SystemRouteRecord> mSystemRouteRecords = 237 new ArrayList<SystemRouteRecord>(); 238 239 // Maintains an association from support library routes to framework routes. 240 protected final ArrayList<UserRouteRecord> mUserRouteRecords = 241 new ArrayList<UserRouteRecord>(); 242 243 private MediaRouterJellybean.SelectRouteWorkaround mSelectRouteWorkaround; 244 private MediaRouterJellybean.GetDefaultRouteWorkaround mGetDefaultRouteWorkaround; 245 246 public JellybeanImpl(Context context, SyncCallback syncCallback) { 247 super(context); 248 mSyncCallback = syncCallback; 249 mRouterObj = MediaRouterJellybean.getMediaRouter(context); 250 mCallbackObj = createCallbackObj(); 251 mVolumeCallbackObj = createVolumeCallbackObj(); 252 253 Resources r = context.getResources(); 254 mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory( 255 mRouterObj, r.getString(R.string.mr_user_route_category_name), false); 256 257 updateSystemRoutes(); 258 } 259 260 @Override 261 public RouteController onCreateRouteController(String routeId) { 262 int index = findSystemRouteRecordByDescriptorId(routeId); 263 if (index >= 0) { 264 SystemRouteRecord record = mSystemRouteRecords.get(index); 265 return new SystemRouteController(record.mRouteObj); 266 } 267 return null; 268 } 269 270 @Override 271 public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { 272 int newRouteTypes = 0; 273 boolean newActiveScan = false; 274 if (request != null) { 275 final MediaRouteSelector selector = request.getSelector(); 276 final List<String> categories = selector.getControlCategories(); 277 final int count = categories.size(); 278 for (int i = 0; i < count; i++) { 279 String category = categories.get(i); 280 if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) { 281 newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO; 282 } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) { 283 newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO; 284 } else { 285 newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER; 286 } 287 } 288 newActiveScan = request.isActiveScan(); 289 } 290 291 if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) { 292 mRouteTypes = newRouteTypes; 293 mActiveScan = newActiveScan; 294 updateCallback(); 295 updateSystemRoutes(); 296 } 297 } 298 299 @Override 300 public void onRouteAdded(Object routeObj) { 301 if (addSystemRouteNoPublish(routeObj)) { 302 publishRoutes(); 303 } 304 } 305 306 private void updateSystemRoutes() { 307 boolean changed = false; 308 for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) { 309 changed |= addSystemRouteNoPublish(routeObj); 310 } 311 if (changed) { 312 publishRoutes(); 313 } 314 } 315 316 private boolean addSystemRouteNoPublish(Object routeObj) { 317 if (getUserRouteRecord(routeObj) == null 318 && findSystemRouteRecord(routeObj) < 0) { 319 String id = assignRouteId(routeObj); 320 SystemRouteRecord record = new SystemRouteRecord(routeObj, id); 321 updateSystemRouteDescriptor(record); 322 mSystemRouteRecords.add(record); 323 return true; 324 } 325 return false; 326 } 327 328 private String assignRouteId(Object routeObj) { 329 // TODO: The framework media router should supply a unique route id that 330 // we can use here. For now we use a hash of the route name and take care 331 // to dedupe it. 332 boolean isDefault = (getDefaultRoute() == routeObj); 333 String id = isDefault ? DEFAULT_ROUTE_ID : 334 String.format(Locale.US, "ROUTE_%08x", getRouteName(routeObj).hashCode()); 335 if (findSystemRouteRecordByDescriptorId(id) < 0) { 336 return id; 337 } 338 for (int i = 2; ; i++) { 339 String newId = String.format(Locale.US, "%s_%d", id, i); 340 if (findSystemRouteRecordByDescriptorId(newId) < 0) { 341 return newId; 342 } 343 } 344 } 345 346 @Override 347 public void onRouteRemoved(Object routeObj) { 348 if (getUserRouteRecord(routeObj) == null) { 349 int index = findSystemRouteRecord(routeObj); 350 if (index >= 0) { 351 mSystemRouteRecords.remove(index); 352 publishRoutes(); 353 } 354 } 355 } 356 357 @Override 358 public void onRouteChanged(Object routeObj) { 359 if (getUserRouteRecord(routeObj) == null) { 360 int index = findSystemRouteRecord(routeObj); 361 if (index >= 0) { 362 SystemRouteRecord record = mSystemRouteRecords.get(index); 363 updateSystemRouteDescriptor(record); 364 publishRoutes(); 365 } 366 } 367 } 368 369 @Override 370 public void onRouteVolumeChanged(Object routeObj) { 371 if (getUserRouteRecord(routeObj) == null) { 372 int index = findSystemRouteRecord(routeObj); 373 if (index >= 0) { 374 SystemRouteRecord record = mSystemRouteRecords.get(index); 375 int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj); 376 if (newVolume != record.mRouteDescriptor.getVolume()) { 377 record.mRouteDescriptor = 378 new MediaRouteDescriptor.Builder(record.mRouteDescriptor) 379 .setVolume(newVolume) 380 .build(); 381 publishRoutes(); 382 } 383 } 384 } 385 } 386 387 @Override 388 public void onRouteSelected(int type, Object routeObj) { 389 if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj, 390 MediaRouterJellybean.ALL_ROUTE_TYPES)) { 391 // The currently selected route has already changed so this callback 392 // is stale. Drop it to prevent getting into sync loops. 393 return; 394 } 395 396 UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj); 397 if (userRouteRecord != null) { 398 userRouteRecord.mRoute.select(); 399 } else { 400 // Select the route if it already exists in the compat media router. 401 // If not, we will select it instead when the route is added. 402 int index = findSystemRouteRecord(routeObj); 403 if (index >= 0) { 404 SystemRouteRecord record = mSystemRouteRecords.get(index); 405 MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId( 406 record.mRouteDescriptorId); 407 if (route != null) { 408 route.select(); 409 } 410 } 411 } 412 } 413 414 @Override 415 public void onRouteUnselected(int type, Object routeObj) { 416 // Nothing to do when a route is unselected. 417 // We only need to handle when a route is selected. 418 } 419 420 @Override 421 public void onRouteGrouped(Object routeObj, Object groupObj, int index) { 422 // Route grouping is deprecated and no longer supported. 423 } 424 425 @Override 426 public void onRouteUngrouped(Object routeObj, Object groupObj) { 427 // Route grouping is deprecated and no longer supported. 428 } 429 430 @Override 431 public void onVolumeSetRequest(Object routeObj, int volume) { 432 UserRouteRecord record = getUserRouteRecord(routeObj); 433 if (record != null) { 434 record.mRoute.requestSetVolume(volume); 435 } 436 } 437 438 @Override 439 public void onVolumeUpdateRequest(Object routeObj, int direction) { 440 UserRouteRecord record = getUserRouteRecord(routeObj); 441 if (record != null) { 442 record.mRoute.requestUpdateVolume(direction); 443 } 444 } 445 446 @Override 447 public void onSyncRouteAdded(MediaRouter.RouteInfo route) { 448 if (route.getProviderInstance() != this) { 449 Object routeObj = MediaRouterJellybean.createUserRoute( 450 mRouterObj, mUserRouteCategoryObj); 451 UserRouteRecord record = new UserRouteRecord(route, routeObj); 452 MediaRouterJellybean.RouteInfo.setTag(routeObj, record); 453 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj); 454 updateUserRouteProperties(record); 455 mUserRouteRecords.add(record); 456 MediaRouterJellybean.addUserRoute(mRouterObj, routeObj); 457 } else { 458 // If the newly added route is the counterpart of the currently selected 459 // route in the framework media router then ensure it is selected in 460 // the compat media router. 461 Object routeObj = MediaRouterJellybean.getSelectedRoute( 462 mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES); 463 int index = findSystemRouteRecord(routeObj); 464 if (index >= 0) { 465 SystemRouteRecord record = mSystemRouteRecords.get(index); 466 if (record.mRouteDescriptorId.equals(route.getDescriptorId())) { 467 route.select(); 468 } 469 } 470 } 471 } 472 473 @Override 474 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) { 475 if (route.getProviderInstance() != this) { 476 int index = findUserRouteRecord(route); 477 if (index >= 0) { 478 UserRouteRecord record = mUserRouteRecords.remove(index); 479 MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null); 480 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null); 481 MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj); 482 } 483 } 484 } 485 486 @Override 487 public void onSyncRouteChanged(MediaRouter.RouteInfo route) { 488 if (route.getProviderInstance() != this) { 489 int index = findUserRouteRecord(route); 490 if (index >= 0) { 491 UserRouteRecord record = mUserRouteRecords.get(index); 492 updateUserRouteProperties(record); 493 } 494 } 495 } 496 497 @Override 498 public void onSyncRouteSelected(MediaRouter.RouteInfo route) { 499 if (!route.isSelected()) { 500 // The currently selected route has already changed so this callback 501 // is stale. Drop it to prevent getting into sync loops. 502 return; 503 } 504 505 if (route.getProviderInstance() != this) { 506 int index = findUserRouteRecord(route); 507 if (index >= 0) { 508 UserRouteRecord record = mUserRouteRecords.get(index); 509 selectRoute(record.mRouteObj); 510 } 511 } else { 512 int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId()); 513 if (index >= 0) { 514 SystemRouteRecord record = mSystemRouteRecords.get(index); 515 selectRoute(record.mRouteObj); 516 } 517 } 518 } 519 520 protected void publishRoutes() { 521 MediaRouteProviderDescriptor.Builder builder = 522 new MediaRouteProviderDescriptor.Builder(); 523 int count = mSystemRouteRecords.size(); 524 for (int i = 0; i < count; i++) { 525 builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor); 526 } 527 528 setDescriptor(builder.build()); 529 } 530 531 protected int findSystemRouteRecord(Object routeObj) { 532 final int count = mSystemRouteRecords.size(); 533 for (int i = 0; i < count; i++) { 534 if (mSystemRouteRecords.get(i).mRouteObj == routeObj) { 535 return i; 536 } 537 } 538 return -1; 539 } 540 541 protected int findSystemRouteRecordByDescriptorId(String id) { 542 final int count = mSystemRouteRecords.size(); 543 for (int i = 0; i < count; i++) { 544 if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) { 545 return i; 546 } 547 } 548 return -1; 549 } 550 551 protected int findUserRouteRecord(MediaRouter.RouteInfo route) { 552 final int count = mUserRouteRecords.size(); 553 for (int i = 0; i < count; i++) { 554 if (mUserRouteRecords.get(i).mRoute == route) { 555 return i; 556 } 557 } 558 return -1; 559 } 560 561 protected UserRouteRecord getUserRouteRecord(Object routeObj) { 562 Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj); 563 return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null; 564 } 565 566 protected void updateSystemRouteDescriptor(SystemRouteRecord record) { 567 // We must always recreate the route descriptor when making any changes 568 // because they are intended to be immutable once published. 569 MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder( 570 record.mRouteDescriptorId, getRouteName(record.mRouteObj)); 571 onBuildSystemRouteDescriptor(record, builder); 572 record.mRouteDescriptor = builder.build(); 573 } 574 575 protected String getRouteName(Object routeObj) { 576 // Routes should not have null names but it may happen for badly configured 577 // user routes. We tolerate this by using an empty name string here but 578 // such unnamed routes will be discarded by the media router upstream 579 // (with a log message so we can track down the problem). 580 CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext()); 581 return name != null ? name.toString() : ""; 582 } 583 584 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record, 585 MediaRouteDescriptor.Builder builder) { 586 int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes( 587 record.mRouteObj); 588 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) { 589 builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS); 590 } 591 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) { 592 builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS); 593 } 594 595 builder.setPlaybackType( 596 MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj)); 597 builder.setPlaybackStream( 598 MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj)); 599 builder.setVolume( 600 MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj)); 601 builder.setVolumeMax( 602 MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj)); 603 builder.setVolumeHandling( 604 MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj)); 605 } 606 607 protected void updateUserRouteProperties(UserRouteRecord record) { 608 MediaRouterJellybean.UserRouteInfo.setName( 609 record.mRouteObj, record.mRoute.getName()); 610 MediaRouterJellybean.UserRouteInfo.setPlaybackType( 611 record.mRouteObj, record.mRoute.getPlaybackType()); 612 MediaRouterJellybean.UserRouteInfo.setPlaybackStream( 613 record.mRouteObj, record.mRoute.getPlaybackStream()); 614 MediaRouterJellybean.UserRouteInfo.setVolume( 615 record.mRouteObj, record.mRoute.getVolume()); 616 MediaRouterJellybean.UserRouteInfo.setVolumeMax( 617 record.mRouteObj, record.mRoute.getVolumeMax()); 618 MediaRouterJellybean.UserRouteInfo.setVolumeHandling( 619 record.mRouteObj, record.mRoute.getVolumeHandling()); 620 } 621 622 protected void updateCallback() { 623 if (mCallbackRegistered) { 624 mCallbackRegistered = false; 625 MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj); 626 } 627 628 if (mRouteTypes != 0) { 629 mCallbackRegistered = true; 630 MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj); 631 } 632 } 633 634 protected Object createCallbackObj() { 635 return MediaRouterJellybean.createCallback(this); 636 } 637 638 protected Object createVolumeCallbackObj() { 639 return MediaRouterJellybean.createVolumeCallback(this); 640 } 641 642 protected void selectRoute(Object routeObj) { 643 if (mSelectRouteWorkaround == null) { 644 mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround(); 645 } 646 mSelectRouteWorkaround.selectRoute(mRouterObj, 647 MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj); 648 } 649 650 protected Object getDefaultRoute() { 651 if (mGetDefaultRouteWorkaround == null) { 652 mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround(); 653 } 654 return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj); 655 } 656 657 /** 658 * Represents a route that is provided by the framework media router 659 * and published by this route provider to the support library media router. 660 */ 661 protected static final class SystemRouteRecord { 662 public final Object mRouteObj; 663 public final String mRouteDescriptorId; 664 public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation 665 666 public SystemRouteRecord(Object routeObj, String id) { 667 mRouteObj = routeObj; 668 mRouteDescriptorId = id; 669 } 670 } 671 672 /** 673 * Represents a route that is provided by the support library media router 674 * and published by this route provider to the framework media router. 675 */ 676 protected static final class UserRouteRecord { 677 public final MediaRouter.RouteInfo mRoute; 678 public final Object mRouteObj; 679 680 public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) { 681 mRoute = route; 682 mRouteObj = routeObj; 683 } 684 } 685 686 protected final class SystemRouteController extends RouteController { 687 private final Object mRouteObj; 688 689 public SystemRouteController(Object routeObj) { 690 mRouteObj = routeObj; 691 } 692 693 @Override 694 public void onSetVolume(int volume) { 695 MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume); 696 } 697 698 @Override 699 public void onUpdateVolume(int delta) { 700 MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta); 701 } 702 } 703 } 704 705 /** 706 * Jellybean MR1 implementation. 707 */ 708 private static class JellybeanMr1Impl extends JellybeanImpl 709 implements MediaRouterJellybeanMr1.Callback { 710 private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround; 711 private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround; 712 713 public JellybeanMr1Impl(Context context, SyncCallback syncCallback) { 714 super(context, syncCallback); 715 } 716 717 @Override 718 public void onRoutePresentationDisplayChanged(Object routeObj) { 719 int index = findSystemRouteRecord(routeObj); 720 if (index >= 0) { 721 SystemRouteRecord record = mSystemRouteRecords.get(index); 722 Display newPresentationDisplay = 723 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj); 724 int newPresentationDisplayId = (newPresentationDisplay != null 725 ? newPresentationDisplay.getDisplayId() : -1); 726 if (newPresentationDisplayId 727 != record.mRouteDescriptor.getPresentationDisplayId()) { 728 record.mRouteDescriptor = 729 new MediaRouteDescriptor.Builder(record.mRouteDescriptor) 730 .setPresentationDisplayId(newPresentationDisplayId) 731 .build(); 732 publishRoutes(); 733 } 734 } 735 } 736 737 @Override 738 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record, 739 MediaRouteDescriptor.Builder builder) { 740 super.onBuildSystemRouteDescriptor(record, builder); 741 742 if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) { 743 builder.setEnabled(false); 744 } 745 746 if (isConnecting(record)) { 747 builder.setConnecting(true); 748 } 749 750 Display presentationDisplay = 751 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj); 752 if (presentationDisplay != null) { 753 builder.setPresentationDisplayId(presentationDisplay.getDisplayId()); 754 } 755 } 756 757 @Override 758 protected void updateCallback() { 759 super.updateCallback(); 760 761 if (mActiveScanWorkaround == null) { 762 mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround( 763 getContext(), getHandler()); 764 } 765 mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0); 766 } 767 768 @Override 769 protected Object createCallbackObj() { 770 return MediaRouterJellybeanMr1.createCallback(this); 771 } 772 773 protected boolean isConnecting(SystemRouteRecord record) { 774 if (mIsConnectingWorkaround == null) { 775 mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround(); 776 } 777 return mIsConnectingWorkaround.isConnecting(record.mRouteObj); 778 } 779 } 780 781 /** 782 * Jellybean MR2 implementation. 783 */ 784 private static class JellybeanMr2Impl extends JellybeanMr1Impl { 785 public JellybeanMr2Impl(Context context, SyncCallback syncCallback) { 786 super(context, syncCallback); 787 } 788 789 @Override 790 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record, 791 MediaRouteDescriptor.Builder builder) { 792 super.onBuildSystemRouteDescriptor(record, builder); 793 794 CharSequence description = 795 MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj); 796 if (description != null) { 797 builder.setDescription(description.toString()); 798 } 799 } 800 801 @Override 802 protected void selectRoute(Object routeObj) { 803 MediaRouterJellybean.selectRoute(mRouterObj, 804 MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj); 805 } 806 807 @Override 808 protected Object getDefaultRoute() { 809 return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj); 810 } 811 812 @Override 813 protected void updateUserRouteProperties(UserRouteRecord record) { 814 super.updateUserRouteProperties(record); 815 816 MediaRouterJellybeanMr2.UserRouteInfo.setDescription( 817 record.mRouteObj, record.mRoute.getDescription()); 818 } 819 820 @Override 821 protected void updateCallback() { 822 if (mCallbackRegistered) { 823 MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj); 824 } 825 826 mCallbackRegistered = true; 827 MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj, 828 MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS 829 | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0)); 830 } 831 832 @Override 833 protected boolean isConnecting(SystemRouteRecord record) { 834 return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj); 835 } 836 } 837} 838