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