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