• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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