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