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