1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.support.v7.media;
18 
19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.app.ActivityManager;
22 import android.content.ComponentName;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.IntentSender;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.res.Resources;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.support.annotation.IntDef;
36 import android.support.annotation.NonNull;
37 import android.support.annotation.Nullable;
38 import android.support.annotation.RestrictTo;
39 import android.support.v4.app.ActivityManagerCompat;
40 import android.support.v4.hardware.display.DisplayManagerCompat;
41 import android.support.v4.media.VolumeProviderCompat;
42 import android.support.v4.media.session.MediaSessionCompat;
43 import android.support.v4.util.Pair;
44 import android.support.v7.media.MediaRouteProvider.ProviderMetadata;
45 import android.support.v7.media.MediaRouteProvider.RouteController;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.view.Display;
49 
50 import java.lang.annotation.Retention;
51 import java.lang.annotation.RetentionPolicy;
52 import java.lang.ref.WeakReference;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.HashMap;
56 import java.util.HashSet;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.Locale;
60 import java.util.Map;
61 import java.util.Set;
62 
63 /**
64  * MediaRouter allows applications to control the routing of media channels
65  * and streams from the current device to external speakers and destination devices.
66  * <p>
67  * A MediaRouter instance is retrieved through {@link #getInstance}.  Applications
68  * can query the media router about the currently selected route and its capabilities
69  * to determine how to send content to the route's destination.  Applications can
70  * also {@link RouteInfo#sendControlRequest send control requests} to the route
71  * to ask the route's destination to perform certain remote control functions
72  * such as playing media.
73  * </p><p>
74  * See also {@link MediaRouteProvider} for information on how an application
75  * can publish new media routes to the media router.
76  * </p><p>
77  * The media router API is not thread-safe; all interactions with it must be
78  * done from the main thread of the process.
79  * </p>
80  */
81 public final class MediaRouter {
82     static final String TAG = "MediaRouter";
83     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
84 
85     /**
86      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
87      * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the reason the route
88      * was unselected is unknown.
89      */
90     public static final int UNSELECT_REASON_UNKNOWN = 0;
91     /**
92      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
93      * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
94      * the disconnect button to disconnect and keep playing.
95      * <p>
96      *
97      * @see MediaRouteDescriptor#canDisconnectAndKeepPlaying()
98      */
99     public static final int UNSELECT_REASON_DISCONNECTED = 1;
100     /**
101      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
102      * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
103      * the stop casting button.
104      */
105     public static final int UNSELECT_REASON_STOPPED = 2;
106     /**
107      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
108      * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user selected
109      * a different route.
110      */
111     public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
112 
113     // Maintains global media router state for the process.
114     // This field is initialized in MediaRouter.getInstance() before any
115     // MediaRouter objects are instantiated so it is guaranteed to be
116     // valid whenever any instance method is invoked.
117     static GlobalMediaRouter sGlobal;
118 
119     // Context-bound state of the media router.
120     final Context mContext;
121     final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
122 
123     @IntDef(flag = true,
124             value = {
125                     CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
126                     CALLBACK_FLAG_REQUEST_DISCOVERY,
127                     CALLBACK_FLAG_UNFILTERED_EVENTS
128             }
129     )
130     @Retention(RetentionPolicy.SOURCE)
131     private @interface CallbackFlags {}
132 
133     /**
134      * Flag for {@link #addCallback}: Actively scan for routes while this callback
135      * is registered.
136      * <p>
137      * When this flag is specified, the media router will actively scan for new
138      * routes.  Certain routes, such as wifi display routes, may not be discoverable
139      * except when actively scanning.  This flag is typically used when the route picker
140      * dialog has been opened by the user to ensure that the route information is
141      * up to date.
142      * </p><p>
143      * Active scanning may consume a significant amount of power and may have intrusive
144      * effects on wireless connectivity.  Therefore it is important that active scanning
145      * only be requested when it is actually needed to satisfy a user request to
146      * discover and select a new route.
147      * </p><p>
148      * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing
149      * active scans is much more expensive than a normal discovery request.
150      * </p>
151      *
152      * @see #CALLBACK_FLAG_REQUEST_DISCOVERY
153      */
154     public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
155 
156     /**
157      * Flag for {@link #addCallback}: Do not filter route events.
158      * <p>
159      * When this flag is specified, the callback will be invoked for events that affect any
160      * route even if they do not match the callback's filter.
161      * </p>
162      */
163     public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
164 
165     /**
166      * Flag for {@link #addCallback}: Request passive route discovery while this
167      * callback is registered, except on {@link ActivityManager#isLowRamDevice low-RAM devices}.
168      * <p>
169      * When this flag is specified, the media router will try to discover routes.
170      * Although route discovery is intended to be efficient, checking for new routes may
171      * result in some network activity and could slowly drain the battery.  Therefore
172      * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when
173      * they are running in the foreground and would like to provide the user with the
174      * option of connecting to new routes.
175      * </p><p>
176      * Applications should typically add a callback using this flag in the
177      * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart}
178      * method and remove it in the {@link android.app.Activity#onStop onStop} method.
179      * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may
180      * also be used for this purpose.
181      * </p><p class="note">
182      * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag
183      * will be ignored.  Refer to
184      * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
185      * </p>
186      *
187      * @see android.support.v7.app.MediaRouteDiscoveryFragment
188      */
189     public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
190 
191     /**
192      * Flag for {@link #addCallback}: Request passive route discovery while this
193      * callback is registered, even on {@link ActivityManager#isLowRamDevice low-RAM devices}.
194      * <p class="note">
195      * This flag has a significant performance impact on low-RAM devices
196      * since it may cause many media route providers to be started simultaneously.
197      * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
198      * performing passive discovery on these devices altogether.  Refer to
199      * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
200      * </p>
201      *
202      * @see android.support.v7.app.MediaRouteDiscoveryFragment
203      */
204     public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 1 << 3;
205 
206     /**
207      * Flag for {@link #isRouteAvailable}: Ignore the default route.
208      * <p>
209      * This flag is used to determine whether a matching non-default route is available.
210      * This constraint may be used to decide whether to offer the route chooser dialog
211      * to the user.  There is no point offering the chooser if there are no
212      * non-default choices.
213      * </p>
214      */
215     public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
216 
217     /**
218      * Flag for {@link #isRouteAvailable}: Require an actual route to be matched.
219      * <p>
220      * If this flag is not set, then {@link #isRouteAvailable} will return true
221      * if it is possible to discover a matching route even if discovery is not in
222      * progress or if no matching route has yet been found.  This feature is used to
223      * save resources by removing the need to perform passive route discovery on
224      * {@link ActivityManager#isLowRamDevice low-RAM devices}.
225      * </p><p>
226      * If this flag is set, then {@link #isRouteAvailable} will only return true if
227      * a matching route has actually been discovered.
228      * </p>
229      */
230     public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1;
231 
MediaRouter(Context context)232     MediaRouter(Context context) {
233         mContext = context;
234     }
235 
236     /**
237      * Gets an instance of the media router service associated with the context.
238      * <p>
239      * The application is responsible for holding a strong reference to the returned
240      * {@link MediaRouter} instance, such as by storing the instance in a field of
241      * the {@link android.app.Activity}, to ensure that the media router remains alive
242      * as long as the application is using its features.
243      * </p><p>
244      * In other words, the support library only holds a {@link WeakReference weak reference}
245      * to each media router instance.  When there are no remaining strong references to the
246      * media router instance, all of its callbacks will be removed and route discovery
247      * will no longer be performed on its behalf.
248      * </p>
249      *
250      * @return The media router instance for the context.  The application must hold
251      * a strong reference to this object as long as it is in use.
252      */
getInstance(@onNull Context context)253     public static MediaRouter getInstance(@NonNull Context context) {
254         if (context == null) {
255             throw new IllegalArgumentException("context must not be null");
256         }
257         checkCallingThread();
258 
259         if (sGlobal == null) {
260             sGlobal = new GlobalMediaRouter(context.getApplicationContext());
261             sGlobal.start();
262         }
263         return sGlobal.getRouter(context);
264     }
265 
266     /**
267      * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
268      * this media router.
269      */
getRoutes()270     public List<RouteInfo> getRoutes() {
271         checkCallingThread();
272         return sGlobal.getRoutes();
273     }
274 
275     /**
276      * Gets information about the {@link MediaRouter.ProviderInfo route providers}
277      * currently known to this media router.
278      */
getProviders()279     public List<ProviderInfo> getProviders() {
280         checkCallingThread();
281         return sGlobal.getProviders();
282     }
283 
284     /**
285      * Gets the default route for playing media content on the system.
286      * <p>
287      * The system always provides a default route.
288      * </p>
289      *
290      * @return The default route, which is guaranteed to never be null.
291      */
292     @NonNull
getDefaultRoute()293     public RouteInfo getDefaultRoute() {
294         checkCallingThread();
295         return sGlobal.getDefaultRoute();
296     }
297 
298     /**
299      * Gets a bluetooth route for playing media content on the system.
300      *
301      * @return A bluetooth route, if exist, otherwise null.
302      */
getBluetoothRoute()303     public RouteInfo getBluetoothRoute() {
304         checkCallingThread();
305         return sGlobal.getBluetoothRoute();
306     }
307 
308     /**
309      * Gets the currently selected route.
310      * <p>
311      * The application should examine the route's
312      * {@link RouteInfo#getControlFilters media control intent filters} to assess the
313      * capabilities of the route before attempting to use it.
314      * </p>
315      *
316      * <h3>Example</h3>
317      * <pre>
318      * public boolean playMovie() {
319      *     MediaRouter mediaRouter = MediaRouter.getInstance(context);
320      *     MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
321      *
322      *     // First try using the remote playback interface, if supported.
323      *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
324      *         // The route supports remote playback.
325      *         // Try to send it the Uri of the movie to play.
326      *         Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
327      *         intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
328      *         intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
329      *         if (route.supportsControlRequest(intent)) {
330      *             route.sendControlRequest(intent, null);
331      *             return true; // sent the request to play the movie
332      *         }
333      *     }
334      *
335      *     // If remote playback was not possible, then play locally.
336      *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
337      *         // The route supports live video streaming.
338      *         // Prepare to play content locally in a window or in a presentation.
339      *         return playMovieInWindow();
340      *     }
341      *
342      *     // Neither interface is supported, so we can't play the movie to this route.
343      *     return false;
344      * }
345      * </pre>
346      *
347      * @return The selected route, which is guaranteed to never be null.
348      *
349      * @see RouteInfo#getControlFilters
350      * @see RouteInfo#supportsControlCategory
351      * @see RouteInfo#supportsControlRequest
352      */
353     @NonNull
getSelectedRoute()354     public RouteInfo getSelectedRoute() {
355         checkCallingThread();
356         return sGlobal.getSelectedRoute();
357     }
358 
359     /**
360      * Returns the selected route if it matches the specified selector, otherwise
361      * selects the default route and returns it. If there is one live audio route
362      * (usually Bluetooth A2DP), it will be selected instead of default route.
363      *
364      * @param selector The selector to match.
365      * @return The previously selected route if it matched the selector, otherwise the
366      * newly selected default route which is guaranteed to never be null.
367      *
368      * @see MediaRouteSelector
369      * @see RouteInfo#matchesSelector
370      */
371     @NonNull
updateSelectedRoute(@onNull MediaRouteSelector selector)372     public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
373         if (selector == null) {
374             throw new IllegalArgumentException("selector must not be null");
375         }
376         checkCallingThread();
377 
378         if (DEBUG) {
379             Log.d(TAG, "updateSelectedRoute: " + selector);
380         }
381         RouteInfo route = sGlobal.getSelectedRoute();
382         if (!route.isDefaultOrBluetooth() && !route.matchesSelector(selector)) {
383             route = sGlobal.chooseFallbackRoute();
384             sGlobal.selectRoute(route);
385         }
386         return route;
387     }
388 
389     /**
390      * Selects the specified route.
391      *
392      * @param route The route to select.
393      */
selectRoute(@onNull RouteInfo route)394     public void selectRoute(@NonNull RouteInfo route) {
395         if (route == null) {
396             throw new IllegalArgumentException("route must not be null");
397         }
398         checkCallingThread();
399 
400         if (DEBUG) {
401             Log.d(TAG, "selectRoute: " + route);
402         }
403         sGlobal.selectRoute(route);
404     }
405 
406     /**
407      * Unselects the current round and selects the default route instead.
408      * <p>
409      * The reason given must be one of:
410      * <ul>
411      * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
412      * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
413      * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
414      * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
415      * </ul>
416      *
417      * @param reason The reason for disconnecting the current route.
418      */
unselect(int reason)419     public void unselect(int reason) {
420         if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN ||
421                 reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
422             throw new IllegalArgumentException("Unsupported reason to unselect route");
423         }
424         checkCallingThread();
425 
426         // Choose the fallback route if it's not already selected.
427         // Otherwise, select the default route.
428         RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
429         if (sGlobal.getSelectedRoute() != fallbackRoute) {
430             sGlobal.selectRoute(fallbackRoute, reason);
431         } else {
432             sGlobal.selectRoute(sGlobal.getDefaultRoute(), reason);
433         }
434     }
435 
436     /**
437      * Returns true if there is a route that matches the specified selector.
438      * <p>
439      * This method returns true if there are any available routes that match the
440      * selector regardless of whether they are enabled or disabled. If the
441      * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
442      * the method will only consider non-default routes.
443      * </p>
444      * <p class="note">
445      * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
446      * will return true if it is possible to discover a matching route even if
447      * discovery is not in progress or if no matching route has yet been found.
448      * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
449      * </p>
450      *
451      * @param selector The selector to match.
452      * @param flags Flags to control the determination of whether a route may be
453      *            available. May be zero or some combination of
454      *            {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
455      *            {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
456      * @return True if a matching route may be available.
457      */
isRouteAvailable(@onNull MediaRouteSelector selector, int flags)458     public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
459         if (selector == null) {
460             throw new IllegalArgumentException("selector must not be null");
461         }
462         checkCallingThread();
463 
464         return sGlobal.isRouteAvailable(selector, flags);
465     }
466 
467     /**
468      * Registers a callback to discover routes that match the selector and to receive
469      * events when they change.
470      * <p>
471      * This is a convenience method that has the same effect as calling
472      * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
473      * </p>
474      *
475      * @param selector A route selector that indicates the kinds of routes that the
476      * callback would like to discover.
477      * @param callback The callback to add.
478      * @see #removeCallback
479      */
addCallback(MediaRouteSelector selector, Callback callback)480     public void addCallback(MediaRouteSelector selector, Callback callback) {
481         addCallback(selector, callback, 0);
482     }
483 
484     /**
485      * Registers a callback to discover routes that match the selector and to receive
486      * events when they change.
487      * <p>
488      * The selector describes the kinds of routes that the application wants to
489      * discover.  For example, if the application wants to use
490      * live audio routes then it should include the
491      * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
492      * in its selector when it adds a callback to the media router.
493      * The selector may include any number of categories.
494      * </p><p>
495      * If the callback has already been registered, then the selector is added to
496      * the set of selectors being monitored by the callback.
497      * </p><p>
498      * By default, the callback will only be invoked for events that affect routes
499      * that match the specified selector.  Event filtering may be disabled by specifying
500      * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
501      * </p><p>
502      * Applications should use the {@link #isRouteAvailable} method to determine
503      * whether is it possible to discover a route with the desired capabilities
504      * and therefore whether the media route button should be shown to the user.
505      * </p><p>
506      * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application
507      * is in the foreground to request that passive discovery be performed if there are
508      * sufficient resources to allow continuous passive discovery.
509      * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be
510      * ignored to conserve resources.
511      * </p><p>
512      * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when
513      * passive discovery absolutely must be performed, even on low-RAM devices.
514      * This flag has a significant performance impact on low-RAM devices
515      * since it may cause many media route providers to be started simultaneously.
516      * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
517      * performing passive discovery on these devices altogether.
518      * </p><p>
519      * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the
520      * media route chooser dialog is showing to confirm the presence of available
521      * routes that the user may connect to.  This flag may use substantially more
522      * power.
523      * </p>
524      *
525      * <h3>Example</h3>
526      * <pre>
527      * public class MyActivity extends Activity {
528      *     private MediaRouter mRouter;
529      *     private MediaRouter.Callback mCallback;
530      *     private MediaRouteSelector mSelector;
531      *
532      *     protected void onCreate(Bundle savedInstanceState) {
533      *         super.onCreate(savedInstanceState);
534      *
535      *         mRouter = Mediarouter.getInstance(this);
536      *         mCallback = new MyCallback();
537      *         mSelector = new MediaRouteSelector.Builder()
538      *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
539      *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
540      *                 .build();
541      *     }
542      *
543      *     // Add the callback on start to tell the media router what kinds of routes
544      *     // the application is interested in so that it can try to discover suitable ones.
545      *     public void onStart() {
546      *         super.onStart();
547      *
548      *         mediaRouter.addCallback(mSelector, mCallback,
549      *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
550      *
551      *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
552      *         // do something with the route...
553      *     }
554      *
555      *     // Remove the selector on stop to tell the media router that it no longer
556      *     // needs to invest effort trying to discover routes of these kinds for now.
557      *     public void onStop() {
558      *         super.onStop();
559      *
560      *         mediaRouter.removeCallback(mCallback);
561      *     }
562      *
563      *     private final class MyCallback extends MediaRouter.Callback {
564      *         // Implement callback methods as needed.
565      *     }
566      * }
567      * </pre>
568      *
569      * @param selector A route selector that indicates the kinds of routes that the
570      * callback would like to discover.
571      * @param callback The callback to add.
572      * @param flags Flags to control the behavior of the callback.
573      * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
574      * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
575      * @see #removeCallback
576      */
addCallback(@onNull MediaRouteSelector selector, @NonNull Callback callback, @CallbackFlags int flags)577     public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
578             @CallbackFlags int flags) {
579         if (selector == null) {
580             throw new IllegalArgumentException("selector must not be null");
581         }
582         if (callback == null) {
583             throw new IllegalArgumentException("callback must not be null");
584         }
585         checkCallingThread();
586 
587         if (DEBUG) {
588             Log.d(TAG, "addCallback: selector=" + selector
589                     + ", callback=" + callback + ", flags=" + Integer.toHexString(flags));
590         }
591 
592         CallbackRecord record;
593         int index = findCallbackRecord(callback);
594         if (index < 0) {
595             record = new CallbackRecord(this, callback);
596             mCallbackRecords.add(record);
597         } else {
598             record = mCallbackRecords.get(index);
599         }
600         boolean updateNeeded = false;
601         if ((flags & ~record.mFlags) != 0) {
602             record.mFlags |= flags;
603             updateNeeded = true;
604         }
605         if (!record.mSelector.contains(selector)) {
606             record.mSelector = new MediaRouteSelector.Builder(record.mSelector)
607                     .addSelector(selector)
608                     .build();
609             updateNeeded = true;
610         }
611         if (updateNeeded) {
612             sGlobal.updateDiscoveryRequest();
613         }
614     }
615 
616     /**
617      * Removes the specified callback.  It will no longer receive events about
618      * changes to media routes.
619      *
620      * @param callback The callback to remove.
621      * @see #addCallback
622      */
removeCallback(@onNull Callback callback)623     public void removeCallback(@NonNull Callback callback) {
624         if (callback == null) {
625             throw new IllegalArgumentException("callback must not be null");
626         }
627         checkCallingThread();
628 
629         if (DEBUG) {
630             Log.d(TAG, "removeCallback: callback=" + callback);
631         }
632 
633         int index = findCallbackRecord(callback);
634         if (index >= 0) {
635             mCallbackRecords.remove(index);
636             sGlobal.updateDiscoveryRequest();
637         }
638     }
639 
findCallbackRecord(Callback callback)640     private int findCallbackRecord(Callback callback) {
641         final int count = mCallbackRecords.size();
642         for (int i = 0; i < count; i++) {
643             if (mCallbackRecords.get(i).mCallback == callback) {
644                 return i;
645             }
646         }
647         return -1;
648     }
649 
650     /**
651      * Registers a media route provider within this application process.
652      * <p>
653      * The provider will be added to the list of providers that all {@link MediaRouter}
654      * instances within this process can use to discover routes.
655      * </p>
656      *
657      * @param providerInstance The media route provider instance to add.
658      *
659      * @see MediaRouteProvider
660      * @see #removeCallback
661      */
addProvider(@onNull MediaRouteProvider providerInstance)662     public void addProvider(@NonNull MediaRouteProvider providerInstance) {
663         if (providerInstance == null) {
664             throw new IllegalArgumentException("providerInstance must not be null");
665         }
666         checkCallingThread();
667 
668         if (DEBUG) {
669             Log.d(TAG, "addProvider: " + providerInstance);
670         }
671         sGlobal.addProvider(providerInstance);
672     }
673 
674     /**
675      * Unregisters a media route provider within this application process.
676      * <p>
677      * The provider will be removed from the list of providers that all {@link MediaRouter}
678      * instances within this process can use to discover routes.
679      * </p>
680      *
681      * @param providerInstance The media route provider instance to remove.
682      *
683      * @see MediaRouteProvider
684      * @see #addCallback
685      */
removeProvider(@onNull MediaRouteProvider providerInstance)686     public void removeProvider(@NonNull MediaRouteProvider providerInstance) {
687         if (providerInstance == null) {
688             throw new IllegalArgumentException("providerInstance must not be null");
689         }
690         checkCallingThread();
691 
692         if (DEBUG) {
693             Log.d(TAG, "removeProvider: " + providerInstance);
694         }
695         sGlobal.removeProvider(providerInstance);
696     }
697 
698     /**
699      * Adds a remote control client to enable remote control of the volume
700      * of the selected route.
701      * <p>
702      * The remote control client must have previously been registered with
703      * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient
704      * AudioManager.registerRemoteControlClient} method.
705      * </p>
706      *
707      * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
708      */
addRemoteControlClient(@onNull Object remoteControlClient)709     public void addRemoteControlClient(@NonNull Object remoteControlClient) {
710         if (remoteControlClient == null) {
711             throw new IllegalArgumentException("remoteControlClient must not be null");
712         }
713         checkCallingThread();
714 
715         if (DEBUG) {
716             Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
717         }
718         sGlobal.addRemoteControlClient(remoteControlClient);
719     }
720 
721     /**
722      * Removes a remote control client.
723      *
724      * @param remoteControlClient The {@link android.media.RemoteControlClient}
725      *            to unregister.
726      */
removeRemoteControlClient(@onNull Object remoteControlClient)727     public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
728         if (remoteControlClient == null) {
729             throw new IllegalArgumentException("remoteControlClient must not be null");
730         }
731 
732         if (DEBUG) {
733             Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
734         }
735         sGlobal.removeRemoteControlClient(remoteControlClient);
736     }
737 
738     /**
739      * Sets the media session to enable remote control of the volume of the
740      * selected route. This should be used instead of
741      * {@link #addRemoteControlClient} when using media sessions. Set the
742      * session to null to clear it.
743      *
744      * @param mediaSession The {@link android.media.session.MediaSession} to
745      *            use.
746      */
setMediaSession(Object mediaSession)747     public void setMediaSession(Object mediaSession) {
748         if (DEBUG) {
749             Log.d(TAG, "addMediaSession: " + mediaSession);
750         }
751         sGlobal.setMediaSession(mediaSession);
752     }
753 
754     /**
755      * Sets a compat media session to enable remote control of the volume of the
756      * selected route. This should be used instead of
757      * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}.
758      * Set the session to null to clear it.
759      *
760      * @param mediaSession
761      */
setMediaSessionCompat(MediaSessionCompat mediaSession)762     public void setMediaSessionCompat(MediaSessionCompat mediaSession) {
763         if (DEBUG) {
764             Log.d(TAG, "addMediaSessionCompat: " + mediaSession);
765         }
766         sGlobal.setMediaSessionCompat(mediaSession);
767     }
768 
getMediaSessionToken()769     public MediaSessionCompat.Token getMediaSessionToken() {
770         return sGlobal.getMediaSessionToken();
771     }
772 
773     /**
774      * Ensures that calls into the media router are on the correct thread.
775      * It pays to be a little paranoid when global state invariants are at risk.
776      */
checkCallingThread()777     static void checkCallingThread() {
778         if (Looper.myLooper() != Looper.getMainLooper()) {
779             throw new IllegalStateException("The media router service must only be "
780                     + "accessed on the application's main thread.");
781         }
782     }
783 
equal(T a, T b)784     static <T> boolean equal(T a, T b) {
785         return a == b || (a != null && b != null && a.equals(b));
786     }
787 
788     /**
789      * Provides information about a media route.
790      * <p>
791      * Each media route has a list of {@link MediaControlIntent media control}
792      * {@link #getControlFilters intent filters} that describe the capabilities of the
793      * route and the manner in which it is used and controlled.
794      * </p>
795      */
796     public static class RouteInfo {
797         private final ProviderInfo mProvider;
798         private final String mDescriptorId;
799         private final String mUniqueId;
800         private String mName;
801         private String mDescription;
802         private Uri mIconUri;
803         private boolean mEnabled;
804         private boolean mConnecting;
805         private int mConnectionState;
806         private boolean mCanDisconnect;
807         private final ArrayList<IntentFilter> mControlFilters = new ArrayList<>();
808         private int mPlaybackType;
809         private int mPlaybackStream;
810         private int mDeviceType;
811         private int mVolumeHandling;
812         private int mVolume;
813         private int mVolumeMax;
814         private Display mPresentationDisplay;
815         private int mPresentationDisplayId = PRESENTATION_DISPLAY_ID_NONE;
816         private Bundle mExtras;
817         private IntentSender mSettingsIntent;
818         MediaRouteDescriptor mDescriptor;
819 
820         @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
821                 CONNECTION_STATE_CONNECTED})
822         @Retention(RetentionPolicy.SOURCE)
823         private @interface ConnectionState {}
824 
825         /**
826          * The default connection state indicating the route is disconnected.
827          *
828          * @see #getConnectionState
829          */
830         public static final int CONNECTION_STATE_DISCONNECTED = 0;
831 
832         /**
833          * A connection state indicating the route is in the process of connecting and is not yet
834          * ready for use.
835          *
836          * @see #getConnectionState
837          */
838         public static final int CONNECTION_STATE_CONNECTING = 1;
839 
840         /**
841          * A connection state indicating the route is connected.
842          *
843          * @see #getConnectionState
844          */
845         public static final int CONNECTION_STATE_CONNECTED = 2;
846 
847         @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
848         @Retention(RetentionPolicy.SOURCE)
849         private @interface PlaybackType {}
850 
851         /**
852          * The default playback type, "local", indicating the presentation of the media
853          * is happening on the same device (e.g. a phone, a tablet) as where it is
854          * controlled from.
855          *
856          * @see #getPlaybackType
857          */
858         public static final int PLAYBACK_TYPE_LOCAL = 0;
859 
860         /**
861          * A playback type indicating the presentation of the media is happening on
862          * a different device (i.e. the remote device) than where it is controlled from.
863          *
864          * @see #getPlaybackType
865          */
866         public static final int PLAYBACK_TYPE_REMOTE = 1;
867 
868         @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
869         @Retention(RetentionPolicy.SOURCE)
870         private @interface DeviceType {}
871 
872         /**
873          * The default receiver device type of the route indicating the type is unknown.
874          *
875          * @see #getDeviceType
876          * @hide
877          */
878         @RestrictTo(LIBRARY_GROUP)
879         public static final int DEVICE_TYPE_UNKNOWN = 0;
880 
881         /**
882          * A receiver device type of the route indicating the presentation of the media is happening
883          * on a TV.
884          *
885          * @see #getDeviceType
886          */
887         public static final int DEVICE_TYPE_TV = 1;
888 
889         /**
890          * A receiver device type of the route indicating the presentation of the media is happening
891          * on a speaker.
892          *
893          * @see #getDeviceType
894          */
895         public static final int DEVICE_TYPE_SPEAKER = 2;
896 
897         /**
898          * A receiver device type of the route indicating the presentation of the media is happening
899          * on a bluetooth device such as a bluetooth speaker.
900          *
901          * @see #getDeviceType
902          * @hide
903          */
904         @RestrictTo(LIBRARY_GROUP)
905         public static final int DEVICE_TYPE_BLUETOOTH = 3;
906 
907         @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
908         @Retention(RetentionPolicy.SOURCE)
909         private @interface PlaybackVolume {}
910 
911         /**
912          * Playback information indicating the playback volume is fixed, i.e. it cannot be
913          * controlled from this object. An example of fixed playback volume is a remote player,
914          * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
915          * than attenuate at the source.
916          *
917          * @see #getVolumeHandling
918          */
919         public static final int PLAYBACK_VOLUME_FIXED = 0;
920 
921         /**
922          * Playback information indicating the playback volume is variable and can be controlled
923          * from this object.
924          *
925          * @see #getVolumeHandling
926          */
927         public static final int PLAYBACK_VOLUME_VARIABLE = 1;
928 
929         /**
930          * The default presentation display id indicating no presentation display is associated
931          * with the route.
932          * @hide
933          */
934         @RestrictTo(LIBRARY_GROUP)
935         public static final int PRESENTATION_DISPLAY_ID_NONE = -1;
936 
937         static final int CHANGE_GENERAL = 1 << 0;
938         static final int CHANGE_VOLUME = 1 << 1;
939         static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
940 
941         // Should match to SystemMediaRouteProvider.PACKAGE_NAME.
942         static final String SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME = "android";
943 
RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId)944         RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) {
945             mProvider = provider;
946             mDescriptorId = descriptorId;
947             mUniqueId = uniqueId;
948         }
949 
950         /**
951          * Gets information about the provider of this media route.
952          */
getProvider()953         public ProviderInfo getProvider() {
954             return mProvider;
955         }
956 
957         /**
958          * Gets the unique id of the route.
959          * <p>
960          * The route unique id functions as a stable identifier by which the route is known.
961          * For example, an application can use this id as a token to remember the
962          * selected route across restarts or to communicate its identity to a service.
963          * </p>
964          *
965          * @return The unique id of the route, never null.
966          */
967         @NonNull
getId()968         public String getId() {
969             return mUniqueId;
970         }
971 
972         /**
973          * Gets the user-visible name of the route.
974          * <p>
975          * The route name identifies the destination represented by the route.
976          * It may be a user-supplied name, an alias, or device serial number.
977          * </p>
978          *
979          * @return The user-visible name of a media route.  This is the string presented
980          * to users who may select this as the active route.
981          */
getName()982         public String getName() {
983             return mName;
984         }
985 
986         /**
987          * Gets the user-visible description of the route.
988          * <p>
989          * The route description describes the kind of destination represented by the route.
990          * It may be a user-supplied string, a model number or brand of device.
991          * </p>
992          *
993          * @return The description of the route, or null if none.
994          */
995         @Nullable
getDescription()996         public String getDescription() {
997             return mDescription;
998         }
999 
1000         /**
1001          * Gets the URI of the icon representing this route.
1002          * <p>
1003          * This icon will be used in picker UIs if available.
1004          * </p>
1005          *
1006          * @return The URI of the icon representing this route, or null if none.
1007          */
getIconUri()1008         public Uri getIconUri() {
1009             return mIconUri;
1010         }
1011 
1012         /**
1013          * Returns true if this route is enabled and may be selected.
1014          *
1015          * @return True if this route is enabled.
1016          */
isEnabled()1017         public boolean isEnabled() {
1018             return mEnabled;
1019         }
1020 
1021         /**
1022          * Returns true if the route is in the process of connecting and is not
1023          * yet ready for use.
1024          *
1025          * @return True if this route is in the process of connecting.
1026          */
isConnecting()1027         public boolean isConnecting() {
1028             return mConnecting;
1029         }
1030 
1031         /**
1032          * Gets the connection state of the route.
1033          *
1034          * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
1035          * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
1036          */
1037         @ConnectionState
getConnectionState()1038         public int getConnectionState() {
1039             return mConnectionState;
1040         }
1041 
1042         /**
1043          * Returns true if this route is currently selected.
1044          *
1045          * @return True if this route is currently selected.
1046          *
1047          * @see MediaRouter#getSelectedRoute
1048          */
isSelected()1049         public boolean isSelected() {
1050             checkCallingThread();
1051             return sGlobal.getSelectedRoute() == this;
1052         }
1053 
1054         /**
1055          * Returns true if this route is the default route.
1056          *
1057          * @return True if this route is the default route.
1058          *
1059          * @see MediaRouter#getDefaultRoute
1060          */
isDefault()1061         public boolean isDefault() {
1062             checkCallingThread();
1063             return sGlobal.getDefaultRoute() == this;
1064         }
1065 
1066         /**
1067          * Returns true if this route is a bluetooth route.
1068          *
1069          * @return True if this route is a bluetooth route.
1070          *
1071          * @see MediaRouter#getBluetoothRoute
1072          */
isBluetooth()1073         public boolean isBluetooth() {
1074             checkCallingThread();
1075             return sGlobal.getBluetoothRoute() == this;
1076         }
1077 
1078         /**
1079          * Returns true if this route is the default route and the device speaker.
1080          *
1081          * @return True if this route is the default route and the device speaker.
1082          */
isDeviceSpeaker()1083         public boolean isDeviceSpeaker() {
1084             int defaultAudioRouteNameResourceId = Resources.getSystem().getIdentifier(
1085                     "default_audio_route_name", "string", "android");
1086             return isDefault()
1087                     && Resources.getSystem().getText(defaultAudioRouteNameResourceId).equals(mName);
1088         }
1089 
1090         /**
1091          * Gets a list of {@link MediaControlIntent media control intent} filters that
1092          * describe the capabilities of this route and the media control actions that
1093          * it supports.
1094          *
1095          * @return A list of intent filters that specifies the media control intents that
1096          * this route supports.
1097          *
1098          * @see MediaControlIntent
1099          * @see #supportsControlCategory
1100          * @see #supportsControlRequest
1101          */
getControlFilters()1102         public List<IntentFilter> getControlFilters() {
1103             return mControlFilters;
1104         }
1105 
1106         /**
1107          * Returns true if the route supports at least one of the capabilities
1108          * described by a media route selector.
1109          *
1110          * @param selector The selector that specifies the capabilities to check.
1111          * @return True if the route supports at least one of the capabilities
1112          * described in the media route selector.
1113          */
matchesSelector(@onNull MediaRouteSelector selector)1114         public boolean matchesSelector(@NonNull MediaRouteSelector selector) {
1115             if (selector == null) {
1116                 throw new IllegalArgumentException("selector must not be null");
1117             }
1118             checkCallingThread();
1119             return selector.matchesControlFilters(mControlFilters);
1120         }
1121 
1122         /**
1123          * Returns true if the route supports the specified
1124          * {@link MediaControlIntent media control} category.
1125          * <p>
1126          * Media control categories describe the capabilities of this route
1127          * such as whether it supports live audio streaming or remote playback.
1128          * </p>
1129          *
1130          * @param category A {@link MediaControlIntent media control} category
1131          * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
1132          * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
1133          * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
1134          * media control category.
1135          * @return True if the route supports the specified intent category.
1136          *
1137          * @see MediaControlIntent
1138          * @see #getControlFilters
1139          */
supportsControlCategory(@onNull String category)1140         public boolean supportsControlCategory(@NonNull String category) {
1141             if (category == null) {
1142                 throw new IllegalArgumentException("category must not be null");
1143             }
1144             checkCallingThread();
1145 
1146             int count = mControlFilters.size();
1147             for (int i = 0; i < count; i++) {
1148                 if (mControlFilters.get(i).hasCategory(category)) {
1149                     return true;
1150                 }
1151             }
1152             return false;
1153         }
1154 
1155         /**
1156          * Returns true if the route supports the specified
1157          * {@link MediaControlIntent media control} category and action.
1158          * <p>
1159          * Media control actions describe specific requests that an application
1160          * can ask a route to perform.
1161          * </p>
1162          *
1163          * @param category A {@link MediaControlIntent media control} category
1164          * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
1165          * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
1166          * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
1167          * media control category.
1168          * @param action A {@link MediaControlIntent media control} action
1169          * such as {@link MediaControlIntent#ACTION_PLAY}.
1170          * @return True if the route supports the specified intent action.
1171          *
1172          * @see MediaControlIntent
1173          * @see #getControlFilters
1174          */
supportsControlAction(@onNull String category, @NonNull String action)1175         public boolean supportsControlAction(@NonNull String category, @NonNull String action) {
1176             if (category == null) {
1177                 throw new IllegalArgumentException("category must not be null");
1178             }
1179             if (action == null) {
1180                 throw new IllegalArgumentException("action must not be null");
1181             }
1182             checkCallingThread();
1183 
1184             int count = mControlFilters.size();
1185             for (int i = 0; i < count; i++) {
1186                 IntentFilter filter = mControlFilters.get(i);
1187                 if (filter.hasCategory(category) && filter.hasAction(action)) {
1188                     return true;
1189                 }
1190             }
1191             return false;
1192         }
1193 
1194         /**
1195          * Returns true if the route supports the specified
1196          * {@link MediaControlIntent media control} request.
1197          * <p>
1198          * Media control requests are used to request the route to perform
1199          * actions such as starting remote playback of a media item.
1200          * </p>
1201          *
1202          * @param intent A {@link MediaControlIntent media control intent}.
1203          * @return True if the route can handle the specified intent.
1204          *
1205          * @see MediaControlIntent
1206          * @see #getControlFilters
1207          */
supportsControlRequest(@onNull Intent intent)1208         public boolean supportsControlRequest(@NonNull Intent intent) {
1209             if (intent == null) {
1210                 throw new IllegalArgumentException("intent must not be null");
1211             }
1212             checkCallingThread();
1213 
1214             ContentResolver contentResolver = sGlobal.getContentResolver();
1215             int count = mControlFilters.size();
1216             for (int i = 0; i < count; i++) {
1217                 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
1218                     return true;
1219                 }
1220             }
1221             return false;
1222         }
1223 
1224         /**
1225          * Sends a {@link MediaControlIntent media control} request to be performed
1226          * asynchronously by the route's destination.
1227          * <p>
1228          * Media control requests are used to request the route to perform
1229          * actions such as starting remote playback of a media item.
1230          * </p><p>
1231          * This function may only be called on a selected route.  Control requests
1232          * sent to unselected routes will fail.
1233          * </p>
1234          *
1235          * @param intent A {@link MediaControlIntent media control intent}.
1236          * @param callback A {@link ControlRequestCallback} to invoke with the result
1237          * of the request, or null if no result is required.
1238          *
1239          * @see MediaControlIntent
1240          */
sendControlRequest(@onNull Intent intent, @Nullable ControlRequestCallback callback)1241         public void sendControlRequest(@NonNull Intent intent,
1242                 @Nullable ControlRequestCallback callback) {
1243             if (intent == null) {
1244                 throw new IllegalArgumentException("intent must not be null");
1245             }
1246             checkCallingThread();
1247 
1248             sGlobal.sendControlRequest(this, intent, callback);
1249         }
1250 
1251         /**
1252          * Gets the type of playback associated with this route.
1253          *
1254          * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
1255          * or {@link #PLAYBACK_TYPE_REMOTE}.
1256          */
1257         @PlaybackType
getPlaybackType()1258         public int getPlaybackType() {
1259             return mPlaybackType;
1260         }
1261 
1262         /**
1263          * Gets the audio stream over which the playback associated with this route is performed.
1264          *
1265          * @return The stream over which the playback associated with this route is performed.
1266          */
getPlaybackStream()1267         public int getPlaybackStream() {
1268             return mPlaybackStream;
1269         }
1270 
1271         /**
1272          * Gets the type of the receiver device associated with this route.
1273          *
1274          * @return The type of the receiver device associated with this route:
1275          * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
1276          */
getDeviceType()1277         public int getDeviceType() {
1278             return mDeviceType;
1279         }
1280 
1281 
1282         /**
1283          * @hide
1284          */
1285         @RestrictTo(LIBRARY_GROUP)
isDefaultOrBluetooth()1286         public boolean isDefaultOrBluetooth() {
1287             if (isDefault() || mDeviceType == DEVICE_TYPE_BLUETOOTH) {
1288                 return true;
1289             }
1290             // This is a workaround for platform version 23 or below where the system route
1291             // provider doesn't specify device type for bluetooth media routes.
1292             return isSystemMediaRouteProvider(this)
1293                     && supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
1294                     && !supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
1295         }
1296 
isSystemMediaRouteProvider(MediaRouter.RouteInfo route)1297         private static boolean isSystemMediaRouteProvider(MediaRouter.RouteInfo route) {
1298             return TextUtils.equals(route.getProviderInstance().getMetadata().getPackageName(),
1299                     SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME);
1300         }
1301 
1302         /**
1303          * Gets information about how volume is handled on the route.
1304          *
1305          * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
1306          * or {@link #PLAYBACK_VOLUME_VARIABLE}.
1307          */
1308         @PlaybackVolume
getVolumeHandling()1309         public int getVolumeHandling() {
1310             return mVolumeHandling;
1311         }
1312 
1313         /**
1314          * Gets the current volume for this route. Depending on the route, this may only
1315          * be valid if the route is currently selected.
1316          *
1317          * @return The volume at which the playback associated with this route is performed.
1318          */
getVolume()1319         public int getVolume() {
1320             return mVolume;
1321         }
1322 
1323         /**
1324          * Gets the maximum volume at which the playback associated with this route is performed.
1325          *
1326          * @return The maximum volume at which the playback associated with
1327          * this route is performed.
1328          */
getVolumeMax()1329         public int getVolumeMax() {
1330             return mVolumeMax;
1331         }
1332 
1333         /**
1334          * Gets whether this route supports disconnecting without interrupting
1335          * playback.
1336          *
1337          * @return True if this route can disconnect without stopping playback,
1338          *         false otherwise.
1339          */
canDisconnect()1340         public boolean canDisconnect() {
1341             return mCanDisconnect;
1342         }
1343 
1344         /**
1345          * Requests a volume change for this route asynchronously.
1346          * <p>
1347          * This function may only be called on a selected route.  It will have
1348          * no effect if the route is currently unselected.
1349          * </p>
1350          *
1351          * @param volume The new volume value between 0 and {@link #getVolumeMax}.
1352          */
requestSetVolume(int volume)1353         public void requestSetVolume(int volume) {
1354             checkCallingThread();
1355             sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
1356         }
1357 
1358         /**
1359          * Requests an incremental volume update for this route asynchronously.
1360          * <p>
1361          * This function may only be called on a selected route.  It will have
1362          * no effect if the route is currently unselected.
1363          * </p>
1364          *
1365          * @param delta The delta to add to the current volume.
1366          */
requestUpdateVolume(int delta)1367         public void requestUpdateVolume(int delta) {
1368             checkCallingThread();
1369             if (delta != 0) {
1370                 sGlobal.requestUpdateVolume(this, delta);
1371             }
1372         }
1373 
1374         /**
1375          * Gets the {@link Display} that should be used by the application to show
1376          * a {@link android.app.Presentation} on an external display when this route is selected.
1377          * Depending on the route, this may only be valid if the route is currently
1378          * selected.
1379          * <p>
1380          * The preferred presentation display may change independently of the route
1381          * being selected or unselected.  For example, the presentation display
1382          * of the default system route may change when an external HDMI display is connected
1383          * or disconnected even though the route itself has not changed.
1384          * </p><p>
1385          * This method may return null if there is no external display associated with
1386          * the route or if the display is not ready to show UI yet.
1387          * </p><p>
1388          * The application should listen for changes to the presentation display
1389          * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
1390          * show or dismiss its {@link android.app.Presentation} accordingly when the display
1391          * becomes available or is removed.
1392          * </p><p>
1393          * This method only makes sense for
1394          * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
1395          * </p>
1396          *
1397          * @return The preferred presentation display to use when this route is
1398          * selected or null if none.
1399          *
1400          * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
1401          * @see android.app.Presentation
1402          */
1403         @Nullable
getPresentationDisplay()1404         public Display getPresentationDisplay() {
1405             checkCallingThread();
1406             if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
1407                 mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
1408             }
1409             return mPresentationDisplay;
1410         }
1411 
1412         /**
1413          * Gets the route's presentation display id, or -1 if none.
1414          * @hide
1415          */
1416         @RestrictTo(LIBRARY_GROUP)
getPresentationDisplayId()1417         public int getPresentationDisplayId() {
1418             return mPresentationDisplayId;
1419         }
1420 
1421         /**
1422          * Gets a collection of extra properties about this route that were supplied
1423          * by its media route provider, or null if none.
1424          */
1425         @Nullable
getExtras()1426         public Bundle getExtras() {
1427             return mExtras;
1428         }
1429 
1430         /**
1431          * Gets an intent sender for launching a settings activity for this
1432          * route.
1433          */
1434         @Nullable
getSettingsIntent()1435         public IntentSender getSettingsIntent() {
1436             return mSettingsIntent;
1437         }
1438 
1439         /**
1440          * Selects this media route.
1441          */
select()1442         public void select() {
1443             checkCallingThread();
1444             sGlobal.selectRoute(this);
1445         }
1446 
1447         @Override
toString()1448         public String toString() {
1449             return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
1450                     + ", name=" + mName
1451                     + ", description=" + mDescription
1452                     + ", iconUri=" + mIconUri
1453                     + ", enabled=" + mEnabled
1454                     + ", connecting=" + mConnecting
1455                     + ", connectionState=" + mConnectionState
1456                     + ", canDisconnect=" + mCanDisconnect
1457                     + ", playbackType=" + mPlaybackType
1458                     + ", playbackStream=" + mPlaybackStream
1459                     + ", deviceType=" + mDeviceType
1460                     + ", volumeHandling=" + mVolumeHandling
1461                     + ", volume=" + mVolume
1462                     + ", volumeMax=" + mVolumeMax
1463                     + ", presentationDisplayId=" + mPresentationDisplayId
1464                     + ", extras=" + mExtras
1465                     + ", settingsIntent=" + mSettingsIntent
1466                     + ", providerPackageName=" + mProvider.getPackageName()
1467                     + " }";
1468         }
1469 
maybeUpdateDescriptor(MediaRouteDescriptor descriptor)1470         int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
1471             int changes = 0;
1472             if (mDescriptor != descriptor) {
1473                 changes = updateDescriptor(descriptor);
1474             }
1475             return changes;
1476         }
1477 
updateDescriptor(MediaRouteDescriptor descriptor)1478         int updateDescriptor(MediaRouteDescriptor descriptor) {
1479             int changes = 0;
1480             mDescriptor = descriptor;
1481             if (descriptor != null) {
1482                 if (!equal(mName, descriptor.getName())) {
1483                     mName = descriptor.getName();
1484                     changes |= CHANGE_GENERAL;
1485                 }
1486                 if (!equal(mDescription, descriptor.getDescription())) {
1487                     mDescription = descriptor.getDescription();
1488                     changes |= CHANGE_GENERAL;
1489                 }
1490                 if (!equal(mIconUri, descriptor.getIconUri())) {
1491                     mIconUri = descriptor.getIconUri();
1492                     changes |= CHANGE_GENERAL;
1493                 }
1494                 if (mEnabled != descriptor.isEnabled()) {
1495                     mEnabled = descriptor.isEnabled();
1496                     changes |= CHANGE_GENERAL;
1497                 }
1498                 if (mConnecting != descriptor.isConnecting()) {
1499                     mConnecting = descriptor.isConnecting();
1500                     changes |= CHANGE_GENERAL;
1501                 }
1502                 if (mConnectionState != descriptor.getConnectionState()) {
1503                     mConnectionState = descriptor.getConnectionState();
1504                     changes |= CHANGE_GENERAL;
1505                 }
1506                 if (!mControlFilters.equals(descriptor.getControlFilters())) {
1507                     mControlFilters.clear();
1508                     mControlFilters.addAll(descriptor.getControlFilters());
1509                     changes |= CHANGE_GENERAL;
1510                 }
1511                 if (mPlaybackType != descriptor.getPlaybackType()) {
1512                     mPlaybackType = descriptor.getPlaybackType();
1513                     changes |= CHANGE_GENERAL;
1514                 }
1515                 if (mPlaybackStream != descriptor.getPlaybackStream()) {
1516                     mPlaybackStream = descriptor.getPlaybackStream();
1517                     changes |= CHANGE_GENERAL;
1518                 }
1519                 if (mDeviceType != descriptor.getDeviceType()) {
1520                     mDeviceType = descriptor.getDeviceType();
1521                     changes |= CHANGE_GENERAL;
1522                 }
1523                 if (mVolumeHandling != descriptor.getVolumeHandling()) {
1524                     mVolumeHandling = descriptor.getVolumeHandling();
1525                     changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1526                 }
1527                 if (mVolume != descriptor.getVolume()) {
1528                     mVolume = descriptor.getVolume();
1529                     changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1530                 }
1531                 if (mVolumeMax != descriptor.getVolumeMax()) {
1532                     mVolumeMax = descriptor.getVolumeMax();
1533                     changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1534                 }
1535                 if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
1536                     mPresentationDisplayId = descriptor.getPresentationDisplayId();
1537                     mPresentationDisplay = null;
1538                     changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
1539                 }
1540                 if (!equal(mExtras, descriptor.getExtras())) {
1541                     mExtras = descriptor.getExtras();
1542                     changes |= CHANGE_GENERAL;
1543                 }
1544                 if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
1545                     mSettingsIntent = descriptor.getSettingsActivity();
1546                     changes |= CHANGE_GENERAL;
1547                 }
1548                 if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
1549                     mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
1550                     changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
1551                 }
1552             }
1553             return changes;
1554         }
1555 
getDescriptorId()1556         String getDescriptorId() {
1557             return mDescriptorId;
1558         }
1559 
1560         /** @hide */
1561         @RestrictTo(LIBRARY_GROUP)
getProviderInstance()1562         public MediaRouteProvider getProviderInstance() {
1563             return mProvider.getProviderInstance();
1564         }
1565     }
1566 
1567     /**
1568      * Information about a route that consists of multiple other routes in a group.
1569      * @hide
1570      */
1571     @RestrictTo(LIBRARY_GROUP)
1572     public static class RouteGroup extends RouteInfo {
1573         private List<RouteInfo> mRoutes = new ArrayList<>();
1574 
RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId)1575         RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId) {
1576             super(provider, descriptorId, uniqueId);
1577         }
1578 
1579         /**
1580          * @return The number of routes in this group
1581          */
getRouteCount()1582         public int getRouteCount() {
1583             return mRoutes.size();
1584         }
1585 
1586         /**
1587          * Returns the route in this group at the specified index
1588          *
1589          * @param index Index to fetch
1590          * @return The route at index
1591          */
getRouteAt(int index)1592         public RouteInfo getRouteAt(int index) {
1593             return mRoutes.get(index);
1594         }
1595 
1596         /**
1597          * Returns the routes in this group
1598          *
1599          * @return The list of the routes in this group
1600          */
getRoutes()1601         public List<RouteInfo> getRoutes() {
1602             return mRoutes;
1603         }
1604 
1605         @Override
toString()1606         public String toString() {
1607             StringBuilder sb = new StringBuilder(super.toString());
1608             sb.append('[');
1609             final int count = mRoutes.size();
1610             for (int i = 0; i < count; i++) {
1611                 if (i > 0) sb.append(", ");
1612                 sb.append(mRoutes.get(i));
1613             }
1614             sb.append(']');
1615             return sb.toString();
1616         }
1617 
1618         @Override
maybeUpdateDescriptor(MediaRouteDescriptor descriptor)1619         int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
1620             boolean changed = false;
1621             if (mDescriptor != descriptor) {
1622                 mDescriptor = descriptor;
1623                 if (descriptor != null) {
1624                     List<String> groupMemberIds = descriptor.getGroupMemberIds();
1625                     List<RouteInfo> routes = new ArrayList<>();
1626                     changed = groupMemberIds.size() != mRoutes.size();
1627                     for (String groupMemberId : groupMemberIds) {
1628                         String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId);
1629                         RouteInfo groupMember = sGlobal.getRoute(uniqueId);
1630                         if (groupMember != null) {
1631                             routes.add(groupMember);
1632                             if (!changed && !mRoutes.contains(groupMember)) {
1633                                 changed = true;
1634                             }
1635                         }
1636                     }
1637                     if (changed) {
1638                         mRoutes = routes;
1639                     }
1640                 }
1641             }
1642             return (changed ? CHANGE_GENERAL : 0) | super.updateDescriptor(descriptor);
1643         }
1644     }
1645 
1646     /**
1647      * Provides information about a media route provider.
1648      * <p>
1649      * This object may be used to determine which media route provider has
1650      * published a particular route.
1651      * </p>
1652      */
1653     public static final class ProviderInfo {
1654         private final MediaRouteProvider mProviderInstance;
1655         private final List<RouteInfo> mRoutes = new ArrayList<>();
1656 
1657         private final ProviderMetadata mMetadata;
1658         private MediaRouteProviderDescriptor mDescriptor;
1659         private Resources mResources;
1660         private boolean mResourcesNotAvailable;
1661 
ProviderInfo(MediaRouteProvider provider)1662         ProviderInfo(MediaRouteProvider provider) {
1663             mProviderInstance = provider;
1664             mMetadata = provider.getMetadata();
1665         }
1666 
1667         /**
1668          * Gets the provider's underlying {@link MediaRouteProvider} instance.
1669          */
getProviderInstance()1670         public MediaRouteProvider getProviderInstance() {
1671             checkCallingThread();
1672             return mProviderInstance;
1673         }
1674 
1675         /**
1676          * Gets the package name of the media route provider.
1677          */
getPackageName()1678         public String getPackageName() {
1679             return mMetadata.getPackageName();
1680         }
1681 
1682         /**
1683          * Gets the component name of the media route provider.
1684          */
getComponentName()1685         public ComponentName getComponentName() {
1686             return mMetadata.getComponentName();
1687         }
1688 
1689         /**
1690          * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
1691          */
getRoutes()1692         public List<RouteInfo> getRoutes() {
1693             checkCallingThread();
1694             return mRoutes;
1695         }
1696 
getResources()1697         Resources getResources() {
1698             if (mResources == null && !mResourcesNotAvailable) {
1699                 String packageName = getPackageName();
1700                 Context context = sGlobal.getProviderContext(packageName);
1701                 if (context != null) {
1702                     mResources = context.getResources();
1703                 } else {
1704                     Log.w(TAG, "Unable to obtain resources for route provider package: "
1705                             + packageName);
1706                     mResourcesNotAvailable = true;
1707                 }
1708             }
1709             return mResources;
1710         }
1711 
updateDescriptor(MediaRouteProviderDescriptor descriptor)1712         boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
1713             if (mDescriptor != descriptor) {
1714                 mDescriptor = descriptor;
1715                 return true;
1716             }
1717             return false;
1718         }
1719 
findRouteByDescriptorId(String id)1720         int findRouteByDescriptorId(String id) {
1721             final int count = mRoutes.size();
1722             for (int i = 0; i < count; i++) {
1723                 if (mRoutes.get(i).mDescriptorId.equals(id)) {
1724                     return i;
1725                 }
1726             }
1727             return -1;
1728         }
1729 
1730         @Override
toString()1731         public String toString() {
1732             return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
1733                     + " }";
1734         }
1735     }
1736 
1737     /**
1738      * Interface for receiving events about media routing changes.
1739      * All methods of this interface will be called from the application's main thread.
1740      * <p>
1741      * A Callback will only receive events relevant to routes that the callback
1742      * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
1743      * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
1744      * </p>
1745      *
1746      * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
1747      * @see MediaRouter#removeCallback(Callback)
1748      */
1749     public static abstract class Callback {
1750         /**
1751          * Called when the supplied media route becomes selected as the active route.
1752          *
1753          * @param router The media router reporting the event.
1754          * @param route The route that has been selected.
1755          */
onRouteSelected(MediaRouter router, RouteInfo route)1756         public void onRouteSelected(MediaRouter router, RouteInfo route) {
1757         }
1758 
1759         /**
1760          * Called when the supplied media route becomes unselected as the active route.
1761          * For detailed reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)}
1762          * instead.
1763          *
1764          * @param router The media router reporting the event.
1765          * @param route The route that has been unselected.
1766          */
onRouteUnselected(MediaRouter router, RouteInfo route)1767         public void onRouteUnselected(MediaRouter router, RouteInfo route) {
1768         }
1769 
1770         /**
1771          * Called when the supplied media route becomes unselected as the active route.
1772          * The default implementation calls {@link #onRouteUnselected}.
1773          * <p>
1774          * The reason provided will be one of the following:
1775          * <ul>
1776          * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
1777          * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
1778          * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
1779          * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
1780          * </ul>
1781          *
1782          * @param router The media router reporting the event.
1783          * @param route The route that has been unselected.
1784          * @param reason The reason for unselecting the route.
1785          */
onRouteUnselected(MediaRouter router, RouteInfo route, int reason)1786         public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
1787             onRouteUnselected(router, route);
1788         }
1789 
1790         /**
1791          * Called when a media route has been added.
1792          *
1793          * @param router The media router reporting the event.
1794          * @param route The route that has become available for use.
1795          */
onRouteAdded(MediaRouter router, RouteInfo route)1796         public void onRouteAdded(MediaRouter router, RouteInfo route) {
1797         }
1798 
1799         /**
1800          * Called when a media route has been removed.
1801          *
1802          * @param router The media router reporting the event.
1803          * @param route The route that has been removed from availability.
1804          */
onRouteRemoved(MediaRouter router, RouteInfo route)1805         public void onRouteRemoved(MediaRouter router, RouteInfo route) {
1806         }
1807 
1808         /**
1809          * Called when a property of the indicated media route has changed.
1810          *
1811          * @param router The media router reporting the event.
1812          * @param route The route that was changed.
1813          */
onRouteChanged(MediaRouter router, RouteInfo route)1814         public void onRouteChanged(MediaRouter router, RouteInfo route) {
1815         }
1816 
1817         /**
1818          * Called when a media route's volume changes.
1819          *
1820          * @param router The media router reporting the event.
1821          * @param route The route whose volume changed.
1822          */
onRouteVolumeChanged(MediaRouter router, RouteInfo route)1823         public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
1824         }
1825 
1826         /**
1827          * Called when a media route's presentation display changes.
1828          * <p>
1829          * This method is called whenever the route's presentation display becomes
1830          * available, is removed or has changes to some of its properties (such as its size).
1831          * </p>
1832          *
1833          * @param router The media router reporting the event.
1834          * @param route The route whose presentation display changed.
1835          *
1836          * @see RouteInfo#getPresentationDisplay()
1837          */
onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route)1838         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
1839         }
1840 
1841         /**
1842          * Called when a media route provider has been added.
1843          *
1844          * @param router The media router reporting the event.
1845          * @param provider The provider that has become available for use.
1846          */
onProviderAdded(MediaRouter router, ProviderInfo provider)1847         public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
1848         }
1849 
1850         /**
1851          * Called when a media route provider has been removed.
1852          *
1853          * @param router The media router reporting the event.
1854          * @param provider The provider that has been removed from availability.
1855          */
onProviderRemoved(MediaRouter router, ProviderInfo provider)1856         public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
1857         }
1858 
1859         /**
1860          * Called when a property of the indicated media route provider has changed.
1861          *
1862          * @param router The media router reporting the event.
1863          * @param provider The provider that was changed.
1864          */
onProviderChanged(MediaRouter router, ProviderInfo provider)1865         public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
1866         }
1867     }
1868 
1869     /**
1870      * Callback which is invoked with the result of a media control request.
1871      *
1872      * @see RouteInfo#sendControlRequest
1873      */
1874     public static abstract class ControlRequestCallback {
1875         /**
1876          * Called when a media control request succeeds.
1877          *
1878          * @param data Result data, or null if none.
1879          * Contents depend on the {@link MediaControlIntent media control action}.
1880          */
onResult(Bundle data)1881         public void onResult(Bundle data) {
1882         }
1883 
1884         /**
1885          * Called when a media control request fails.
1886          *
1887          * @param error A localized error message which may be shown to the user, or null
1888          * if the cause of the error is unclear.
1889          * @param data Error data, or null if none.
1890          * Contents depend on the {@link MediaControlIntent media control action}.
1891          */
onError(String error, Bundle data)1892         public void onError(String error, Bundle data) {
1893         }
1894     }
1895 
1896     private static final class CallbackRecord {
1897         public final MediaRouter mRouter;
1898         public final Callback mCallback;
1899         public MediaRouteSelector mSelector;
1900         public int mFlags;
1901 
CallbackRecord(MediaRouter router, Callback callback)1902         public CallbackRecord(MediaRouter router, Callback callback) {
1903             mRouter = router;
1904             mCallback = callback;
1905             mSelector = MediaRouteSelector.EMPTY;
1906         }
1907 
filterRouteEvent(RouteInfo route)1908         public boolean filterRouteEvent(RouteInfo route) {
1909             return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
1910                     || route.matchesSelector(mSelector);
1911         }
1912     }
1913 
1914     /**
1915      * Global state for the media router.
1916      * <p>
1917      * Media routes and media route providers are global to the process; their
1918      * state and the bulk of the media router implementation lives here.
1919      * </p>
1920      */
1921     private static final class GlobalMediaRouter
1922             implements SystemMediaRouteProvider.SyncCallback,
1923             RegisteredMediaRouteProviderWatcher.Callback {
1924         final Context mApplicationContext;
1925         final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
1926         private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
1927         private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
1928         private final ArrayList<ProviderInfo> mProviders = new ArrayList<>();
1929         private final ArrayList<RemoteControlClientRecord> mRemoteControlClients =
1930                 new ArrayList<>();
1931         final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo =
1932                 new RemoteControlClientCompat.PlaybackInfo();
1933         private final ProviderCallback mProviderCallback = new ProviderCallback();
1934         final CallbackHandler mCallbackHandler = new CallbackHandler();
1935         private final DisplayManagerCompat mDisplayManager;
1936         final SystemMediaRouteProvider mSystemProvider;
1937         private final boolean mLowRam;
1938 
1939         private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
1940         private RouteInfo mDefaultRoute;
1941         private RouteInfo mBluetoothRoute;
1942         RouteInfo mSelectedRoute;
1943         private RouteController mSelectedRouteController;
1944         // A map from route descriptor ID to RouteController for the member routes in the currently
1945         // selected route group.
1946         private final Map<String, RouteController> mRouteControllerMap = new HashMap<>();
1947         private MediaRouteDiscoveryRequest mDiscoveryRequest;
1948         private MediaSessionRecord mMediaSession;
1949         MediaSessionCompat mRccMediaSession;
1950         private MediaSessionCompat mCompatSession;
1951         private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
1952                 new MediaSessionCompat.OnActiveChangeListener() {
1953             @Override
1954             public void onActiveChanged() {
1955                 if(mRccMediaSession != null) {
1956                     if (mRccMediaSession.isActive()) {
1957                         addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
1958                     } else {
1959                         removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
1960                     }
1961                 }
1962             }
1963         };
1964 
GlobalMediaRouter(Context applicationContext)1965         GlobalMediaRouter(Context applicationContext) {
1966             mApplicationContext = applicationContext;
1967             mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
1968             mLowRam = ActivityManagerCompat.isLowRamDevice(
1969                     (ActivityManager)applicationContext.getSystemService(
1970                             Context.ACTIVITY_SERVICE));
1971 
1972             // Add the system media route provider for interoperating with
1973             // the framework media router.  This one is special and receives
1974             // synchronization messages from the media router.
1975             mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
1976             addProvider(mSystemProvider);
1977         }
1978 
start()1979         public void start() {
1980             // Start watching for routes published by registered media route
1981             // provider services.
1982             mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
1983                     mApplicationContext, this);
1984             mRegisteredProviderWatcher.start();
1985         }
1986 
getRouter(Context context)1987         public MediaRouter getRouter(Context context) {
1988             MediaRouter router;
1989             for (int i = mRouters.size(); --i >= 0; ) {
1990                 router = mRouters.get(i).get();
1991                 if (router == null) {
1992                     mRouters.remove(i);
1993                 } else if (router.mContext == context) {
1994                     return router;
1995                 }
1996             }
1997             router = new MediaRouter(context);
1998             mRouters.add(new WeakReference<MediaRouter>(router));
1999             return router;
2000         }
2001 
getContentResolver()2002         public ContentResolver getContentResolver() {
2003             return mApplicationContext.getContentResolver();
2004         }
2005 
getProviderContext(String packageName)2006         public Context getProviderContext(String packageName) {
2007             if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
2008                 return mApplicationContext;
2009             }
2010             try {
2011                 return mApplicationContext.createPackageContext(
2012                         packageName, Context.CONTEXT_RESTRICTED);
2013             } catch (NameNotFoundException ex) {
2014                 return null;
2015             }
2016         }
2017 
getDisplay(int displayId)2018         public Display getDisplay(int displayId) {
2019             return mDisplayManager.getDisplay(displayId);
2020         }
2021 
sendControlRequest(RouteInfo route, Intent intent, ControlRequestCallback callback)2022         public void sendControlRequest(RouteInfo route,
2023                 Intent intent, ControlRequestCallback callback) {
2024             if (route == mSelectedRoute && mSelectedRouteController != null) {
2025                 if (mSelectedRouteController.onControlRequest(intent, callback)) {
2026                     return;
2027                 }
2028             }
2029             if (callback != null) {
2030                 callback.onError(null, null);
2031             }
2032         }
2033 
requestSetVolume(RouteInfo route, int volume)2034         public void requestSetVolume(RouteInfo route, int volume) {
2035             if (route == mSelectedRoute && mSelectedRouteController != null) {
2036                 mSelectedRouteController.onSetVolume(volume);
2037             } else if (!mRouteControllerMap.isEmpty()) {
2038                 RouteController controller = mRouteControllerMap.get(route.mDescriptorId);
2039                 if (controller != null) {
2040                     controller.onSetVolume(volume);
2041                 }
2042             }
2043         }
2044 
requestUpdateVolume(RouteInfo route, int delta)2045         public void requestUpdateVolume(RouteInfo route, int delta) {
2046             if (route == mSelectedRoute && mSelectedRouteController != null) {
2047                 mSelectedRouteController.onUpdateVolume(delta);
2048             }
2049         }
2050 
getRoute(String uniqueId)2051         public RouteInfo getRoute(String uniqueId) {
2052             for (RouteInfo info : mRoutes) {
2053                 if (info.mUniqueId.equals(uniqueId)) {
2054                     return info;
2055                 }
2056             }
2057             return null;
2058         }
2059 
getRoutes()2060         public List<RouteInfo> getRoutes() {
2061             return mRoutes;
2062         }
2063 
getProviders()2064         public List<ProviderInfo> getProviders() {
2065             return mProviders;
2066         }
2067 
getDefaultRoute()2068         public RouteInfo getDefaultRoute() {
2069             if (mDefaultRoute == null) {
2070                 // This should never happen once the media router has been fully
2071                 // initialized but it is good to check for the error in case there
2072                 // is a bug in provider initialization.
2073                 throw new IllegalStateException("There is no default route.  "
2074                         + "The media router has not yet been fully initialized.");
2075             }
2076             return mDefaultRoute;
2077         }
2078 
getBluetoothRoute()2079         public RouteInfo getBluetoothRoute() {
2080             return mBluetoothRoute;
2081         }
2082 
getSelectedRoute()2083         public RouteInfo getSelectedRoute() {
2084             if (mSelectedRoute == null) {
2085                 // This should never happen once the media router has been fully
2086                 // initialized but it is good to check for the error in case there
2087                 // is a bug in provider initialization.
2088                 throw new IllegalStateException("There is no currently selected route.  "
2089                         + "The media router has not yet been fully initialized.");
2090             }
2091             return mSelectedRoute;
2092         }
2093 
selectRoute(RouteInfo route)2094         public void selectRoute(RouteInfo route) {
2095             selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
2096         }
2097 
selectRoute(RouteInfo route, int unselectReason)2098         public void selectRoute(RouteInfo route, int unselectReason) {
2099             if (!mRoutes.contains(route)) {
2100                 Log.w(TAG, "Ignoring attempt to select removed route: " + route);
2101                 return;
2102             }
2103             if (!route.mEnabled) {
2104                 Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
2105                 return;
2106             }
2107             setSelectedRouteInternal(route, unselectReason);
2108         }
2109 
isRouteAvailable(MediaRouteSelector selector, int flags)2110         public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
2111             if (selector.isEmpty()) {
2112                 return false;
2113             }
2114 
2115             // On low-RAM devices, do not rely on actual discovery results unless asked to.
2116             if ((flags & AVAILABILITY_FLAG_REQUIRE_MATCH) == 0 && mLowRam) {
2117                 return true;
2118             }
2119 
2120             // Check whether any existing routes match the selector.
2121             final int routeCount = mRoutes.size();
2122             for (int i = 0; i < routeCount; i++) {
2123                 RouteInfo route = mRoutes.get(i);
2124                 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
2125                         && route.isDefaultOrBluetooth()) {
2126                     continue;
2127                 }
2128                 if (route.matchesSelector(selector)) {
2129                     return true;
2130                 }
2131             }
2132 
2133             // It doesn't look like we can find a matching route right now.
2134             return false;
2135         }
2136 
updateDiscoveryRequest()2137         public void updateDiscoveryRequest() {
2138             // Combine all of the callback selectors and active scan flags.
2139             boolean discover = false;
2140             boolean activeScan = false;
2141             MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
2142             for (int i = mRouters.size(); --i >= 0; ) {
2143                 MediaRouter router = mRouters.get(i).get();
2144                 if (router == null) {
2145                     mRouters.remove(i);
2146                 } else {
2147                     final int count = router.mCallbackRecords.size();
2148                     for (int j = 0; j < count; j++) {
2149                         CallbackRecord callback = router.mCallbackRecords.get(j);
2150                         builder.addSelector(callback.mSelector);
2151                         if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
2152                             activeScan = true;
2153                             discover = true; // perform active scan implies request discovery
2154                         }
2155                         if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
2156                             if (!mLowRam) {
2157                                 discover = true;
2158                             }
2159                         }
2160                         if ((callback.mFlags & CALLBACK_FLAG_FORCE_DISCOVERY) != 0) {
2161                             discover = true;
2162                         }
2163                     }
2164                 }
2165             }
2166             MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
2167 
2168             // Create a new discovery request.
2169             if (mDiscoveryRequest != null
2170                     && mDiscoveryRequest.getSelector().equals(selector)
2171                     && mDiscoveryRequest.isActiveScan() == activeScan) {
2172                 return; // no change
2173             }
2174             if (selector.isEmpty() && !activeScan) {
2175                 // Discovery is not needed.
2176                 if (mDiscoveryRequest == null) {
2177                     return; // no change
2178                 }
2179                 mDiscoveryRequest = null;
2180             } else {
2181                 // Discovery is needed.
2182                 mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
2183             }
2184             if (DEBUG) {
2185                 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
2186             }
2187             if (discover && !activeScan && mLowRam) {
2188                 Log.i(TAG, "Forcing passive route discovery on a low-RAM device, "
2189                         + "system performance may be affected.  Please consider using "
2190                         + "CALLBACK_FLAG_REQUEST_DISCOVERY instead of "
2191                         + "CALLBACK_FLAG_FORCE_DISCOVERY.");
2192             }
2193 
2194             // Notify providers.
2195             final int providerCount = mProviders.size();
2196             for (int i = 0; i < providerCount; i++) {
2197                 mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
2198             }
2199         }
2200 
2201         @Override
addProvider(MediaRouteProvider providerInstance)2202         public void addProvider(MediaRouteProvider providerInstance) {
2203             int index = findProviderInfo(providerInstance);
2204             if (index < 0) {
2205                 // 1. Add the provider to the list.
2206                 ProviderInfo provider = new ProviderInfo(providerInstance);
2207                 mProviders.add(provider);
2208                 if (DEBUG) {
2209                     Log.d(TAG, "Provider added: " + provider);
2210                 }
2211                 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
2212                 // 2. Create the provider's contents.
2213                 updateProviderContents(provider, providerInstance.getDescriptor());
2214                 // 3. Register the provider callback.
2215                 providerInstance.setCallback(mProviderCallback);
2216                 // 4. Set the discovery request.
2217                 providerInstance.setDiscoveryRequest(mDiscoveryRequest);
2218             }
2219         }
2220 
2221         @Override
removeProvider(MediaRouteProvider providerInstance)2222         public void removeProvider(MediaRouteProvider providerInstance) {
2223             int index = findProviderInfo(providerInstance);
2224             if (index >= 0) {
2225                 // 1. Unregister the provider callback.
2226                 providerInstance.setCallback(null);
2227                 // 2. Clear the discovery request.
2228                 providerInstance.setDiscoveryRequest(null);
2229                 // 3. Delete the provider's contents.
2230                 ProviderInfo provider = mProviders.get(index);
2231                 updateProviderContents(provider, null);
2232                 // 4. Remove the provider from the list.
2233                 if (DEBUG) {
2234                     Log.d(TAG, "Provider removed: " + provider);
2235                 }
2236                 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
2237                 mProviders.remove(index);
2238             }
2239         }
2240 
updateProviderDescriptor(MediaRouteProvider providerInstance, MediaRouteProviderDescriptor descriptor)2241         void updateProviderDescriptor(MediaRouteProvider providerInstance,
2242                 MediaRouteProviderDescriptor descriptor) {
2243             int index = findProviderInfo(providerInstance);
2244             if (index >= 0) {
2245                 // Update the provider's contents.
2246                 ProviderInfo provider = mProviders.get(index);
2247                 updateProviderContents(provider, descriptor);
2248             }
2249         }
2250 
findProviderInfo(MediaRouteProvider providerInstance)2251         private int findProviderInfo(MediaRouteProvider providerInstance) {
2252             final int count = mProviders.size();
2253             for (int i = 0; i < count; i++) {
2254                 if (mProviders.get(i).mProviderInstance == providerInstance) {
2255                     return i;
2256                 }
2257             }
2258             return -1;
2259         }
2260 
updateProviderContents(ProviderInfo provider, MediaRouteProviderDescriptor providerDescriptor)2261         private void updateProviderContents(ProviderInfo provider,
2262                 MediaRouteProviderDescriptor providerDescriptor) {
2263             if (provider.updateDescriptor(providerDescriptor)) {
2264                 // Update all existing routes and reorder them to match
2265                 // the order of their descriptors.
2266                 int targetIndex = 0;
2267                 boolean selectedRouteDescriptorChanged = false;
2268                 if (providerDescriptor != null) {
2269                     if (providerDescriptor.isValid()) {
2270                         final List<MediaRouteDescriptor> routeDescriptors =
2271                                 providerDescriptor.getRoutes();
2272                         final int routeCount = routeDescriptors.size();
2273                         // Updating route group's contents requires all member routes' information.
2274                         // Add the groups to the lists and update them later.
2275                         List<Pair<RouteInfo, MediaRouteDescriptor>> addedGroups = new ArrayList<>();
2276                         List<Pair<RouteInfo, MediaRouteDescriptor>> updatedGroups =
2277                                 new ArrayList<>();
2278                         for (int i = 0; i < routeCount; i++) {
2279                             final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
2280                             final String id = routeDescriptor.getId();
2281                             final int sourceIndex = provider.findRouteByDescriptorId(id);
2282                             if (sourceIndex < 0) {
2283                                 // 1. Add the route to the list.
2284                                 String uniqueId = assignRouteUniqueId(provider, id);
2285                                 boolean isGroup = routeDescriptor.getGroupMemberIds() != null;
2286                                 RouteInfo route = isGroup ? new RouteGroup(provider, id, uniqueId) :
2287                                         new RouteInfo(provider, id, uniqueId);
2288                                 provider.mRoutes.add(targetIndex++, route);
2289                                 mRoutes.add(route);
2290                                 // 2. Create the route's contents.
2291                                 if (isGroup) {
2292                                     addedGroups.add(new Pair<>(route, routeDescriptor));
2293                                 } else {
2294                                     route.maybeUpdateDescriptor(routeDescriptor);
2295                                     // 3. Notify clients about addition.
2296                                     if (DEBUG) {
2297                                         Log.d(TAG, "Route added: " + route);
2298                                     }
2299                                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
2300                                 }
2301 
2302                             } else if (sourceIndex < targetIndex) {
2303                                 Log.w(TAG, "Ignoring route descriptor with duplicate id: "
2304                                         + routeDescriptor);
2305                             } else {
2306                                 // 1. Reorder the route within the list.
2307                                 RouteInfo route = provider.mRoutes.get(sourceIndex);
2308                                 Collections.swap(provider.mRoutes,
2309                                         sourceIndex, targetIndex++);
2310                                 // 2. Update the route's contents.
2311                                 if (route instanceof RouteGroup) {
2312                                     updatedGroups.add(new Pair<>(route, routeDescriptor));
2313                                 } else {
2314                                     // 3. Notify clients about changes.
2315                                     if (updateRouteDescriptorAndNotify(route, routeDescriptor)
2316                                             != 0) {
2317                                         if (route == mSelectedRoute) {
2318                                             selectedRouteDescriptorChanged = true;
2319                                         }
2320                                     }
2321                                 }
2322                             }
2323                         }
2324                         // Update the new and/or existing groups.
2325                         for (Pair<RouteInfo, MediaRouteDescriptor> pair : addedGroups) {
2326                             RouteInfo route = pair.first;
2327                             route.maybeUpdateDescriptor(pair.second);
2328                             if (DEBUG) {
2329                                 Log.d(TAG, "Route added: " + route);
2330                             }
2331                             mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
2332                         }
2333                         for (Pair<RouteInfo, MediaRouteDescriptor> pair : updatedGroups) {
2334                             RouteInfo route = pair.first;
2335                             if (updateRouteDescriptorAndNotify(route, pair.second) != 0) {
2336                                 if (route == mSelectedRoute) {
2337                                     selectedRouteDescriptorChanged = true;
2338                                 }
2339                             }
2340                         }
2341                     } else {
2342                         Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
2343                     }
2344                 }
2345 
2346                 // Dispose all remaining routes that do not have matching descriptors.
2347                 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
2348                     // 1. Delete the route's contents.
2349                     RouteInfo route = provider.mRoutes.get(i);
2350                     route.maybeUpdateDescriptor(null);
2351                     // 2. Remove the route from the list.
2352                     mRoutes.remove(route);
2353                 }
2354 
2355                 // Update the selected route if needed.
2356                 updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged);
2357 
2358                 // Now notify clients about routes that were removed.
2359                 // We do this after updating the selected route to ensure
2360                 // that the framework media router observes the new route
2361                 // selection before the removal since removing the currently
2362                 // selected route may have side-effects.
2363                 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
2364                     RouteInfo route = provider.mRoutes.remove(i);
2365                     if (DEBUG) {
2366                         Log.d(TAG, "Route removed: " + route);
2367                     }
2368                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
2369                 }
2370 
2371                 // Notify provider changed.
2372                 if (DEBUG) {
2373                     Log.d(TAG, "Provider changed: " + provider);
2374                 }
2375                 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
2376             }
2377         }
2378 
updateRouteDescriptorAndNotify(RouteInfo route, MediaRouteDescriptor routeDescriptor)2379         private int updateRouteDescriptorAndNotify(RouteInfo route,
2380                 MediaRouteDescriptor routeDescriptor) {
2381             int changes = route.maybeUpdateDescriptor(routeDescriptor);
2382             if (changes != 0) {
2383                 if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
2384                     if (DEBUG) {
2385                         Log.d(TAG, "Route changed: " + route);
2386                     }
2387                     mCallbackHandler.post(
2388                             CallbackHandler.MSG_ROUTE_CHANGED, route);
2389                 }
2390                 if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
2391                     if (DEBUG) {
2392                         Log.d(TAG, "Route volume changed: " + route);
2393                     }
2394                     mCallbackHandler.post(
2395                             CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
2396                 }
2397                 if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
2398                     if (DEBUG) {
2399                         Log.d(TAG, "Route presentation display changed: "
2400                                 + route);
2401                     }
2402                     mCallbackHandler.post(CallbackHandler.
2403                             MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
2404                 }
2405             }
2406             return changes;
2407         }
2408 
assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId)2409         private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
2410             // Although route descriptor ids are unique within a provider, it's
2411             // possible for there to be two providers with the same package name.
2412             // Therefore we must dedupe the composite id.
2413             String componentName = provider.getComponentName().flattenToShortString();
2414             String uniqueId = componentName + ":" + routeDescriptorId;
2415             if (findRouteByUniqueId(uniqueId) < 0) {
2416                 mUniqueIdMap.put(new Pair<>(componentName, routeDescriptorId), uniqueId);
2417                 return uniqueId;
2418             }
2419             Log.w(TAG, "Either " + routeDescriptorId + " isn't unique in " + componentName
2420                     + " or we're trying to assign a unique ID for an already added route");
2421             for (int i = 2; ; i++) {
2422                 String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
2423                 if (findRouteByUniqueId(newUniqueId) < 0) {
2424                     mUniqueIdMap.put(new Pair<>(componentName, routeDescriptorId), newUniqueId);
2425                     return newUniqueId;
2426                 }
2427             }
2428         }
2429 
findRouteByUniqueId(String uniqueId)2430         private int findRouteByUniqueId(String uniqueId) {
2431             final int count = mRoutes.size();
2432             for (int i = 0; i < count; i++) {
2433                 if (mRoutes.get(i).mUniqueId.equals(uniqueId)) {
2434                     return i;
2435                 }
2436             }
2437             return -1;
2438         }
2439 
getUniqueId(ProviderInfo provider, String routeDescriptorId)2440         private String getUniqueId(ProviderInfo provider, String routeDescriptorId) {
2441             String componentName = provider.getComponentName().flattenToShortString();
2442             return mUniqueIdMap.get(new Pair<>(componentName, routeDescriptorId));
2443         }
2444 
updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged)2445         private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) {
2446             // Update default route.
2447             if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) {
2448                 Log.i(TAG, "Clearing the default route because it "
2449                         + "is no longer selectable: " + mDefaultRoute);
2450                 mDefaultRoute = null;
2451             }
2452             if (mDefaultRoute == null && !mRoutes.isEmpty()) {
2453                 for (RouteInfo route : mRoutes) {
2454                     if (isSystemDefaultRoute(route) && isRouteSelectable(route)) {
2455                         mDefaultRoute = route;
2456                         Log.i(TAG, "Found default route: " + mDefaultRoute);
2457                         break;
2458                     }
2459                 }
2460             }
2461 
2462             // Update bluetooth route.
2463             if (mBluetoothRoute != null && !isRouteSelectable(mBluetoothRoute)) {
2464                 Log.i(TAG, "Clearing the bluetooth route because it "
2465                         + "is no longer selectable: " + mBluetoothRoute);
2466                 mBluetoothRoute = null;
2467             }
2468             if (mBluetoothRoute == null && !mRoutes.isEmpty()) {
2469                 for (RouteInfo route : mRoutes) {
2470                     if (isSystemLiveAudioOnlyRoute(route) && isRouteSelectable(route)) {
2471                         mBluetoothRoute = route;
2472                         Log.i(TAG, "Found bluetooth route: " + mBluetoothRoute);
2473                         break;
2474                     }
2475                 }
2476             }
2477 
2478             // Update selected route.
2479             if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) {
2480                 Log.i(TAG, "Unselecting the current route because it "
2481                         + "is no longer selectable: " + mSelectedRoute);
2482                 setSelectedRouteInternal(null,
2483                         MediaRouter.UNSELECT_REASON_UNKNOWN);
2484             }
2485             if (mSelectedRoute == null) {
2486                 // Choose a new route.
2487                 // This will have the side-effect of updating the playback info when
2488                 // the new route is selected.
2489                 setSelectedRouteInternal(chooseFallbackRoute(),
2490                         MediaRouter.UNSELECT_REASON_UNKNOWN);
2491             } else if (selectedRouteDescriptorChanged) {
2492                 // In case the selected route is a route group, select/unselect route controllers
2493                 // for the added/removed route members.
2494                 if (mSelectedRoute instanceof RouteGroup) {
2495                     List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
2496                     // Build a set of descriptor IDs for the new route group.
2497                     Set<String> idSet = new HashSet<>();
2498                     for (RouteInfo route : routes) {
2499                         idSet.add(route.mDescriptorId);
2500                     }
2501                     // Unselect route controllers for the removed routes.
2502                     Iterator<Map.Entry<String, RouteController>> iter =
2503                             mRouteControllerMap.entrySet().iterator();
2504                     while (iter.hasNext()) {
2505                         Map.Entry<String, RouteController> entry = iter.next();
2506                         if (!idSet.contains(entry.getKey())) {
2507                             RouteController controller = entry.getValue();
2508                             controller.onUnselect();
2509                             controller.onRelease();
2510                             iter.remove();
2511                         }
2512                     }
2513                     // Select route controllers for the added routes.
2514                     for (RouteInfo route : routes) {
2515                         if (!mRouteControllerMap.containsKey(route.mDescriptorId)) {
2516                             RouteController controller = route.getProviderInstance()
2517                                     .onCreateRouteController(
2518                                             route.mDescriptorId, mSelectedRoute.mDescriptorId);
2519                             controller.onSelect();
2520                             mRouteControllerMap.put(route.mDescriptorId, controller);
2521                         }
2522                     }
2523                 }
2524                 // Update the playback info because the properties of the route have changed.
2525                 updatePlaybackInfoFromSelectedRoute();
2526             }
2527         }
2528 
chooseFallbackRoute()2529         RouteInfo chooseFallbackRoute() {
2530             // When the current route is removed or no longer selectable,
2531             // we want to revert to a live audio route if there is
2532             // one (usually Bluetooth A2DP).  Failing that, use
2533             // the default route.
2534             for (RouteInfo route : mRoutes) {
2535                 if (route != mDefaultRoute
2536                         && isSystemLiveAudioOnlyRoute(route)
2537                         && isRouteSelectable(route)) {
2538                     return route;
2539                 }
2540             }
2541             return mDefaultRoute;
2542         }
2543 
isSystemLiveAudioOnlyRoute(RouteInfo route)2544         private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) {
2545             return route.getProviderInstance() == mSystemProvider
2546                     && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
2547                     && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
2548         }
2549 
isRouteSelectable(RouteInfo route)2550         private boolean isRouteSelectable(RouteInfo route) {
2551             // This tests whether the route is still valid and enabled.
2552             // The route descriptor field is set to null when the route is removed.
2553             return route.mDescriptor != null && route.mEnabled;
2554         }
2555 
isSystemDefaultRoute(RouteInfo route)2556         private boolean isSystemDefaultRoute(RouteInfo route) {
2557             return route.getProviderInstance() == mSystemProvider
2558                     && route.mDescriptorId.equals(
2559                             SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
2560         }
2561 
setSelectedRouteInternal(RouteInfo route, int unselectReason)2562         private void setSelectedRouteInternal(RouteInfo route, int unselectReason) {
2563             if (mSelectedRoute != route) {
2564                 if (mSelectedRoute != null) {
2565                     if (DEBUG) {
2566                         Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
2567                                 + unselectReason);
2568                     }
2569                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute,
2570                             unselectReason);
2571                     if (mSelectedRouteController != null) {
2572                         mSelectedRouteController.onUnselect(unselectReason);
2573                         mSelectedRouteController.onRelease();
2574                         mSelectedRouteController = null;
2575                     }
2576                     if (!mRouteControllerMap.isEmpty()) {
2577                         for (RouteController controller : mRouteControllerMap.values()) {
2578                             controller.onUnselect(unselectReason);
2579                             controller.onRelease();
2580                         }
2581                         mRouteControllerMap.clear();
2582                     }
2583                 }
2584 
2585                 mSelectedRoute = route;
2586 
2587                 if (mSelectedRoute != null) {
2588                     mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
2589                             route.mDescriptorId);
2590                     if (mSelectedRouteController != null) {
2591                         mSelectedRouteController.onSelect();
2592                     }
2593                     if (DEBUG) {
2594                         Log.d(TAG, "Route selected: " + mSelectedRoute);
2595                     }
2596                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
2597 
2598                     if (mSelectedRoute instanceof RouteGroup) {
2599                         List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
2600                         mRouteControllerMap.clear();
2601                         for (RouteInfo r : routes) {
2602                             RouteController controller =
2603                                     r.getProviderInstance().onCreateRouteController(
2604                                             r.mDescriptorId, mSelectedRoute.mDescriptorId);
2605                             controller.onSelect();
2606                             mRouteControllerMap.put(r.mDescriptorId, controller);
2607                         }
2608                     }
2609                 }
2610 
2611                 updatePlaybackInfoFromSelectedRoute();
2612             }
2613         }
2614 
2615         @Override
getSystemRouteByDescriptorId(String id)2616         public RouteInfo getSystemRouteByDescriptorId(String id) {
2617             int providerIndex = findProviderInfo(mSystemProvider);
2618             if (providerIndex >= 0) {
2619                 ProviderInfo provider = mProviders.get(providerIndex);
2620                 int routeIndex = provider.findRouteByDescriptorId(id);
2621                 if (routeIndex >= 0) {
2622                     return provider.mRoutes.get(routeIndex);
2623                 }
2624             }
2625             return null;
2626         }
2627 
addRemoteControlClient(Object rcc)2628         public void addRemoteControlClient(Object rcc) {
2629             int index = findRemoteControlClientRecord(rcc);
2630             if (index < 0) {
2631                 RemoteControlClientRecord record = new RemoteControlClientRecord(rcc);
2632                 mRemoteControlClients.add(record);
2633             }
2634         }
2635 
removeRemoteControlClient(Object rcc)2636         public void removeRemoteControlClient(Object rcc) {
2637             int index = findRemoteControlClientRecord(rcc);
2638             if (index >= 0) {
2639                 RemoteControlClientRecord record = mRemoteControlClients.remove(index);
2640                 record.disconnect();
2641             }
2642         }
2643 
setMediaSession(Object session)2644         public void setMediaSession(Object session) {
2645             setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
2646         }
2647 
setMediaSessionCompat(final MediaSessionCompat session)2648         public void setMediaSessionCompat(final MediaSessionCompat session) {
2649             mCompatSession = session;
2650             if (android.os.Build.VERSION.SDK_INT >= 21) {
2651                 setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
2652             } else if (android.os.Build.VERSION.SDK_INT >= 14) {
2653                 if (mRccMediaSession != null) {
2654                     removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
2655                     mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
2656                 }
2657                 mRccMediaSession = session;
2658                 if (session != null) {
2659                     session.addOnActiveChangeListener(mSessionActiveListener);
2660                     if (session.isActive()) {
2661                         addRemoteControlClient(session.getRemoteControlClient());
2662                     }
2663                 }
2664             }
2665         }
2666 
setMediaSessionRecord(MediaSessionRecord mediaSessionRecord)2667         private void setMediaSessionRecord(MediaSessionRecord mediaSessionRecord) {
2668             if (mMediaSession != null) {
2669                 mMediaSession.clearVolumeHandling();
2670             }
2671             mMediaSession = mediaSessionRecord;
2672             if (mediaSessionRecord != null) {
2673                 updatePlaybackInfoFromSelectedRoute();
2674             }
2675         }
2676 
getMediaSessionToken()2677         public MediaSessionCompat.Token getMediaSessionToken() {
2678             if (mMediaSession != null) {
2679                 return mMediaSession.getToken();
2680             } else if (mCompatSession != null) {
2681                 return mCompatSession.getSessionToken();
2682             }
2683             return null;
2684         }
2685 
findRemoteControlClientRecord(Object rcc)2686         private int findRemoteControlClientRecord(Object rcc) {
2687             final int count = mRemoteControlClients.size();
2688             for (int i = 0; i < count; i++) {
2689                 RemoteControlClientRecord record = mRemoteControlClients.get(i);
2690                 if (record.getRemoteControlClient() == rcc) {
2691                     return i;
2692                 }
2693             }
2694             return -1;
2695         }
2696 
updatePlaybackInfoFromSelectedRoute()2697         private void updatePlaybackInfoFromSelectedRoute() {
2698             if (mSelectedRoute != null) {
2699                 mPlaybackInfo.volume = mSelectedRoute.getVolume();
2700                 mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax();
2701                 mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling();
2702                 mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream();
2703                 mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType();
2704 
2705                 final int count = mRemoteControlClients.size();
2706                 for (int i = 0; i < count; i++) {
2707                     RemoteControlClientRecord record = mRemoteControlClients.get(i);
2708                     record.updatePlaybackInfo();
2709                 }
2710                 if (mMediaSession != null) {
2711                     if (mSelectedRoute == getDefaultRoute()
2712                             || mSelectedRoute == getBluetoothRoute()) {
2713                         // Local route
2714                         mMediaSession.clearVolumeHandling();
2715                     } else {
2716                         @VolumeProviderCompat.ControlType int controlType =
2717                                 VolumeProviderCompat.VOLUME_CONTROL_FIXED;
2718                         if (mPlaybackInfo.volumeHandling
2719                                 == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
2720                             controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
2721                         }
2722                         mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
2723                                 mPlaybackInfo.volume);
2724                     }
2725                 }
2726             } else {
2727                 if (mMediaSession != null) {
2728                     mMediaSession.clearVolumeHandling();
2729                 }
2730             }
2731         }
2732 
2733         private final class ProviderCallback extends MediaRouteProvider.Callback {
ProviderCallback()2734             ProviderCallback() {
2735             }
2736 
2737             @Override
onDescriptorChanged(MediaRouteProvider provider, MediaRouteProviderDescriptor descriptor)2738             public void onDescriptorChanged(MediaRouteProvider provider,
2739                     MediaRouteProviderDescriptor descriptor) {
2740                 updateProviderDescriptor(provider, descriptor);
2741             }
2742         }
2743 
2744         private final class MediaSessionRecord {
2745             private final MediaSessionCompat mMsCompat;
2746 
2747             private @VolumeProviderCompat.ControlType int mControlType;
2748             private int mMaxVolume;
2749             private VolumeProviderCompat mVpCompat;
2750 
MediaSessionRecord(Object mediaSession)2751             public MediaSessionRecord(Object mediaSession) {
2752                 mMsCompat = MediaSessionCompat.fromMediaSession(mApplicationContext, mediaSession);
2753             }
2754 
MediaSessionRecord(MediaSessionCompat mediaSessionCompat)2755             public MediaSessionRecord(MediaSessionCompat mediaSessionCompat) {
2756                 mMsCompat = mediaSessionCompat;
2757             }
2758 
configureVolume(@olumeProviderCompat.ControlType int controlType, int max, int current)2759             public void configureVolume(@VolumeProviderCompat.ControlType int controlType,
2760                     int max, int current) {
2761                 if (mVpCompat != null && controlType == mControlType && max == mMaxVolume) {
2762                     // If we haven't changed control type or max just set the
2763                     // new current volume
2764                     mVpCompat.setCurrentVolume(current);
2765                 } else {
2766                     // Otherwise create a new provider and update
2767                     mVpCompat = new VolumeProviderCompat(controlType, max, current) {
2768                         @Override
2769                         public void onSetVolumeTo(final int volume) {
2770                             mCallbackHandler.post(new Runnable() {
2771                                 @Override
2772                                 public void run() {
2773                                     if (mSelectedRoute != null) {
2774                                         mSelectedRoute.requestSetVolume(volume);
2775                                     }
2776                                 }
2777                             });
2778                         }
2779 
2780                         @Override
2781                         public void onAdjustVolume(final int direction) {
2782                             mCallbackHandler.post(new Runnable() {
2783                                 @Override
2784                                 public void run() {
2785                                     if (mSelectedRoute != null) {
2786                                         mSelectedRoute.requestUpdateVolume(direction);
2787                                     }
2788                                 }
2789                             });
2790                         }
2791                     };
2792                     mMsCompat.setPlaybackToRemote(mVpCompat);
2793                 }
2794             }
2795 
clearVolumeHandling()2796             public void clearVolumeHandling() {
2797                 mMsCompat.setPlaybackToLocal(mPlaybackInfo.playbackStream);
2798                 mVpCompat = null;
2799             }
2800 
getToken()2801             public MediaSessionCompat.Token getToken() {
2802                 return mMsCompat.getSessionToken();
2803             }
2804         }
2805 
2806         private final class RemoteControlClientRecord
2807                 implements RemoteControlClientCompat.VolumeCallback {
2808             private final RemoteControlClientCompat mRccCompat;
2809             private boolean mDisconnected;
2810 
RemoteControlClientRecord(Object rcc)2811             public RemoteControlClientRecord(Object rcc) {
2812                 mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc);
2813                 mRccCompat.setVolumeCallback(this);
2814                 updatePlaybackInfo();
2815             }
2816 
getRemoteControlClient()2817             public Object getRemoteControlClient() {
2818                 return mRccCompat.getRemoteControlClient();
2819             }
2820 
disconnect()2821             public void disconnect() {
2822                 mDisconnected = true;
2823                 mRccCompat.setVolumeCallback(null);
2824             }
2825 
updatePlaybackInfo()2826             public void updatePlaybackInfo() {
2827                 mRccCompat.setPlaybackInfo(mPlaybackInfo);
2828             }
2829 
2830             @Override
onVolumeSetRequest(int volume)2831             public void onVolumeSetRequest(int volume) {
2832                 if (!mDisconnected && mSelectedRoute != null) {
2833                     mSelectedRoute.requestSetVolume(volume);
2834                 }
2835             }
2836 
2837             @Override
onVolumeUpdateRequest(int direction)2838             public void onVolumeUpdateRequest(int direction) {
2839                 if (!mDisconnected && mSelectedRoute != null) {
2840                     mSelectedRoute.requestUpdateVolume(direction);
2841                 }
2842             }
2843         }
2844 
2845         private final class CallbackHandler extends Handler {
2846             private final ArrayList<CallbackRecord> mTempCallbackRecords =
2847                     new ArrayList<CallbackRecord>();
2848 
2849             private static final int MSG_TYPE_MASK = 0xff00;
2850             private static final int MSG_TYPE_ROUTE = 0x0100;
2851             private static final int MSG_TYPE_PROVIDER = 0x0200;
2852 
2853             public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
2854             public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
2855             public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
2856             public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
2857             public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
2858             public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
2859             public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
2860 
2861             public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
2862             public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
2863             public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
2864 
CallbackHandler()2865             CallbackHandler() {
2866             }
2867 
post(int msg, Object obj)2868             public void post(int msg, Object obj) {
2869                 obtainMessage(msg, obj).sendToTarget();
2870             }
2871 
post(int msg, Object obj, int arg)2872             public void post(int msg, Object obj, int arg) {
2873                 Message message = obtainMessage(msg, obj);
2874                 message.arg1 = arg;
2875                 message.sendToTarget();
2876             }
2877 
2878             @Override
handleMessage(Message msg)2879             public void handleMessage(Message msg) {
2880                 final int what = msg.what;
2881                 final Object obj = msg.obj;
2882                 final int arg = msg.arg1;
2883 
2884                 if (what == MSG_ROUTE_CHANGED
2885                         && getSelectedRoute().getId().equals(((RouteInfo) obj).getId())) {
2886                     updateSelectedRouteIfNeeded(true);
2887                 }
2888 
2889                 // Synchronize state with the system media router.
2890                 syncWithSystemProvider(what, obj);
2891 
2892                 // Invoke all registered callbacks.
2893                 // Build a list of callbacks before invoking them in case callbacks
2894                 // are added or removed during dispatch.
2895                 try {
2896                     for (int i = mRouters.size(); --i >= 0; ) {
2897                         MediaRouter router = mRouters.get(i).get();
2898                         if (router == null) {
2899                             mRouters.remove(i);
2900                         } else {
2901                             mTempCallbackRecords.addAll(router.mCallbackRecords);
2902                         }
2903                     }
2904 
2905                     final int callbackCount = mTempCallbackRecords.size();
2906                     for (int i = 0; i < callbackCount; i++) {
2907                         invokeCallback(mTempCallbackRecords.get(i), what, obj, arg);
2908                     }
2909                 } finally {
2910                     mTempCallbackRecords.clear();
2911                 }
2912             }
2913 
syncWithSystemProvider(int what, Object obj)2914             private void syncWithSystemProvider(int what, Object obj) {
2915                 switch (what) {
2916                     case MSG_ROUTE_ADDED:
2917                         mSystemProvider.onSyncRouteAdded((RouteInfo)obj);
2918                         break;
2919                     case MSG_ROUTE_REMOVED:
2920                         mSystemProvider.onSyncRouteRemoved((RouteInfo)obj);
2921                         break;
2922                     case MSG_ROUTE_CHANGED:
2923                         mSystemProvider.onSyncRouteChanged((RouteInfo)obj);
2924                         break;
2925                     case MSG_ROUTE_SELECTED:
2926                         mSystemProvider.onSyncRouteSelected((RouteInfo)obj);
2927                         break;
2928                 }
2929             }
2930 
invokeCallback(CallbackRecord record, int what, Object obj, int arg)2931             private void invokeCallback(CallbackRecord record, int what, Object obj, int arg) {
2932                 final MediaRouter router = record.mRouter;
2933                 final MediaRouter.Callback callback = record.mCallback;
2934                 switch (what & MSG_TYPE_MASK) {
2935                     case MSG_TYPE_ROUTE: {
2936                         final RouteInfo route = (RouteInfo)obj;
2937                         if (!record.filterRouteEvent(route)) {
2938                             break;
2939                         }
2940                         switch (what) {
2941                             case MSG_ROUTE_ADDED:
2942                                 callback.onRouteAdded(router, route);
2943                                 break;
2944                             case MSG_ROUTE_REMOVED:
2945                                 callback.onRouteRemoved(router, route);
2946                                 break;
2947                             case MSG_ROUTE_CHANGED:
2948                                 callback.onRouteChanged(router, route);
2949                                 break;
2950                             case MSG_ROUTE_VOLUME_CHANGED:
2951                                 callback.onRouteVolumeChanged(router, route);
2952                                 break;
2953                             case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
2954                                 callback.onRoutePresentationDisplayChanged(router, route);
2955                                 break;
2956                             case MSG_ROUTE_SELECTED:
2957                                 callback.onRouteSelected(router, route);
2958                                 break;
2959                             case MSG_ROUTE_UNSELECTED:
2960                                 callback.onRouteUnselected(router, route, arg);
2961                                 break;
2962                         }
2963                         break;
2964                     }
2965                     case MSG_TYPE_PROVIDER: {
2966                         final ProviderInfo provider = (ProviderInfo)obj;
2967                         switch (what) {
2968                             case MSG_PROVIDER_ADDED:
2969                                 callback.onProviderAdded(router, provider);
2970                                 break;
2971                             case MSG_PROVIDER_REMOVED:
2972                                 callback.onProviderRemoved(router, provider);
2973                                 break;
2974                             case MSG_PROVIDER_CHANGED:
2975                                 callback.onProviderChanged(router, provider);
2976                                 break;
2977                         }
2978                     }
2979                 }
2980             }
2981         }
2982     }
2983 }
2984