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