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