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