MediaRouter.java revision cb63b6ecac9786891514f241dec71695f09d3efb
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 event if they do not match the callback's associated media route selector.
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     * Ensures that calls into the media router are on the correct thread.
531     * It pays to be a little paranoid when global state invariants are at risk.
532     */
533    static void checkCallingThread() {
534        if (Looper.myLooper() != Looper.getMainLooper()) {
535            throw new IllegalStateException("The media router service must only be "
536                    + "accessed on the application's main thread.");
537        }
538    }
539
540    static <T> boolean equal(T a, T b) {
541        return a == b || (a != null && b != null && a.equals(b));
542    }
543
544    /**
545     * Provides information about a media route.
546     * <p>
547     * Each media route has a list of {@link MediaControlIntent media control}
548     * {@link #getControlFilters intent filters} that describe the capabilities of the
549     * route and the manner in which it is used and controlled.
550     * </p>
551     */
552    public static final class RouteInfo {
553        private final ProviderInfo mProvider;
554        private final String mDescriptorId;
555        private final String mUniqueId;
556        private String mName;
557        private String mDescription;
558        private boolean mEnabled;
559        private boolean mConnecting;
560        private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>();
561        private int mPlaybackType;
562        private int mPlaybackStream;
563        private int mVolumeHandling;
564        private int mVolume;
565        private int mVolumeMax;
566        private Display mPresentationDisplay;
567        private int mPresentationDisplayId = -1;
568        private Bundle mExtras;
569        private MediaRouteDescriptor mDescriptor;
570
571        /**
572         * The default playback type, "local", indicating the presentation of the media
573         * is happening on the same device (e.g. a phone, a tablet) as where it is
574         * controlled from.
575         *
576         * @see #getPlaybackType
577         */
578        public static final int PLAYBACK_TYPE_LOCAL = 0;
579
580        /**
581         * A playback type indicating the presentation of the media is happening on
582         * a different device (i.e. the remote device) than where it is controlled from.
583         *
584         * @see #getPlaybackType
585         */
586        public static final int PLAYBACK_TYPE_REMOTE = 1;
587
588        /**
589         * Playback information indicating the playback volume is fixed, i.e. it cannot be
590         * controlled from this object. An example of fixed playback volume is a remote player,
591         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
592         * than attenuate at the source.
593         *
594         * @see #getVolumeHandling
595         */
596        public static final int PLAYBACK_VOLUME_FIXED = 0;
597
598        /**
599         * Playback information indicating the playback volume is variable and can be controlled
600         * from this object.
601         *
602         * @see #getVolumeHandling
603         */
604        public static final int PLAYBACK_VOLUME_VARIABLE = 1;
605
606        static final int CHANGE_GENERAL = 1 << 0;
607        static final int CHANGE_VOLUME = 1 << 1;
608        static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
609
610        RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) {
611            mProvider = provider;
612            mDescriptorId = descriptorId;
613            mUniqueId = uniqueId;
614        }
615
616        /**
617         * Gets information about the provider of this media route.
618         */
619        public ProviderInfo getProvider() {
620            return mProvider;
621        }
622
623        /**
624         * Gets the unique id of the route.
625         * <p>
626         * The route unique id functions as a stable identifier by which the route is known.
627         * For example, an application can use this id as a token to remember the
628         * selected route across restarts or to communicate its identity to a service.
629         * </p>
630         *
631         * @return The unique id of the route, never null.
632         */
633        public String getId() {
634            return mUniqueId;
635        }
636
637        /**
638         * Gets the user-visible name of the route.
639         * <p>
640         * The route name identifies the destination represented by the route.
641         * It may be a user-supplied name, an alias, or device serial number.
642         * </p>
643         *
644         * @return The user-visible name of a media route.  This is the string presented
645         * to users who may select this as the active route.
646         */
647        public String getName() {
648            return mName;
649        }
650
651        /**
652         * Gets the user-visible description of the route.
653         * <p>
654         * The route description describes the kind of destination represented by the route.
655         * It may be a user-supplied string, a model number or brand of device.
656         * </p>
657         *
658         * @return The description of the route, or null if none.
659         */
660        public String getDescription() {
661            return mDescription;
662        }
663
664        /**
665         * Returns true if this route is enabled and may be selected.
666         *
667         * @return True if this route is enabled.
668         */
669        public boolean isEnabled() {
670            return mEnabled;
671        }
672
673        /**
674         * Returns true if the route is in the process of connecting and is not
675         * yet ready for use.
676         *
677         * @return True if this route is in the process of connecting.
678         */
679        public boolean isConnecting() {
680            return mConnecting;
681        }
682
683        /**
684         * Returns true if this route is currently selected.
685         *
686         * @return True if this route is currently selected.
687         *
688         * @see MediaRouter#getSelectedRoute
689         */
690        public boolean isSelected() {
691            checkCallingThread();
692            return sGlobal.getSelectedRoute() == this;
693        }
694
695        /**
696         * Returns true if this route is the default route.
697         *
698         * @return True if this route is the default route.
699         *
700         * @see MediaRouter#getDefaultRoute
701         */
702        public boolean isDefault() {
703            checkCallingThread();
704            return sGlobal.getDefaultRoute() == this;
705        }
706
707        /**
708         * Gets a list of {@link MediaControlIntent media control intent} filters that
709         * describe the capabilities of this route and the media control actions that
710         * it supports.
711         *
712         * @return A list of intent filters that specifies the media control intents that
713         * this route supports.
714         *
715         * @see MediaControlIntent
716         * @see #supportsControlCategory
717         * @see #supportsControlRequest
718         */
719        public List<IntentFilter> getControlFilters() {
720            return mControlFilters;
721        }
722
723        /**
724         * Returns true if the route supports at least one of the capabilities
725         * described by a media route selector.
726         *
727         * @param selector The selector that specifies the capabilities to check.
728         * @return True if the route supports at least one of the capabilities
729         * described in the media route selector.
730         */
731        public boolean matchesSelector(MediaRouteSelector selector) {
732            if (selector == null) {
733                throw new IllegalArgumentException("selector must not be null");
734            }
735            checkCallingThread();
736            return selector.matchesControlFilters(mControlFilters);
737        }
738
739        /**
740         * Returns true if the route supports the specified
741         * {@link MediaControlIntent media control} category.
742         * <p>
743         * Media control categories describe the capabilities of this route
744         * such as whether it supports live audio streaming or remote playback.
745         * </p>
746         *
747         * @param category A {@link MediaControlIntent media control} category
748         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
749         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
750         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
751         * media control category.
752         * @return True if the route supports the specified intent category.
753         *
754         * @see MediaControlIntent
755         * @see #getControlFilters
756         */
757        public boolean supportsControlCategory(String category) {
758            if (category == null) {
759                throw new IllegalArgumentException("category must not be null");
760            }
761            checkCallingThread();
762
763            int count = mControlFilters.size();
764            for (int i = 0; i < count; i++) {
765                if (mControlFilters.get(i).hasCategory(category)) {
766                    return true;
767                }
768            }
769            return false;
770        }
771
772        /**
773         * Returns true if the route supports the specified
774         * {@link MediaControlIntent media control} request.
775         * <p>
776         * Media control requests are used to request the route to perform
777         * actions such as starting remote playback of a media item.
778         * </p>
779         *
780         * @param intent A {@link MediaControlIntent media control intent}.
781         * @return True if the route can handle the specified intent.
782         *
783         * @see MediaControlIntent
784         * @see #getControlFilters
785         */
786        public boolean supportsControlRequest(Intent intent) {
787            if (intent == null) {
788                throw new IllegalArgumentException("intent must not be null");
789            }
790            checkCallingThread();
791
792            ContentResolver contentResolver = sGlobal.getContentResolver();
793            int count = mControlFilters.size();
794            for (int i = 0; i < count; i++) {
795                if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
796                    return true;
797                }
798            }
799            return false;
800        }
801
802        /**
803         * Sends a {@link MediaControlIntent media control} request to be performed
804         * asynchronously by the route's destination.
805         * <p>
806         * Media control requests are used to request the route to perform
807         * actions such as starting remote playback of a media item.
808         * </p><p>
809         * This function may only be called on a selected route.  Control requests
810         * sent to unselected routes will fail.
811         * </p>
812         *
813         * @param intent A {@link MediaControlIntent media control intent}.
814         * @param callback A {@link ControlRequestCallback} to invoke with the result
815         * of the request, or null if no result is required.
816         *
817         * @see MediaControlIntent
818         */
819        public void sendControlRequest(Intent intent, ControlRequestCallback callback) {
820            if (intent == null) {
821                throw new IllegalArgumentException("intent must not be null");
822            }
823            checkCallingThread();
824
825            sGlobal.sendControlRequest(this, intent, callback);
826        }
827
828        /**
829         * Gets the type of playback associated with this route.
830         *
831         * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
832         * or {@link #PLAYBACK_TYPE_REMOTE}.
833         */
834        public int getPlaybackType() {
835            return mPlaybackType;
836        }
837
838        /**
839         * Gets the audio stream over which the playback associated with this route is performed.
840         *
841         * @return The stream over which the playback associated with this route is performed.
842         */
843        public int getPlaybackStream() {
844            return mPlaybackStream;
845        }
846
847        /**
848         * Gets information about how volume is handled on the route.
849         *
850         * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
851         * or {@link #PLAYBACK_VOLUME_VARIABLE}.
852         */
853        public int getVolumeHandling() {
854            return mVolumeHandling;
855        }
856
857        /**
858         * Gets the current volume for this route. Depending on the route, this may only
859         * be valid if the route is currently selected.
860         *
861         * @return The volume at which the playback associated with this route is performed.
862         */
863        public int getVolume() {
864            return mVolume;
865        }
866
867        /**
868         * Gets the maximum volume at which the playback associated with this route is performed.
869         *
870         * @return The maximum volume at which the playback associated with
871         * this route is performed.
872         */
873        public int getVolumeMax() {
874            return mVolumeMax;
875        }
876
877        /**
878         * Requests a volume change for this route asynchronously.
879         * <p>
880         * This function may only be called on a selected route.  It will have
881         * no effect if the route is currently unselected.
882         * </p>
883         *
884         * @param volume The new volume value between 0 and {@link #getVolumeMax}.
885         */
886        public void requestSetVolume(int volume) {
887            checkCallingThread();
888            sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
889        }
890
891        /**
892         * Requests an incremental volume update for this route asynchronously.
893         * <p>
894         * This function may only be called on a selected route.  It will have
895         * no effect if the route is currently unselected.
896         * </p>
897         *
898         * @param delta The delta to add to the current volume.
899         */
900        public void requestUpdateVolume(int delta) {
901            checkCallingThread();
902            if (delta != 0) {
903                sGlobal.requestUpdateVolume(this, delta);
904            }
905        }
906
907        /**
908         * Gets the {@link Display} that should be used by the application to show
909         * a {@link android.app.Presentation} on an external display when this route is selected.
910         * Depending on the route, this may only be valid if the route is currently
911         * selected.
912         * <p>
913         * The preferred presentation display may change independently of the route
914         * being selected or unselected.  For example, the presentation display
915         * of the default system route may change when an external HDMI display is connected
916         * or disconnected even though the route itself has not changed.
917         * </p><p>
918         * This method may return null if there is no external display associated with
919         * the route or if the display is not ready to show UI yet.
920         * </p><p>
921         * The application should listen for changes to the presentation display
922         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
923         * show or dismiss its {@link android.app.Presentation} accordingly when the display
924         * becomes available or is removed.
925         * </p><p>
926         * This method only makes sense for
927         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
928         * </p>
929         *
930         * @return The preferred presentation display to use when this route is
931         * selected or null if none.
932         *
933         * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
934         * @see android.app.Presentation
935         */
936        public Display getPresentationDisplay() {
937            checkCallingThread();
938            if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
939                mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
940            }
941            return mPresentationDisplay;
942        }
943
944        /**
945         * Gets a collection of extra properties about this route that were supplied
946         * by its media route provider, or null if none.
947         */
948        public Bundle getExtras() {
949            return mExtras;
950        }
951
952        /**
953         * Selects this media route.
954         */
955        public void select() {
956            checkCallingThread();
957            sGlobal.selectRoute(this);
958        }
959
960        @Override
961        public String toString() {
962            return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
963                    + ", name=" + mName
964                    + ", description=" + mDescription
965                    + ", enabled=" + mEnabled
966                    + ", connecting=" + mConnecting
967                    + ", playbackType=" + mPlaybackType
968                    + ", playbackStream=" + mPlaybackStream
969                    + ", volumeHandling=" + mVolumeHandling
970                    + ", volume=" + mVolume
971                    + ", volumeMax=" + mVolumeMax
972                    + ", presentationDisplayId=" + mPresentationDisplayId
973                    + ", extras=" + mExtras
974                    + ", providerPackageName=" + mProvider.getPackageName()
975                    + " }";
976        }
977
978        int updateDescriptor(MediaRouteDescriptor descriptor) {
979            int changes = 0;
980            if (mDescriptor != descriptor) {
981                mDescriptor = descriptor;
982                if (descriptor != null) {
983                    if (!equal(mName, descriptor.getName())) {
984                        mName = descriptor.getName();
985                        changes |= CHANGE_GENERAL;
986                    }
987                    if (!equal(mDescription, descriptor.getDescription())) {
988                        mDescription = descriptor.getDescription();
989                        changes |= CHANGE_GENERAL;
990                    }
991                    if (mEnabled != descriptor.isEnabled()) {
992                        mEnabled = descriptor.isEnabled();
993                        changes |= CHANGE_GENERAL;
994                    }
995                    if (mConnecting != descriptor.isConnecting()) {
996                        mConnecting = descriptor.isConnecting();
997                        changes |= CHANGE_GENERAL;
998                    }
999                    if (!mControlFilters.equals(descriptor.getControlFilters())) {
1000                        mControlFilters.clear();
1001                        mControlFilters.addAll(descriptor.getControlFilters());
1002                        changes |= CHANGE_GENERAL;
1003                    }
1004                    if (mPlaybackType != descriptor.getPlaybackType()) {
1005                        mPlaybackType = descriptor.getPlaybackType();
1006                        changes |= CHANGE_GENERAL;
1007                    }
1008                    if (mPlaybackStream != descriptor.getPlaybackStream()) {
1009                        mPlaybackStream = descriptor.getPlaybackStream();
1010                        changes |= CHANGE_GENERAL;
1011                    }
1012                    if (mVolumeHandling != descriptor.getVolumeHandling()) {
1013                        mVolumeHandling = descriptor.getVolumeHandling();
1014                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1015                    }
1016                    if (mVolume != descriptor.getVolume()) {
1017                        mVolume = descriptor.getVolume();
1018                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1019                    }
1020                    if (mVolumeMax != descriptor.getVolumeMax()) {
1021                        mVolumeMax = descriptor.getVolumeMax();
1022                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1023                    }
1024                    if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
1025                        mPresentationDisplayId = descriptor.getPresentationDisplayId();
1026                        mPresentationDisplay = null;
1027                        changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
1028                    }
1029                    if (!equal(mExtras, descriptor.getExtras())) {
1030                        mExtras = descriptor.getExtras();
1031                        changes |= CHANGE_GENERAL;
1032                    }
1033                }
1034            }
1035            return changes;
1036        }
1037
1038        String getDescriptorId() {
1039            return mDescriptorId;
1040        }
1041
1042        MediaRouteProvider getProviderInstance() {
1043            return mProvider.getProviderInstance();
1044        }
1045    }
1046
1047    /**
1048     * Provides information about a media route provider.
1049     * <p>
1050     * This object may be used to determine which media route provider has
1051     * published a particular route.
1052     * </p>
1053     */
1054    public static final class ProviderInfo {
1055        private final MediaRouteProvider mProviderInstance;
1056        private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
1057
1058        private final ProviderMetadata mMetadata;
1059        private MediaRouteProviderDescriptor mDescriptor;
1060        private Resources mResources;
1061        private boolean mResourcesNotAvailable;
1062
1063        ProviderInfo(MediaRouteProvider provider) {
1064            mProviderInstance = provider;
1065            mMetadata = provider.getMetadata();
1066        }
1067
1068        /**
1069         * Gets the provider's underlying {@link MediaRouteProvider} instance.
1070         */
1071        public MediaRouteProvider getProviderInstance() {
1072            checkCallingThread();
1073            return mProviderInstance;
1074        }
1075
1076        /**
1077         * Gets the package name of the media route provider service.
1078         */
1079        public String getPackageName() {
1080            return mMetadata.getPackageName();
1081        }
1082
1083        /**
1084         * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
1085         */
1086        public List<RouteInfo> getRoutes() {
1087            checkCallingThread();
1088            return mRoutes;
1089        }
1090
1091        Resources getResources() {
1092            if (mResources == null && !mResourcesNotAvailable) {
1093                String packageName = getPackageName();
1094                Context context = sGlobal.getProviderContext(packageName);
1095                if (context != null) {
1096                    mResources = context.getResources();
1097                } else {
1098                    Log.w(TAG, "Unable to obtain resources for route provider package: "
1099                            + packageName);
1100                    mResourcesNotAvailable = true;
1101                }
1102            }
1103            return mResources;
1104        }
1105
1106        boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
1107            if (mDescriptor != descriptor) {
1108                mDescriptor = descriptor;
1109                return true;
1110            }
1111            return false;
1112        }
1113
1114        int findRouteByDescriptorId(String id) {
1115            final int count = mRoutes.size();
1116            for (int i = 0; i < count; i++) {
1117                if (mRoutes.get(i).mDescriptorId.equals(id)) {
1118                    return i;
1119                }
1120            }
1121            return -1;
1122        }
1123
1124        @Override
1125        public String toString() {
1126            return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
1127                    + " }";
1128        }
1129    }
1130
1131    /**
1132     * Interface for receiving events about media routing changes.
1133     * All methods of this interface will be called from the application's main thread.
1134     * <p>
1135     * A Callback will only receive events relevant to routes that the callback
1136     * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
1137     * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
1138     * </p>
1139     *
1140     * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
1141     * @see MediaRouter#removeCallback(Callback)
1142     */
1143    public static abstract class Callback {
1144        /**
1145         * Called when the supplied media route becomes selected as the active route.
1146         *
1147         * @param router The media router reporting the event.
1148         * @param route The route that has been selected.
1149         */
1150        public void onRouteSelected(MediaRouter router, RouteInfo route) {
1151        }
1152
1153        /**
1154         * Called when the supplied media route becomes unselected as the active route.
1155         *
1156         * @param router The media router reporting the event.
1157         * @param route The route that has been unselected.
1158         */
1159        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
1160        }
1161
1162        /**
1163         * Called when a media route has been added.
1164         *
1165         * @param router The media router reporting the event.
1166         * @param route The route that has become available for use.
1167         */
1168        public void onRouteAdded(MediaRouter router, RouteInfo route) {
1169        }
1170
1171        /**
1172         * Called when a media route has been removed.
1173         *
1174         * @param router The media router reporting the event.
1175         * @param route The route that has been removed from availability.
1176         */
1177        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
1178        }
1179
1180        /**
1181         * Called when a property of the indicated media route has changed.
1182         *
1183         * @param router The media router reporting the event.
1184         * @param route The route that was changed.
1185         */
1186        public void onRouteChanged(MediaRouter router, RouteInfo route) {
1187        }
1188
1189        /**
1190         * Called when a media route's volume changes.
1191         *
1192         * @param router The media router reporting the event.
1193         * @param route The route whose volume changed.
1194         */
1195        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
1196        }
1197
1198        /**
1199         * Called when a media route's presentation display changes.
1200         * <p>
1201         * This method is called whenever the route's presentation display becomes
1202         * available, is removed or has changes to some of its properties (such as its size).
1203         * </p>
1204         *
1205         * @param router The media router reporting the event.
1206         * @param route The route whose presentation display changed.
1207         *
1208         * @see RouteInfo#getPresentationDisplay()
1209         */
1210        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
1211        }
1212
1213        /**
1214         * Called when a media route provider has been added.
1215         *
1216         * @param router The media router reporting the event.
1217         * @param provider The provider that has become available for use.
1218         */
1219        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
1220        }
1221
1222        /**
1223         * Called when a media route provider has been removed.
1224         *
1225         * @param router The media router reporting the event.
1226         * @param provider The provider that has been removed from availability.
1227         */
1228        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
1229        }
1230
1231        /**
1232         * Called when a property of the indicated media route provider has changed.
1233         *
1234         * @param router The media router reporting the event.
1235         * @param provider The provider that was changed.
1236         */
1237        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
1238        }
1239    }
1240
1241    /**
1242     * Callback which is invoked with the result of a media control request.
1243     *
1244     * @see RouteInfo#sendControlRequest
1245     */
1246    public static abstract class ControlRequestCallback {
1247        /**
1248         * Called when a media control request succeeds.
1249         *
1250         * @param data Result data, or null if none.
1251         * Contents depend on the {@link MediaControlIntent media control action}.
1252         */
1253        public void onResult(Bundle data) {
1254        }
1255
1256        /**
1257         * Called when a media control request fails.
1258         *
1259         * @param error A localized error message which may be shown to the user, or null
1260         * if the cause of the error is unclear.
1261         * @param data Error data, or null if none.
1262         * Contents depend on the {@link MediaControlIntent media control action}.
1263         */
1264        public void onError(String error, Bundle data) {
1265        }
1266    }
1267
1268    private static final class CallbackRecord {
1269        public final MediaRouter mRouter;
1270        public final Callback mCallback;
1271        public MediaRouteSelector mSelector;
1272        public int mFlags;
1273
1274        public CallbackRecord(MediaRouter router, Callback callback) {
1275            mRouter = router;
1276            mCallback = callback;
1277            mSelector = MediaRouteSelector.EMPTY;
1278        }
1279
1280        public boolean filterRouteEvent(RouteInfo route) {
1281            return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
1282                    || route.matchesSelector(mSelector);
1283        }
1284    }
1285
1286    /**
1287     * Global state for the media router.
1288     * <p>
1289     * Media routes and media route providers are global to the process; their
1290     * state and the bulk of the media router implementation lives here.
1291     * </p>
1292     */
1293    private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback {
1294        private final Context mApplicationContext;
1295        private final MediaRouter mApplicationRouter;
1296        private final ArrayList<WeakReference<MediaRouter>> mRouters =
1297                new ArrayList<WeakReference<MediaRouter>>();
1298        private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
1299        private final ArrayList<ProviderInfo> mProviders =
1300                new ArrayList<ProviderInfo>();
1301        private final ProviderCallback mProviderCallback = new ProviderCallback();
1302        private final CallbackHandler mCallbackHandler = new CallbackHandler();
1303        private final DisplayManagerCompat mDisplayManager;
1304        private final SystemMediaRouteProvider mSystemProvider;
1305
1306        private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
1307        private RouteInfo mDefaultRoute;
1308        private RouteInfo mSelectedRoute;
1309        private MediaRouteProvider.RouteController mSelectedRouteController;
1310        private MediaRouteDiscoveryRequest mDiscoveryRequest;
1311
1312        GlobalMediaRouter(Context applicationContext) {
1313            mApplicationContext = applicationContext;
1314            mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
1315            mApplicationRouter = getRouter(applicationContext);
1316
1317            // Add the system media route provider for interoperating with
1318            // the framework media router.  This one is special and receives
1319            // synchronization messages from the media router.
1320            mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
1321            addProvider(mSystemProvider);
1322        }
1323
1324        public void start() {
1325            // Start watching for routes published by registered media route
1326            // provider services.
1327            mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
1328                    mApplicationContext, mApplicationRouter);
1329            mRegisteredProviderWatcher.start();
1330        }
1331
1332        public MediaRouter getRouter(Context context) {
1333            MediaRouter router;
1334            for (int i = mRouters.size(); --i >= 0; ) {
1335                router = mRouters.get(i).get();
1336                if (router == null) {
1337                    mRouters.remove(i);
1338                } else if (router.mContext == context) {
1339                    return router;
1340                }
1341            }
1342            router = new MediaRouter(context);
1343            mRouters.add(new WeakReference<MediaRouter>(router));
1344            return router;
1345        }
1346
1347        public ContentResolver getContentResolver() {
1348            return mApplicationContext.getContentResolver();
1349        }
1350
1351        public Context getProviderContext(String packageName) {
1352            if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
1353                return mApplicationContext;
1354            }
1355            try {
1356                return mApplicationContext.createPackageContext(
1357                        packageName, Context.CONTEXT_RESTRICTED);
1358            } catch (NameNotFoundException ex) {
1359                return null;
1360            }
1361        }
1362
1363        public Display getDisplay(int displayId) {
1364            return mDisplayManager.getDisplay(displayId);
1365        }
1366
1367        public void sendControlRequest(RouteInfo route,
1368                Intent intent, ControlRequestCallback callback) {
1369            if (route == mSelectedRoute && mSelectedRouteController != null) {
1370                if (mSelectedRouteController.onControlRequest(intent, callback)) {
1371                    return;
1372                }
1373            }
1374            if (callback != null) {
1375                callback.onError(null, null);
1376            }
1377        }
1378
1379        public void requestSetVolume(RouteInfo route, int volume) {
1380            if (route == mSelectedRoute && mSelectedRouteController != null) {
1381                mSelectedRouteController.onSetVolume(volume);
1382            }
1383        }
1384
1385        public void requestUpdateVolume(RouteInfo route, int delta) {
1386            if (route == mSelectedRoute && mSelectedRouteController != null) {
1387                mSelectedRouteController.onUpdateVolume(delta);
1388            }
1389        }
1390
1391        public List<RouteInfo> getRoutes() {
1392            return mRoutes;
1393        }
1394
1395        public List<ProviderInfo> getProviders() {
1396            return mProviders;
1397        }
1398
1399        public RouteInfo getDefaultRoute() {
1400            if (mDefaultRoute == null) {
1401                // This should never happen once the media router has been fully
1402                // initialized but it is good to check for the error in case there
1403                // is a bug in provider initialization.
1404                throw new IllegalStateException("There is no default route.  "
1405                        + "The media router has not yet been fully initialized.");
1406            }
1407            return mDefaultRoute;
1408        }
1409
1410        public RouteInfo getSelectedRoute() {
1411            if (mSelectedRoute == null) {
1412                // This should never happen once the media router has been fully
1413                // initialized but it is good to check for the error in case there
1414                // is a bug in provider initialization.
1415                throw new IllegalStateException("There is no currently selected route.  "
1416                        + "The media router has not yet been fully initialized.");
1417            }
1418            return mSelectedRoute;
1419        }
1420
1421        public void selectRoute(RouteInfo route) {
1422            if (!mRoutes.contains(route)) {
1423                Log.w(TAG, "Ignoring attempt to select removed route: " + route);
1424                return;
1425            }
1426            if (!route.mEnabled) {
1427                Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
1428                return;
1429            }
1430
1431            setSelectedRouteInternal(route);
1432        }
1433
1434        public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
1435            // Check whether any existing routes match the selector.
1436            final int routeCount = mRoutes.size();
1437            for (int i = 0; i < routeCount; i++) {
1438                RouteInfo route = mRoutes.get(i);
1439                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
1440                        && route.isDefault()) {
1441                    continue;
1442                }
1443                if (route.matchesSelector(selector)) {
1444                    return true;
1445                }
1446            }
1447
1448            // It doesn't look like we can find a matching route right now.
1449            return false;
1450        }
1451
1452        public void updateDiscoveryRequest() {
1453            // Combine all of the callback selectors and active scan flags.
1454            boolean discover = false;
1455            boolean activeScan = false;
1456            MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
1457            for (int i = mRouters.size(); --i >= 0; ) {
1458                MediaRouter router = mRouters.get(i).get();
1459                if (router == null) {
1460                    mRouters.remove(i);
1461                } else {
1462                    final int count = router.mCallbackRecords.size();
1463                    for (int j = 0; j < count; j++) {
1464                        CallbackRecord callback = router.mCallbackRecords.get(j);
1465                        builder.addSelector(callback.mSelector);
1466                        if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
1467                            activeScan = true;
1468                            discover = true; // perform active scan implies request discovery
1469                        }
1470                        if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
1471                            discover = true;
1472                        }
1473                    }
1474                }
1475            }
1476            MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
1477
1478            // Create a new discovery request.
1479            if (mDiscoveryRequest != null
1480                    && mDiscoveryRequest.getSelector().equals(selector)
1481                    && mDiscoveryRequest.isActiveScan() == activeScan) {
1482                return; // no change
1483            }
1484            if (selector.isEmpty() && !activeScan) {
1485                // Discovery is not needed.
1486                if (mDiscoveryRequest == null) {
1487                    return; // no change
1488                }
1489                mDiscoveryRequest = null;
1490            } else {
1491                // Discovery is needed.
1492                mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
1493            }
1494            if (DEBUG) {
1495                Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
1496            }
1497
1498            // Notify providers.
1499            final int providerCount = mProviders.size();
1500            for (int i = 0; i < providerCount; i++) {
1501                mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
1502            }
1503        }
1504
1505        public void addProvider(MediaRouteProvider providerInstance) {
1506            int index = findProviderInfo(providerInstance);
1507            if (index < 0) {
1508                // 1. Add the provider to the list.
1509                ProviderInfo provider = new ProviderInfo(providerInstance);
1510                mProviders.add(provider);
1511                if (DEBUG) {
1512                    Log.d(TAG, "Provider added: " + provider);
1513                }
1514                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
1515                // 2. Create the provider's contents.
1516                updateProviderContents(provider, providerInstance.getDescriptor());
1517                // 3. Register the provider callback.
1518                providerInstance.setCallback(mProviderCallback);
1519                // 4. Set the discovery request.
1520                providerInstance.setDiscoveryRequest(mDiscoveryRequest);
1521            }
1522        }
1523
1524        public void removeProvider(MediaRouteProvider providerInstance) {
1525            int index = findProviderInfo(providerInstance);
1526            if (index >= 0) {
1527                // 1. Unregister the provider callback.
1528                providerInstance.setCallback(null);
1529                // 2. Clear the discovery request.
1530                providerInstance.setDiscoveryRequest(null);
1531                // 3. Delete the provider's contents.
1532                ProviderInfo provider = mProviders.get(index);
1533                updateProviderContents(provider, null);
1534                // 4. Remove the provider from the list.
1535                if (DEBUG) {
1536                    Log.d(TAG, "Provider removed: " + provider);
1537                }
1538                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
1539                mProviders.remove(index);
1540            }
1541        }
1542
1543        private void updateProviderDescriptor(MediaRouteProvider providerInstance,
1544                MediaRouteProviderDescriptor descriptor) {
1545            int index = findProviderInfo(providerInstance);
1546            if (index >= 0) {
1547                // Update the provider's contents.
1548                ProviderInfo provider = mProviders.get(index);
1549                updateProviderContents(provider, descriptor);
1550            }
1551        }
1552
1553        private int findProviderInfo(MediaRouteProvider providerInstance) {
1554            final int count = mProviders.size();
1555            for (int i = 0; i < count; i++) {
1556                if (mProviders.get(i).mProviderInstance == providerInstance) {
1557                    return i;
1558                }
1559            }
1560            return -1;
1561        }
1562
1563        private void updateProviderContents(ProviderInfo provider,
1564                MediaRouteProviderDescriptor providerDescriptor) {
1565            if (provider.updateDescriptor(providerDescriptor)) {
1566                // Update all existing routes and reorder them to match
1567                // the order of their descriptors.
1568                int targetIndex = 0;
1569                if (providerDescriptor != null) {
1570                    if (providerDescriptor.isValid()) {
1571                        final List<MediaRouteDescriptor> routeDescriptors =
1572                                providerDescriptor.getRoutes();
1573                        final int routeCount = routeDescriptors.size();
1574                        for (int i = 0; i < routeCount; i++) {
1575                            final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
1576                            final String id = routeDescriptor.getId();
1577                            final int sourceIndex = provider.findRouteByDescriptorId(id);
1578                            if (sourceIndex < 0) {
1579                                // 1. Add the route to the list.
1580                                String uniqueId = assignRouteUniqueId(provider, id);
1581                                RouteInfo route = new RouteInfo(provider, id, uniqueId);
1582                                provider.mRoutes.add(targetIndex++, route);
1583                                mRoutes.add(route);
1584                                // 2. Create the route's contents.
1585                                route.updateDescriptor(routeDescriptor);
1586                                // 3. Notify clients about addition.
1587                                if (DEBUG) {
1588                                    Log.d(TAG, "Route added: " + route);
1589                                }
1590                                mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
1591                            } else if (sourceIndex < targetIndex) {
1592                                Log.w(TAG, "Ignoring route descriptor with duplicate id: "
1593                                        + routeDescriptor);
1594                            } else {
1595                                // 1. Reorder the route within the list.
1596                                RouteInfo route = provider.mRoutes.get(sourceIndex);
1597                                Collections.swap(provider.mRoutes,
1598                                        sourceIndex, targetIndex++);
1599                                // 2. Update the route's contents.
1600                                int changes = route.updateDescriptor(routeDescriptor);
1601                                // 3. Unselect route if needed before notifying about changes.
1602                                unselectRouteIfNeeded(route);
1603                                // 4. Notify clients about changes.
1604                                if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
1605                                    if (DEBUG) {
1606                                        Log.d(TAG, "Route changed: " + route);
1607                                    }
1608                                    mCallbackHandler.post(
1609                                            CallbackHandler.MSG_ROUTE_CHANGED, route);
1610                                }
1611                                if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
1612                                    if (DEBUG) {
1613                                        Log.d(TAG, "Route volume changed: " + route);
1614                                    }
1615                                    mCallbackHandler.post(
1616                                            CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
1617                                }
1618                                if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
1619                                    if (DEBUG) {
1620                                        Log.d(TAG, "Route presentation display changed: "
1621                                                + route);
1622                                    }
1623                                    mCallbackHandler.post(CallbackHandler.
1624                                            MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
1625                                }
1626                            }
1627                        }
1628                    } else {
1629                        Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
1630                    }
1631                }
1632
1633                // Dispose all remaining routes that do not have matching descriptors.
1634                for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
1635                    // 1. Delete the route's contents.
1636                    RouteInfo route = provider.mRoutes.get(i);
1637                    route.updateDescriptor(null);
1638                    // 2. Remove the route from the list.
1639                    mRoutes.remove(route);
1640                    provider.mRoutes.remove(i);
1641                    // 3. Unselect route if needed before notifying about removal.
1642                    unselectRouteIfNeeded(route);
1643                    // 4. Notify clients about removal.
1644                    if (DEBUG) {
1645                        Log.d(TAG, "Route removed: " + route);
1646                    }
1647                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
1648                }
1649
1650                // Notify provider changed.
1651                if (DEBUG) {
1652                    Log.d(TAG, "Provider changed: " + provider);
1653                }
1654                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
1655
1656                // Choose a new selected route if needed.
1657                selectRouteIfNeeded();
1658            }
1659        }
1660
1661        private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
1662            // Although route descriptor ids are unique within a provider, it's
1663            // possible for there to be two providers with the same package name.
1664            // Therefore we must dedupe the composite id.
1665            String uniqueId = provider.getPackageName() + ":" + routeDescriptorId;
1666            if (findRouteByUniqueId(uniqueId) < 0) {
1667                return uniqueId;
1668            }
1669            for (int i = 2; ; i++) {
1670                String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
1671                if (findRouteByUniqueId(newUniqueId) < 0) {
1672                    return newUniqueId;
1673                }
1674            }
1675        }
1676
1677        private int findRouteByUniqueId(String uniqueId) {
1678            final int count = mRoutes.size();
1679            for (int i = 0; i < count; i++) {
1680                if (mRoutes.get(i).mUniqueId.equals(uniqueId)) {
1681                    return i;
1682                }
1683            }
1684            return -1;
1685        }
1686
1687        private void unselectRouteIfNeeded(RouteInfo route) {
1688            if (mDefaultRoute == route && !isRouteSelectable(route)) {
1689                Log.i(TAG, "Choosing a new default route because the current one "
1690                        + "is no longer selectable: " + route);
1691                mDefaultRoute = null;
1692            }
1693            if (mSelectedRoute == route && !isRouteSelectable(route)) {
1694                Log.i(TAG, "Choosing a new selected route because the current one "
1695                        + "is no longer selectable: " + route);
1696                setSelectedRouteInternal(null);
1697            }
1698        }
1699
1700        private void selectRouteIfNeeded() {
1701            if (mDefaultRoute == null && !mRoutes.isEmpty()) {
1702                for (RouteInfo route : mRoutes) {
1703                    if (isSystemDefaultRoute(route) && isRouteSelectable(route)) {
1704                        mDefaultRoute = route;
1705                        break;
1706                    }
1707                }
1708            }
1709            if (mSelectedRoute == null) {
1710                setSelectedRouteInternal(mDefaultRoute);
1711            }
1712        }
1713
1714        private boolean isRouteSelectable(RouteInfo route) {
1715            // This tests whether the route is still valid and enabled.
1716            // The route descriptor field is set to null when the route is removed.
1717            return route.mDescriptor != null && route.mEnabled;
1718        }
1719
1720        private boolean isSystemDefaultRoute(RouteInfo route) {
1721            return route.getProviderInstance() == mSystemProvider
1722                    && route.mDescriptorId.equals(
1723                            SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
1724        }
1725
1726        private void setSelectedRouteInternal(RouteInfo route) {
1727            if (mSelectedRoute != route) {
1728                if (mSelectedRoute != null) {
1729                    if (DEBUG) {
1730                        Log.d(TAG, "Route unselected: " + mSelectedRoute);
1731                    }
1732                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
1733                    if (mSelectedRouteController != null) {
1734                        mSelectedRouteController.onUnselect();
1735                        mSelectedRouteController.onRelease();
1736                        mSelectedRouteController = null;
1737                    }
1738                }
1739
1740                mSelectedRoute = route;
1741
1742                if (mSelectedRoute != null) {
1743                    mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
1744                            route.mDescriptorId);
1745                    if (mSelectedRouteController != null) {
1746                        mSelectedRouteController.onSelect();
1747                    }
1748                    if (DEBUG) {
1749                        Log.d(TAG, "Route selected: " + mSelectedRoute);
1750                    }
1751                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
1752                }
1753            }
1754        }
1755
1756        @Override
1757        public RouteInfo getSystemRouteByDescriptorId(String id) {
1758            int providerIndex = findProviderInfo(mSystemProvider);
1759            if (providerIndex >= 0) {
1760                ProviderInfo provider = mProviders.get(providerIndex);
1761                int routeIndex = provider.findRouteByDescriptorId(id);
1762                if (routeIndex >= 0) {
1763                    return provider.mRoutes.get(routeIndex);
1764                }
1765            }
1766            return null;
1767        }
1768
1769        private final class ProviderCallback extends MediaRouteProvider.Callback {
1770            @Override
1771            public void onDescriptorChanged(MediaRouteProvider provider,
1772                    MediaRouteProviderDescriptor descriptor) {
1773                updateProviderDescriptor(provider, descriptor);
1774            }
1775        }
1776
1777        private final class CallbackHandler extends Handler {
1778            private final ArrayList<CallbackRecord> mTempCallbackRecords =
1779                    new ArrayList<CallbackRecord>();
1780
1781            private static final int MSG_TYPE_MASK = 0xff00;
1782            private static final int MSG_TYPE_ROUTE = 0x0100;
1783            private static final int MSG_TYPE_PROVIDER = 0x0200;
1784
1785            public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
1786            public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
1787            public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
1788            public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
1789            public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
1790            public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
1791            public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
1792
1793            public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
1794            public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
1795            public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
1796
1797            public void post(int msg, Object obj) {
1798                obtainMessage(msg, obj).sendToTarget();
1799            }
1800
1801            @Override
1802            public void handleMessage(Message msg) {
1803                final int what = msg.what;
1804                final Object obj = msg.obj;
1805
1806                // Synchronize state with the system media router.
1807                syncWithSystemProvider(what, obj);
1808
1809                // Invoke all registered callbacks.
1810                // Build a list of callbacks before invoking them in case callbacks
1811                // are added or removed during dispatch.
1812                try {
1813                    for (int i = mRouters.size(); --i >= 0; ) {
1814                        MediaRouter router = mRouters.get(i).get();
1815                        if (router == null) {
1816                            mRouters.remove(i);
1817                        } else {
1818                            mTempCallbackRecords.addAll(router.mCallbackRecords);
1819                        }
1820                    }
1821
1822                    final int callbackCount = mTempCallbackRecords.size();
1823                    for (int i = 0; i < callbackCount; i++) {
1824                        invokeCallback(mTempCallbackRecords.get(i), what, obj);
1825                    }
1826                } finally {
1827                    mTempCallbackRecords.clear();
1828                }
1829            }
1830
1831            private void syncWithSystemProvider(int what, Object obj) {
1832                switch (what) {
1833                    case MSG_ROUTE_ADDED:
1834                        mSystemProvider.onSyncRouteAdded((RouteInfo)obj);
1835                        break;
1836                    case MSG_ROUTE_REMOVED:
1837                        mSystemProvider.onSyncRouteRemoved((RouteInfo)obj);
1838                        break;
1839                    case MSG_ROUTE_CHANGED:
1840                        mSystemProvider.onSyncRouteChanged((RouteInfo)obj);
1841                        break;
1842                    case MSG_ROUTE_SELECTED:
1843                        mSystemProvider.onSyncRouteSelected((RouteInfo)obj);
1844                        break;
1845                }
1846            }
1847
1848            private void invokeCallback(CallbackRecord record, int what, Object obj) {
1849                final MediaRouter router = record.mRouter;
1850                final MediaRouter.Callback callback = record.mCallback;
1851                switch (what & MSG_TYPE_MASK) {
1852                    case MSG_TYPE_ROUTE: {
1853                        final RouteInfo route = (RouteInfo)obj;
1854                        if (!record.filterRouteEvent(route)) {
1855                            break;
1856                        }
1857                        switch (what) {
1858                            case MSG_ROUTE_ADDED:
1859                                callback.onRouteAdded(router, route);
1860                                break;
1861                            case MSG_ROUTE_REMOVED:
1862                                callback.onRouteRemoved(router, route);
1863                                break;
1864                            case MSG_ROUTE_CHANGED:
1865                                callback.onRouteChanged(router, route);
1866                                break;
1867                            case MSG_ROUTE_VOLUME_CHANGED:
1868                                callback.onRouteVolumeChanged(router, route);
1869                                break;
1870                            case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
1871                                callback.onRoutePresentationDisplayChanged(router, route);
1872                                break;
1873                            case MSG_ROUTE_SELECTED:
1874                                callback.onRouteSelected(router, route);
1875                                break;
1876                            case MSG_ROUTE_UNSELECTED:
1877                                callback.onRouteUnselected(router, route);
1878                                break;
1879                        }
1880                        break;
1881                    }
1882                    case MSG_TYPE_PROVIDER: {
1883                        final ProviderInfo provider = (ProviderInfo)obj;
1884                        switch (what) {
1885                            case MSG_PROVIDER_ADDED:
1886                                callback.onProviderAdded(router, provider);
1887                                break;
1888                            case MSG_PROVIDER_REMOVED:
1889                                callback.onProviderRemoved(router, provider);
1890                                break;
1891                            case MSG_PROVIDER_CHANGED:
1892                                callback.onProviderChanged(router, provider);
1893                                break;
1894                        }
1895                    }
1896                }
1897            }
1898        }
1899    }
1900}
1901