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