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