1 /* 2 * Copyright 2019 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.media; 18 19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 20 21 import android.Manifest; 22 import android.annotation.CallbackExecutor; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SystemApi; 27 import android.annotation.TestApi; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.ArraySet; 38 import android.util.Log; 39 40 import com.android.internal.annotations.GuardedBy; 41 42 import java.util.ArrayList; 43 import java.util.Collections; 44 import java.util.Comparator; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Set; 49 import java.util.concurrent.CopyOnWriteArrayList; 50 import java.util.concurrent.Executor; 51 import java.util.concurrent.atomic.AtomicBoolean; 52 import java.util.concurrent.atomic.AtomicInteger; 53 import java.util.stream.Collectors; 54 55 /** 56 * This API is not generally intended for third party application developers. Use the 57 * <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 58 * <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router 59 * Library</a> for consistent behavior across all devices. 60 * 61 * <p>MediaRouter2 allows applications to control the routing of media channels and streams from 62 * the current device to remote speakers and devices. 63 */ 64 // TODO(b/157873330): Add method names at the beginning of log messages. (e.g. selectRoute) 65 // Not only MediaRouter2, but also to service / manager / provider. 66 // TODO: ensure thread-safe and document it 67 public final class MediaRouter2 { 68 private static final String TAG = "MR2"; 69 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 70 private static final Object sSystemRouterLock = new Object(); 71 private static final Object sRouterLock = new Object(); 72 73 // The maximum time for the old routing controller available after transfer. 74 private static final int TRANSFER_TIMEOUT_MS = 30_000; 75 // The manager request ID representing that no manager is involved. 76 private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE; 77 78 @GuardedBy("sSystemRouterLock") 79 private static Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>(); 80 81 private static MediaRouter2Manager sManager; 82 83 @GuardedBy("sRouterLock") 84 private static MediaRouter2 sInstance; 85 86 private final Context mContext; 87 private final IMediaRouterService mMediaRouterService; 88 private final Object mLock = new Object(); 89 90 private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords = 91 new CopyOnWriteArrayList<>(); 92 private final CopyOnWriteArrayList<TransferCallbackRecord> mTransferCallbackRecords = 93 new CopyOnWriteArrayList<>(); 94 private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords = 95 new CopyOnWriteArrayList<>(); 96 97 private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests = 98 new CopyOnWriteArrayList<>(); 99 100 // TODO: Specify the fields that are only used (or not used) by system media router. 101 private final String mClientPackageName; 102 final ManagerCallback mManagerCallback; 103 104 private final String mPackageName; 105 106 @GuardedBy("mLock") 107 final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>(); 108 109 final RoutingController mSystemController; 110 111 @GuardedBy("mLock") 112 private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; 113 114 // TODO: Make MediaRouter2 is always connected to the MediaRouterService. 115 @GuardedBy("mLock") 116 MediaRouter2Stub mStub; 117 118 @GuardedBy("mLock") 119 private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>(); 120 121 private final AtomicInteger mNextRequestId = new AtomicInteger(1); 122 private final AtomicBoolean mIsScanning = new AtomicBoolean(/* initialValue= */ false); 123 124 final Handler mHandler; 125 126 private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>(); 127 private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList(); 128 private volatile OnGetControllerHintsListener mOnGetControllerHintsListener; 129 130 /** Gets an instance of the media router associated with the context. */ 131 @NonNull getInstance(@onNull Context context)132 public static MediaRouter2 getInstance(@NonNull Context context) { 133 Objects.requireNonNull(context, "context must not be null"); 134 synchronized (sRouterLock) { 135 if (sInstance == null) { 136 sInstance = new MediaRouter2(context.getApplicationContext()); 137 } 138 return sInstance; 139 } 140 } 141 142 /** 143 * Gets an instance of the system media router which controls the app's media routing. Returns 144 * {@code null} if the given package name is invalid. There are several things to note when 145 * using the media routers created with this method. 146 * 147 * <p>First of all, the discovery preference passed to {@link #registerRouteCallback} will have 148 * no effect. The callback will be called accordingly with the client app's discovery 149 * preference. Therefore, it is recommended to pass {@link RouteDiscoveryPreference#EMPTY} 150 * there. 151 * 152 * <p>Also, do not keep/compare the instances of the {@link RoutingController}, since they are 153 * always newly created with the latest session information whenever below methods are called: 154 * 155 * <ul> 156 * <li>{@link #getControllers()} 157 * <li>{@link #getController(String)}} 158 * <li>{@link TransferCallback#onTransfer(RoutingController, RoutingController)} 159 * <li>{@link TransferCallback#onStop(RoutingController)} 160 * <li>{@link ControllerCallback#onControllerUpdated(RoutingController)} 161 * </ul> 162 * 163 * Therefore, in order to track the current routing status, keep the controller's ID instead, 164 * and use {@link #getController(String)} and {@link #getSystemController()} for getting 165 * controllers. 166 * 167 * <p>Finally, it will have no effect to call {@link #setOnGetControllerHintsListener}. 168 * 169 * @param clientPackageName the package name of the app to control 170 * @throws SecurityException if the caller doesn't have MODIFY_AUDIO_ROUTING permission. 171 * @hide 172 */ 173 @SystemApi 174 @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) 175 @Nullable getInstance( @onNull Context context, @NonNull String clientPackageName)176 public static MediaRouter2 getInstance( 177 @NonNull Context context, @NonNull String clientPackageName) { 178 Objects.requireNonNull(context, "context must not be null"); 179 Objects.requireNonNull(clientPackageName, "clientPackageName must not be null"); 180 181 // Note: Even though this check could be somehow bypassed, the other permission checks 182 // in system server will not allow MediaRouter2Manager to be registered. 183 IMediaRouterService serviceBinder = 184 IMediaRouterService.Stub.asInterface( 185 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); 186 try { 187 // SecurityException will be thrown if there's no permission. 188 serviceBinder.enforceMediaContentControlPermission(); 189 } catch (RemoteException e) { 190 e.rethrowFromSystemServer(); 191 } 192 193 PackageManager pm = context.getPackageManager(); 194 try { 195 pm.getPackageInfo(clientPackageName, 0); 196 } catch (PackageManager.NameNotFoundException ex) { 197 Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring."); 198 return null; 199 } 200 201 synchronized (sSystemRouterLock) { 202 MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName); 203 if (instance == null) { 204 if (sManager == null) { 205 sManager = MediaRouter2Manager.getInstance(context.getApplicationContext()); 206 } 207 instance = new MediaRouter2(context, clientPackageName); 208 sSystemMediaRouter2Map.put(clientPackageName, instance); 209 // Using direct executor here, since MediaRouter2Manager also posts 210 // to the main handler. 211 sManager.registerCallback(Runnable::run, instance.mManagerCallback); 212 } 213 return instance; 214 } 215 } 216 217 /** 218 * Starts scanning remote routes. 219 * 220 * <p>Route discovery can happen even when the {@link #startScan()} is not called. This is 221 * because the scanning could be started before by other apps. Therefore, calling this method 222 * after calling {@link #stopScan()} does not necessarily mean that the routes found before are 223 * removed and added again. 224 * 225 * <p>Use {@link RouteCallback} to get the route related events. 226 * 227 * <p>Note that calling start/stopScan is applied to all system routers in the same process. 228 * 229 * <p>This will be no-op for non-system media routers. 230 * 231 * @see #stopScan() 232 * @see #getInstance(Context, String) 233 * @hide 234 */ 235 @SystemApi 236 @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) startScan()237 public void startScan() { 238 if (isSystemRouter()) { 239 if (!mIsScanning.getAndSet(true)) { 240 sManager.registerScanRequest(); 241 } 242 } 243 } 244 245 /** 246 * Stops scanning remote routes to reduce resource consumption. 247 * 248 * <p>Route discovery can be continued even after this method is called. This is because the 249 * scanning is only turned off when all the apps stop scanning. Therefore, calling this method 250 * does not necessarily mean the routes are removed. Also, for the same reason it does not mean 251 * that {@link RouteCallback#onRoutesAdded(List)} is not called afterwards. 252 * 253 * <p>Use {@link RouteCallback} to get the route related events. 254 * 255 * <p>Note that calling start/stopScan is applied to all system routers in the same process. 256 * 257 * <p>This will be no-op for non-system media routers. 258 * 259 * @see #startScan() 260 * @see #getInstance(Context, String) 261 * @hide 262 */ 263 @SystemApi 264 @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) stopScan()265 public void stopScan() { 266 if (isSystemRouter()) { 267 if (mIsScanning.getAndSet(false)) { 268 sManager.unregisterScanRequest(); 269 } 270 } 271 } 272 MediaRouter2(Context appContext)273 private MediaRouter2(Context appContext) { 274 mContext = appContext; 275 mMediaRouterService = 276 IMediaRouterService.Stub.asInterface( 277 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); 278 mPackageName = mContext.getPackageName(); 279 mHandler = new Handler(Looper.getMainLooper()); 280 281 List<MediaRoute2Info> currentSystemRoutes = null; 282 RoutingSessionInfo currentSystemSessionInfo = null; 283 try { 284 currentSystemRoutes = mMediaRouterService.getSystemRoutes(); 285 currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo(); 286 } catch (RemoteException ex) { 287 Log.e(TAG, "Unable to get current system's routes / session info", ex); 288 } 289 290 if (currentSystemRoutes == null || currentSystemRoutes.isEmpty()) { 291 throw new RuntimeException("Null or empty currentSystemRoutes. Something is wrong."); 292 } 293 294 if (currentSystemSessionInfo == null) { 295 throw new RuntimeException("Null currentSystemSessionInfo. Something is wrong."); 296 } 297 298 for (MediaRoute2Info route : currentSystemRoutes) { 299 mRoutes.put(route.getId(), route); 300 } 301 mSystemController = new SystemRoutingController(currentSystemSessionInfo); 302 303 // Only used by system MediaRouter2. 304 mClientPackageName = null; 305 mManagerCallback = null; 306 } 307 MediaRouter2(Context context, String clientPackageName)308 private MediaRouter2(Context context, String clientPackageName) { 309 mContext = context; 310 mClientPackageName = clientPackageName; 311 mManagerCallback = new ManagerCallback(); 312 mHandler = new Handler(Looper.getMainLooper()); 313 mSystemController = 314 new SystemRoutingController( 315 ensureClientPackageNameForSystemSession( 316 sManager.getSystemRoutingSession(clientPackageName))); 317 mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName); 318 updateAllRoutesFromManager(); 319 320 // Only used by non-system MediaRouter2. 321 mMediaRouterService = null; 322 mPackageName = null; 323 } 324 325 /** 326 * Returns whether any route in {@code routeList} has a same unique ID with given route. 327 * 328 * @hide 329 */ checkRouteListContainsRouteId( @onNull List<MediaRoute2Info> routeList, @NonNull String routeId)330 static boolean checkRouteListContainsRouteId( 331 @NonNull List<MediaRoute2Info> routeList, @NonNull String routeId) { 332 for (MediaRoute2Info info : routeList) { 333 if (TextUtils.equals(routeId, info.getId())) { 334 return true; 335 } 336 } 337 return false; 338 } 339 340 /** 341 * Gets the client package name of the app which this media router controls. 342 * 343 * <p>This will return null for non-system media routers. 344 * 345 * @see #getInstance(Context, String) 346 * @hide 347 */ 348 @SystemApi 349 @Nullable getClientPackageName()350 public String getClientPackageName() { 351 return mClientPackageName; 352 } 353 354 /** 355 * Registers a callback to discover routes and to receive events when they change. 356 * 357 * <p>If the specified callback is already registered, its registration will be updated for the 358 * given {@link Executor executor} and {@link RouteDiscoveryPreference discovery preference}. 359 */ registerRouteCallback( @onNull @allbackExecutor Executor executor, @NonNull RouteCallback routeCallback, @NonNull RouteDiscoveryPreference preference)360 public void registerRouteCallback( 361 @NonNull @CallbackExecutor Executor executor, 362 @NonNull RouteCallback routeCallback, 363 @NonNull RouteDiscoveryPreference preference) { 364 Objects.requireNonNull(executor, "executor must not be null"); 365 Objects.requireNonNull(routeCallback, "callback must not be null"); 366 Objects.requireNonNull(preference, "preference must not be null"); 367 if (isSystemRouter()) { 368 preference = RouteDiscoveryPreference.EMPTY; 369 } 370 371 RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference); 372 373 mRouteCallbackRecords.remove(record); 374 // It can fail to add the callback record if another registration with the same callback 375 // is happening but it's okay because either this or the other registration should be done. 376 mRouteCallbackRecords.addIfAbsent(record); 377 378 if (isSystemRouter()) { 379 return; 380 } 381 382 synchronized (mLock) { 383 if (mStub == null) { 384 MediaRouter2Stub stub = new MediaRouter2Stub(); 385 try { 386 mMediaRouterService.registerRouter2(stub, mPackageName); 387 mStub = stub; 388 } catch (RemoteException ex) { 389 Log.e(TAG, "registerRouteCallback: Unable to register MediaRouter2.", ex); 390 } 391 } 392 if (mStub != null && updateDiscoveryPreferenceIfNeededLocked()) { 393 try { 394 mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference); 395 } catch (RemoteException ex) { 396 Log.e(TAG, "registerRouteCallback: Unable to set discovery request.", ex); 397 } 398 } 399 } 400 } 401 402 /** 403 * Unregisters the given callback. The callback will no longer receive events. If the callback 404 * has not been added or been removed already, it is ignored. 405 * 406 * @param routeCallback the callback to unregister 407 * @see #registerRouteCallback 408 */ unregisterRouteCallback(@onNull RouteCallback routeCallback)409 public void unregisterRouteCallback(@NonNull RouteCallback routeCallback) { 410 Objects.requireNonNull(routeCallback, "callback must not be null"); 411 412 if (!mRouteCallbackRecords.remove(new RouteCallbackRecord(null, routeCallback, null))) { 413 Log.w(TAG, "unregisterRouteCallback: Ignoring unknown callback"); 414 return; 415 } 416 417 if (isSystemRouter()) { 418 return; 419 } 420 421 synchronized (mLock) { 422 if (mStub == null) { 423 return; 424 } 425 if (updateDiscoveryPreferenceIfNeededLocked()) { 426 try { 427 mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference); 428 } catch (RemoteException ex) { 429 Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); 430 } 431 } 432 if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { 433 try { 434 mMediaRouterService.unregisterRouter2(mStub); 435 } catch (RemoteException ex) { 436 Log.e(TAG, "Unable to unregister media router.", ex); 437 } 438 mStub = null; 439 } 440 } 441 } 442 443 @GuardedBy("mLock") updateDiscoveryPreferenceIfNeededLocked()444 private boolean updateDiscoveryPreferenceIfNeededLocked() { 445 RouteDiscoveryPreference newDiscoveryPreference = new RouteDiscoveryPreference.Builder( 446 mRouteCallbackRecords.stream().map(record -> record.mPreference).collect( 447 Collectors.toList())).build(); 448 449 if (Objects.equals(mDiscoveryPreference, newDiscoveryPreference)) { 450 return false; 451 } 452 mDiscoveryPreference = newDiscoveryPreference; 453 updateFilteredRoutesLocked(); 454 return true; 455 } 456 457 /** 458 * Gets the list of all discovered routes. This list includes the routes that are not related to 459 * the client app. 460 * 461 * <p>This will return an empty list for non-system media routers. 462 * 463 * @hide 464 */ 465 @SystemApi 466 @NonNull getAllRoutes()467 public List<MediaRoute2Info> getAllRoutes() { 468 if (isSystemRouter()) { 469 return sManager.getAllRoutes(); 470 } 471 return Collections.emptyList(); 472 } 473 474 /** 475 * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently known to the media 476 * router. 477 * 478 * <p>Please note that the list can be changed before callbacks are invoked. 479 * 480 * @return the list of routes that contains at least one of the route features in discovery 481 * preferences registered by the application 482 */ 483 @NonNull getRoutes()484 public List<MediaRoute2Info> getRoutes() { 485 synchronized (mLock) { 486 return mFilteredRoutes; 487 } 488 } 489 490 /** 491 * Registers a callback to get the result of {@link #transferTo(MediaRoute2Info)}. 492 * If you register the same callback twice or more, it will be ignored. 493 * 494 * @param executor the executor to execute the callback on 495 * @param callback the callback to register 496 * @see #unregisterTransferCallback 497 */ registerTransferCallback( @onNull @allbackExecutor Executor executor, @NonNull TransferCallback callback)498 public void registerTransferCallback( 499 @NonNull @CallbackExecutor Executor executor, @NonNull TransferCallback callback) { 500 Objects.requireNonNull(executor, "executor must not be null"); 501 Objects.requireNonNull(callback, "callback must not be null"); 502 503 TransferCallbackRecord record = new TransferCallbackRecord(executor, callback); 504 if (!mTransferCallbackRecords.addIfAbsent(record)) { 505 Log.w(TAG, "registerTransferCallback: Ignoring the same callback"); 506 return; 507 } 508 } 509 510 /** 511 * Unregisters the given callback. The callback will no longer receive events. 512 * If the callback has not been added or been removed already, it is ignored. 513 * 514 * @param callback the callback to unregister 515 * @see #registerTransferCallback 516 */ unregisterTransferCallback(@onNull TransferCallback callback)517 public void unregisterTransferCallback(@NonNull TransferCallback callback) { 518 Objects.requireNonNull(callback, "callback must not be null"); 519 520 if (!mTransferCallbackRecords.remove(new TransferCallbackRecord(null, callback))) { 521 Log.w(TAG, "unregisterTransferCallback: Ignoring an unknown callback"); 522 return; 523 } 524 } 525 526 /** 527 * Registers a {@link ControllerCallback}. If you register the same callback twice or more, it 528 * will be ignored. 529 * 530 * @see #unregisterControllerCallback(ControllerCallback) 531 */ registerControllerCallback( @onNull @allbackExecutor Executor executor, @NonNull ControllerCallback callback)532 public void registerControllerCallback( 533 @NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) { 534 Objects.requireNonNull(executor, "executor must not be null"); 535 Objects.requireNonNull(callback, "callback must not be null"); 536 537 ControllerCallbackRecord record = new ControllerCallbackRecord(executor, callback); 538 if (!mControllerCallbackRecords.addIfAbsent(record)) { 539 Log.w(TAG, "registerControllerCallback: Ignoring the same callback"); 540 return; 541 } 542 } 543 544 /** 545 * Unregisters a {@link ControllerCallback}. The callback will no longer receive events. 546 * If the callback has not been added or been removed already, it is ignored. 547 * 548 * @see #registerControllerCallback(Executor, ControllerCallback) 549 */ unregisterControllerCallback(@onNull ControllerCallback callback)550 public void unregisterControllerCallback(@NonNull ControllerCallback callback) { 551 Objects.requireNonNull(callback, "callback must not be null"); 552 553 if (!mControllerCallbackRecords.remove(new ControllerCallbackRecord(null, callback))) { 554 Log.w(TAG, "unregisterControllerCallback: Ignoring an unknown callback"); 555 return; 556 } 557 } 558 559 /** 560 * Sets an {@link OnGetControllerHintsListener} to send hints when creating a 561 * {@link RoutingController}. To send the hints, listener should be set <em>BEFORE</em> calling 562 * {@link #transferTo(MediaRoute2Info)}. 563 * 564 * @param listener A listener to send optional app-specific hints when creating a controller. 565 * {@code null} for unset. 566 */ setOnGetControllerHintsListener(@ullable OnGetControllerHintsListener listener)567 public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) { 568 if (isSystemRouter()) { 569 return; 570 } 571 mOnGetControllerHintsListener = listener; 572 } 573 574 /** 575 * Transfers the current media to the given route. If it's necessary a new 576 * {@link RoutingController} is created or it is handled within the current routing controller. 577 * 578 * @param route the route you want to transfer the current media to. Pass {@code null} to 579 * stop routing of the current media. 580 * @see TransferCallback#onTransfer 581 * @see TransferCallback#onTransferFailure 582 */ transferTo(@onNull MediaRoute2Info route)583 public void transferTo(@NonNull MediaRoute2Info route) { 584 if (isSystemRouter()) { 585 sManager.selectRoute(mClientPackageName, route); 586 return; 587 } 588 589 Log.v(TAG, "Transferring to route: " + route); 590 591 boolean routeFound; 592 synchronized (mLock) { 593 // TODO: Check thread-safety 594 routeFound = mRoutes.containsKey(route.getId()); 595 } 596 if (!routeFound) { 597 notifyTransferFailure(route); 598 return; 599 } 600 601 RoutingController controller = getCurrentController(); 602 if (controller.getRoutingSessionInfo().getTransferableRoutes().contains(route.getId())) { 603 controller.transferToRoute(route); 604 return; 605 } 606 607 requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE); 608 } 609 610 /** 611 * Stops the current media routing. If the {@link #getSystemController() system controller} 612 * controls the media routing, this method is a no-op. 613 */ stop()614 public void stop() { 615 if (isSystemRouter()) { 616 List<RoutingSessionInfo> sessionInfos = sManager.getRoutingSessions(mClientPackageName); 617 RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1); 618 sManager.releaseSession(sessionToRelease); 619 return; 620 } 621 getCurrentController().release(); 622 } 623 624 /** 625 * Transfers the media of a routing controller to the given route. 626 * 627 * <p>This will be no-op for non-system media routers. 628 * 629 * @param controller a routing controller controlling media routing. 630 * @param route the route you want to transfer the media to. 631 * @hide 632 */ 633 @SystemApi 634 @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) transfer(@onNull RoutingController controller, @NonNull MediaRoute2Info route)635 public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) { 636 if (isSystemRouter()) { 637 sManager.transfer(controller.getRoutingSessionInfo(), route); 638 return; 639 } 640 } 641 requestCreateController( @onNull RoutingController controller, @NonNull MediaRoute2Info route, long managerRequestId)642 void requestCreateController( 643 @NonNull RoutingController controller, 644 @NonNull MediaRoute2Info route, 645 long managerRequestId) { 646 647 final int requestId = mNextRequestId.getAndIncrement(); 648 649 ControllerCreationRequest request = 650 new ControllerCreationRequest(requestId, managerRequestId, route, controller); 651 mControllerCreationRequests.add(request); 652 653 OnGetControllerHintsListener listener = mOnGetControllerHintsListener; 654 Bundle controllerHints = null; 655 if (listener != null) { 656 controllerHints = listener.onGetControllerHints(route); 657 if (controllerHints != null) { 658 controllerHints = new Bundle(controllerHints); 659 } 660 } 661 662 MediaRouter2Stub stub; 663 synchronized (mLock) { 664 stub = mStub; 665 } 666 if (stub != null) { 667 try { 668 mMediaRouterService.requestCreateSessionWithRouter2( 669 stub, 670 requestId, 671 managerRequestId, 672 controller.getRoutingSessionInfo(), 673 route, 674 controllerHints); 675 } catch (RemoteException ex) { 676 Log.e(TAG, "createControllerForTransfer: " 677 + "Failed to request for creating a controller.", ex); 678 mControllerCreationRequests.remove(request); 679 if (managerRequestId == MANAGER_REQUEST_ID_NONE) { 680 notifyTransferFailure(route); 681 } 682 } 683 } 684 } 685 686 @NonNull getCurrentController()687 private RoutingController getCurrentController() { 688 List<RoutingController> controllers = getControllers(); 689 return controllers.get(controllers.size() - 1); 690 } 691 692 /** 693 * Gets a {@link RoutingController} which can control the routes provided by system. 694 * e.g. Phone speaker, wired headset, Bluetooth, etc. 695 * 696 * <p>Note: The system controller can't be released. Calling {@link RoutingController#release()} 697 * will be ignored. 698 * 699 * <p>This method always returns the same instance. 700 */ 701 @NonNull getSystemController()702 public RoutingController getSystemController() { 703 return mSystemController; 704 } 705 706 /** 707 * Gets a {@link RoutingController} whose ID is equal to the given ID. 708 * Returns {@code null} if there is no matching controller. 709 */ 710 @Nullable getController(@onNull String id)711 public RoutingController getController(@NonNull String id) { 712 Objects.requireNonNull(id, "id must not be null"); 713 for (RoutingController controller : getControllers()) { 714 if (TextUtils.equals(id, controller.getId())) { 715 return controller; 716 } 717 } 718 return null; 719 } 720 721 /** 722 * Gets the list of currently active {@link RoutingController routing controllers} on which 723 * media can be played. 724 * 725 * <p>Note: The list returned here will never be empty. The first element in the list is 726 * always the {@link #getSystemController() system controller}. 727 */ 728 @NonNull getControllers()729 public List<RoutingController> getControllers() { 730 List<RoutingController> result = new ArrayList<>(); 731 732 if (isSystemRouter()) { 733 // Unlike non-system MediaRouter2, controller instances cannot be kept, 734 // since the transfer events initiated from other apps will not come through manager. 735 List<RoutingSessionInfo> sessions = sManager.getRoutingSessions(mClientPackageName); 736 for (RoutingSessionInfo session : sessions) { 737 RoutingController controller; 738 if (session.isSystemSession()) { 739 mSystemController.setRoutingSessionInfo( 740 ensureClientPackageNameForSystemSession(session)); 741 controller = mSystemController; 742 } else { 743 controller = new RoutingController(session); 744 } 745 result.add(controller); 746 } 747 return result; 748 } 749 750 result.add(0, mSystemController); 751 synchronized (mLock) { 752 result.addAll(mNonSystemRoutingControllers.values()); 753 } 754 return result; 755 } 756 757 /** 758 * Requests a volume change for the route asynchronously. 759 * It may have no effect if the route is currently not selected. 760 * 761 * <p>This will be no-op for non-system media routers. 762 * 763 * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}. 764 * @see #getInstance(Context, String) 765 * @hide 766 */ 767 @SystemApi 768 @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) setRouteVolume(@onNull MediaRoute2Info route, int volume)769 public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) { 770 Objects.requireNonNull(route, "route must not be null"); 771 772 if (isSystemRouter()) { 773 sManager.setRouteVolume(route, volume); 774 return; 775 } 776 // If this API needs to be public, use IMediaRouterService#setRouteVolumeWithRouter2() 777 } 778 syncRoutesOnHandler( List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo)779 void syncRoutesOnHandler( 780 List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) { 781 if (currentRoutes == null || currentRoutes.isEmpty() || currentSystemSessionInfo == null) { 782 Log.e(TAG, "syncRoutesOnHandler: Received wrong data. currentRoutes=" + currentRoutes 783 + ", currentSystemSessionInfo=" + currentSystemSessionInfo); 784 return; 785 } 786 787 synchronized (mLock) { 788 mRoutes.clear(); 789 for (MediaRoute2Info route : currentRoutes) { 790 mRoutes.put(route.getId(), route); 791 } 792 updateFilteredRoutesLocked(); 793 } 794 795 RoutingSessionInfo oldInfo = mSystemController.getRoutingSessionInfo(); 796 mSystemController.setRoutingSessionInfo(currentSystemSessionInfo); 797 if (!oldInfo.equals(currentSystemSessionInfo)) { 798 notifyControllerUpdated(mSystemController); 799 } 800 } 801 dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes)802 void dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes) { 803 List<MediaRoute2Info> addedRoutes = new ArrayList<>(); 804 List<MediaRoute2Info> removedRoutes = new ArrayList<>(); 805 List<MediaRoute2Info> changedRoutes = new ArrayList<>(); 806 807 Set<String> newRouteIds = 808 newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet()); 809 810 for (MediaRoute2Info route : newRoutes) { 811 MediaRoute2Info prevRoute = mPreviousRoutes.get(route.getId()); 812 if (prevRoute == null) { 813 addedRoutes.add(route); 814 } else if (!prevRoute.equals(route)) { 815 changedRoutes.add(route); 816 } 817 } 818 819 for (int i = 0; i < mPreviousRoutes.size(); i++) { 820 if (!newRouteIds.contains(mPreviousRoutes.keyAt(i))) { 821 removedRoutes.add(mPreviousRoutes.valueAt(i)); 822 } 823 } 824 825 // update previous routes 826 for (MediaRoute2Info route : removedRoutes) { 827 mPreviousRoutes.remove(route.getId()); 828 } 829 for (MediaRoute2Info route : addedRoutes) { 830 mPreviousRoutes.put(route.getId(), route); 831 } 832 for (MediaRoute2Info route : changedRoutes) { 833 mPreviousRoutes.put(route.getId(), route); 834 } 835 836 if (!addedRoutes.isEmpty()) { 837 notifyRoutesAdded(addedRoutes); 838 } 839 if (!removedRoutes.isEmpty()) { 840 notifyRoutesRemoved(removedRoutes); 841 } 842 if (!changedRoutes.isEmpty()) { 843 notifyRoutesChanged(changedRoutes); 844 } 845 } 846 addRoutesOnHandler(List<MediaRoute2Info> routes)847 void addRoutesOnHandler(List<MediaRoute2Info> routes) { 848 synchronized (mLock) { 849 for (MediaRoute2Info route : routes) { 850 mRoutes.put(route.getId(), route); 851 } 852 updateFilteredRoutesLocked(); 853 } 854 } 855 removeRoutesOnHandler(List<MediaRoute2Info> routes)856 void removeRoutesOnHandler(List<MediaRoute2Info> routes) { 857 synchronized (mLock) { 858 for (MediaRoute2Info route : routes) { 859 mRoutes.remove(route.getId()); 860 } 861 updateFilteredRoutesLocked(); 862 } 863 } 864 changeRoutesOnHandler(List<MediaRoute2Info> routes)865 void changeRoutesOnHandler(List<MediaRoute2Info> routes) { 866 List<MediaRoute2Info> changedRoutes = new ArrayList<>(); 867 synchronized (mLock) { 868 for (MediaRoute2Info route : routes) { 869 mRoutes.put(route.getId(), route); 870 } 871 updateFilteredRoutesLocked(); 872 } 873 } 874 875 /** Updates filtered routes and dispatch callbacks */ 876 @GuardedBy("mLock") updateFilteredRoutesLocked()877 void updateFilteredRoutesLocked() { 878 mFilteredRoutes = 879 Collections.unmodifiableList( 880 filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values()))); 881 mHandler.sendMessage( 882 obtainMessage(MediaRouter2::dispatchFilteredRoutesChangedLocked, 883 this, mFilteredRoutes)); 884 } 885 886 /** 887 * Creates a controller and calls the {@link TransferCallback#onTransfer}. If the controller 888 * creation has failed, then it calls {@link TransferCallback#onTransferFailure}. 889 * 890 * <p>Pass {@code null} to sessionInfo for the failure case. 891 */ createControllerOnHandler(int requestId, @Nullable RoutingSessionInfo sessionInfo)892 void createControllerOnHandler(int requestId, @Nullable RoutingSessionInfo sessionInfo) { 893 ControllerCreationRequest matchingRequest = null; 894 for (ControllerCreationRequest request : mControllerCreationRequests) { 895 if (request.mRequestId == requestId) { 896 matchingRequest = request; 897 break; 898 } 899 } 900 901 if (matchingRequest == null) { 902 Log.w(TAG, "createControllerOnHandler: Ignoring an unknown request."); 903 return; 904 } 905 906 mControllerCreationRequests.remove(matchingRequest); 907 MediaRoute2Info requestedRoute = matchingRequest.mRoute; 908 909 // TODO: Notify the reason for failure. 910 if (sessionInfo == null) { 911 notifyTransferFailure(requestedRoute); 912 return; 913 } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) { 914 Log.w( 915 TAG, 916 "The session's provider ID does not match the requested route's. " 917 + "(requested route's providerId=" 918 + requestedRoute.getProviderId() 919 + ", actual providerId=" 920 + sessionInfo.getProviderId() 921 + ")"); 922 notifyTransferFailure(requestedRoute); 923 return; 924 } 925 926 RoutingController oldController = matchingRequest.mOldController; 927 // When the old controller is released before transferred, treat it as a failure. 928 // This could also happen when transfer is requested twice or more. 929 if (!oldController.scheduleRelease()) { 930 Log.w( 931 TAG, 932 "createControllerOnHandler: " 933 + "Ignoring controller creation for released old controller. " 934 + "oldController=" 935 + oldController); 936 if (!sessionInfo.isSystemSession()) { 937 new RoutingController(sessionInfo).release(); 938 } 939 notifyTransferFailure(requestedRoute); 940 return; 941 } 942 943 RoutingController newController; 944 if (sessionInfo.isSystemSession()) { 945 newController = getSystemController(); 946 newController.setRoutingSessionInfo(sessionInfo); 947 } else { 948 newController = new RoutingController(sessionInfo); 949 synchronized (mLock) { 950 mNonSystemRoutingControllers.put(newController.getId(), newController); 951 } 952 } 953 954 notifyTransfer(oldController, newController); 955 } 956 updateControllerOnHandler(RoutingSessionInfo sessionInfo)957 void updateControllerOnHandler(RoutingSessionInfo sessionInfo) { 958 if (sessionInfo == null) { 959 Log.w(TAG, "updateControllerOnHandler: Ignoring null sessionInfo."); 960 return; 961 } 962 963 if (sessionInfo.isSystemSession()) { 964 // The session info is sent from SystemMediaRoute2Provider. 965 RoutingController systemController = getSystemController(); 966 systemController.setRoutingSessionInfo(sessionInfo); 967 notifyControllerUpdated(systemController); 968 return; 969 } 970 971 RoutingController matchingController; 972 synchronized (mLock) { 973 matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId()); 974 } 975 976 if (matchingController == null) { 977 Log.w( 978 TAG, 979 "updateControllerOnHandler: Matching controller not found. uniqueSessionId=" 980 + sessionInfo.getId()); 981 return; 982 } 983 984 RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); 985 if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { 986 Log.w( 987 TAG, 988 "updateControllerOnHandler: Provider IDs are not matched. old=" 989 + oldInfo.getProviderId() 990 + ", new=" 991 + sessionInfo.getProviderId()); 992 return; 993 } 994 995 matchingController.setRoutingSessionInfo(sessionInfo); 996 notifyControllerUpdated(matchingController); 997 } 998 releaseControllerOnHandler(RoutingSessionInfo sessionInfo)999 void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) { 1000 if (sessionInfo == null) { 1001 Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo."); 1002 return; 1003 } 1004 1005 RoutingController matchingController; 1006 synchronized (mLock) { 1007 matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId()); 1008 } 1009 1010 if (matchingController == null) { 1011 if (DEBUG) { 1012 Log.d( 1013 TAG, 1014 "releaseControllerOnHandler: Matching controller not found. " 1015 + "uniqueSessionId=" 1016 + sessionInfo.getId()); 1017 } 1018 return; 1019 } 1020 1021 RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); 1022 if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { 1023 Log.w( 1024 TAG, 1025 "releaseControllerOnHandler: Provider IDs are not matched. old=" 1026 + oldInfo.getProviderId() 1027 + ", new=" 1028 + sessionInfo.getProviderId()); 1029 return; 1030 } 1031 1032 matchingController.releaseInternal(/* shouldReleaseSession= */ false); 1033 } 1034 onRequestCreateControllerByManagerOnHandler( RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId)1035 void onRequestCreateControllerByManagerOnHandler( 1036 RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) { 1037 RoutingController controller; 1038 if (oldSession.isSystemSession()) { 1039 controller = getSystemController(); 1040 } else { 1041 synchronized (mLock) { 1042 controller = mNonSystemRoutingControllers.get(oldSession.getId()); 1043 } 1044 } 1045 if (controller == null) { 1046 return; 1047 } 1048 requestCreateController(controller, route, managerRequestId); 1049 } 1050 1051 /** 1052 * Returns whether this router is created with {@link #getInstance(Context, String)}. This kind 1053 * of router can control the target app's media routing. 1054 */ isSystemRouter()1055 private boolean isSystemRouter() { 1056 return mClientPackageName != null; 1057 } 1058 1059 /** 1060 * Returns a {@link RoutingSessionInfo} which has the client package name. The client package 1061 * name is set only when the given sessionInfo doesn't have it. Should only used for system 1062 * media routers. 1063 */ ensureClientPackageNameForSystemSession( @onNull RoutingSessionInfo sessionInfo)1064 private RoutingSessionInfo ensureClientPackageNameForSystemSession( 1065 @NonNull RoutingSessionInfo sessionInfo) { 1066 if (!sessionInfo.isSystemSession() 1067 || !TextUtils.isEmpty(sessionInfo.getClientPackageName())) { 1068 return sessionInfo; 1069 } 1070 1071 return new RoutingSessionInfo.Builder(sessionInfo) 1072 .setClientPackageName(mClientPackageName) 1073 .build(); 1074 } 1075 getSortedRoutes( List<MediaRoute2Info> routes, List<String> packageOrder)1076 private List<MediaRoute2Info> getSortedRoutes( 1077 List<MediaRoute2Info> routes, List<String> packageOrder) { 1078 if (packageOrder.isEmpty()) { 1079 return routes; 1080 } 1081 Map<String, Integer> packagePriority = new ArrayMap<>(); 1082 int count = packageOrder.size(); 1083 for (int i = 0; i < count; i++) { 1084 // the last package will have 1 as the priority 1085 packagePriority.put(packageOrder.get(i), count - i); 1086 } 1087 ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes); 1088 // take the negative for descending order 1089 sortedRoutes.sort( 1090 Comparator.comparingInt(r -> -packagePriority.getOrDefault(r.getPackageName(), 0))); 1091 return sortedRoutes; 1092 } 1093 1094 @GuardedBy("mLock") filterRoutesWithCompositePreferenceLocked( List<MediaRoute2Info> routes)1095 private List<MediaRoute2Info> filterRoutesWithCompositePreferenceLocked( 1096 List<MediaRoute2Info> routes) { 1097 1098 Set<String> deduplicationIdSet = new ArraySet<>(); 1099 1100 List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); 1101 for (MediaRoute2Info route : 1102 getSortedRoutes(routes, mDiscoveryPreference.getDeduplicationPackageOrder())) { 1103 if (!route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { 1104 continue; 1105 } 1106 if (!mDiscoveryPreference.getAllowedPackages().isEmpty() 1107 && (route.getPackageName() == null 1108 || !mDiscoveryPreference 1109 .getAllowedPackages() 1110 .contains(route.getPackageName()))) { 1111 continue; 1112 } 1113 if (mDiscoveryPreference.shouldRemoveDuplicates()) { 1114 if (!Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) { 1115 continue; 1116 } 1117 deduplicationIdSet.addAll(route.getDeduplicationIds()); 1118 } 1119 filteredRoutes.add(route); 1120 } 1121 return filteredRoutes; 1122 } 1123 filterRoutesWithIndividualPreference( List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference)1124 private List<MediaRoute2Info> filterRoutesWithIndividualPreference( 1125 List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) { 1126 List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); 1127 if (isSystemRouter()) { 1128 // Individual discovery preferences do not apply for the system router. 1129 filteredRoutes.addAll(routes); 1130 return filteredRoutes; 1131 } 1132 for (MediaRoute2Info route : routes) { 1133 if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) { 1134 continue; 1135 } 1136 if (!discoveryPreference.getAllowedPackages().isEmpty() 1137 && (route.getPackageName() == null 1138 || !discoveryPreference 1139 .getAllowedPackages() 1140 .contains(route.getPackageName()))) { 1141 continue; 1142 } 1143 filteredRoutes.add(route); 1144 } 1145 return filteredRoutes; 1146 } 1147 updateAllRoutesFromManager()1148 private void updateAllRoutesFromManager() { 1149 if (!isSystemRouter()) { 1150 return; 1151 } 1152 synchronized (mLock) { 1153 mRoutes.clear(); 1154 for (MediaRoute2Info route : sManager.getAllRoutes()) { 1155 mRoutes.put(route.getId(), route); 1156 } 1157 updateFilteredRoutesLocked(); 1158 } 1159 } 1160 notifyRoutesAdded(List<MediaRoute2Info> routes)1161 private void notifyRoutesAdded(List<MediaRoute2Info> routes) { 1162 for (RouteCallbackRecord record : mRouteCallbackRecords) { 1163 List<MediaRoute2Info> filteredRoutes = 1164 filterRoutesWithIndividualPreference(routes, record.mPreference); 1165 if (!filteredRoutes.isEmpty()) { 1166 record.mExecutor.execute(() -> record.mRouteCallback.onRoutesAdded(filteredRoutes)); 1167 } 1168 } 1169 } 1170 notifyRoutesRemoved(List<MediaRoute2Info> routes)1171 private void notifyRoutesRemoved(List<MediaRoute2Info> routes) { 1172 for (RouteCallbackRecord record : mRouteCallbackRecords) { 1173 List<MediaRoute2Info> filteredRoutes = 1174 filterRoutesWithIndividualPreference(routes, record.mPreference); 1175 if (!filteredRoutes.isEmpty()) { 1176 record.mExecutor.execute( 1177 () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes)); 1178 } 1179 } 1180 } 1181 notifyRoutesChanged(List<MediaRoute2Info> routes)1182 private void notifyRoutesChanged(List<MediaRoute2Info> routes) { 1183 for (RouteCallbackRecord record : mRouteCallbackRecords) { 1184 List<MediaRoute2Info> filteredRoutes = 1185 filterRoutesWithIndividualPreference(routes, record.mPreference); 1186 if (!filteredRoutes.isEmpty()) { 1187 record.mExecutor.execute( 1188 () -> record.mRouteCallback.onRoutesChanged(filteredRoutes)); 1189 } 1190 } 1191 } 1192 notifyPreferredFeaturesChanged(List<String> features)1193 private void notifyPreferredFeaturesChanged(List<String> features) { 1194 for (RouteCallbackRecord record : mRouteCallbackRecords) { 1195 record.mExecutor.execute( 1196 () -> record.mRouteCallback.onPreferredFeaturesChanged(features)); 1197 } 1198 } 1199 notifyTransfer(RoutingController oldController, RoutingController newController)1200 private void notifyTransfer(RoutingController oldController, RoutingController newController) { 1201 for (TransferCallbackRecord record : mTransferCallbackRecords) { 1202 record.mExecutor.execute( 1203 () -> record.mTransferCallback.onTransfer(oldController, newController)); 1204 } 1205 } 1206 notifyTransferFailure(MediaRoute2Info route)1207 private void notifyTransferFailure(MediaRoute2Info route) { 1208 for (TransferCallbackRecord record : mTransferCallbackRecords) { 1209 record.mExecutor.execute(() -> record.mTransferCallback.onTransferFailure(route)); 1210 } 1211 } 1212 notifyStop(RoutingController controller)1213 private void notifyStop(RoutingController controller) { 1214 for (TransferCallbackRecord record : mTransferCallbackRecords) { 1215 record.mExecutor.execute(() -> record.mTransferCallback.onStop(controller)); 1216 } 1217 } 1218 notifyControllerUpdated(RoutingController controller)1219 private void notifyControllerUpdated(RoutingController controller) { 1220 for (ControllerCallbackRecord record : mControllerCallbackRecords) { 1221 record.mExecutor.execute(() -> record.mCallback.onControllerUpdated(controller)); 1222 } 1223 } 1224 1225 /** Callback for receiving events about media route discovery. */ 1226 public abstract static class RouteCallback { 1227 /** 1228 * Called when routes are added. Whenever you registers a callback, this will be invoked 1229 * with known routes. 1230 * 1231 * @param routes the list of routes that have been added. It's never empty. 1232 */ onRoutesAdded(@onNull List<MediaRoute2Info> routes)1233 public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {} 1234 1235 /** 1236 * Called when routes are removed. 1237 * 1238 * @param routes the list of routes that have been removed. It's never empty. 1239 */ onRoutesRemoved(@onNull List<MediaRoute2Info> routes)1240 public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {} 1241 1242 /** 1243 * Called when routes are changed. For example, it is called when the route's name or volume 1244 * have been changed. 1245 * 1246 * @param routes the list of routes that have been changed. It's never empty. 1247 */ onRoutesChanged(@onNull List<MediaRoute2Info> routes)1248 public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} 1249 1250 /** 1251 * Called when the client app's preferred features are changed. When this is called, it is 1252 * recommended to {@link #getRoutes()} to get the routes that are currently available to the 1253 * app. 1254 * 1255 * @param preferredFeatures the new preferred features set by the application 1256 * @hide 1257 */ 1258 @SystemApi onPreferredFeaturesChanged(@onNull List<String> preferredFeatures)1259 public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {} 1260 } 1261 1262 /** Callback for receiving events on media transfer. */ 1263 public abstract static class TransferCallback { 1264 /** 1265 * Called when a media is transferred between two different routing controllers. This can 1266 * happen by calling {@link #transferTo(MediaRoute2Info)}. 1267 * 1268 * <p>Override this to start playback with {@code newController}. You may want to get the 1269 * status of the media that is being played with {@code oldController} and resume it 1270 * continuously with {@code newController}. After this is called, any callbacks with {@code 1271 * oldController} will not be invoked unless {@code oldController} is the {@link 1272 * #getSystemController() system controller}. You need to {@link RoutingController#release() 1273 * release} {@code oldController} before playing the media with {@code newController}. 1274 * 1275 * @param oldController the previous controller that controlled routing 1276 * @param newController the new controller to control routing 1277 * @see #transferTo(MediaRoute2Info) 1278 */ onTransfer( @onNull RoutingController oldController, @NonNull RoutingController newController)1279 public void onTransfer( 1280 @NonNull RoutingController oldController, 1281 @NonNull RoutingController newController) {} 1282 1283 /** 1284 * Called when {@link #transferTo(MediaRoute2Info)} failed. 1285 * 1286 * @param requestedRoute the route info which was used for the transfer 1287 */ onTransferFailure(@onNull MediaRoute2Info requestedRoute)1288 public void onTransferFailure(@NonNull MediaRoute2Info requestedRoute) {} 1289 1290 /** 1291 * Called when a media routing stops. It can be stopped by a user or a provider. App should 1292 * not continue playing media locally when this method is called. The {@code controller} is 1293 * released before this method is called. 1294 * 1295 * @param controller the controller that controlled the stopped media routing 1296 */ onStop(@onNull RoutingController controller)1297 public void onStop(@NonNull RoutingController controller) {} 1298 } 1299 1300 /** 1301 * A listener interface to send optional app-specific hints when creating a {@link 1302 * RoutingController}. 1303 */ 1304 public interface OnGetControllerHintsListener { 1305 /** 1306 * Called when the {@link MediaRouter2} or the system is about to request a media route 1307 * provider service to create a controller with the given route. The {@link Bundle} returned 1308 * here will be sent to media route provider service as a hint. 1309 * 1310 * <p>Since controller creation can be requested by the {@link MediaRouter2} and the system, 1311 * set the listener as soon as possible after acquiring {@link MediaRouter2} instance. The 1312 * method will be called on the same thread that calls {@link #transferTo(MediaRoute2Info)} 1313 * or the main thread if it is requested by the system. 1314 * 1315 * @param route the route to create a controller with 1316 * @return An optional bundle of app-specific arguments to send to the provider, or {@code 1317 * null} if none. The contents of this bundle may affect the result of controller 1318 * creation. 1319 * @see MediaRoute2ProviderService#onCreateSession(long, String, String, Bundle) 1320 */ 1321 @Nullable onGetControllerHints(@onNull MediaRoute2Info route)1322 Bundle onGetControllerHints(@NonNull MediaRoute2Info route); 1323 } 1324 1325 /** Callback for receiving {@link RoutingController} updates. */ 1326 public abstract static class ControllerCallback { 1327 /** 1328 * Called when a controller is updated. (e.g., when the selected routes of the controller is 1329 * changed or when the volume of the controller is changed.) 1330 * 1331 * @param controller the updated controller. It may be the {@link #getSystemController() 1332 * system controller}. 1333 * @see #getSystemController() 1334 */ onControllerUpdated(@onNull RoutingController controller)1335 public void onControllerUpdated(@NonNull RoutingController controller) {} 1336 } 1337 1338 /** 1339 * A class to control media routing session in media route provider. For example, 1340 * selecting/deselecting/transferring to routes of a session can be done through this. Instances 1341 * are created when {@link TransferCallback#onTransfer(RoutingController, RoutingController)} is 1342 * called, which is invoked after {@link #transferTo(MediaRoute2Info)} is called. 1343 */ 1344 public class RoutingController { 1345 private final Object mControllerLock = new Object(); 1346 1347 private static final int CONTROLLER_STATE_UNKNOWN = 0; 1348 private static final int CONTROLLER_STATE_ACTIVE = 1; 1349 private static final int CONTROLLER_STATE_RELEASING = 2; 1350 private static final int CONTROLLER_STATE_RELEASED = 3; 1351 1352 @GuardedBy("mControllerLock") 1353 private RoutingSessionInfo mSessionInfo; 1354 1355 @GuardedBy("mControllerLock") 1356 private int mState; 1357 RoutingController(@onNull RoutingSessionInfo sessionInfo)1358 RoutingController(@NonNull RoutingSessionInfo sessionInfo) { 1359 mSessionInfo = sessionInfo; 1360 mState = CONTROLLER_STATE_ACTIVE; 1361 } 1362 RoutingController(@onNull RoutingSessionInfo sessionInfo, int state)1363 RoutingController(@NonNull RoutingSessionInfo sessionInfo, int state) { 1364 mSessionInfo = sessionInfo; 1365 mState = state; 1366 } 1367 1368 /** 1369 * @return the ID of the controller. It is globally unique. 1370 */ 1371 @NonNull getId()1372 public String getId() { 1373 synchronized (mControllerLock) { 1374 return mSessionInfo.getId(); 1375 } 1376 } 1377 1378 /** 1379 * Gets the original session ID set by {@link RoutingSessionInfo.Builder#Builder(String, 1380 * String)}. 1381 * 1382 * @hide 1383 */ 1384 @NonNull 1385 @TestApi getOriginalId()1386 public String getOriginalId() { 1387 synchronized (mControllerLock) { 1388 return mSessionInfo.getOriginalId(); 1389 } 1390 } 1391 1392 /** 1393 * Gets the control hints used to control routing session if available. It is set by the 1394 * media route provider. 1395 */ 1396 @Nullable getControlHints()1397 public Bundle getControlHints() { 1398 synchronized (mControllerLock) { 1399 return mSessionInfo.getControlHints(); 1400 } 1401 } 1402 1403 /** 1404 * @return the unmodifiable list of currently selected routes 1405 */ 1406 @NonNull getSelectedRoutes()1407 public List<MediaRoute2Info> getSelectedRoutes() { 1408 List<String> selectedRouteIds; 1409 synchronized (mControllerLock) { 1410 selectedRouteIds = mSessionInfo.getSelectedRoutes(); 1411 } 1412 return getRoutesWithIds(selectedRouteIds); 1413 } 1414 1415 /** 1416 * @return the unmodifiable list of selectable routes for the session. 1417 */ 1418 @NonNull getSelectableRoutes()1419 public List<MediaRoute2Info> getSelectableRoutes() { 1420 List<String> selectableRouteIds; 1421 synchronized (mControllerLock) { 1422 selectableRouteIds = mSessionInfo.getSelectableRoutes(); 1423 } 1424 return getRoutesWithIds(selectableRouteIds); 1425 } 1426 1427 /** 1428 * @return the unmodifiable list of deselectable routes for the session. 1429 */ 1430 @NonNull getDeselectableRoutes()1431 public List<MediaRoute2Info> getDeselectableRoutes() { 1432 List<String> deselectableRouteIds; 1433 synchronized (mControllerLock) { 1434 deselectableRouteIds = mSessionInfo.getDeselectableRoutes(); 1435 } 1436 return getRoutesWithIds(deselectableRouteIds); 1437 } 1438 1439 /** 1440 * Gets the information about how volume is handled on the session. 1441 * 1442 * <p>Please note that you may not control the volume of the session even when you can 1443 * control the volume of each selected route in the session. 1444 * 1445 * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or {@link 1446 * MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE} 1447 */ 1448 @MediaRoute2Info.PlaybackVolume getVolumeHandling()1449 public int getVolumeHandling() { 1450 synchronized (mControllerLock) { 1451 return mSessionInfo.getVolumeHandling(); 1452 } 1453 } 1454 1455 /** Gets the maximum volume of the session. */ getVolumeMax()1456 public int getVolumeMax() { 1457 synchronized (mControllerLock) { 1458 return mSessionInfo.getVolumeMax(); 1459 } 1460 } 1461 1462 /** 1463 * Gets the current volume of the session. 1464 * 1465 * <p>When it's available, it represents the volume of routing session, which is a group of 1466 * selected routes. Use {@link MediaRoute2Info#getVolume()} to get the volume of a route, 1467 * 1468 * @see MediaRoute2Info#getVolume() 1469 */ getVolume()1470 public int getVolume() { 1471 synchronized (mControllerLock) { 1472 return mSessionInfo.getVolume(); 1473 } 1474 } 1475 1476 /** 1477 * Returns true if this controller is released, false otherwise. If it is released, then all 1478 * other getters from this instance may return invalid values. Also, any operations to this 1479 * instance will be ignored once released. 1480 * 1481 * @see #release 1482 */ isReleased()1483 public boolean isReleased() { 1484 synchronized (mControllerLock) { 1485 return mState == CONTROLLER_STATE_RELEASED; 1486 } 1487 } 1488 1489 /** 1490 * Selects a route for the remote session. After a route is selected, the media is expected 1491 * to be played to the all the selected routes. This is different from {@link 1492 * MediaRouter2#transferTo(MediaRoute2Info)} transferring to a route}, where the media is 1493 * expected to 'move' from one route to another. 1494 * 1495 * <p>The given route must satisfy all of the following conditions: 1496 * 1497 * <ul> 1498 * <li>It should not be included in {@link #getSelectedRoutes()} 1499 * <li>It should be included in {@link #getSelectableRoutes()} 1500 * </ul> 1501 * 1502 * If the route doesn't meet any of above conditions, it will be ignored. 1503 * 1504 * @see #deselectRoute(MediaRoute2Info) 1505 * @see #getSelectedRoutes() 1506 * @see #getSelectableRoutes() 1507 * @see ControllerCallback#onControllerUpdated 1508 */ selectRoute(@onNull MediaRoute2Info route)1509 public void selectRoute(@NonNull MediaRoute2Info route) { 1510 Objects.requireNonNull(route, "route must not be null"); 1511 if (isReleased()) { 1512 Log.w(TAG, "selectRoute: Called on released controller. Ignoring."); 1513 return; 1514 } 1515 1516 List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(); 1517 if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) { 1518 Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route); 1519 return; 1520 } 1521 1522 List<MediaRoute2Info> selectableRoutes = getSelectableRoutes(); 1523 if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) { 1524 Log.w(TAG, "Ignoring selecting a non-selectable route=" + route); 1525 return; 1526 } 1527 1528 if (isSystemRouter()) { 1529 sManager.selectRoute(getRoutingSessionInfo(), route); 1530 return; 1531 } 1532 1533 MediaRouter2Stub stub; 1534 synchronized (mLock) { 1535 stub = mStub; 1536 } 1537 if (stub != null) { 1538 try { 1539 mMediaRouterService.selectRouteWithRouter2(stub, getId(), route); 1540 } catch (RemoteException ex) { 1541 Log.e(TAG, "Unable to select route for session.", ex); 1542 } 1543 } 1544 } 1545 1546 /** 1547 * Deselects a route from the remote session. After a route is deselected, the media is 1548 * expected to be stopped on the deselected route. 1549 * 1550 * <p>The given route must satisfy all of the following conditions: 1551 * 1552 * <ul> 1553 * <li>It should be included in {@link #getSelectedRoutes()} 1554 * <li>It should be included in {@link #getDeselectableRoutes()} 1555 * </ul> 1556 * 1557 * If the route doesn't meet any of above conditions, it will be ignored. 1558 * 1559 * @see #getSelectedRoutes() 1560 * @see #getDeselectableRoutes() 1561 * @see ControllerCallback#onControllerUpdated 1562 */ deselectRoute(@onNull MediaRoute2Info route)1563 public void deselectRoute(@NonNull MediaRoute2Info route) { 1564 Objects.requireNonNull(route, "route must not be null"); 1565 if (isReleased()) { 1566 Log.w(TAG, "deselectRoute: called on released controller. Ignoring."); 1567 return; 1568 } 1569 1570 List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(); 1571 if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) { 1572 Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route); 1573 return; 1574 } 1575 1576 List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes(); 1577 if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) { 1578 Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route); 1579 return; 1580 } 1581 1582 if (isSystemRouter()) { 1583 sManager.deselectRoute(getRoutingSessionInfo(), route); 1584 return; 1585 } 1586 1587 MediaRouter2Stub stub; 1588 synchronized (mLock) { 1589 stub = mStub; 1590 } 1591 if (stub != null) { 1592 try { 1593 mMediaRouterService.deselectRouteWithRouter2(stub, getId(), route); 1594 } catch (RemoteException ex) { 1595 Log.e(TAG, "Unable to deselect route from session.", ex); 1596 } 1597 } 1598 } 1599 1600 /** 1601 * Transfers to a given route for the remote session. The given route must be included in 1602 * {@link RoutingSessionInfo#getTransferableRoutes()}. 1603 * 1604 * @see RoutingSessionInfo#getSelectedRoutes() 1605 * @see RoutingSessionInfo#getTransferableRoutes() 1606 * @see ControllerCallback#onControllerUpdated 1607 */ transferToRoute(@onNull MediaRoute2Info route)1608 void transferToRoute(@NonNull MediaRoute2Info route) { 1609 Objects.requireNonNull(route, "route must not be null"); 1610 synchronized (mControllerLock) { 1611 if (isReleased()) { 1612 Log.w(TAG, "transferToRoute: Called on released controller. Ignoring."); 1613 return; 1614 } 1615 1616 if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) { 1617 Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route); 1618 return; 1619 } 1620 } 1621 1622 MediaRouter2Stub stub; 1623 synchronized (mLock) { 1624 stub = mStub; 1625 } 1626 if (stub != null) { 1627 try { 1628 mMediaRouterService.transferToRouteWithRouter2(stub, getId(), route); 1629 } catch (RemoteException ex) { 1630 Log.e(TAG, "Unable to transfer to route for session.", ex); 1631 } 1632 } 1633 } 1634 1635 /** 1636 * Requests a volume change for the remote session asynchronously. 1637 * 1638 * @param volume The new volume value between 0 and {@link RoutingController#getVolumeMax} 1639 * (inclusive). 1640 * @see #getVolume() 1641 */ setVolume(int volume)1642 public void setVolume(int volume) { 1643 if (getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) { 1644 Log.w(TAG, "setVolume: The routing session has fixed volume. Ignoring."); 1645 return; 1646 } 1647 if (volume < 0 || volume > getVolumeMax()) { 1648 Log.w(TAG, "setVolume: The target volume is out of range. Ignoring"); 1649 return; 1650 } 1651 1652 if (isReleased()) { 1653 Log.w(TAG, "setVolume: Called on released controller. Ignoring."); 1654 return; 1655 } 1656 1657 if (isSystemRouter()) { 1658 sManager.setSessionVolume(getRoutingSessionInfo(), volume); 1659 return; 1660 } 1661 1662 MediaRouter2Stub stub; 1663 synchronized (mLock) { 1664 stub = mStub; 1665 } 1666 if (stub != null) { 1667 try { 1668 mMediaRouterService.setSessionVolumeWithRouter2(stub, getId(), volume); 1669 } catch (RemoteException ex) { 1670 Log.e(TAG, "setVolume: Failed to deliver request.", ex); 1671 } 1672 } 1673 } 1674 1675 /** 1676 * Releases this controller and the corresponding session. Any operations on this controller 1677 * after calling this method will be ignored. The devices that are playing media will stop 1678 * playing it. 1679 */ release()1680 public void release() { 1681 releaseInternal(/* shouldReleaseSession= */ true); 1682 } 1683 1684 /** 1685 * Schedules release of the controller. 1686 * 1687 * @return {@code true} if it's successfully scheduled, {@code false} if it's already 1688 * scheduled to be released or released. 1689 */ scheduleRelease()1690 boolean scheduleRelease() { 1691 synchronized (mControllerLock) { 1692 if (mState != CONTROLLER_STATE_ACTIVE) { 1693 return false; 1694 } 1695 mState = CONTROLLER_STATE_RELEASING; 1696 } 1697 1698 synchronized (mLock) { 1699 // It could happen if the controller is released by the another thread 1700 // in between two locks 1701 if (!mNonSystemRoutingControllers.remove(getId(), this)) { 1702 // In that case, onStop isn't called so we return true to call onTransfer. 1703 // It's also consistent with that the another thread acquires the lock later. 1704 return true; 1705 } 1706 } 1707 1708 mHandler.postDelayed(this::release, TRANSFER_TIMEOUT_MS); 1709 1710 return true; 1711 } 1712 releaseInternal(boolean shouldReleaseSession)1713 void releaseInternal(boolean shouldReleaseSession) { 1714 boolean shouldNotifyStop; 1715 1716 synchronized (mControllerLock) { 1717 if (mState == CONTROLLER_STATE_RELEASED) { 1718 if (DEBUG) { 1719 Log.d(TAG, "releaseInternal: Called on released controller. Ignoring."); 1720 } 1721 return; 1722 } 1723 shouldNotifyStop = (mState == CONTROLLER_STATE_ACTIVE); 1724 mState = CONTROLLER_STATE_RELEASED; 1725 } 1726 1727 if (isSystemRouter()) { 1728 sManager.releaseSession(getRoutingSessionInfo()); 1729 return; 1730 } 1731 1732 synchronized (mLock) { 1733 mNonSystemRoutingControllers.remove(getId(), this); 1734 1735 if (shouldReleaseSession && mStub != null) { 1736 try { 1737 mMediaRouterService.releaseSessionWithRouter2(mStub, getId()); 1738 } catch (RemoteException ex) { 1739 Log.e(TAG, "Unable to release session", ex); 1740 } 1741 } 1742 1743 if (shouldNotifyStop) { 1744 mHandler.sendMessage( 1745 obtainMessage( 1746 MediaRouter2::notifyStop, 1747 MediaRouter2.this, 1748 RoutingController.this)); 1749 } 1750 1751 if (mRouteCallbackRecords.isEmpty() 1752 && mNonSystemRoutingControllers.isEmpty() 1753 && mStub != null) { 1754 try { 1755 mMediaRouterService.unregisterRouter2(mStub); 1756 } catch (RemoteException ex) { 1757 Log.e(TAG, "releaseInternal: Unable to unregister media router.", ex); 1758 } 1759 mStub = null; 1760 } 1761 } 1762 } 1763 1764 @Override toString()1765 public String toString() { 1766 // To prevent logging spam, we only print the ID of each route. 1767 List<String> selectedRoutes = 1768 getSelectedRoutes().stream() 1769 .map(MediaRoute2Info::getId) 1770 .collect(Collectors.toList()); 1771 List<String> selectableRoutes = 1772 getSelectableRoutes().stream() 1773 .map(MediaRoute2Info::getId) 1774 .collect(Collectors.toList()); 1775 List<String> deselectableRoutes = 1776 getDeselectableRoutes().stream() 1777 .map(MediaRoute2Info::getId) 1778 .collect(Collectors.toList()); 1779 1780 StringBuilder result = 1781 new StringBuilder() 1782 .append("RoutingController{ ") 1783 .append("id=") 1784 .append(getId()) 1785 .append(", selectedRoutes={") 1786 .append(selectedRoutes) 1787 .append("}") 1788 .append(", selectableRoutes={") 1789 .append(selectableRoutes) 1790 .append("}") 1791 .append(", deselectableRoutes={") 1792 .append(deselectableRoutes) 1793 .append("}") 1794 .append(" }"); 1795 return result.toString(); 1796 } 1797 1798 @NonNull getRoutingSessionInfo()1799 RoutingSessionInfo getRoutingSessionInfo() { 1800 synchronized (mControllerLock) { 1801 return mSessionInfo; 1802 } 1803 } 1804 setRoutingSessionInfo(@onNull RoutingSessionInfo info)1805 void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) { 1806 synchronized (mControllerLock) { 1807 mSessionInfo = info; 1808 } 1809 } 1810 getRoutesWithIds(List<String> routeIds)1811 private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) { 1812 if (isSystemRouter()) { 1813 return getRoutes().stream() 1814 .filter(r -> routeIds.contains(r.getId())) 1815 .collect(Collectors.toList()); 1816 } 1817 1818 synchronized (mLock) { 1819 return routeIds.stream() 1820 .map(mRoutes::get) 1821 .filter(Objects::nonNull) 1822 .collect(Collectors.toList()); 1823 } 1824 } 1825 } 1826 1827 class SystemRoutingController extends RoutingController { SystemRoutingController(@onNull RoutingSessionInfo sessionInfo)1828 SystemRoutingController(@NonNull RoutingSessionInfo sessionInfo) { 1829 super(sessionInfo); 1830 } 1831 1832 @Override isReleased()1833 public boolean isReleased() { 1834 // SystemRoutingController will never be released 1835 return false; 1836 } 1837 1838 @Override scheduleRelease()1839 boolean scheduleRelease() { 1840 // SystemRoutingController can be always transferred 1841 return true; 1842 } 1843 1844 @Override releaseInternal(boolean shouldReleaseSession)1845 void releaseInternal(boolean shouldReleaseSession) { 1846 // Do nothing. SystemRoutingController will never be released 1847 } 1848 } 1849 1850 static final class RouteCallbackRecord { 1851 public final Executor mExecutor; 1852 public final RouteCallback mRouteCallback; 1853 public final RouteDiscoveryPreference mPreference; 1854 RouteCallbackRecord( @ullable Executor executor, @NonNull RouteCallback routeCallback, @Nullable RouteDiscoveryPreference preference)1855 RouteCallbackRecord( 1856 @Nullable Executor executor, 1857 @NonNull RouteCallback routeCallback, 1858 @Nullable RouteDiscoveryPreference preference) { 1859 mRouteCallback = routeCallback; 1860 mExecutor = executor; 1861 mPreference = preference; 1862 } 1863 1864 @Override equals(Object obj)1865 public boolean equals(Object obj) { 1866 if (this == obj) { 1867 return true; 1868 } 1869 if (!(obj instanceof RouteCallbackRecord)) { 1870 return false; 1871 } 1872 return mRouteCallback == ((RouteCallbackRecord) obj).mRouteCallback; 1873 } 1874 1875 @Override hashCode()1876 public int hashCode() { 1877 return mRouteCallback.hashCode(); 1878 } 1879 } 1880 1881 static final class TransferCallbackRecord { 1882 public final Executor mExecutor; 1883 public final TransferCallback mTransferCallback; 1884 TransferCallbackRecord( @onNull Executor executor, @NonNull TransferCallback transferCallback)1885 TransferCallbackRecord( 1886 @NonNull Executor executor, @NonNull TransferCallback transferCallback) { 1887 mTransferCallback = transferCallback; 1888 mExecutor = executor; 1889 } 1890 1891 @Override equals(Object obj)1892 public boolean equals(Object obj) { 1893 if (this == obj) { 1894 return true; 1895 } 1896 if (!(obj instanceof TransferCallbackRecord)) { 1897 return false; 1898 } 1899 return mTransferCallback == ((TransferCallbackRecord) obj).mTransferCallback; 1900 } 1901 1902 @Override hashCode()1903 public int hashCode() { 1904 return mTransferCallback.hashCode(); 1905 } 1906 } 1907 1908 static final class ControllerCallbackRecord { 1909 public final Executor mExecutor; 1910 public final ControllerCallback mCallback; 1911 ControllerCallbackRecord( @ullable Executor executor, @NonNull ControllerCallback callback)1912 ControllerCallbackRecord( 1913 @Nullable Executor executor, @NonNull ControllerCallback callback) { 1914 mCallback = callback; 1915 mExecutor = executor; 1916 } 1917 1918 @Override equals(Object obj)1919 public boolean equals(Object obj) { 1920 if (this == obj) { 1921 return true; 1922 } 1923 if (!(obj instanceof ControllerCallbackRecord)) { 1924 return false; 1925 } 1926 return mCallback == ((ControllerCallbackRecord) obj).mCallback; 1927 } 1928 1929 @Override hashCode()1930 public int hashCode() { 1931 return mCallback.hashCode(); 1932 } 1933 } 1934 1935 static final class ControllerCreationRequest { 1936 public final int mRequestId; 1937 public final long mManagerRequestId; 1938 public final MediaRoute2Info mRoute; 1939 public final RoutingController mOldController; 1940 ControllerCreationRequest( int requestId, long managerRequestId, @NonNull MediaRoute2Info route, @NonNull RoutingController oldController)1941 ControllerCreationRequest( 1942 int requestId, 1943 long managerRequestId, 1944 @NonNull MediaRoute2Info route, 1945 @NonNull RoutingController oldController) { 1946 mRequestId = requestId; 1947 mManagerRequestId = managerRequestId; 1948 mRoute = Objects.requireNonNull(route, "route must not be null"); 1949 mOldController = 1950 Objects.requireNonNull(oldController, "oldController must not be null"); 1951 } 1952 } 1953 1954 class MediaRouter2Stub extends IMediaRouter2.Stub { 1955 @Override notifyRouterRegistered( List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo)1956 public void notifyRouterRegistered( 1957 List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) { 1958 mHandler.sendMessage( 1959 obtainMessage( 1960 MediaRouter2::syncRoutesOnHandler, 1961 MediaRouter2.this, 1962 currentRoutes, 1963 currentSystemSessionInfo)); 1964 } 1965 1966 @Override notifyRoutesAdded(List<MediaRoute2Info> routes)1967 public void notifyRoutesAdded(List<MediaRoute2Info> routes) { 1968 mHandler.sendMessage( 1969 obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes)); 1970 } 1971 1972 @Override notifyRoutesRemoved(List<MediaRoute2Info> routes)1973 public void notifyRoutesRemoved(List<MediaRoute2Info> routes) { 1974 mHandler.sendMessage( 1975 obtainMessage(MediaRouter2::removeRoutesOnHandler, MediaRouter2.this, routes)); 1976 } 1977 1978 @Override notifyRoutesChanged(List<MediaRoute2Info> routes)1979 public void notifyRoutesChanged(List<MediaRoute2Info> routes) { 1980 mHandler.sendMessage( 1981 obtainMessage(MediaRouter2::changeRoutesOnHandler, MediaRouter2.this, routes)); 1982 } 1983 1984 @Override notifySessionCreated(int requestId, @Nullable RoutingSessionInfo sessionInfo)1985 public void notifySessionCreated(int requestId, @Nullable RoutingSessionInfo sessionInfo) { 1986 mHandler.sendMessage( 1987 obtainMessage( 1988 MediaRouter2::createControllerOnHandler, 1989 MediaRouter2.this, 1990 requestId, 1991 sessionInfo)); 1992 } 1993 1994 @Override notifySessionInfoChanged(@ullable RoutingSessionInfo sessionInfo)1995 public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) { 1996 mHandler.sendMessage( 1997 obtainMessage( 1998 MediaRouter2::updateControllerOnHandler, 1999 MediaRouter2.this, 2000 sessionInfo)); 2001 } 2002 2003 @Override notifySessionReleased(RoutingSessionInfo sessionInfo)2004 public void notifySessionReleased(RoutingSessionInfo sessionInfo) { 2005 mHandler.sendMessage( 2006 obtainMessage( 2007 MediaRouter2::releaseControllerOnHandler, 2008 MediaRouter2.this, 2009 sessionInfo)); 2010 } 2011 2012 @Override requestCreateSessionByManager( long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route)2013 public void requestCreateSessionByManager( 2014 long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) { 2015 mHandler.sendMessage( 2016 obtainMessage( 2017 MediaRouter2::onRequestCreateControllerByManagerOnHandler, 2018 MediaRouter2.this, 2019 oldSession, 2020 route, 2021 managerRequestId)); 2022 } 2023 } 2024 2025 // Note: All methods are run on main thread. 2026 class ManagerCallback implements MediaRouter2Manager.Callback { 2027 2028 @Override onRoutesAdded(@onNull List<MediaRoute2Info> routes)2029 public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) { 2030 updateAllRoutesFromManager(); 2031 } 2032 2033 @Override onRoutesRemoved(@onNull List<MediaRoute2Info> routes)2034 public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) { 2035 updateAllRoutesFromManager(); 2036 } 2037 2038 @Override onRoutesChanged(@onNull List<MediaRoute2Info> routes)2039 public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) { 2040 updateAllRoutesFromManager(); 2041 } 2042 2043 @Override onTransferred( @onNull RoutingSessionInfo oldSession, @NonNull RoutingSessionInfo newSession)2044 public void onTransferred( 2045 @NonNull RoutingSessionInfo oldSession, @NonNull RoutingSessionInfo newSession) { 2046 if (!oldSession.isSystemSession() 2047 && !TextUtils.equals(mClientPackageName, oldSession.getClientPackageName())) { 2048 return; 2049 } 2050 2051 if (!newSession.isSystemSession() 2052 && !TextUtils.equals(mClientPackageName, newSession.getClientPackageName())) { 2053 return; 2054 } 2055 2056 // For successful in-session transfer, onControllerUpdated() handles it. 2057 if (TextUtils.equals(oldSession.getId(), newSession.getId())) { 2058 return; 2059 } 2060 2061 RoutingController oldController; 2062 if (oldSession.isSystemSession()) { 2063 mSystemController.setRoutingSessionInfo( 2064 ensureClientPackageNameForSystemSession(oldSession)); 2065 oldController = mSystemController; 2066 } else { 2067 oldController = new RoutingController(oldSession); 2068 } 2069 2070 RoutingController newController; 2071 if (newSession.isSystemSession()) { 2072 mSystemController.setRoutingSessionInfo( 2073 ensureClientPackageNameForSystemSession(newSession)); 2074 newController = mSystemController; 2075 } else { 2076 newController = new RoutingController(newSession); 2077 } 2078 2079 notifyTransfer(oldController, newController); 2080 } 2081 2082 @Override onTransferFailed( @onNull RoutingSessionInfo session, @NonNull MediaRoute2Info route)2083 public void onTransferFailed( 2084 @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) { 2085 if (!session.isSystemSession() 2086 && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) { 2087 return; 2088 } 2089 notifyTransferFailure(route); 2090 } 2091 2092 @Override onSessionUpdated(@onNull RoutingSessionInfo session)2093 public void onSessionUpdated(@NonNull RoutingSessionInfo session) { 2094 if (!session.isSystemSession() 2095 && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) { 2096 return; 2097 } 2098 2099 RoutingController controller; 2100 if (session.isSystemSession()) { 2101 mSystemController.setRoutingSessionInfo( 2102 ensureClientPackageNameForSystemSession(session)); 2103 controller = mSystemController; 2104 } else { 2105 controller = new RoutingController(session); 2106 } 2107 notifyControllerUpdated(controller); 2108 } 2109 2110 @Override onSessionReleased(@onNull RoutingSessionInfo session)2111 public void onSessionReleased(@NonNull RoutingSessionInfo session) { 2112 if (session.isSystemSession()) { 2113 Log.e(TAG, "onSessionReleased: Called on system session. Ignoring."); 2114 return; 2115 } 2116 2117 if (!TextUtils.equals(mClientPackageName, session.getClientPackageName())) { 2118 return; 2119 } 2120 2121 notifyStop(new RoutingController(session, RoutingController.CONTROLLER_STATE_RELEASED)); 2122 } 2123 2124 @Override onDiscoveryPreferenceChanged( @onNull String packageName, @NonNull RouteDiscoveryPreference preference)2125 public void onDiscoveryPreferenceChanged( 2126 @NonNull String packageName, @NonNull RouteDiscoveryPreference preference) { 2127 if (!TextUtils.equals(mClientPackageName, packageName)) { 2128 return; 2129 } 2130 2131 synchronized (mLock) { 2132 mDiscoveryPreference = preference; 2133 } 2134 updateAllRoutesFromManager(); 2135 notifyPreferredFeaturesChanged(preference.getPreferredFeatures()); 2136 } 2137 2138 @Override onRequestFailed(int reason)2139 public void onRequestFailed(int reason) { 2140 // Does nothing. 2141 } 2142 } 2143 } 2144