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