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