SystemMediaRouteProvider.java revision b507e525a61ed761eecfc2eaaf19af7e8db5dca5
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.util.Log; 28import android.view.Display; 29 30import java.lang.reflect.InvocationTargetException; 31import java.lang.reflect.Method; 32import java.util.ArrayList; 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(PACKAGE_NAME)); 48 } 49 50 public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) { 51 if (Build.VERSION.SDK_INT >= 18) { 52 return new JellybeanMr2Impl(context, syncCallback); 53 } 54 if (Build.VERSION.SDK_INT >= 17) { 55 return new JellybeanMr1Impl(context, syncCallback); 56 } 57 if (Build.VERSION.SDK_INT >= 16) { 58 return new JellybeanImpl(context, syncCallback); 59 } 60 return new LegacyImpl(context); 61 } 62 63 /** 64 * Called by the media router when a route is added to synchronize state with 65 * the framework media router. 66 */ 67 public void onSyncRouteAdded(MediaRouter.RouteInfo route) { 68 } 69 70 /** 71 * Called by the media router when a route is removed to synchronize state with 72 * the framework media router. 73 */ 74 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) { 75 } 76 77 /** 78 * Called by the media router when a route is changed to synchronize state with 79 * the framework media router. 80 */ 81 public void onSyncRouteChanged(MediaRouter.RouteInfo route) { 82 } 83 84 /** 85 * Called by the media router when a route is selected to synchronize state with 86 * the framework media router. 87 */ 88 public void onSyncRouteSelected(MediaRouter.RouteInfo route) { 89 } 90 91 /** 92 * Callbacks into the media router to synchronize state with the framework media router. 93 */ 94 public interface SyncCallback { 95 public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id); 96 } 97 98 /** 99 * Legacy implementation for platform versions prior to Jellybean. 100 */ 101 static class LegacyImpl extends SystemMediaRouteProvider { 102 private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC; 103 104 private static final IntentFilter[] CONTROL_FILTERS; 105 static { 106 CONTROL_FILTERS = new IntentFilter[1]; 107 CONTROL_FILTERS[0] = new IntentFilter(); 108 CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 109 CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 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 RouteDescriptor defaultRoute = new RouteDescriptor( 129 DEFAULT_ROUTE_ID, r.getString(R.string.system_route_name)); 130 defaultRoute.setControlFilters(CONTROL_FILTERS); 131 defaultRoute.setPlaybackStream(PLAYBACK_STREAM); 132 defaultRoute.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL); 133 defaultRoute.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE); 134 defaultRoute.setVolumeMax(mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM)); 135 mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM); 136 defaultRoute.setVolume(mLastReportedVolume); 137 138 ProviderDescriptor providerDescriptor = new ProviderDescriptor(); 139 providerDescriptor.setRoutes(new RouteDescriptor[] { defaultRoute }); 140 setDescriptor(providerDescriptor); 141 } 142 143 @Override 144 public RouteController onCreateRouteController(String routeId) { 145 if (routeId.equals(DEFAULT_ROUTE_ID)) { 146 return new DefaultRouteController(); 147 } 148 return null; 149 } 150 151 final class DefaultRouteController extends RouteController { 152 @Override 153 public void setVolume(int volume) { 154 mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0); 155 publishRoutes(); 156 } 157 158 @Override 159 public void updateVolume(int delta) { 160 int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM); 161 int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM); 162 int newVolume = Math.min(maxVolume, Math.max(0, volume + delta)); 163 if (newVolume != volume) { 164 mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0); 165 } 166 publishRoutes(); 167 } 168 } 169 170 final class VolumeChangeReceiver extends BroadcastReceiver { 171 // These constants come from AudioManager. 172 public static final String VOLUME_CHANGED_ACTION = 173 "android.media.VOLUME_CHANGED_ACTION"; 174 public static final String EXTRA_VOLUME_STREAM_TYPE = 175 "android.media.EXTRA_VOLUME_STREAM_TYPE"; 176 public static final String EXTRA_VOLUME_STREAM_VALUE = 177 "android.media.EXTRA_VOLUME_STREAM_VALUE"; 178 179 @Override 180 public void onReceive(Context context, Intent intent) { 181 if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) { 182 final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1); 183 if (streamType == PLAYBACK_STREAM) { 184 final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1); 185 if (volume >= 0 && volume != mLastReportedVolume) { 186 publishRoutes(); 187 } 188 } 189 } 190 } 191 } 192 } 193 194 /** 195 * Jellybean implementation. 196 */ 197 static class JellybeanImpl extends SystemMediaRouteProvider 198 implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback { 199 protected static final int ALL_ROUTE_TYPES = 200 MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO 201 | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO 202 | MediaRouterJellybean.ROUTE_TYPE_USER; 203 204 private static final IntentFilter[] LIVE_AUDIO_CONTROL_FILTERS; 205 static { 206 LIVE_AUDIO_CONTROL_FILTERS = new IntentFilter[1]; 207 LIVE_AUDIO_CONTROL_FILTERS[0] = new IntentFilter(); 208 LIVE_AUDIO_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 209 } 210 211 private static final IntentFilter[] LIVE_VIDEO_CONTROL_FILTERS; 212 static { 213 LIVE_VIDEO_CONTROL_FILTERS = new IntentFilter[1]; 214 LIVE_VIDEO_CONTROL_FILTERS[0] = new IntentFilter(); 215 LIVE_VIDEO_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 216 } 217 218 private static final IntentFilter[] ALL_CONTROL_FILTERS; 219 static { 220 ALL_CONTROL_FILTERS = new IntentFilter[1]; 221 ALL_CONTROL_FILTERS[0] = new IntentFilter(); 222 ALL_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 223 ALL_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 224 } 225 226 private final SyncCallback mSyncCallback; 227 private Method mSelectRouteIntMethod; 228 private Method mGetSystemAudioRouteMethod; 229 230 protected final Object mRouterObj; 231 protected final Object mCallbackObj; 232 protected final Object mVolumeCallbackObj; 233 protected final Object mUserRouteCategoryObj; 234 235 // Maintains an association from framework routes to support library routes. 236 // Note that we cannot use the tag field for this because an application may 237 // have published its own user routes to the framework media router and already 238 // used the tag for its own purposes. 239 protected final ArrayList<SystemRouteRecord> mSystemRouteRecords = 240 new ArrayList<SystemRouteRecord>(); 241 242 // Maintains an association from support library routes to framework routes. 243 protected final ArrayList<UserRouteRecord> mUserRouteRecords = 244 new ArrayList<UserRouteRecord>(); 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 addInitialSystemRoutes(); 258 MediaRouterJellybean.addCallback(mRouterObj, ALL_ROUTE_TYPES, mCallbackObj); 259 } 260 261 @Override 262 public void onRouteAdded(Object routeObj) { 263 if (getUserRouteRecord(routeObj) == null) { 264 int index = findSystemRouteRecord(routeObj); 265 if (index < 0) { 266 addSystemRouteNoPublish(routeObj); 267 publishRoutes(); 268 } 269 } 270 } 271 272 private void addInitialSystemRoutes() { 273 for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) { 274 addSystemRouteNoPublish(routeObj); 275 } 276 publishRoutes(); 277 } 278 279 private void addSystemRouteNoPublish(Object routeObj) { 280 if (getUserRouteRecord(routeObj) == null) { 281 boolean isDefault = (getDefaultRoute() == routeObj); 282 SystemRouteRecord record = new SystemRouteRecord(routeObj, isDefault); 283 updateSystemRouteDescriptor(record); 284 mSystemRouteRecords.add(record); 285 } 286 } 287 288 @Override 289 public void onRouteRemoved(Object routeObj) { 290 if (getUserRouteRecord(routeObj) == null) { 291 int index = findSystemRouteRecord(routeObj); 292 if (index >= 0) { 293 mSystemRouteRecords.remove(index); 294 publishRoutes(); 295 } 296 } 297 } 298 299 @Override 300 public void onRouteChanged(Object routeObj) { 301 if (getUserRouteRecord(routeObj) == null) { 302 int index = findSystemRouteRecord(routeObj); 303 if (index >= 0) { 304 SystemRouteRecord record = mSystemRouteRecords.get(index); 305 updateSystemRouteDescriptor(record); 306 publishRoutes(); 307 } 308 } 309 } 310 311 @Override 312 public void onRouteVolumeChanged(Object routeObj) { 313 if (getUserRouteRecord(routeObj) == null) { 314 int index = findSystemRouteRecord(routeObj); 315 if (index >= 0) { 316 SystemRouteRecord record = mSystemRouteRecords.get(index); 317 int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj); 318 if (newVolume != record.mRouteDescriptor.getVolume()) { 319 record.mRouteDescriptor = new RouteDescriptor(record.mRouteDescriptor); 320 record.mRouteDescriptor.setVolume(newVolume); 321 publishRoutes(); 322 } 323 } 324 } 325 } 326 327 @Override 328 public void onRouteSelected(int type, Object routeObj) { 329 if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj, ALL_ROUTE_TYPES)) { 330 // The currently selected route has already changed so this callback 331 // is stale. Drop it to prevent getting into sync loops. 332 return; 333 } 334 335 UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj); 336 if (userRouteRecord != null) { 337 userRouteRecord.mRoute.select(); 338 } else { 339 // Select the route if it already exists in the compat media router. 340 // If not, we will select it instead when the route is added. 341 int index = findSystemRouteRecord(routeObj); 342 if (index >= 0) { 343 SystemRouteRecord record = mSystemRouteRecords.get(index); 344 MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId( 345 record.mRouteDescriptorId); 346 if (route != null) { 347 route.select(); 348 } 349 } 350 } 351 } 352 353 @Override 354 public void onRouteUnselected(int type, Object routeObj) { 355 // Nothing to do when a route is unselected. 356 // We only need to handle when a route is selected. 357 } 358 359 @Override 360 public void onRouteGrouped(Object routeObj, Object groupObj, int index) { 361 // Route grouping is deprecated and no longer supported. 362 } 363 364 @Override 365 public void onRouteUngrouped(Object routeObj, Object groupObj) { 366 // Route grouping is deprecated and no longer supported. 367 } 368 369 @Override 370 public void onVolumeSetRequest(Object routeObj, int volume) { 371 UserRouteRecord record = getUserRouteRecord(routeObj); 372 if (record != null) { 373 record.mRoute.requestSetVolume(volume); 374 } 375 } 376 377 @Override 378 public void onVolumeUpdateRequest(Object routeObj, int direction) { 379 UserRouteRecord record = getUserRouteRecord(routeObj); 380 if (record != null) { 381 record.mRoute.requestUpdateVolume(direction); 382 } 383 } 384 385 @Override 386 public void onSyncRouteAdded(MediaRouter.RouteInfo route) { 387 if (route.getProviderInstance() != this) { 388 Object routeObj = MediaRouterJellybean.createUserRoute( 389 mRouterObj, mUserRouteCategoryObj); 390 UserRouteRecord record = new UserRouteRecord(route, routeObj); 391 MediaRouterJellybean.RouteInfo.setTag(routeObj, record); 392 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj); 393 updateUserRouteProperties(record); 394 mUserRouteRecords.add(record); 395 MediaRouterJellybean.addUserRoute(mRouterObj, routeObj); 396 } else { 397 // If the newly added route is the counterpart of the currently selected 398 // route in the framework media router then ensure it is selected in 399 // the compat media router. 400 Object routeObj = MediaRouterJellybean.getSelectedRoute( 401 mRouterObj, ALL_ROUTE_TYPES); 402 int index = findSystemRouteRecord(routeObj); 403 if (index >= 0) { 404 SystemRouteRecord record = mSystemRouteRecords.get(index); 405 if (record.mRouteDescriptorId.equals(route.getDescriptorId())) { 406 route.select(); 407 } 408 } 409 } 410 } 411 412 @Override 413 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) { 414 if (route.getProviderInstance() != this) { 415 int index = findUserRouteRecord(route); 416 if (index >= 0) { 417 UserRouteRecord record = mUserRouteRecords.remove(index); 418 MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null); 419 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null); 420 MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj); 421 } 422 } 423 } 424 425 @Override 426 public void onSyncRouteChanged(MediaRouter.RouteInfo route) { 427 if (route.getProviderInstance() != this) { 428 int index = findUserRouteRecord(route); 429 if (index >= 0) { 430 UserRouteRecord record = mUserRouteRecords.get(index); 431 updateUserRouteProperties(record); 432 } 433 } 434 } 435 436 @Override 437 public void onSyncRouteSelected(MediaRouter.RouteInfo route) { 438 if (!route.isSelected()) { 439 // The currently selected route has already changed so this callback 440 // is stale. Drop it to prevent getting into sync loops. 441 return; 442 } 443 444 if (route.getProviderInstance() != this) { 445 int index = findUserRouteRecord(route); 446 if (index >= 0) { 447 UserRouteRecord record = mUserRouteRecords.get(index); 448 selectRoute(record.mRouteObj); 449 } 450 } else { 451 int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId()); 452 if (index >= 0) { 453 SystemRouteRecord record = mSystemRouteRecords.get(index); 454 selectRoute(record.mRouteObj); 455 } 456 } 457 } 458 459 protected void publishRoutes() { 460 int count = mSystemRouteRecords.size(); 461 RouteDescriptor[] routeDescriptors = new RouteDescriptor[count]; 462 for (int i = 0; i < count; i++) { 463 routeDescriptors[i] = mSystemRouteRecords.get(i).mRouteDescriptor; 464 } 465 466 ProviderDescriptor providerDescriptor = new ProviderDescriptor(); 467 providerDescriptor.setRoutes(routeDescriptors); 468 setDescriptor(providerDescriptor); 469 } 470 471 protected int findSystemRouteRecord(Object routeObj) { 472 final int count = mSystemRouteRecords.size(); 473 for (int i = 0; i < count; i++) { 474 if (mSystemRouteRecords.get(i).mRouteObj == routeObj) { 475 return i; 476 } 477 } 478 return -1; 479 } 480 481 protected int findSystemRouteRecordByDescriptorId(String id) { 482 final int count = mSystemRouteRecords.size(); 483 for (int i = 0; i < count; i++) { 484 if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) { 485 return i; 486 } 487 } 488 return -1; 489 } 490 491 protected int findUserRouteRecord(MediaRouter.RouteInfo route) { 492 final int count = mUserRouteRecords.size(); 493 for (int i = 0; i < count; i++) { 494 if (mUserRouteRecords.get(i).mRoute == route) { 495 return i; 496 } 497 } 498 return -1; 499 } 500 501 protected UserRouteRecord getUserRouteRecord(Object routeObj) { 502 Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj); 503 return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null; 504 } 505 506 protected void updateSystemRouteDescriptor(SystemRouteRecord record) { 507 // We must always recreate the route descriptor when making any changes 508 // because they are intended to be immutable once published. 509 String name = MediaRouterJellybean.RouteInfo.getName( 510 record.mRouteObj, getContext()).toString(); 511 record.mRouteDescriptor = new RouteDescriptor( 512 record.mRouteDescriptorId, name); 513 514 int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes( 515 record.mRouteObj); 516 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) { 517 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) { 518 record.mRouteDescriptor.setControlFilters(ALL_CONTROL_FILTERS); 519 } else { 520 record.mRouteDescriptor.setControlFilters(LIVE_AUDIO_CONTROL_FILTERS); 521 } 522 } else if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) { 523 record.mRouteDescriptor.setControlFilters(LIVE_VIDEO_CONTROL_FILTERS); 524 } 525 526 CharSequence status = MediaRouterJellybean.RouteInfo.getStatus(record.mRouteObj); 527 if (status != null) { 528 record.mRouteDescriptor.setStatus(status.toString()); 529 } 530 record.mRouteDescriptor.setIconDrawable( 531 MediaRouterJellybean.RouteInfo.getIconDrawable(record.mRouteObj)); 532 record.mRouteDescriptor.setPlaybackType( 533 MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj)); 534 record.mRouteDescriptor.setPlaybackStream( 535 MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj)); 536 record.mRouteDescriptor.setVolume( 537 MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj)); 538 record.mRouteDescriptor.setVolumeMax( 539 MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj)); 540 record.mRouteDescriptor.setVolumeHandling( 541 MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj)); 542 } 543 544 protected void updateUserRouteProperties(UserRouteRecord record) { 545 MediaRouterJellybean.UserRouteInfo.setName( 546 record.mRouteObj, record.mRoute.getName()); 547 MediaRouterJellybean.UserRouteInfo.setStatus( 548 record.mRouteObj, normalizeStatus(record.mRoute.getStatus())); 549 MediaRouterJellybean.UserRouteInfo.setIconDrawable( 550 record.mRouteObj, record.mRoute.getIconDrawable()); 551 MediaRouterJellybean.UserRouteInfo.setPlaybackType( 552 record.mRouteObj, record.mRoute.getPlaybackType()); 553 MediaRouterJellybean.UserRouteInfo.setPlaybackStream( 554 record.mRouteObj, record.mRoute.getPlaybackStream()); 555 MediaRouterJellybean.UserRouteInfo.setVolume( 556 record.mRouteObj, record.mRoute.getVolume()); 557 MediaRouterJellybean.UserRouteInfo.setVolumeMax( 558 record.mRouteObj, record.mRoute.getVolumeMax()); 559 MediaRouterJellybean.UserRouteInfo.setVolumeHandling( 560 record.mRouteObj, record.mRoute.getVolumeHandling()); 561 } 562 563 // The framework MediaRouter crashes if we set a null status even though 564 // RouteInfo.getStatus() may return null. So we need to use a different 565 // value instead. 566 private static String normalizeStatus(String status) { 567 return status != null ? status : ""; 568 } 569 570 protected Object createCallbackObj() { 571 return MediaRouterJellybean.createCallback(this); 572 } 573 574 protected Object createVolumeCallbackObj() { 575 return MediaRouterJellybean.createVolumeCallback(this); 576 } 577 578 protected void selectRoute(Object routeObj) { 579 int types = MediaRouterJellybean.RouteInfo.getSupportedTypes(routeObj); 580 if ((types & MediaRouterJellybean.ROUTE_TYPE_USER) == 0) { 581 // Handle non-user routes. 582 // On JB and JB MR1, the selectRoute() API only supports programmatically 583 // selecting user routes. So instead we rely on the hidden selectRouteInt() 584 // method on these versions of the platform. This limitation was removed 585 // in JB MR2. See also the JellybeanMr2Impl implementation of this method. 586 if (mSelectRouteIntMethod == null) { 587 try { 588 mSelectRouteIntMethod = mRouterObj.getClass().getMethod( 589 "selectRouteInt", int.class, MediaRouterJellybean.RouteInfo.clazz); 590 } catch (NoSuchMethodException ex) { 591 Log.w(TAG, "Cannot programmatically select non-user route " 592 + "because the platform is missing the selectRouteInt() " 593 + "method. Media routing may not work.", ex); 594 return; 595 } 596 } 597 try { 598 mSelectRouteIntMethod.invoke(mRouterObj, ALL_ROUTE_TYPES, routeObj); 599 } catch (IllegalAccessException ex) { 600 Log.w(TAG, "Cannot programmatically select non-user route. " 601 + "Media routing may not work.", ex); 602 } catch (InvocationTargetException ex) { 603 Log.w(TAG, "Cannot programmatically select non-user route. " 604 + "Media routing may not work.", ex); 605 } 606 } else { 607 // Handle user routes. 608 MediaRouterJellybean.selectRoute(mRouterObj, ALL_ROUTE_TYPES, routeObj); 609 } 610 } 611 612 protected Object getDefaultRoute() { 613 // On JB and JB MR1, the getDefaultRoute() API does not exist. 614 // Instead there is a hidden getSystemAudioRoute() that does the same thing. 615 // See also the JellybeanMr2Impl implementation of this method. 616 if (mGetSystemAudioRouteMethod == null) { 617 try { 618 mGetSystemAudioRouteMethod = mRouterObj.getClass().getMethod( 619 "getSystemAudioRoute"); 620 } catch (NoSuchMethodException ex) { 621 // Fall through. 622 } 623 } 624 if (mGetSystemAudioRouteMethod != null) { 625 try { 626 return mGetSystemAudioRouteMethod.invoke(mRouterObj); 627 } catch (IllegalAccessException ex) { 628 // Fall through. 629 } catch (InvocationTargetException ex) { 630 // Fall through. 631 } 632 } 633 // Could not find the method or it does not work. 634 // Return the first route and hope for the best. 635 return MediaRouterJellybean.getRoutes(mRouterObj).get(0); 636 } 637 638 /** 639 * Represents a route that is provided by the framework media router 640 * and published by this route provider to the support library media router. 641 */ 642 protected static final class SystemRouteRecord { 643 private static int sNextId; 644 645 public final Object mRouteObj; 646 public final String mRouteDescriptorId; 647 public RouteDescriptor mRouteDescriptor; // assigned immediately after creation 648 649 public SystemRouteRecord(Object routeObj, boolean isDefault) { 650 mRouteObj = routeObj; 651 mRouteDescriptorId = isDefault ? DEFAULT_ROUTE_ID : "ROUTE_" + (sNextId++); 652 } 653 } 654 655 /** 656 * Represents a route that is provided by the support library media router 657 * and published by this route provider to the framework media router. 658 */ 659 protected static final class UserRouteRecord { 660 public final MediaRouter.RouteInfo mRoute; 661 public final Object mRouteObj; 662 663 public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) { 664 mRoute = route; 665 mRouteObj = routeObj; 666 } 667 } 668 } 669 670 /** 671 * Jellybean MR1 implementation. 672 */ 673 private static class JellybeanMr1Impl extends JellybeanImpl 674 implements MediaRouterJellybeanMr1.Callback { 675 public JellybeanMr1Impl(Context context, SyncCallback syncCallback) { 676 super(context, syncCallback); 677 } 678 679 @Override 680 public void onRoutePresentationDisplayChanged(Object routeObj) { 681 int index = findSystemRouteRecord(routeObj); 682 if (index >= 0) { 683 SystemRouteRecord record = mSystemRouteRecords.get(index); 684 Display newPresentationDisplay = 685 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj); 686 int newPresentationDisplayId = (newPresentationDisplay != null 687 ? newPresentationDisplay.getDisplayId() : -1); 688 if (newPresentationDisplayId 689 != record.mRouteDescriptor.getPresentationDisplayId()) { 690 record.mRouteDescriptor = new RouteDescriptor(record.mRouteDescriptor); 691 record.mRouteDescriptor.setPresentationDisplayId(newPresentationDisplayId); 692 publishRoutes(); 693 } 694 } 695 } 696 697 @Override 698 protected void updateSystemRouteDescriptor(SystemRouteRecord record) { 699 super.updateSystemRouteDescriptor(record); 700 701 if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) { 702 record.mRouteDescriptor.setEnabled(false); 703 } 704 Display presentationDisplay = 705 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj); 706 if (presentationDisplay != null) { 707 record.mRouteDescriptor.setPresentationDisplayId( 708 presentationDisplay.getDisplayId()); 709 } 710 } 711 712 @Override 713 protected Object createCallbackObj() { 714 return MediaRouterJellybeanMr1.createCallback(this); 715 } 716 } 717 718 /** 719 * Jellybean MR2 implementation. 720 */ 721 private static class JellybeanMr2Impl extends JellybeanMr1Impl { 722 public JellybeanMr2Impl(Context context, SyncCallback syncCallback) { 723 super(context, syncCallback); 724 } 725 726 @Override 727 protected void selectRoute(Object routeObj) { 728 MediaRouterJellybean.selectRoute(mRouterObj, ALL_ROUTE_TYPES, routeObj); 729 } 730 731 @Override 732 protected Object getDefaultRoute() { 733 return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj); 734 } 735 } 736} 737