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