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