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