SystemMediaRouteProvider.java revision 55b361aea868e53e848bc45af3a55ae43e7871c3
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; 31 32/** 33 * Provides routes for built-in system destinations such as the local display 34 * and speaker. On Jellybean and newer platform releases, queries the framework 35 * MediaRouter for framework-provided routes and registers non-framework-provided 36 * routes as user routes. 37 */ 38abstract class SystemMediaRouteProvider extends MediaRouteProvider { 39 private static final String TAG = "SystemMediaRouteProvider"; 40 41 public static final String PACKAGE_NAME = "android"; 42 public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE"; 43 44 protected SystemMediaRouteProvider(Context context) { 45 super(context, new ProviderMetadata(PACKAGE_NAME)); 46 } 47 48 public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) { 49 if (Build.VERSION.SDK_INT >= 18) { 50 return new JellybeanMr2Impl(context, syncCallback); 51 } 52 if (Build.VERSION.SDK_INT >= 17) { 53 return new JellybeanMr1Impl(context, syncCallback); 54 } 55 if (Build.VERSION.SDK_INT >= 16) { 56 return new JellybeanImpl(context, syncCallback); 57 } 58 return new LegacyImpl(context); 59 } 60 61 /** 62 * Called by the media router when a route is added to synchronize state with 63 * the framework media router. 64 */ 65 public void onSyncRouteAdded(MediaRouter.RouteInfo route) { 66 } 67 68 /** 69 * Called by the media router when a route is removed to synchronize state with 70 * the framework media router. 71 */ 72 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) { 73 } 74 75 /** 76 * Called by the media router when a route is changed to synchronize state with 77 * the framework media router. 78 */ 79 public void onSyncRouteChanged(MediaRouter.RouteInfo route) { 80 } 81 82 /** 83 * Called by the media router when a route is selected to synchronize state with 84 * the framework media router. 85 */ 86 public void onSyncRouteSelected(MediaRouter.RouteInfo route) { 87 } 88 89 /** 90 * Callbacks into the media router to synchronize state with the framework media router. 91 */ 92 public interface SyncCallback { 93 public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id); 94 } 95 96 /** 97 * Legacy implementation for platform versions prior to Jellybean. 98 */ 99 static class LegacyImpl extends SystemMediaRouteProvider { 100 private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC; 101 102 private static final ArrayList<IntentFilter> CONTROL_FILTERS; 103 static { 104 IntentFilter f = new IntentFilter(); 105 f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 106 f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 107 108 CONTROL_FILTERS = new ArrayList<IntentFilter>(); 109 CONTROL_FILTERS.add(f); 110 } 111 112 private final AudioManager mAudioManager; 113 private final VolumeChangeReceiver mVolumeChangeReceiver; 114 private int mLastReportedVolume = -1; 115 116 public LegacyImpl(Context context) { 117 super(context); 118 mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 119 mVolumeChangeReceiver = new VolumeChangeReceiver(); 120 121 context.registerReceiver(mVolumeChangeReceiver, 122 new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION)); 123 publishRoutes(); 124 } 125 126 private void publishRoutes() { 127 Resources r = getContext().getResources(); 128 int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM); 129 mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM); 130 MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder( 131 DEFAULT_ROUTE_ID, r.getString(R.string.system_route_name)) 132 .addControlFilters(CONTROL_FILTERS) 133 .setPlaybackStream(PLAYBACK_STREAM) 134 .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL) 135 .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) 136 .setVolumeMax(maxVolume) 137 .setVolume(mLastReportedVolume) 138 .build(); 139 140 MediaRouteProviderDescriptor providerDescriptor = 141 new MediaRouteProviderDescriptor.Builder() 142 .addDiscoverableControlFilters(CONTROL_FILTERS) 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.user_route_category_name), false); 256 257 updateSystemRoutes(); 258 } 259 260 @Override 261 public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { 262 int newRouteTypes = 0; 263 boolean newActiveScan = false; 264 if (request != null) { 265 final MediaRouteSelector selector = request.getSelector(); 266 final List<String> categories = selector.getControlCategories(); 267 final int count = categories.size(); 268 for (int i = 0; i < count; i++) { 269 String category = categories.get(i); 270 if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) { 271 newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO; 272 } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) { 273 newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO; 274 } else { 275 newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER; 276 } 277 } 278 newActiveScan = request.isActiveScan(); 279 } 280 281 if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) { 282 mRouteTypes = newRouteTypes; 283 mActiveScan = newActiveScan; 284 updateCallback(); 285 updateSystemRoutes(); 286 } 287 } 288 289 @Override 290 public void onRouteAdded(Object routeObj) { 291 if (addSystemRouteNoPublish(routeObj)) { 292 publishRoutes(); 293 } 294 } 295 296 private void updateSystemRoutes() { 297 boolean changed = false; 298 for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) { 299 changed |= addSystemRouteNoPublish(routeObj); 300 } 301 if (changed) { 302 publishRoutes(); 303 } 304 } 305 306 private boolean addSystemRouteNoPublish(Object routeObj) { 307 if (getUserRouteRecord(routeObj) == null 308 && findSystemRouteRecord(routeObj) < 0) { 309 boolean isDefault = (getDefaultRoute() == routeObj); 310 SystemRouteRecord record = new SystemRouteRecord(routeObj, isDefault); 311 updateSystemRouteDescriptor(record); 312 mSystemRouteRecords.add(record); 313 return true; 314 } 315 return false; 316 } 317 318 @Override 319 public void onRouteRemoved(Object routeObj) { 320 if (getUserRouteRecord(routeObj) == null) { 321 int index = findSystemRouteRecord(routeObj); 322 if (index >= 0) { 323 mSystemRouteRecords.remove(index); 324 publishRoutes(); 325 } 326 } 327 } 328 329 @Override 330 public void onRouteChanged(Object routeObj) { 331 if (getUserRouteRecord(routeObj) == null) { 332 int index = findSystemRouteRecord(routeObj); 333 if (index >= 0) { 334 SystemRouteRecord record = mSystemRouteRecords.get(index); 335 updateSystemRouteDescriptor(record); 336 publishRoutes(); 337 } 338 } 339 } 340 341 @Override 342 public void onRouteVolumeChanged(Object routeObj) { 343 if (getUserRouteRecord(routeObj) == null) { 344 int index = findSystemRouteRecord(routeObj); 345 if (index >= 0) { 346 SystemRouteRecord record = mSystemRouteRecords.get(index); 347 int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj); 348 if (newVolume != record.mRouteDescriptor.getVolume()) { 349 record.mRouteDescriptor = 350 new MediaRouteDescriptor.Builder(record.mRouteDescriptor) 351 .setVolume(newVolume) 352 .build(); 353 publishRoutes(); 354 } 355 } 356 } 357 } 358 359 @Override 360 public void onRouteSelected(int type, Object routeObj) { 361 if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj, 362 MediaRouterJellybean.ALL_ROUTE_TYPES)) { 363 // The currently selected route has already changed so this callback 364 // is stale. Drop it to prevent getting into sync loops. 365 return; 366 } 367 368 UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj); 369 if (userRouteRecord != null) { 370 userRouteRecord.mRoute.select(); 371 } else { 372 // Select the route if it already exists in the compat media router. 373 // If not, we will select it instead when the route is added. 374 int index = findSystemRouteRecord(routeObj); 375 if (index >= 0) { 376 SystemRouteRecord record = mSystemRouteRecords.get(index); 377 MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId( 378 record.mRouteDescriptorId); 379 if (route != null) { 380 route.select(); 381 } 382 } 383 } 384 } 385 386 @Override 387 public void onRouteUnselected(int type, Object routeObj) { 388 // Nothing to do when a route is unselected. 389 // We only need to handle when a route is selected. 390 } 391 392 @Override 393 public void onRouteGrouped(Object routeObj, Object groupObj, int index) { 394 // Route grouping is deprecated and no longer supported. 395 } 396 397 @Override 398 public void onRouteUngrouped(Object routeObj, Object groupObj) { 399 // Route grouping is deprecated and no longer supported. 400 } 401 402 @Override 403 public void onVolumeSetRequest(Object routeObj, int volume) { 404 UserRouteRecord record = getUserRouteRecord(routeObj); 405 if (record != null) { 406 record.mRoute.requestSetVolume(volume); 407 } 408 } 409 410 @Override 411 public void onVolumeUpdateRequest(Object routeObj, int direction) { 412 UserRouteRecord record = getUserRouteRecord(routeObj); 413 if (record != null) { 414 record.mRoute.requestUpdateVolume(direction); 415 } 416 } 417 418 @Override 419 public void onSyncRouteAdded(MediaRouter.RouteInfo route) { 420 if (route.getProviderInstance() != this) { 421 Object routeObj = MediaRouterJellybean.createUserRoute( 422 mRouterObj, mUserRouteCategoryObj); 423 UserRouteRecord record = new UserRouteRecord(route, routeObj); 424 MediaRouterJellybean.RouteInfo.setTag(routeObj, record); 425 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj); 426 updateUserRouteProperties(record); 427 mUserRouteRecords.add(record); 428 MediaRouterJellybean.addUserRoute(mRouterObj, routeObj); 429 } else { 430 // If the newly added route is the counterpart of the currently selected 431 // route in the framework media router then ensure it is selected in 432 // the compat media router. 433 Object routeObj = MediaRouterJellybean.getSelectedRoute( 434 mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES); 435 int index = findSystemRouteRecord(routeObj); 436 if (index >= 0) { 437 SystemRouteRecord record = mSystemRouteRecords.get(index); 438 if (record.mRouteDescriptorId.equals(route.getDescriptorId())) { 439 route.select(); 440 } 441 } 442 } 443 } 444 445 @Override 446 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) { 447 if (route.getProviderInstance() != this) { 448 int index = findUserRouteRecord(route); 449 if (index >= 0) { 450 UserRouteRecord record = mUserRouteRecords.remove(index); 451 MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null); 452 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null); 453 MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj); 454 } 455 } 456 } 457 458 @Override 459 public void onSyncRouteChanged(MediaRouter.RouteInfo route) { 460 if (route.getProviderInstance() != this) { 461 int index = findUserRouteRecord(route); 462 if (index >= 0) { 463 UserRouteRecord record = mUserRouteRecords.get(index); 464 updateUserRouteProperties(record); 465 } 466 } 467 } 468 469 @Override 470 public void onSyncRouteSelected(MediaRouter.RouteInfo route) { 471 if (!route.isSelected()) { 472 // The currently selected route has already changed so this callback 473 // is stale. Drop it to prevent getting into sync loops. 474 return; 475 } 476 477 if (route.getProviderInstance() != this) { 478 int index = findUserRouteRecord(route); 479 if (index >= 0) { 480 UserRouteRecord record = mUserRouteRecords.get(index); 481 selectRoute(record.mRouteObj); 482 } 483 } else { 484 int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId()); 485 if (index >= 0) { 486 SystemRouteRecord record = mSystemRouteRecords.get(index); 487 selectRoute(record.mRouteObj); 488 } 489 } 490 } 491 492 protected void publishRoutes() { 493 MediaRouteProviderDescriptor.Builder builder = 494 new MediaRouteProviderDescriptor.Builder() 495 .addDiscoverableControlFilters(LIVE_AUDIO_CONTROL_FILTERS) 496 .addDiscoverableControlFilters(LIVE_VIDEO_CONTROL_FILTERS); 497 498 int count = mSystemRouteRecords.size(); 499 for (int i = 0; i < count; i++) { 500 builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor); 501 } 502 503 setDescriptor(builder.build()); 504 } 505 506 protected int findSystemRouteRecord(Object routeObj) { 507 final int count = mSystemRouteRecords.size(); 508 for (int i = 0; i < count; i++) { 509 if (mSystemRouteRecords.get(i).mRouteObj == routeObj) { 510 return i; 511 } 512 } 513 return -1; 514 } 515 516 protected int findSystemRouteRecordByDescriptorId(String id) { 517 final int count = mSystemRouteRecords.size(); 518 for (int i = 0; i < count; i++) { 519 if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) { 520 return i; 521 } 522 } 523 return -1; 524 } 525 526 protected int findUserRouteRecord(MediaRouter.RouteInfo route) { 527 final int count = mUserRouteRecords.size(); 528 for (int i = 0; i < count; i++) { 529 if (mUserRouteRecords.get(i).mRoute == route) { 530 return i; 531 } 532 } 533 return -1; 534 } 535 536 protected UserRouteRecord getUserRouteRecord(Object routeObj) { 537 Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj); 538 return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null; 539 } 540 541 protected void updateSystemRouteDescriptor(SystemRouteRecord record) { 542 // We must always recreate the route descriptor when making any changes 543 // because they are intended to be immutable once published. 544 545 // Routes should not have null names but it may happen for badly configured 546 // user routes. We tolerate this by using an empty name string here but 547 // such unnamed routes will be discarded by the media router upstream 548 // (with a log message so we can track down the problem). 549 CharSequence name = MediaRouterJellybean.RouteInfo.getName( 550 record.mRouteObj, getContext()); 551 MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder( 552 record.mRouteDescriptorId, name != null ? name.toString() : ""); 553 onBuildSystemRouteDescriptor(record, builder); 554 record.mRouteDescriptor = builder.build(); 555 } 556 557 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record, 558 MediaRouteDescriptor.Builder builder) { 559 int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes( 560 record.mRouteObj); 561 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) { 562 builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS); 563 } 564 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) { 565 builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS); 566 } 567 568 CharSequence status = MediaRouterJellybean.RouteInfo.getStatus(record.mRouteObj); 569 if (status != null) { 570 builder.setStatus(status.toString()); 571 } 572 builder.setPlaybackType( 573 MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj)); 574 builder.setPlaybackStream( 575 MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj)); 576 builder.setVolume( 577 MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj)); 578 builder.setVolumeMax( 579 MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj)); 580 builder.setVolumeHandling( 581 MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj)); 582 } 583 584 protected void updateUserRouteProperties(UserRouteRecord record) { 585 MediaRouterJellybean.UserRouteInfo.setName( 586 record.mRouteObj, record.mRoute.getName()); 587 MediaRouterJellybean.UserRouteInfo.setStatus( 588 record.mRouteObj, normalizeStatus(record.mRoute.getStatus())); 589 MediaRouterJellybean.UserRouteInfo.setPlaybackType( 590 record.mRouteObj, record.mRoute.getPlaybackType()); 591 MediaRouterJellybean.UserRouteInfo.setPlaybackStream( 592 record.mRouteObj, record.mRoute.getPlaybackStream()); 593 MediaRouterJellybean.UserRouteInfo.setVolume( 594 record.mRouteObj, record.mRoute.getVolume()); 595 MediaRouterJellybean.UserRouteInfo.setVolumeMax( 596 record.mRouteObj, record.mRoute.getVolumeMax()); 597 MediaRouterJellybean.UserRouteInfo.setVolumeHandling( 598 record.mRouteObj, record.mRoute.getVolumeHandling()); 599 } 600 601 protected void updateCallback() { 602 if (mCallbackRegistered) { 603 mCallbackRegistered = false; 604 MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj); 605 } 606 607 if (mRouteTypes != 0) { 608 mCallbackRegistered = true; 609 MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj); 610 } 611 } 612 613 // The framework MediaRouter crashes if we set a null status even though 614 // RouteInfo.getStatus() may return null. So we need to use a different 615 // value instead. 616 private static String normalizeStatus(String status) { 617 return status != null ? status : ""; 618 } 619 620 protected Object createCallbackObj() { 621 return MediaRouterJellybean.createCallback(this); 622 } 623 624 protected Object createVolumeCallbackObj() { 625 return MediaRouterJellybean.createVolumeCallback(this); 626 } 627 628 protected void selectRoute(Object routeObj) { 629 if (mSelectRouteWorkaround == null) { 630 mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround(); 631 } 632 mSelectRouteWorkaround.selectRoute(mRouterObj, 633 MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj); 634 } 635 636 protected Object getDefaultRoute() { 637 if (mGetDefaultRouteWorkaround == null) { 638 mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround(); 639 } 640 return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj); 641 } 642 643 /** 644 * Represents a route that is provided by the framework media router 645 * and published by this route provider to the support library media router. 646 */ 647 protected static final class SystemRouteRecord { 648 private static int sNextId; 649 650 public final Object mRouteObj; 651 public final String mRouteDescriptorId; 652 public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation 653 654 public SystemRouteRecord(Object routeObj, boolean isDefault) { 655 mRouteObj = routeObj; 656 mRouteDescriptorId = isDefault ? DEFAULT_ROUTE_ID : "ROUTE_" + (sNextId++); 657 } 658 } 659 660 /** 661 * Represents a route that is provided by the support library media router 662 * and published by this route provider to the framework media router. 663 */ 664 protected static final class UserRouteRecord { 665 public final MediaRouter.RouteInfo mRoute; 666 public final Object mRouteObj; 667 668 public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) { 669 mRoute = route; 670 mRouteObj = routeObj; 671 } 672 } 673 } 674 675 /** 676 * Jellybean MR1 implementation. 677 */ 678 private static class JellybeanMr1Impl extends JellybeanImpl 679 implements MediaRouterJellybeanMr1.Callback { 680 private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround; 681 private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround; 682 683 public JellybeanMr1Impl(Context context, SyncCallback syncCallback) { 684 super(context, syncCallback); 685 } 686 687 @Override 688 public void onRoutePresentationDisplayChanged(Object routeObj) { 689 int index = findSystemRouteRecord(routeObj); 690 if (index >= 0) { 691 SystemRouteRecord record = mSystemRouteRecords.get(index); 692 Display newPresentationDisplay = 693 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj); 694 int newPresentationDisplayId = (newPresentationDisplay != null 695 ? newPresentationDisplay.getDisplayId() : -1); 696 if (newPresentationDisplayId 697 != record.mRouteDescriptor.getPresentationDisplayId()) { 698 record.mRouteDescriptor = 699 new MediaRouteDescriptor.Builder(record.mRouteDescriptor) 700 .setPresentationDisplayId(newPresentationDisplayId) 701 .build(); 702 publishRoutes(); 703 } 704 } 705 } 706 707 @Override 708 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record, 709 MediaRouteDescriptor.Builder builder) { 710 super.onBuildSystemRouteDescriptor(record, builder); 711 712 if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) { 713 builder.setEnabled(false); 714 } 715 716 if (isConnecting(record)) { 717 builder.setConnecting(true); 718 } 719 720 Display presentationDisplay = 721 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj); 722 if (presentationDisplay != null) { 723 builder.setPresentationDisplayId(presentationDisplay.getDisplayId()); 724 } 725 } 726 727 @Override 728 protected void updateCallback() { 729 super.updateCallback(); 730 731 if (mActiveScanWorkaround == null) { 732 mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround( 733 getContext(), getHandler()); 734 } 735 mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0); 736 } 737 738 @Override 739 protected Object createCallbackObj() { 740 return MediaRouterJellybeanMr1.createCallback(this); 741 } 742 743 protected boolean isConnecting(SystemRouteRecord record) { 744 if (mIsConnectingWorkaround == null) { 745 mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround(); 746 } 747 return mIsConnectingWorkaround.isConnecting(record.mRouteObj); 748 } 749 } 750 751 /** 752 * Jellybean MR2 implementation. 753 */ 754 private static class JellybeanMr2Impl extends JellybeanMr1Impl { 755 public JellybeanMr2Impl(Context context, SyncCallback syncCallback) { 756 super(context, syncCallback); 757 } 758 759 @Override 760 protected void selectRoute(Object routeObj) { 761 MediaRouterJellybean.selectRoute(mRouterObj, 762 MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj); 763 } 764 765 @Override 766 protected Object getDefaultRoute() { 767 return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj); 768 } 769 770 @Override 771 protected void updateCallback() { 772 if (mCallbackRegistered) { 773 MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj); 774 } 775 776 mCallbackRegistered = true; 777 MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj, 778 MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS 779 | (mActiveScan ? MediaRouter.CALLBACK_FLAG_ACTIVE_SCAN : 0)); 780 } 781 782 @Override 783 protected boolean isConnecting(SystemRouteRecord record) { 784 return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj); 785 } 786 } 787} 788