MediaRouter.java revision 77367b4a1871417198d0399d9ad074314c758567
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.app.ActivityManager;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.IntentSender;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.res.Resources;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Looper;
32import android.os.Message;
33import android.support.annotation.IntDef;
34import android.support.annotation.NonNull;
35import android.support.annotation.Nullable;
36import android.support.v4.app.ActivityManagerCompat;
37import android.support.v4.hardware.display.DisplayManagerCompat;
38import android.support.v4.media.VolumeProviderCompat;
39import android.support.v4.media.session.MediaSessionCompat;
40import android.support.v4.util.Pair;
41import android.support.v7.media.MediaRouteProvider.ProviderMetadata;
42import android.support.v7.media.MediaRouteProvider.RouteController;
43import android.util.Log;
44import android.view.Display;
45
46import java.lang.annotation.Retention;
47import java.lang.annotation.RetentionPolicy;
48import java.lang.ref.WeakReference;
49import java.util.ArrayList;
50import java.util.Collections;
51import java.util.HashMap;
52import java.util.List;
53import java.util.Locale;
54import java.util.Map;
55
56/**
57 * MediaRouter allows applications to control the routing of media channels
58 * and streams from the current device to external speakers and destination devices.
59 * <p>
60 * A MediaRouter instance is retrieved through {@link #getInstance}.  Applications
61 * can query the media router about the currently selected route and its capabilities
62 * to determine how to send content to the route's destination.  Applications can
63 * also {@link RouteInfo#sendControlRequest send control requests} to the route
64 * to ask the route's destination to perform certain remote control functions
65 * such as playing media.
66 * </p><p>
67 * See also {@link MediaRouteProvider} for information on how an application
68 * can publish new media routes to the media router.
69 * </p><p>
70 * The media router API is not thread-safe; all interactions with it must be
71 * done from the main thread of the process.
72 * </p>
73 */
74public final class MediaRouter {
75    private static final String TAG = "MediaRouter";
76    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
77
78    /**
79     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
80     * when the reason the route was unselected is unknown.
81     */
82    public static final int UNSELECT_REASON_UNKNOWN = 0;
83    /**
84     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
85     * when the user pressed the disconnect button to disconnect and keep playing.
86     * <p>
87     *
88     * @see {@link MediaRouteDescriptor#canDisconnectAndKeepPlaying()}.
89     */
90    public static final int UNSELECT_REASON_DISCONNECTED = 1;
91    /**
92     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
93     * when the user pressed the stop casting button.
94     */
95    public static final int UNSELECT_REASON_STOPPED = 2;
96    /**
97     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
98     * when the user selected a different route.
99     */
100    public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
101
102    // Maintains global media router state for the process.
103    // This field is initialized in MediaRouter.getInstance() before any
104    // MediaRouter objects are instantiated so it is guaranteed to be
105    // valid whenever any instance method is invoked.
106    static GlobalMediaRouter sGlobal;
107
108    // Context-bound state of the media router.
109    final Context mContext;
110    final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
111
112    /** @hide */
113    @IntDef(flag = true,
114            value = {
115                    CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
116                    CALLBACK_FLAG_REQUEST_DISCOVERY,
117                    CALLBACK_FLAG_UNFILTERED_EVENTS
118            }
119    )
120    @Retention(RetentionPolicy.SOURCE)
121    private @interface CallbackFlags {}
122
123    /**
124     * Flag for {@link #addCallback}: Actively scan for routes while this callback
125     * is registered.
126     * <p>
127     * When this flag is specified, the media router will actively scan for new
128     * routes.  Certain routes, such as wifi display routes, may not be discoverable
129     * except when actively scanning.  This flag is typically used when the route picker
130     * dialog has been opened by the user to ensure that the route information is
131     * up to date.
132     * </p><p>
133     * Active scanning may consume a significant amount of power and may have intrusive
134     * effects on wireless connectivity.  Therefore it is important that active scanning
135     * only be requested when it is actually needed to satisfy a user request to
136     * discover and select a new route.
137     * </p><p>
138     * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing
139     * active scans is much more expensive than a normal discovery request.
140     * </p>
141     *
142     * @see #CALLBACK_FLAG_REQUEST_DISCOVERY
143     */
144    public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
145
146    /**
147     * Flag for {@link #addCallback}: Do not filter route events.
148     * <p>
149     * When this flag is specified, the callback will be invoked for events that affect any
150     * route even if they do not match the callback's filter.
151     * </p>
152     */
153    public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
154
155    /**
156     * Flag for {@link #addCallback}: Request passive route discovery while this
157     * callback is registered, except on {@link ActivityManager#isLowRamDevice low-RAM devices}.
158     * <p>
159     * When this flag is specified, the media router will try to discover routes.
160     * Although route discovery is intended to be efficient, checking for new routes may
161     * result in some network activity and could slowly drain the battery.  Therefore
162     * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when
163     * they are running in the foreground and would like to provide the user with the
164     * option of connecting to new routes.
165     * </p><p>
166     * Applications should typically add a callback using this flag in the
167     * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart}
168     * method and remove it in the {@link android.app.Activity#onStop onStop} method.
169     * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may
170     * also be used for this purpose.
171     * </p><p class="note">
172     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag
173     * will be ignored.  Refer to
174     * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
175     * </p>
176     *
177     * @see android.support.v7.app.MediaRouteDiscoveryFragment
178     */
179    public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
180
181    /**
182     * Flag for {@link #addCallback}: Request passive route discovery while this
183     * callback is registered, even on {@link ActivityManager#isLowRamDevice low-RAM devices}.
184     * <p class="note">
185     * This flag has a significant performance impact on low-RAM devices
186     * since it may cause many media route providers to be started simultaneously.
187     * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
188     * performing passive discovery on these devices altogether.  Refer to
189     * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
190     * </p>
191     *
192     * @see android.support.v7.app.MediaRouteDiscoveryFragment
193     */
194    public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 1 << 3;
195
196    /**
197     * Flag for {@link #isRouteAvailable}: Ignore the default route.
198     * <p>
199     * This flag is used to determine whether a matching non-default route is available.
200     * This constraint may be used to decide whether to offer the route chooser dialog
201     * to the user.  There is no point offering the chooser if there are no
202     * non-default choices.
203     * </p>
204     */
205    public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
206
207    /**
208     * Flag for {@link #isRouteAvailable}: Require an actual route to be matched.
209     * <p>
210     * If this flag is not set, then {@link #isRouteAvailable} will return true
211     * if it is possible to discover a matching route even if discovery is not in
212     * progress or if no matching route has yet been found.  This feature is used to
213     * save resources by removing the need to perform passive route discovery on
214     * {@link ActivityManager#isLowRamDevice low-RAM devices}.
215     * </p><p>
216     * If this flag is set, then {@link #isRouteAvailable} will only return true if
217     * a matching route has actually been discovered.
218     * </p>
219     */
220    public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1;
221
222    MediaRouter(Context context) {
223        mContext = context;
224    }
225
226    /**
227     * Gets an instance of the media router service associated with the context.
228     * <p>
229     * The application is responsible for holding a strong reference to the returned
230     * {@link MediaRouter} instance, such as by storing the instance in a field of
231     * the {@link android.app.Activity}, to ensure that the media router remains alive
232     * as long as the application is using its features.
233     * </p><p>
234     * In other words, the support library only holds a {@link WeakReference weak reference}
235     * to each media router instance.  When there are no remaining strong references to the
236     * media router instance, all of its callbacks will be removed and route discovery
237     * will no longer be performed on its behalf.
238     * </p>
239     *
240     * @return The media router instance for the context.  The application must hold
241     * a strong reference to this object as long as it is in use.
242     */
243    public static MediaRouter getInstance(@NonNull Context context) {
244        if (context == null) {
245            throw new IllegalArgumentException("context must not be null");
246        }
247        checkCallingThread();
248
249        if (sGlobal == null) {
250            sGlobal = new GlobalMediaRouter(context.getApplicationContext());
251            sGlobal.start();
252        }
253        return sGlobal.getRouter(context);
254    }
255
256    /**
257     * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
258     * this media router.
259     */
260    public List<RouteInfo> getRoutes() {
261        checkCallingThread();
262        return sGlobal.getRoutes();
263    }
264
265    /**
266     * Gets information about the {@link MediaRouter.ProviderInfo route providers}
267     * currently known to this media router.
268     */
269    public List<ProviderInfo> getProviders() {
270        checkCallingThread();
271        return sGlobal.getProviders();
272    }
273
274    /**
275     * Gets the default route for playing media content on the system.
276     * <p>
277     * The system always provides a default route.
278     * </p>
279     *
280     * @return The default route, which is guaranteed to never be null.
281     */
282    @NonNull
283    public RouteInfo getDefaultRoute() {
284        checkCallingThread();
285        return sGlobal.getDefaultRoute();
286    }
287
288    /**
289     * Gets the currently selected route.
290     * <p>
291     * The application should examine the route's
292     * {@link RouteInfo#getControlFilters media control intent filters} to assess the
293     * capabilities of the route before attempting to use it.
294     * </p>
295     *
296     * <h3>Example</h3>
297     * <pre>
298     * public boolean playMovie() {
299     *     MediaRouter mediaRouter = MediaRouter.getInstance(context);
300     *     MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
301     *
302     *     // First try using the remote playback interface, if supported.
303     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
304     *         // The route supports remote playback.
305     *         // Try to send it the Uri of the movie to play.
306     *         Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
307     *         intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
308     *         intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
309     *         if (route.supportsControlRequest(intent)) {
310     *             route.sendControlRequest(intent, null);
311     *             return true; // sent the request to play the movie
312     *         }
313     *     }
314     *
315     *     // If remote playback was not possible, then play locally.
316     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
317     *         // The route supports live video streaming.
318     *         // Prepare to play content locally in a window or in a presentation.
319     *         return playMovieInWindow();
320     *     }
321     *
322     *     // Neither interface is supported, so we can't play the movie to this route.
323     *     return false;
324     * }
325     * </pre>
326     *
327     * @return The selected route, which is guaranteed to never be null.
328     *
329     * @see RouteInfo#getControlFilters
330     * @see RouteInfo#supportsControlCategory
331     * @see RouteInfo#supportsControlRequest
332     */
333    @NonNull
334    public RouteInfo getSelectedRoute() {
335        checkCallingThread();
336        return sGlobal.getSelectedRoute();
337    }
338
339    /**
340     * Returns the selected route if it matches the specified selector, otherwise
341     * selects the default route and returns it.
342     *
343     * @param selector The selector to match.
344     * @return The previously selected route if it matched the selector, otherwise the
345     * newly selected default route which is guaranteed to never be null.
346     *
347     * @see MediaRouteSelector
348     * @see RouteInfo#matchesSelector
349     * @see RouteInfo#isDefault
350     */
351    @NonNull
352    public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
353        if (selector == null) {
354            throw new IllegalArgumentException("selector must not be null");
355        }
356        checkCallingThread();
357
358        if (DEBUG) {
359            Log.d(TAG, "updateSelectedRoute: " + selector);
360        }
361        RouteInfo route = sGlobal.getSelectedRoute();
362        if (!route.isDefault() && !route.matchesSelector(selector)) {
363            route = sGlobal.getDefaultRoute();
364            sGlobal.selectRoute(route);
365        }
366        return route;
367    }
368
369    /**
370     * Selects the specified route.
371     *
372     * @param route The route to select.
373     */
374    public void selectRoute(@NonNull RouteInfo route) {
375        if (route == null) {
376            throw new IllegalArgumentException("route must not be null");
377        }
378        checkCallingThread();
379
380        if (DEBUG) {
381            Log.d(TAG, "selectRoute: " + route);
382        }
383        sGlobal.selectRoute(route);
384    }
385
386    /**
387     * Unselects the current round and selects the default route instead.
388     * <p>
389     * The reason given must be one of:
390     * <ul>
391     * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
392     * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
393     * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
394     * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
395     * </ul>
396     *
397     * @param reason The reason for disconnecting the current route.
398     */
399    public void unselect(int reason) {
400        if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN ||
401                reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
402            throw new IllegalArgumentException("Unsupported reason to unselect route");
403        }
404        checkCallingThread();
405
406        sGlobal.selectRoute(getDefaultRoute(), reason);
407    }
408
409    /**
410     * Returns true if there is a route that matches the specified selector.
411     * <p>
412     * This method returns true if there are any available routes that match the
413     * selector regardless of whether they are enabled or disabled. If the
414     * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
415     * the method will only consider non-default routes.
416     * </p>
417     * <p class="note">
418     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
419     * will return true if it is possible to discover a matching route even if
420     * discovery is not in progress or if no matching route has yet been found.
421     * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
422     * </p>
423     *
424     * @param selector The selector to match.
425     * @param flags Flags to control the determination of whether a route may be
426     *            available. May be zero or some combination of
427     *            {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
428     *            {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
429     * @return True if a matching route may be available.
430     */
431    public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
432        if (selector == null) {
433            throw new IllegalArgumentException("selector must not be null");
434        }
435        checkCallingThread();
436
437        return sGlobal.isRouteAvailable(selector, flags);
438    }
439
440    /**
441     * Registers a callback to discover routes that match the selector and to receive
442     * events when they change.
443     * <p>
444     * This is a convenience method that has the same effect as calling
445     * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
446     * </p>
447     *
448     * @param selector A route selector that indicates the kinds of routes that the
449     * callback would like to discover.
450     * @param callback The callback to add.
451     * @see #removeCallback
452     */
453    public void addCallback(MediaRouteSelector selector, Callback callback) {
454        addCallback(selector, callback, 0);
455    }
456
457    /**
458     * Registers a callback to discover routes that match the selector and to receive
459     * events when they change.
460     * <p>
461     * The selector describes the kinds of routes that the application wants to
462     * discover.  For example, if the application wants to use
463     * live audio routes then it should include the
464     * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
465     * in its selector when it adds a callback to the media router.
466     * The selector may include any number of categories.
467     * </p><p>
468     * If the callback has already been registered, then the selector is added to
469     * the set of selectors being monitored by the callback.
470     * </p><p>
471     * By default, the callback will only be invoked for events that affect routes
472     * that match the specified selector.  Event filtering may be disabled by specifying
473     * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
474     * </p><p>
475     * Applications should use the {@link #isRouteAvailable} method to determine
476     * whether is it possible to discover a route with the desired capabilities
477     * and therefore whether the media route button should be shown to the user.
478     * </p><p>
479     * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application
480     * is in the foreground to request that passive discovery be performed if there are
481     * sufficient resources to allow continuous passive discovery.
482     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be
483     * ignored to conserve resources.
484     * </p><p>
485     * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when
486     * passive discovery absolutely must be performed, even on low-RAM devices.
487     * This flag has a significant performance impact on low-RAM devices
488     * since it may cause many media route providers to be started simultaneously.
489     * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
490     * performing passive discovery on these devices altogether.
491     * </p><p>
492     * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the
493     * media route chooser dialog is showing to confirm the presence of available
494     * routes that the user may connect to.  This flag may use substantially more
495     * power.
496     * </p>
497     *
498     * <h3>Example</h3>
499     * <pre>
500     * public class MyActivity extends Activity {
501     *     private MediaRouter mRouter;
502     *     private MediaRouter.Callback mCallback;
503     *     private MediaRouteSelector mSelector;
504     *
505     *     protected void onCreate(Bundle savedInstanceState) {
506     *         super.onCreate(savedInstanceState);
507     *
508     *         mRouter = Mediarouter.getInstance(this);
509     *         mCallback = new MyCallback();
510     *         mSelector = new MediaRouteSelector.Builder()
511     *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
512     *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
513     *                 .build();
514     *     }
515     *
516     *     // Add the callback on start to tell the media router what kinds of routes
517     *     // the application is interested in so that it can try to discover suitable ones.
518     *     public void onStart() {
519     *         super.onStart();
520     *
521     *         mediaRouter.addCallback(mSelector, mCallback,
522     *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
523     *
524     *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
525     *         // do something with the route...
526     *     }
527     *
528     *     // Remove the selector on stop to tell the media router that it no longer
529     *     // needs to invest effort trying to discover routes of these kinds for now.
530     *     public void onStop() {
531     *         super.onStop();
532     *
533     *         mediaRouter.removeCallback(mCallback);
534     *     }
535     *
536     *     private final class MyCallback extends MediaRouter.Callback {
537     *         // Implement callback methods as needed.
538     *     }
539     * }
540     * </pre>
541     *
542     * @param selector A route selector that indicates the kinds of routes that the
543     * callback would like to discover.
544     * @param callback The callback to add.
545     * @param flags Flags to control the behavior of the callback.
546     * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
547     * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
548     * @see #removeCallback
549     */
550    public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
551            @CallbackFlags int flags) {
552        if (selector == null) {
553            throw new IllegalArgumentException("selector must not be null");
554        }
555        if (callback == null) {
556            throw new IllegalArgumentException("callback must not be null");
557        }
558        checkCallingThread();
559
560        if (DEBUG) {
561            Log.d(TAG, "addCallback: selector=" + selector
562                    + ", callback=" + callback + ", flags=" + Integer.toHexString(flags));
563        }
564
565        CallbackRecord record;
566        int index = findCallbackRecord(callback);
567        if (index < 0) {
568            record = new CallbackRecord(this, callback);
569            mCallbackRecords.add(record);
570        } else {
571            record = mCallbackRecords.get(index);
572        }
573        boolean updateNeeded = false;
574        if ((flags & ~record.mFlags) != 0) {
575            record.mFlags |= flags;
576            updateNeeded = true;
577        }
578        if (!record.mSelector.contains(selector)) {
579            record.mSelector = new MediaRouteSelector.Builder(record.mSelector)
580                    .addSelector(selector)
581                    .build();
582            updateNeeded = true;
583        }
584        if (updateNeeded) {
585            sGlobal.updateDiscoveryRequest();
586        }
587    }
588
589    /**
590     * Removes the specified callback.  It will no longer receive events about
591     * changes to media routes.
592     *
593     * @param callback The callback to remove.
594     * @see #addCallback
595     */
596    public void removeCallback(@NonNull Callback callback) {
597        if (callback == null) {
598            throw new IllegalArgumentException("callback must not be null");
599        }
600        checkCallingThread();
601
602        if (DEBUG) {
603            Log.d(TAG, "removeCallback: callback=" + callback);
604        }
605
606        int index = findCallbackRecord(callback);
607        if (index >= 0) {
608            mCallbackRecords.remove(index);
609            sGlobal.updateDiscoveryRequest();
610        }
611    }
612
613    private int findCallbackRecord(Callback callback) {
614        final int count = mCallbackRecords.size();
615        for (int i = 0; i < count; i++) {
616            if (mCallbackRecords.get(i).mCallback == callback) {
617                return i;
618            }
619        }
620        return -1;
621    }
622
623    /**
624     * Registers a media route provider within this application process.
625     * <p>
626     * The provider will be added to the list of providers that all {@link MediaRouter}
627     * instances within this process can use to discover routes.
628     * </p>
629     *
630     * @param providerInstance The media route provider instance to add.
631     *
632     * @see MediaRouteProvider
633     * @see #removeCallback
634     */
635    public void addProvider(@NonNull MediaRouteProvider providerInstance) {
636        if (providerInstance == null) {
637            throw new IllegalArgumentException("providerInstance must not be null");
638        }
639        checkCallingThread();
640
641        if (DEBUG) {
642            Log.d(TAG, "addProvider: " + providerInstance);
643        }
644        sGlobal.addProvider(providerInstance);
645    }
646
647    /**
648     * Unregisters a media route provider within this application process.
649     * <p>
650     * The provider will be removed from the list of providers that all {@link MediaRouter}
651     * instances within this process can use to discover routes.
652     * </p>
653     *
654     * @param providerInstance The media route provider instance to remove.
655     *
656     * @see MediaRouteProvider
657     * @see #addCallback
658     */
659    public void removeProvider(@NonNull MediaRouteProvider providerInstance) {
660        if (providerInstance == null) {
661            throw new IllegalArgumentException("providerInstance must not be null");
662        }
663        checkCallingThread();
664
665        if (DEBUG) {
666            Log.d(TAG, "removeProvider: " + providerInstance);
667        }
668        sGlobal.removeProvider(providerInstance);
669    }
670
671    /**
672     * Adds a remote control client to enable remote control of the volume
673     * of the selected route.
674     * <p>
675     * The remote control client must have previously been registered with
676     * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient
677     * AudioManager.registerRemoteControlClient} method.
678     * </p>
679     *
680     * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
681     */
682    public void addRemoteControlClient(@NonNull Object remoteControlClient) {
683        if (remoteControlClient == null) {
684            throw new IllegalArgumentException("remoteControlClient must not be null");
685        }
686        checkCallingThread();
687
688        if (DEBUG) {
689            Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
690        }
691        sGlobal.addRemoteControlClient(remoteControlClient);
692    }
693
694    /**
695     * Removes a remote control client.
696     *
697     * @param remoteControlClient The {@link android.media.RemoteControlClient}
698     *            to unregister.
699     */
700    public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
701        if (remoteControlClient == null) {
702            throw new IllegalArgumentException("remoteControlClient must not be null");
703        }
704
705        if (DEBUG) {
706            Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
707        }
708        sGlobal.removeRemoteControlClient(remoteControlClient);
709    }
710
711    /**
712     * Sets the media session to enable remote control of the volume of the
713     * selected route. This should be used instead of
714     * {@link #addRemoteControlClient} when using media sessions. Set the
715     * session to null to clear it.
716     *
717     * @param mediaSession The {@link android.media.session.MediaSession} to
718     *            use.
719     */
720    public void setMediaSession(Object mediaSession) {
721        if (DEBUG) {
722            Log.d(TAG, "addMediaSession: " + mediaSession);
723        }
724        sGlobal.setMediaSession(mediaSession);
725    }
726
727    /**
728     * Sets a compat media session to enable remote control of the volume of the
729     * selected route. This should be used instead of
730     * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}.
731     * Set the session to null to clear it.
732     *
733     * @param mediaSession
734     */
735    public void setMediaSessionCompat(MediaSessionCompat mediaSession) {
736        if (DEBUG) {
737            Log.d(TAG, "addMediaSessionCompat: " + mediaSession);
738        }
739        sGlobal.setMediaSessionCompat(mediaSession);
740    }
741
742    public MediaSessionCompat.Token getMediaSessionToken() {
743        return sGlobal.getMediaSessionToken();
744    }
745
746    /**
747     * Ensures that calls into the media router are on the correct thread.
748     * It pays to be a little paranoid when global state invariants are at risk.
749     */
750    static void checkCallingThread() {
751        if (Looper.myLooper() != Looper.getMainLooper()) {
752            throw new IllegalStateException("The media router service must only be "
753                    + "accessed on the application's main thread.");
754        }
755    }
756
757    static <T> boolean equal(T a, T b) {
758        return a == b || (a != null && b != null && a.equals(b));
759    }
760
761    /**
762     * Provides information about a media route.
763     * <p>
764     * Each media route has a list of {@link MediaControlIntent media control}
765     * {@link #getControlFilters intent filters} that describe the capabilities of the
766     * route and the manner in which it is used and controlled.
767     * </p>
768     */
769    public static class RouteInfo {
770        private final ProviderInfo mProvider;
771        private final String mDescriptorId;
772        private final String mUniqueId;
773        private String mName;
774        private String mDescription;
775        private Uri mIconUri;
776        private boolean mEnabled;
777        private boolean mConnecting;
778        private int mConnectionState;
779        private boolean mCanDisconnect;
780        private final ArrayList<IntentFilter> mControlFilters = new ArrayList<>();
781        private int mPlaybackType;
782        private int mPlaybackStream;
783        private int mVolumeHandling;
784        private int mVolume;
785        private int mVolumeMax;
786        private Display mPresentationDisplay;
787        private int mPresentationDisplayId = PRESENTATION_DISPLAY_ID_NONE;
788        private Bundle mExtras;
789        private IntentSender mSettingsIntent;
790        MediaRouteDescriptor mDescriptor;
791
792        /** @hide */
793        @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
794                CONNECTION_STATE_CONNECTED})
795        @Retention(RetentionPolicy.SOURCE)
796        private @interface ConnectionState {}
797
798        /**
799         * The default connection state indicating the route is disconnected.
800         *
801         * @see #getConnectionState
802         * @hide
803         * STOPSHIP: Unhide or remove.
804         */
805        public static final int CONNECTION_STATE_DISCONNECTED = 0;
806
807        /**
808         * A connection state indicating the route is in the process of connecting and is not yet
809         * ready for use.
810         *
811         * @see #getConnectionState
812         * @hide
813         * STOPSHIP: Unhide or remove.
814         */
815        public static final int CONNECTION_STATE_CONNECTING = 1;
816
817        /**
818         * A connection state indicating the route is connected.
819         *
820         * @see #getConnectionState
821         * @hide
822         * STOPSHIP: Unhide or remove.
823         */
824        public static final int CONNECTION_STATE_CONNECTED = 2;
825
826        /** @hide */
827        @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
828        @Retention(RetentionPolicy.SOURCE)
829        private @interface PlaybackType {}
830
831        /**
832         * The default playback type, "local", indicating the presentation of the media
833         * is happening on the same device (e.g. a phone, a tablet) as where it is
834         * controlled from.
835         *
836         * @see #getPlaybackType
837         */
838        public static final int PLAYBACK_TYPE_LOCAL = 0;
839
840        /**
841         * A playback type indicating the presentation of the media is happening on
842         * a different device (i.e. the remote device) than where it is controlled from.
843         *
844         * @see #getPlaybackType
845         */
846        public static final int PLAYBACK_TYPE_REMOTE = 1;
847
848        /** @hide */
849        @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
850        @Retention(RetentionPolicy.SOURCE)
851        private @interface PlaybackVolume {}
852
853        /**
854         * Playback information indicating the playback volume is fixed, i.e. it cannot be
855         * controlled from this object. An example of fixed playback volume is a remote player,
856         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
857         * than attenuate at the source.
858         *
859         * @see #getVolumeHandling
860         */
861        public static final int PLAYBACK_VOLUME_FIXED = 0;
862
863        /**
864         * Playback information indicating the playback volume is variable and can be controlled
865         * from this object.
866         *
867         * @see #getVolumeHandling
868         */
869        public static final int PLAYBACK_VOLUME_VARIABLE = 1;
870
871        /**
872         * The default presentation display id indicating no presentation display is associated
873         * with the route.
874         * @hide
875         */
876        public static final int PRESENTATION_DISPLAY_ID_NONE = -1;
877
878        static final int CHANGE_GENERAL = 1 << 0;
879        static final int CHANGE_VOLUME = 1 << 1;
880        static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
881
882        RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) {
883            mProvider = provider;
884            mDescriptorId = descriptorId;
885            mUniqueId = uniqueId;
886        }
887
888        /**
889         * Gets information about the provider of this media route.
890         */
891        public ProviderInfo getProvider() {
892            return mProvider;
893        }
894
895        /**
896         * Gets the unique id of the route.
897         * <p>
898         * The route unique id functions as a stable identifier by which the route is known.
899         * For example, an application can use this id as a token to remember the
900         * selected route across restarts or to communicate its identity to a service.
901         * </p>
902         *
903         * @return The unique id of the route, never null.
904         */
905        @NonNull
906        public String getId() {
907            return mUniqueId;
908        }
909
910        /**
911         * Gets the user-visible name of the route.
912         * <p>
913         * The route name identifies the destination represented by the route.
914         * It may be a user-supplied name, an alias, or device serial number.
915         * </p>
916         *
917         * @return The user-visible name of a media route.  This is the string presented
918         * to users who may select this as the active route.
919         */
920        public String getName() {
921            return mName;
922        }
923
924        /**
925         * Gets the user-visible description of the route.
926         * <p>
927         * The route description describes the kind of destination represented by the route.
928         * It may be a user-supplied string, a model number or brand of device.
929         * </p>
930         *
931         * @return The description of the route, or null if none.
932         */
933        @Nullable
934        public String getDescription() {
935            return mDescription;
936        }
937
938        /**
939         * Gets the URI of the icon representing this route.
940         * <p>
941         * This icon will be used in picker UIs if available.
942         * </p>
943         *
944         * @return The URI of the icon representing this route, or null if none.
945         * @hide
946         * STOPSHIP: Unhide or remove.
947         */
948        public Uri getIconUri() {
949            return mIconUri;
950        }
951
952        /**
953         * Returns true if this route is enabled and may be selected.
954         *
955         * @return True if this route is enabled.
956         */
957        public boolean isEnabled() {
958            return mEnabled;
959        }
960
961        /**
962         * Returns true if the route is in the process of connecting and is not
963         * yet ready for use.
964         *
965         * @return True if this route is in the process of connecting.
966         * STOPSHIP: Deprecate or keep.
967         */
968        public boolean isConnecting() {
969            return mConnecting;
970        }
971
972        /**
973         * Gets the connection state of the route.
974         *
975         * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
976         * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
977         * @hide
978         * STOPSHIP: Unhide or remove.
979         */
980        @ConnectionState
981        public int getConnectionState() {
982            return mConnectionState;
983        }
984
985        /**
986         * Returns true if this route is currently selected.
987         *
988         * @return True if this route is currently selected.
989         *
990         * @see MediaRouter#getSelectedRoute
991         */
992        public boolean isSelected() {
993            checkCallingThread();
994            return sGlobal.getSelectedRoute() == this;
995        }
996
997        /**
998         * Returns true if this route is the default route.
999         *
1000         * @return True if this route is the default route.
1001         *
1002         * @see MediaRouter#getDefaultRoute
1003         */
1004        public boolean isDefault() {
1005            checkCallingThread();
1006            return sGlobal.getDefaultRoute() == this;
1007        }
1008
1009        /**
1010         * Gets a list of {@link MediaControlIntent media control intent} filters that
1011         * describe the capabilities of this route and the media control actions that
1012         * it supports.
1013         *
1014         * @return A list of intent filters that specifies the media control intents that
1015         * this route supports.
1016         *
1017         * @see MediaControlIntent
1018         * @see #supportsControlCategory
1019         * @see #supportsControlRequest
1020         */
1021        public List<IntentFilter> getControlFilters() {
1022            return mControlFilters;
1023        }
1024
1025        /**
1026         * Returns true if the route supports at least one of the capabilities
1027         * described by a media route selector.
1028         *
1029         * @param selector The selector that specifies the capabilities to check.
1030         * @return True if the route supports at least one of the capabilities
1031         * described in the media route selector.
1032         */
1033        public boolean matchesSelector(@NonNull MediaRouteSelector selector) {
1034            if (selector == null) {
1035                throw new IllegalArgumentException("selector must not be null");
1036            }
1037            checkCallingThread();
1038            return selector.matchesControlFilters(mControlFilters);
1039        }
1040
1041        /**
1042         * Returns true if the route supports the specified
1043         * {@link MediaControlIntent media control} category.
1044         * <p>
1045         * Media control categories describe the capabilities of this route
1046         * such as whether it supports live audio streaming or remote playback.
1047         * </p>
1048         *
1049         * @param category A {@link MediaControlIntent media control} category
1050         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
1051         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
1052         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
1053         * media control category.
1054         * @return True if the route supports the specified intent category.
1055         *
1056         * @see MediaControlIntent
1057         * @see #getControlFilters
1058         */
1059        public boolean supportsControlCategory(@NonNull String category) {
1060            if (category == null) {
1061                throw new IllegalArgumentException("category must not be null");
1062            }
1063            checkCallingThread();
1064
1065            int count = mControlFilters.size();
1066            for (int i = 0; i < count; i++) {
1067                if (mControlFilters.get(i).hasCategory(category)) {
1068                    return true;
1069                }
1070            }
1071            return false;
1072        }
1073
1074        /**
1075         * Returns true if the route supports the specified
1076         * {@link MediaControlIntent media control} category and action.
1077         * <p>
1078         * Media control actions describe specific requests that an application
1079         * can ask a route to perform.
1080         * </p>
1081         *
1082         * @param category A {@link MediaControlIntent media control} category
1083         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
1084         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
1085         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
1086         * media control category.
1087         * @param action A {@link MediaControlIntent media control} action
1088         * such as {@link MediaControlIntent#ACTION_PLAY}.
1089         * @return True if the route supports the specified intent action.
1090         *
1091         * @see MediaControlIntent
1092         * @see #getControlFilters
1093         */
1094        public boolean supportsControlAction(@NonNull String category, @NonNull String action) {
1095            if (category == null) {
1096                throw new IllegalArgumentException("category must not be null");
1097            }
1098            if (action == null) {
1099                throw new IllegalArgumentException("action must not be null");
1100            }
1101            checkCallingThread();
1102
1103            int count = mControlFilters.size();
1104            for (int i = 0; i < count; i++) {
1105                IntentFilter filter = mControlFilters.get(i);
1106                if (filter.hasCategory(category) && filter.hasAction(action)) {
1107                    return true;
1108                }
1109            }
1110            return false;
1111        }
1112
1113        /**
1114         * Returns true if the route supports the specified
1115         * {@link MediaControlIntent media control} request.
1116         * <p>
1117         * Media control requests are used to request the route to perform
1118         * actions such as starting remote playback of a media item.
1119         * </p>
1120         *
1121         * @param intent A {@link MediaControlIntent media control intent}.
1122         * @return True if the route can handle the specified intent.
1123         *
1124         * @see MediaControlIntent
1125         * @see #getControlFilters
1126         */
1127        public boolean supportsControlRequest(@NonNull Intent intent) {
1128            if (intent == null) {
1129                throw new IllegalArgumentException("intent must not be null");
1130            }
1131            checkCallingThread();
1132
1133            ContentResolver contentResolver = sGlobal.getContentResolver();
1134            int count = mControlFilters.size();
1135            for (int i = 0; i < count; i++) {
1136                if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
1137                    return true;
1138                }
1139            }
1140            return false;
1141        }
1142
1143        /**
1144         * Sends a {@link MediaControlIntent media control} request to be performed
1145         * asynchronously by the route's destination.
1146         * <p>
1147         * Media control requests are used to request the route to perform
1148         * actions such as starting remote playback of a media item.
1149         * </p><p>
1150         * This function may only be called on a selected route.  Control requests
1151         * sent to unselected routes will fail.
1152         * </p>
1153         *
1154         * @param intent A {@link MediaControlIntent media control intent}.
1155         * @param callback A {@link ControlRequestCallback} to invoke with the result
1156         * of the request, or null if no result is required.
1157         *
1158         * @see MediaControlIntent
1159         */
1160        public void sendControlRequest(@NonNull Intent intent,
1161                @Nullable ControlRequestCallback callback) {
1162            if (intent == null) {
1163                throw new IllegalArgumentException("intent must not be null");
1164            }
1165            checkCallingThread();
1166
1167            sGlobal.sendControlRequest(this, intent, callback);
1168        }
1169
1170        /**
1171         * Gets the type of playback associated with this route.
1172         *
1173         * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
1174         * or {@link #PLAYBACK_TYPE_REMOTE}.
1175         */
1176        @PlaybackType
1177        public int getPlaybackType() {
1178            return mPlaybackType;
1179        }
1180
1181        /**
1182         * Gets the audio stream over which the playback associated with this route is performed.
1183         *
1184         * @return The stream over which the playback associated with this route is performed.
1185         */
1186        public int getPlaybackStream() {
1187            return mPlaybackStream;
1188        }
1189
1190        /**
1191         * Gets information about how volume is handled on the route.
1192         *
1193         * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
1194         * or {@link #PLAYBACK_VOLUME_VARIABLE}.
1195         */
1196        @PlaybackVolume
1197        public int getVolumeHandling() {
1198            return mVolumeHandling;
1199        }
1200
1201        /**
1202         * Gets the current volume for this route. Depending on the route, this may only
1203         * be valid if the route is currently selected.
1204         *
1205         * @return The volume at which the playback associated with this route is performed.
1206         */
1207        public int getVolume() {
1208            return mVolume;
1209        }
1210
1211        /**
1212         * Gets the maximum volume at which the playback associated with this route is performed.
1213         *
1214         * @return The maximum volume at which the playback associated with
1215         * this route is performed.
1216         */
1217        public int getVolumeMax() {
1218            return mVolumeMax;
1219        }
1220
1221        /**
1222         * Gets whether this route supports disconnecting without interrupting
1223         * playback.
1224         *
1225         * @return True if this route can disconnect without stopping playback,
1226         *         false otherwise.
1227         */
1228        public boolean canDisconnect() {
1229            return mCanDisconnect;
1230        }
1231
1232        /**
1233         * Requests a volume change for this route asynchronously.
1234         * <p>
1235         * This function may only be called on a selected route.  It will have
1236         * no effect if the route is currently unselected.
1237         * </p>
1238         *
1239         * @param volume The new volume value between 0 and {@link #getVolumeMax}.
1240         */
1241        public void requestSetVolume(int volume) {
1242            checkCallingThread();
1243            sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
1244        }
1245
1246        /**
1247         * Requests an incremental volume update for this route asynchronously.
1248         * <p>
1249         * This function may only be called on a selected route.  It will have
1250         * no effect if the route is currently unselected.
1251         * </p>
1252         *
1253         * @param delta The delta to add to the current volume.
1254         */
1255        public void requestUpdateVolume(int delta) {
1256            checkCallingThread();
1257            if (delta != 0) {
1258                sGlobal.requestUpdateVolume(this, delta);
1259            }
1260        }
1261
1262        /**
1263         * Gets the {@link Display} that should be used by the application to show
1264         * a {@link android.app.Presentation} on an external display when this route is selected.
1265         * Depending on the route, this may only be valid if the route is currently
1266         * selected.
1267         * <p>
1268         * The preferred presentation display may change independently of the route
1269         * being selected or unselected.  For example, the presentation display
1270         * of the default system route may change when an external HDMI display is connected
1271         * or disconnected even though the route itself has not changed.
1272         * </p><p>
1273         * This method may return null if there is no external display associated with
1274         * the route or if the display is not ready to show UI yet.
1275         * </p><p>
1276         * The application should listen for changes to the presentation display
1277         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
1278         * show or dismiss its {@link android.app.Presentation} accordingly when the display
1279         * becomes available or is removed.
1280         * </p><p>
1281         * This method only makes sense for
1282         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
1283         * </p>
1284         *
1285         * @return The preferred presentation display to use when this route is
1286         * selected or null if none.
1287         *
1288         * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
1289         * @see android.app.Presentation
1290         */
1291        @Nullable
1292        public Display getPresentationDisplay() {
1293            checkCallingThread();
1294            if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
1295                mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
1296            }
1297            return mPresentationDisplay;
1298        }
1299
1300        /**
1301         * Gets the route's presentation display id, or -1 if none.
1302         * @hide
1303         */
1304        public int getPresentationDisplayId() {
1305            return mPresentationDisplayId;
1306        }
1307
1308        /**
1309         * Gets a collection of extra properties about this route that were supplied
1310         * by its media route provider, or null if none.
1311         */
1312        @Nullable
1313        public Bundle getExtras() {
1314            return mExtras;
1315        }
1316
1317        /**
1318         * Gets an intent sender for launching a settings activity for this
1319         * route.
1320         */
1321        @Nullable
1322        public IntentSender getSettingsIntent() {
1323            return mSettingsIntent;
1324        }
1325
1326        /**
1327         * Selects this media route.
1328         */
1329        public void select() {
1330            checkCallingThread();
1331            sGlobal.selectRoute(this);
1332        }
1333
1334        @Override
1335        public String toString() {
1336            return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
1337                    + ", name=" + mName
1338                    + ", description=" + mDescription
1339                    + ", iconUri=" + mIconUri
1340                    + ", enabled=" + mEnabled
1341                    + ", connecting=" + mConnecting
1342                    + ", connectionState=" + mConnectionState
1343                    + ", canDisconnect=" + mCanDisconnect
1344                    + ", playbackType=" + mPlaybackType
1345                    + ", playbackStream=" + mPlaybackStream
1346                    + ", volumeHandling=" + mVolumeHandling
1347                    + ", volume=" + mVolume
1348                    + ", volumeMax=" + mVolumeMax
1349                    + ", presentationDisplayId=" + mPresentationDisplayId
1350                    + ", extras=" + mExtras
1351                    + ", settingsIntent=" + mSettingsIntent
1352                    + ", providerPackageName=" + mProvider.getPackageName()
1353                    + " }";
1354        }
1355
1356        int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
1357            int changes = 0;
1358            if (mDescriptor != descriptor) {
1359                changes = updateDescriptor(descriptor);
1360            }
1361            return changes;
1362        }
1363
1364        int updateDescriptor(MediaRouteDescriptor descriptor) {
1365            int changes = 0;
1366            mDescriptor = descriptor;
1367            if (descriptor != null) {
1368                if (!equal(mName, descriptor.getName())) {
1369                    mName = descriptor.getName();
1370                    changes |= CHANGE_GENERAL;
1371                }
1372                if (!equal(mDescription, descriptor.getDescription())) {
1373                    mDescription = descriptor.getDescription();
1374                    changes |= CHANGE_GENERAL;
1375                }
1376                if (!equal(mIconUri, descriptor.getIconUri())) {
1377                    mIconUri = descriptor.getIconUri();
1378                    changes |= CHANGE_GENERAL;
1379                }
1380                if (mEnabled != descriptor.isEnabled()) {
1381                    mEnabled = descriptor.isEnabled();
1382                    changes |= CHANGE_GENERAL;
1383                }
1384                if (mConnecting != descriptor.isConnecting()) {
1385                    mConnecting = descriptor.isConnecting();
1386                    changes |= CHANGE_GENERAL;
1387                }
1388                if (mConnectionState != descriptor.getConnectionState()) {
1389                    mConnectionState = descriptor.getConnectionState();
1390                    changes |= CHANGE_GENERAL;
1391                }
1392                if (!mControlFilters.equals(descriptor.getControlFilters())) {
1393                    mControlFilters.clear();
1394                    mControlFilters.addAll(descriptor.getControlFilters());
1395                    changes |= CHANGE_GENERAL;
1396                }
1397                if (mPlaybackType != descriptor.getPlaybackType()) {
1398                    mPlaybackType = descriptor.getPlaybackType();
1399                    changes |= CHANGE_GENERAL;
1400                }
1401                if (mPlaybackStream != descriptor.getPlaybackStream()) {
1402                    mPlaybackStream = descriptor.getPlaybackStream();
1403                    changes |= CHANGE_GENERAL;
1404                }
1405                if (mVolumeHandling != descriptor.getVolumeHandling()) {
1406                    mVolumeHandling = descriptor.getVolumeHandling();
1407                    changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1408                }
1409                if (mVolume != descriptor.getVolume()) {
1410                    mVolume = descriptor.getVolume();
1411                    changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1412                }
1413                if (mVolumeMax != descriptor.getVolumeMax()) {
1414                    mVolumeMax = descriptor.getVolumeMax();
1415                    changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1416                }
1417                if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
1418                    mPresentationDisplayId = descriptor.getPresentationDisplayId();
1419                    mPresentationDisplay = null;
1420                    changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
1421                }
1422                if (!equal(mExtras, descriptor.getExtras())) {
1423                    mExtras = descriptor.getExtras();
1424                    changes |= CHANGE_GENERAL;
1425                }
1426                if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
1427                    mSettingsIntent = descriptor.getSettingsActivity();
1428                    changes |= CHANGE_GENERAL;
1429                }
1430                if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
1431                    mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
1432                    changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
1433                }
1434            }
1435            return changes;
1436        }
1437
1438        String getDescriptorId() {
1439            return mDescriptorId;
1440        }
1441
1442        /** @hide */
1443        public MediaRouteProvider getProviderInstance() {
1444            return mProvider.getProviderInstance();
1445        }
1446    }
1447
1448    /**
1449     * Information about a route that consists of multiple other routes in a group.
1450     * @hide
1451     * STOPSHIP: Unhide or remove.
1452     */
1453    public static class RouteGroup extends RouteInfo {
1454        private List<RouteInfo> mRoutes = new ArrayList<>();
1455
1456        RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId) {
1457            super(provider, descriptorId, uniqueId);
1458        }
1459
1460        /**
1461         * @return The number of routes in this group
1462         */
1463        public int getRouteCount() {
1464            return mRoutes.size();
1465        }
1466
1467        /**
1468         * Returns the route in this group at the specified index
1469         *
1470         * @param index Index to fetch
1471         * @return The route at index
1472         */
1473        public RouteInfo getRouteAt(int index) {
1474            return mRoutes.get(index);
1475        }
1476
1477        /**
1478         * Returns the routes in this group
1479         *
1480         * @return The list of the routes in this group
1481         */
1482        public List<RouteInfo> getRoutes() {
1483            return mRoutes;
1484        }
1485
1486        @Override
1487        public String toString() {
1488            StringBuilder sb = new StringBuilder(super.toString());
1489            sb.append('[');
1490            final int count = mRoutes.size();
1491            for (int i = 0; i < count; i++) {
1492                if (i > 0) sb.append(", ");
1493                sb.append(mRoutes.get(i));
1494            }
1495            sb.append(']');
1496            return sb.toString();
1497        }
1498
1499        @Override
1500        int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
1501            boolean changed = false;
1502            if (mDescriptor != descriptor) {
1503                mDescriptor = descriptor;
1504                if (descriptor != null) {
1505                    List<String> groupMemberIds = descriptor.getGroupMemberIds();
1506                    List<RouteInfo> routes = new ArrayList<>();
1507                    changed = groupMemberIds.size() != mRoutes.size();
1508                    for (String groupMemberId : groupMemberIds) {
1509                        String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId);
1510                        RouteInfo groupMember = sGlobal.getRoute(uniqueId);
1511                        if (groupMember != null) {
1512                            routes.add(groupMember);
1513                            if (!changed && !mRoutes.contains(groupMember)) {
1514                                changed = true;
1515                            }
1516                        }
1517                    }
1518                    if (changed) {
1519                        mRoutes = routes;
1520                    }
1521                }
1522            }
1523            return (changed ? CHANGE_GENERAL : 0) | super.updateDescriptor(descriptor);
1524        }
1525    }
1526
1527    /**
1528     * Provides information about a media route provider.
1529     * <p>
1530     * This object may be used to determine which media route provider has
1531     * published a particular route.
1532     * </p>
1533     */
1534    public static final class ProviderInfo {
1535        private final MediaRouteProvider mProviderInstance;
1536        private final List<RouteInfo> mRoutes = new ArrayList<>();
1537
1538        private final ProviderMetadata mMetadata;
1539        private MediaRouteProviderDescriptor mDescriptor;
1540        private Resources mResources;
1541        private boolean mResourcesNotAvailable;
1542
1543        ProviderInfo(MediaRouteProvider provider) {
1544            mProviderInstance = provider;
1545            mMetadata = provider.getMetadata();
1546        }
1547
1548        /**
1549         * Gets the provider's underlying {@link MediaRouteProvider} instance.
1550         */
1551        public MediaRouteProvider getProviderInstance() {
1552            checkCallingThread();
1553            return mProviderInstance;
1554        }
1555
1556        /**
1557         * Gets the package name of the media route provider.
1558         */
1559        public String getPackageName() {
1560            return mMetadata.getPackageName();
1561        }
1562
1563        /**
1564         * Gets the component name of the media route provider.
1565         */
1566        public ComponentName getComponentName() {
1567            return mMetadata.getComponentName();
1568        }
1569
1570        /**
1571         * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
1572         */
1573        public List<RouteInfo> getRoutes() {
1574            checkCallingThread();
1575            return mRoutes;
1576        }
1577
1578        Resources getResources() {
1579            if (mResources == null && !mResourcesNotAvailable) {
1580                String packageName = getPackageName();
1581                Context context = sGlobal.getProviderContext(packageName);
1582                if (context != null) {
1583                    mResources = context.getResources();
1584                } else {
1585                    Log.w(TAG, "Unable to obtain resources for route provider package: "
1586                            + packageName);
1587                    mResourcesNotAvailable = true;
1588                }
1589            }
1590            return mResources;
1591        }
1592
1593        boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
1594            if (mDescriptor != descriptor) {
1595                mDescriptor = descriptor;
1596                return true;
1597            }
1598            return false;
1599        }
1600
1601        int findRouteByDescriptorId(String id) {
1602            final int count = mRoutes.size();
1603            for (int i = 0; i < count; i++) {
1604                if (mRoutes.get(i).mDescriptorId.equals(id)) {
1605                    return i;
1606                }
1607            }
1608            return -1;
1609        }
1610
1611        @Override
1612        public String toString() {
1613            return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
1614                    + " }";
1615        }
1616    }
1617
1618    /**
1619     * Interface for receiving events about media routing changes.
1620     * All methods of this interface will be called from the application's main thread.
1621     * <p>
1622     * A Callback will only receive events relevant to routes that the callback
1623     * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
1624     * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
1625     * </p>
1626     *
1627     * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
1628     * @see MediaRouter#removeCallback(Callback)
1629     */
1630    public static abstract class Callback {
1631        /**
1632         * Called when the supplied media route becomes selected as the active route.
1633         *
1634         * @param router The media router reporting the event.
1635         * @param route The route that has been selected.
1636         */
1637        public void onRouteSelected(MediaRouter router, RouteInfo route) {
1638        }
1639
1640        /**
1641         * Called when the supplied media route becomes unselected as the active route.
1642         *
1643         * @param router The media router reporting the event.
1644         * @param route The route that has been unselected.
1645         */
1646        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
1647        }
1648
1649        /**
1650         * Called when a media route has been added.
1651         *
1652         * @param router The media router reporting the event.
1653         * @param route The route that has become available for use.
1654         */
1655        public void onRouteAdded(MediaRouter router, RouteInfo route) {
1656        }
1657
1658        /**
1659         * Called when a media route has been removed.
1660         *
1661         * @param router The media router reporting the event.
1662         * @param route The route that has been removed from availability.
1663         */
1664        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
1665        }
1666
1667        /**
1668         * Called when a property of the indicated media route has changed.
1669         *
1670         * @param router The media router reporting the event.
1671         * @param route The route that was changed.
1672         */
1673        public void onRouteChanged(MediaRouter router, RouteInfo route) {
1674        }
1675
1676        /**
1677         * Called when a media route's volume changes.
1678         *
1679         * @param router The media router reporting the event.
1680         * @param route The route whose volume changed.
1681         */
1682        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
1683        }
1684
1685        /**
1686         * Called when a media route's presentation display changes.
1687         * <p>
1688         * This method is called whenever the route's presentation display becomes
1689         * available, is removed or has changes to some of its properties (such as its size).
1690         * </p>
1691         *
1692         * @param router The media router reporting the event.
1693         * @param route The route whose presentation display changed.
1694         *
1695         * @see RouteInfo#getPresentationDisplay()
1696         */
1697        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
1698        }
1699
1700        /**
1701         * Called when a media route provider has been added.
1702         *
1703         * @param router The media router reporting the event.
1704         * @param provider The provider that has become available for use.
1705         */
1706        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
1707        }
1708
1709        /**
1710         * Called when a media route provider has been removed.
1711         *
1712         * @param router The media router reporting the event.
1713         * @param provider The provider that has been removed from availability.
1714         */
1715        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
1716        }
1717
1718        /**
1719         * Called when a property of the indicated media route provider has changed.
1720         *
1721         * @param router The media router reporting the event.
1722         * @param provider The provider that was changed.
1723         */
1724        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
1725        }
1726    }
1727
1728    /**
1729     * Callback which is invoked with the result of a media control request.
1730     *
1731     * @see RouteInfo#sendControlRequest
1732     */
1733    public static abstract class ControlRequestCallback {
1734        /**
1735         * Called when a media control request succeeds.
1736         *
1737         * @param data Result data, or null if none.
1738         * Contents depend on the {@link MediaControlIntent media control action}.
1739         */
1740        public void onResult(Bundle data) {
1741        }
1742
1743        /**
1744         * Called when a media control request fails.
1745         *
1746         * @param error A localized error message which may be shown to the user, or null
1747         * if the cause of the error is unclear.
1748         * @param data Error data, or null if none.
1749         * Contents depend on the {@link MediaControlIntent media control action}.
1750         */
1751        public void onError(String error, Bundle data) {
1752        }
1753    }
1754
1755    private static final class CallbackRecord {
1756        public final MediaRouter mRouter;
1757        public final Callback mCallback;
1758        public MediaRouteSelector mSelector;
1759        public int mFlags;
1760
1761        public CallbackRecord(MediaRouter router, Callback callback) {
1762            mRouter = router;
1763            mCallback = callback;
1764            mSelector = MediaRouteSelector.EMPTY;
1765        }
1766
1767        public boolean filterRouteEvent(RouteInfo route) {
1768            return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
1769                    || route.matchesSelector(mSelector);
1770        }
1771    }
1772
1773    /**
1774     * Global state for the media router.
1775     * <p>
1776     * Media routes and media route providers are global to the process; their
1777     * state and the bulk of the media router implementation lives here.
1778     * </p>
1779     */
1780    private static final class GlobalMediaRouter
1781            implements SystemMediaRouteProvider.SyncCallback,
1782            RegisteredMediaRouteProviderWatcher.Callback {
1783        private final Context mApplicationContext;
1784        private final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
1785        private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
1786        private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
1787        private final ArrayList<ProviderInfo> mProviders = new ArrayList<>();
1788        private final ArrayList<RemoteControlClientRecord> mRemoteControlClients =
1789                new ArrayList<>();
1790        private final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo =
1791                new RemoteControlClientCompat.PlaybackInfo();
1792        private final ProviderCallback mProviderCallback = new ProviderCallback();
1793        private final CallbackHandler mCallbackHandler = new CallbackHandler();
1794        private final DisplayManagerCompat mDisplayManager;
1795        private final SystemMediaRouteProvider mSystemProvider;
1796        private final boolean mLowRam;
1797
1798        private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
1799        private RouteInfo mDefaultRoute;
1800        private RouteInfo mSelectedRoute;
1801        private RouteController mSelectedRouteController;
1802        private Map<String, RouteController> mGroupMemberControllers;
1803        private MediaRouteDiscoveryRequest mDiscoveryRequest;
1804        private MediaSessionRecord mMediaSession;
1805        private MediaSessionCompat mRccMediaSession;
1806        private MediaSessionCompat mCompatSession;
1807        private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
1808                new MediaSessionCompat.OnActiveChangeListener() {
1809            @Override
1810            public void onActiveChanged() {
1811                if(mRccMediaSession != null) {
1812                    if (mRccMediaSession.isActive()) {
1813                        addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
1814                    } else {
1815                        removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
1816                    }
1817                }
1818            }
1819        };
1820
1821        GlobalMediaRouter(Context applicationContext) {
1822            mApplicationContext = applicationContext;
1823            mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
1824            mLowRam = ActivityManagerCompat.isLowRamDevice(
1825                    (ActivityManager)applicationContext.getSystemService(
1826                            Context.ACTIVITY_SERVICE));
1827
1828            // Add the system media route provider for interoperating with
1829            // the framework media router.  This one is special and receives
1830            // synchronization messages from the media router.
1831            mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
1832            addProvider(mSystemProvider);
1833        }
1834
1835        public void start() {
1836            // Start watching for routes published by registered media route
1837            // provider services.
1838            mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
1839                    mApplicationContext, this);
1840            mRegisteredProviderWatcher.start();
1841        }
1842
1843        public MediaRouter getRouter(Context context) {
1844            MediaRouter router;
1845            for (int i = mRouters.size(); --i >= 0; ) {
1846                router = mRouters.get(i).get();
1847                if (router == null) {
1848                    mRouters.remove(i);
1849                } else if (router.mContext == context) {
1850                    return router;
1851                }
1852            }
1853            router = new MediaRouter(context);
1854            mRouters.add(new WeakReference<MediaRouter>(router));
1855            return router;
1856        }
1857
1858        public ContentResolver getContentResolver() {
1859            return mApplicationContext.getContentResolver();
1860        }
1861
1862        public Context getProviderContext(String packageName) {
1863            if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
1864                return mApplicationContext;
1865            }
1866            try {
1867                return mApplicationContext.createPackageContext(
1868                        packageName, Context.CONTEXT_RESTRICTED);
1869            } catch (NameNotFoundException ex) {
1870                return null;
1871            }
1872        }
1873
1874        public Display getDisplay(int displayId) {
1875            return mDisplayManager.getDisplay(displayId);
1876        }
1877
1878        public void sendControlRequest(RouteInfo route,
1879                Intent intent, ControlRequestCallback callback) {
1880            if (route == mSelectedRoute && mSelectedRouteController != null) {
1881                if (mSelectedRouteController.onControlRequest(intent, callback)) {
1882                    return;
1883                }
1884            }
1885            if (callback != null) {
1886                callback.onError(null, null);
1887            }
1888        }
1889
1890        public void requestSetVolume(RouteInfo route, int volume) {
1891            if (route == mSelectedRoute && mSelectedRouteController != null) {
1892                mSelectedRouteController.onSetVolume(volume);
1893            } else if (mGroupMemberControllers != null) {
1894                RouteController controller = mGroupMemberControllers.get(route.mDescriptorId);
1895                if (controller != null) {
1896                    controller.onSetVolume(volume);
1897                }
1898            }
1899        }
1900
1901        public void requestUpdateVolume(RouteInfo route, int delta) {
1902            if (route == mSelectedRoute && mSelectedRouteController != null) {
1903                mSelectedRouteController.onUpdateVolume(delta);
1904            }
1905        }
1906
1907        public RouteInfo getRoute(String uniqueId) {
1908            for (RouteInfo info : mRoutes) {
1909                if (info.mUniqueId.equals(uniqueId)) {
1910                    return info;
1911                }
1912            }
1913            return null;
1914        }
1915
1916        public List<RouteInfo> getRoutes() {
1917            return mRoutes;
1918        }
1919
1920        public List<ProviderInfo> getProviders() {
1921            return mProviders;
1922        }
1923
1924        public RouteInfo getDefaultRoute() {
1925            if (mDefaultRoute == null) {
1926                // This should never happen once the media router has been fully
1927                // initialized but it is good to check for the error in case there
1928                // is a bug in provider initialization.
1929                throw new IllegalStateException("There is no default route.  "
1930                        + "The media router has not yet been fully initialized.");
1931            }
1932            return mDefaultRoute;
1933        }
1934
1935        public RouteInfo getSelectedRoute() {
1936            if (mSelectedRoute == null) {
1937                // This should never happen once the media router has been fully
1938                // initialized but it is good to check for the error in case there
1939                // is a bug in provider initialization.
1940                throw new IllegalStateException("There is no currently selected route.  "
1941                        + "The media router has not yet been fully initialized.");
1942            }
1943            return mSelectedRoute;
1944        }
1945
1946        public void selectRoute(RouteInfo route) {
1947            selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
1948        }
1949
1950        public void selectRoute(RouteInfo route, int unselectReason) {
1951            if (!mRoutes.contains(route)) {
1952                Log.w(TAG, "Ignoring attempt to select removed route: " + route);
1953                return;
1954            }
1955            if (!route.mEnabled) {
1956                Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
1957                return;
1958            }
1959
1960            setSelectedRouteInternal(route, unselectReason);
1961        }
1962
1963        public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
1964            if (selector.isEmpty()) {
1965                return false;
1966            }
1967
1968            // On low-RAM devices, do not rely on actual discovery results unless asked to.
1969            if ((flags & AVAILABILITY_FLAG_REQUIRE_MATCH) == 0 && mLowRam) {
1970                return true;
1971            }
1972
1973            // Check whether any existing routes match the selector.
1974            final int routeCount = mRoutes.size();
1975            for (int i = 0; i < routeCount; i++) {
1976                RouteInfo route = mRoutes.get(i);
1977                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
1978                        && route.isDefault()) {
1979                    continue;
1980                }
1981                if (route.matchesSelector(selector)) {
1982                    return true;
1983                }
1984            }
1985
1986            // It doesn't look like we can find a matching route right now.
1987            return false;
1988        }
1989
1990        public void updateDiscoveryRequest() {
1991            // Combine all of the callback selectors and active scan flags.
1992            boolean discover = false;
1993            boolean activeScan = false;
1994            MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
1995            for (int i = mRouters.size(); --i >= 0; ) {
1996                MediaRouter router = mRouters.get(i).get();
1997                if (router == null) {
1998                    mRouters.remove(i);
1999                } else {
2000                    final int count = router.mCallbackRecords.size();
2001                    for (int j = 0; j < count; j++) {
2002                        CallbackRecord callback = router.mCallbackRecords.get(j);
2003                        builder.addSelector(callback.mSelector);
2004                        if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
2005                            activeScan = true;
2006                            discover = true; // perform active scan implies request discovery
2007                        }
2008                        if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
2009                            if (!mLowRam) {
2010                                discover = true;
2011                            }
2012                        }
2013                        if ((callback.mFlags & CALLBACK_FLAG_FORCE_DISCOVERY) != 0) {
2014                            discover = true;
2015                        }
2016                    }
2017                }
2018            }
2019            MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
2020
2021            // Create a new discovery request.
2022            if (mDiscoveryRequest != null
2023                    && mDiscoveryRequest.getSelector().equals(selector)
2024                    && mDiscoveryRequest.isActiveScan() == activeScan) {
2025                return; // no change
2026            }
2027            if (selector.isEmpty() && !activeScan) {
2028                // Discovery is not needed.
2029                if (mDiscoveryRequest == null) {
2030                    return; // no change
2031                }
2032                mDiscoveryRequest = null;
2033            } else {
2034                // Discovery is needed.
2035                mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
2036            }
2037            if (DEBUG) {
2038                Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
2039            }
2040            if (discover && !activeScan && mLowRam) {
2041                Log.i(TAG, "Forcing passive route discovery on a low-RAM device, "
2042                        + "system performance may be affected.  Please consider using "
2043                        + "CALLBACK_FLAG_REQUEST_DISCOVERY instead of "
2044                        + "CALLBACK_FLAG_FORCE_DISCOVERY.");
2045            }
2046
2047            // Notify providers.
2048            final int providerCount = mProviders.size();
2049            for (int i = 0; i < providerCount; i++) {
2050                mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
2051            }
2052        }
2053
2054        @Override
2055        public void addProvider(MediaRouteProvider providerInstance) {
2056            int index = findProviderInfo(providerInstance);
2057            if (index < 0) {
2058                // 1. Add the provider to the list.
2059                ProviderInfo provider = new ProviderInfo(providerInstance);
2060                mProviders.add(provider);
2061                if (DEBUG) {
2062                    Log.d(TAG, "Provider added: " + provider);
2063                }
2064                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
2065                // 2. Create the provider's contents.
2066                updateProviderContents(provider, providerInstance.getDescriptor());
2067                // 3. Register the provider callback.
2068                providerInstance.setCallback(mProviderCallback);
2069                // 4. Set the discovery request.
2070                providerInstance.setDiscoveryRequest(mDiscoveryRequest);
2071            }
2072        }
2073
2074        @Override
2075        public void removeProvider(MediaRouteProvider providerInstance) {
2076            int index = findProviderInfo(providerInstance);
2077            if (index >= 0) {
2078                // 1. Unregister the provider callback.
2079                providerInstance.setCallback(null);
2080                // 2. Clear the discovery request.
2081                providerInstance.setDiscoveryRequest(null);
2082                // 3. Delete the provider's contents.
2083                ProviderInfo provider = mProviders.get(index);
2084                updateProviderContents(provider, null);
2085                // 4. Remove the provider from the list.
2086                if (DEBUG) {
2087                    Log.d(TAG, "Provider removed: " + provider);
2088                }
2089                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
2090                mProviders.remove(index);
2091            }
2092        }
2093
2094        private void updateProviderDescriptor(MediaRouteProvider providerInstance,
2095                MediaRouteProviderDescriptor descriptor) {
2096            int index = findProviderInfo(providerInstance);
2097            if (index >= 0) {
2098                // Update the provider's contents.
2099                ProviderInfo provider = mProviders.get(index);
2100                updateProviderContents(provider, descriptor);
2101            }
2102        }
2103
2104        private int findProviderInfo(MediaRouteProvider providerInstance) {
2105            final int count = mProviders.size();
2106            for (int i = 0; i < count; i++) {
2107                if (mProviders.get(i).mProviderInstance == providerInstance) {
2108                    return i;
2109                }
2110            }
2111            return -1;
2112        }
2113
2114        private void updateProviderContents(ProviderInfo provider,
2115                MediaRouteProviderDescriptor providerDescriptor) {
2116            if (provider.updateDescriptor(providerDescriptor)) {
2117                // Update all existing routes and reorder them to match
2118                // the order of their descriptors.
2119                int targetIndex = 0;
2120                boolean selectedRouteDescriptorChanged = false;
2121                if (providerDescriptor != null) {
2122                    if (providerDescriptor.isValid()) {
2123                        final List<MediaRouteDescriptor> routeDescriptors =
2124                                providerDescriptor.getRoutes();
2125                        final int routeCount = routeDescriptors.size();
2126                        // Updating route group's contents requires all member routes' information.
2127                        // Add the groups to the lists and update them later.
2128                        List<Pair<RouteInfo, MediaRouteDescriptor>> addedGroups = new ArrayList<>();
2129                        List<Pair<RouteInfo, MediaRouteDescriptor>> updatedGroups =
2130                                new ArrayList<>();
2131                        for (int i = 0; i < routeCount; i++) {
2132                            final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
2133                            final String id = routeDescriptor.getId();
2134                            final int sourceIndex = provider.findRouteByDescriptorId(id);
2135                            if (sourceIndex < 0) {
2136                                // 1. Add the route to the list.
2137                                String uniqueId = assignRouteUniqueId(provider, id);
2138                                boolean isGroup = routeDescriptor.getGroupMemberIds() != null;
2139                                RouteInfo route = isGroup ? new RouteGroup(provider, id, uniqueId) :
2140                                        new RouteInfo(provider, id, uniqueId);
2141                                provider.mRoutes.add(targetIndex++, route);
2142                                mRoutes.add(route);
2143                                // 2. Create the route's contents.
2144                                if (isGroup) {
2145                                    addedGroups.add(new Pair(route, routeDescriptor));
2146                                } else {
2147                                    route.maybeUpdateDescriptor(routeDescriptor);
2148                                    // 3. Notify clients about addition.
2149                                    if (DEBUG) {
2150                                        Log.d(TAG, "Route added: " + route);
2151                                    }
2152                                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
2153                                }
2154
2155                            } else if (sourceIndex < targetIndex) {
2156                                Log.w(TAG, "Ignoring route descriptor with duplicate id: "
2157                                        + routeDescriptor);
2158                            } else {
2159                                // 1. Reorder the route within the list.
2160                                RouteInfo route = provider.mRoutes.get(sourceIndex);
2161                                Collections.swap(provider.mRoutes,
2162                                        sourceIndex, targetIndex++);
2163                                // 2. Update the route's contents.
2164                                if (route instanceof RouteGroup) {
2165                                    updatedGroups.add(new Pair(route, routeDescriptor));
2166                                } else {
2167                                    // 3. Notify clients about changes.
2168                                    if (updateRouteDescriptorAndNotify(route, routeDescriptor)
2169                                            != 0) {
2170                                        if (route == mSelectedRoute) {
2171                                            selectedRouteDescriptorChanged = true;
2172                                        }
2173                                    }
2174                                }
2175                            }
2176                        }
2177                        // Update the new and/or existing groups.
2178                        for (Pair<RouteInfo, MediaRouteDescriptor> pair : addedGroups) {
2179                            RouteInfo route = pair.first;
2180                            route.maybeUpdateDescriptor(pair.second);
2181                            if (DEBUG) {
2182                                Log.d(TAG, "Route added: " + route);
2183                            }
2184                            mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
2185                        }
2186                        for (Pair<RouteInfo, MediaRouteDescriptor> pair : updatedGroups) {
2187                            RouteInfo route = pair.first;
2188                            if (updateRouteDescriptorAndNotify(route, pair.second) != 0) {
2189                                if (route == mSelectedRoute) {
2190                                    selectedRouteDescriptorChanged = true;
2191                                }
2192                            }
2193                        }
2194                    } else {
2195                        Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
2196                    }
2197                }
2198
2199                // Dispose all remaining routes that do not have matching descriptors.
2200                for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
2201                    // 1. Delete the route's contents.
2202                    RouteInfo route = provider.mRoutes.get(i);
2203                    route.maybeUpdateDescriptor(null);
2204                    // 2. Remove the route from the list.
2205                    mRoutes.remove(route);
2206                }
2207
2208                // Update the selected route if needed.
2209                updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged);
2210
2211                // Now notify clients about routes that were removed.
2212                // We do this after updating the selected route to ensure
2213                // that the framework media router observes the new route
2214                // selection before the removal since removing the currently
2215                // selected route may have side-effects.
2216                for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
2217                    RouteInfo route = provider.mRoutes.remove(i);
2218                    if (DEBUG) {
2219                        Log.d(TAG, "Route removed: " + route);
2220                    }
2221                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
2222                }
2223
2224                // Notify provider changed.
2225                if (DEBUG) {
2226                    Log.d(TAG, "Provider changed: " + provider);
2227                }
2228                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
2229            }
2230        }
2231
2232        private int updateRouteDescriptorAndNotify(RouteInfo route,
2233                MediaRouteDescriptor routeDescriptor) {
2234            int changes = route.maybeUpdateDescriptor(routeDescriptor);
2235            if (changes != 0) {
2236                if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
2237                    if (DEBUG) {
2238                        Log.d(TAG, "Route changed: " + route);
2239                    }
2240                    mCallbackHandler.post(
2241                            CallbackHandler.MSG_ROUTE_CHANGED, route);
2242                }
2243                if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
2244                    if (DEBUG) {
2245                        Log.d(TAG, "Route volume changed: " + route);
2246                    }
2247                    mCallbackHandler.post(
2248                            CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
2249                }
2250                if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
2251                    if (DEBUG) {
2252                        Log.d(TAG, "Route presentation display changed: "
2253                                + route);
2254                    }
2255                    mCallbackHandler.post(CallbackHandler.
2256                            MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
2257                }
2258            }
2259            return changes;
2260        }
2261
2262        private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
2263            // Although route descriptor ids are unique within a provider, it's
2264            // possible for there to be two providers with the same package name.
2265            // Therefore we must dedupe the composite id.
2266            String componentName = provider.getComponentName().flattenToShortString();
2267            String uniqueId = componentName + ":" + routeDescriptorId;
2268            if (findRouteByUniqueId(uniqueId) < 0) {
2269                mUniqueIdMap.put(new Pair(componentName, routeDescriptorId), uniqueId);
2270                return uniqueId;
2271            }
2272            Log.w(TAG, "Either " + routeDescriptorId + " isn't unique in " + componentName
2273                    + " or we're trying to assign a unique ID for an already added route");
2274            for (int i = 2; ; i++) {
2275                String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
2276                if (findRouteByUniqueId(newUniqueId) < 0) {
2277                    mUniqueIdMap.put(new Pair(componentName, routeDescriptorId), newUniqueId);
2278                    return newUniqueId;
2279                }
2280            }
2281        }
2282
2283        private int findRouteByUniqueId(String uniqueId) {
2284            final int count = mRoutes.size();
2285            for (int i = 0; i < count; i++) {
2286                if (mRoutes.get(i).mUniqueId.equals(uniqueId)) {
2287                    return i;
2288                }
2289            }
2290            return -1;
2291        }
2292
2293        private String getUniqueId(ProviderInfo provider, String routeDescriptorId) {
2294            String componentName = provider.getComponentName().flattenToShortString();
2295            return mUniqueIdMap.get(new Pair(componentName, routeDescriptorId));
2296        }
2297
2298        private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) {
2299            // Update default route.
2300            if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) {
2301                Log.i(TAG, "Clearing the default route because it "
2302                        + "is no longer selectable: " + mDefaultRoute);
2303                mDefaultRoute = null;
2304            }
2305            if (mDefaultRoute == null && !mRoutes.isEmpty()) {
2306                for (RouteInfo route : mRoutes) {
2307                    if (isSystemDefaultRoute(route) && isRouteSelectable(route)) {
2308                        mDefaultRoute = route;
2309                        Log.i(TAG, "Found default route: " + mDefaultRoute);
2310                        break;
2311                    }
2312                }
2313            }
2314
2315            // Update selected route.
2316            if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) {
2317                Log.i(TAG, "Unselecting the current route because it "
2318                        + "is no longer selectable: " + mSelectedRoute);
2319                setSelectedRouteInternal(null,
2320                        MediaRouter.UNSELECT_REASON_UNKNOWN);
2321            }
2322            if (mSelectedRoute == null) {
2323                // Choose a new route.
2324                // This will have the side-effect of updating the playback info when
2325                // the new route is selected.
2326                setSelectedRouteInternal(chooseFallbackRoute(),
2327                        MediaRouter.UNSELECT_REASON_UNKNOWN);
2328            } else if (selectedRouteDescriptorChanged) {
2329                // Update the playback info because the properties of the route have changed.
2330                updatePlaybackInfoFromSelectedRoute();
2331            }
2332        }
2333
2334        private RouteInfo chooseFallbackRoute() {
2335            // When the current route is removed or no longer selectable,
2336            // we want to revert to a live audio route if there is
2337            // one (usually Bluetooth A2DP).  Failing that, use
2338            // the default route.
2339            for (RouteInfo route : mRoutes) {
2340                if (route != mDefaultRoute
2341                        && isSystemLiveAudioOnlyRoute(route)
2342                        && isRouteSelectable(route)) {
2343                    return route;
2344                }
2345            }
2346            return mDefaultRoute;
2347        }
2348
2349        private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) {
2350            return route.getProviderInstance() == mSystemProvider
2351                    && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
2352                    && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
2353        }
2354
2355        private boolean isRouteSelectable(RouteInfo route) {
2356            // This tests whether the route is still valid and enabled.
2357            // The route descriptor field is set to null when the route is removed.
2358            return route.mDescriptor != null && route.mEnabled;
2359        }
2360
2361        private boolean isSystemDefaultRoute(RouteInfo route) {
2362            return route.getProviderInstance() == mSystemProvider
2363                    && route.mDescriptorId.equals(
2364                            SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
2365        }
2366
2367        private void setSelectedRouteInternal(RouteInfo route, int unselectReason) {
2368            if (mSelectedRoute != route) {
2369                if (mSelectedRoute != null) {
2370                    if (DEBUG) {
2371                        Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
2372                                + unselectReason);
2373                    }
2374                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
2375                    if (mSelectedRouteController != null) {
2376                        mSelectedRouteController.onUnselect(unselectReason);
2377                        mSelectedRouteController.onRelease();
2378                        mSelectedRouteController = null;
2379                    }
2380                    if (mGroupMemberControllers != null) {
2381                        for (RouteController controller : mGroupMemberControllers.values()) {
2382                            controller.onUnselect();
2383                            controller.onRelease();
2384                        }
2385                        mGroupMemberControllers = null;
2386                    }
2387                }
2388
2389                mSelectedRoute = route;
2390
2391                if (mSelectedRoute != null) {
2392                    mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
2393                            route.mDescriptorId);
2394                    if (mSelectedRouteController != null) {
2395                        mSelectedRouteController.onSelect();
2396                    }
2397                    if (DEBUG) {
2398                        Log.d(TAG, "Route selected: " + mSelectedRoute);
2399                    }
2400                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
2401
2402                    if (mSelectedRoute instanceof RouteGroup) {
2403                        mGroupMemberControllers = new HashMap<>();
2404                        RouteGroup group = (RouteGroup) mSelectedRoute;
2405                        for (RouteInfo groupMember : group.getRoutes()) {
2406                            RouteController controller = groupMember.getProviderInstance()
2407                                    .onCreateRouteController(groupMember.mDescriptorId);
2408                            controller.onSelect();
2409                            mGroupMemberControllers.put(groupMember.mDescriptorId, controller);
2410                        }
2411                    }
2412                }
2413
2414                updatePlaybackInfoFromSelectedRoute();
2415            }
2416        }
2417
2418        @Override
2419        public RouteInfo getSystemRouteByDescriptorId(String id) {
2420            int providerIndex = findProviderInfo(mSystemProvider);
2421            if (providerIndex >= 0) {
2422                ProviderInfo provider = mProviders.get(providerIndex);
2423                int routeIndex = provider.findRouteByDescriptorId(id);
2424                if (routeIndex >= 0) {
2425                    return provider.mRoutes.get(routeIndex);
2426                }
2427            }
2428            return null;
2429        }
2430
2431        public void addRemoteControlClient(Object rcc) {
2432            int index = findRemoteControlClientRecord(rcc);
2433            if (index < 0) {
2434                RemoteControlClientRecord record = new RemoteControlClientRecord(rcc);
2435                mRemoteControlClients.add(record);
2436            }
2437        }
2438
2439        public void removeRemoteControlClient(Object rcc) {
2440            int index = findRemoteControlClientRecord(rcc);
2441            if (index >= 0) {
2442                RemoteControlClientRecord record = mRemoteControlClients.remove(index);
2443                record.disconnect();
2444            }
2445        }
2446
2447        public void setMediaSession(Object session) {
2448            if (mMediaSession != null) {
2449                mMediaSession.clearVolumeHandling();
2450            }
2451            if (session == null) {
2452                mMediaSession = null;
2453            } else {
2454                mMediaSession = new MediaSessionRecord(session);
2455                updatePlaybackInfoFromSelectedRoute();
2456            }
2457        }
2458
2459        public void setMediaSessionCompat(final MediaSessionCompat session) {
2460            mCompatSession = session;
2461            if (android.os.Build.VERSION.SDK_INT >= 21) {
2462                setMediaSession(session != null ? session.getMediaSession() : null);
2463            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
2464                if (mRccMediaSession != null) {
2465                    removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
2466                    mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
2467                }
2468                mRccMediaSession = session;
2469                if (session != null) {
2470                    session.addOnActiveChangeListener(mSessionActiveListener);
2471                    if (session.isActive()) {
2472                        addRemoteControlClient(session.getRemoteControlClient());
2473                    }
2474                }
2475            }
2476        }
2477
2478        public MediaSessionCompat.Token getMediaSessionToken() {
2479            if (mMediaSession != null) {
2480                return mMediaSession.getToken();
2481            } else if (mCompatSession != null) {
2482                return mCompatSession.getSessionToken();
2483            }
2484            return null;
2485        }
2486
2487        private int findRemoteControlClientRecord(Object rcc) {
2488            final int count = mRemoteControlClients.size();
2489            for (int i = 0; i < count; i++) {
2490                RemoteControlClientRecord record = mRemoteControlClients.get(i);
2491                if (record.getRemoteControlClient() == rcc) {
2492                    return i;
2493                }
2494            }
2495            return -1;
2496        }
2497
2498        private void updatePlaybackInfoFromSelectedRoute() {
2499            if (mSelectedRoute != null) {
2500                mPlaybackInfo.volume = mSelectedRoute.getVolume();
2501                mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax();
2502                mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling();
2503                mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream();
2504                mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType();
2505
2506                final int count = mRemoteControlClients.size();
2507                for (int i = 0; i < count; i++) {
2508                    RemoteControlClientRecord record = mRemoteControlClients.get(i);
2509                    record.updatePlaybackInfo();
2510                }
2511                if (mMediaSession != null) {
2512                    if (mSelectedRoute == getDefaultRoute()) {
2513                        // Local route
2514                        mMediaSession.clearVolumeHandling();
2515                    } else {
2516                        @VolumeProviderCompat.ControlType int controlType =
2517                                VolumeProviderCompat.VOLUME_CONTROL_FIXED;
2518                        if (mPlaybackInfo.volumeHandling
2519                                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
2520                            controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
2521                        }
2522                        mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
2523                                mPlaybackInfo.volume);
2524                    }
2525                }
2526            } else {
2527                if (mMediaSession != null) {
2528                    mMediaSession.clearVolumeHandling();
2529                }
2530            }
2531        }
2532
2533        private final class ProviderCallback extends MediaRouteProvider.Callback {
2534            @Override
2535            public void onDescriptorChanged(MediaRouteProvider provider,
2536                    MediaRouteProviderDescriptor descriptor) {
2537                updateProviderDescriptor(provider, descriptor);
2538            }
2539        }
2540
2541        private final class MediaSessionRecord {
2542            private final MediaSessionCompat mMsCompat;
2543
2544            private @VolumeProviderCompat.ControlType int mControlType;
2545            private int mMaxVolume;
2546            private VolumeProviderCompat mVpCompat;
2547
2548            public MediaSessionRecord(Object mediaSession) {
2549                mMsCompat = MediaSessionCompat.obtain(mApplicationContext, mediaSession);
2550            }
2551
2552            public void configureVolume(@VolumeProviderCompat.ControlType int controlType,
2553                    int max, int current) {
2554                if (mVpCompat != null && controlType == mControlType && max == mMaxVolume) {
2555                    // If we haven't changed control type or max just set the
2556                    // new current volume
2557                    mVpCompat.setCurrentVolume(current);
2558                } else {
2559                    // Otherwise create a new provider and update
2560                    mVpCompat = new VolumeProviderCompat(controlType, max, current) {
2561                        @Override
2562                        public void onSetVolumeTo(final int volume) {
2563                            mCallbackHandler.post(new Runnable() {
2564                                @Override
2565                                public void run() {
2566                                    if (mSelectedRoute != null) {
2567                                        mSelectedRoute.requestSetVolume(volume);
2568                                    }
2569                                }
2570                            });
2571                        }
2572
2573                        @Override
2574                        public void onAdjustVolume(final int direction) {
2575                            mCallbackHandler.post(new Runnable() {
2576                                @Override
2577                                public void run() {
2578                                    if (mSelectedRoute != null) {
2579                                        mSelectedRoute.requestUpdateVolume(direction);
2580                                    }
2581                                }
2582                            });
2583                        }
2584                    };
2585                    mMsCompat.setPlaybackToRemote(mVpCompat);
2586                }
2587            }
2588
2589            public void clearVolumeHandling() {
2590                mMsCompat.setPlaybackToLocal(mPlaybackInfo.playbackStream);
2591                mVpCompat = null;
2592            }
2593
2594            public MediaSessionCompat.Token getToken() {
2595                return mMsCompat.getSessionToken();
2596            }
2597
2598        }
2599
2600        private final class RemoteControlClientRecord
2601                implements RemoteControlClientCompat.VolumeCallback {
2602            private final RemoteControlClientCompat mRccCompat;
2603            private boolean mDisconnected;
2604
2605            public RemoteControlClientRecord(Object rcc) {
2606                mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc);
2607                mRccCompat.setVolumeCallback(this);
2608                updatePlaybackInfo();
2609            }
2610
2611            public Object getRemoteControlClient() {
2612                return mRccCompat.getRemoteControlClient();
2613            }
2614
2615            public void disconnect() {
2616                mDisconnected = true;
2617                mRccCompat.setVolumeCallback(null);
2618            }
2619
2620            public void updatePlaybackInfo() {
2621                mRccCompat.setPlaybackInfo(mPlaybackInfo);
2622            }
2623
2624            @Override
2625            public void onVolumeSetRequest(int volume) {
2626                if (!mDisconnected && mSelectedRoute != null) {
2627                    mSelectedRoute.requestSetVolume(volume);
2628                }
2629            }
2630
2631            @Override
2632            public void onVolumeUpdateRequest(int direction) {
2633                if (!mDisconnected && mSelectedRoute != null) {
2634                    mSelectedRoute.requestUpdateVolume(direction);
2635                }
2636            }
2637        }
2638
2639        private final class CallbackHandler extends Handler {
2640            private final ArrayList<CallbackRecord> mTempCallbackRecords =
2641                    new ArrayList<CallbackRecord>();
2642
2643            private static final int MSG_TYPE_MASK = 0xff00;
2644            private static final int MSG_TYPE_ROUTE = 0x0100;
2645            private static final int MSG_TYPE_PROVIDER = 0x0200;
2646
2647            public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
2648            public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
2649            public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
2650            public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
2651            public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
2652            public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
2653            public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
2654
2655            public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
2656            public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
2657            public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
2658
2659            public void post(int msg, Object obj) {
2660                obtainMessage(msg, obj).sendToTarget();
2661            }
2662
2663            @Override
2664            public void handleMessage(Message msg) {
2665                final int what = msg.what;
2666                final Object obj = msg.obj;
2667
2668                // Synchronize state with the system media router.
2669                syncWithSystemProvider(what, obj);
2670
2671                // Invoke all registered callbacks.
2672                // Build a list of callbacks before invoking them in case callbacks
2673                // are added or removed during dispatch.
2674                try {
2675                    for (int i = mRouters.size(); --i >= 0; ) {
2676                        MediaRouter router = mRouters.get(i).get();
2677                        if (router == null) {
2678                            mRouters.remove(i);
2679                        } else {
2680                            mTempCallbackRecords.addAll(router.mCallbackRecords);
2681                        }
2682                    }
2683
2684                    final int callbackCount = mTempCallbackRecords.size();
2685                    for (int i = 0; i < callbackCount; i++) {
2686                        invokeCallback(mTempCallbackRecords.get(i), what, obj);
2687                    }
2688                } finally {
2689                    mTempCallbackRecords.clear();
2690                }
2691            }
2692
2693            private void syncWithSystemProvider(int what, Object obj) {
2694                switch (what) {
2695                    case MSG_ROUTE_ADDED:
2696                        mSystemProvider.onSyncRouteAdded((RouteInfo)obj);
2697                        break;
2698                    case MSG_ROUTE_REMOVED:
2699                        mSystemProvider.onSyncRouteRemoved((RouteInfo)obj);
2700                        break;
2701                    case MSG_ROUTE_CHANGED:
2702                        mSystemProvider.onSyncRouteChanged((RouteInfo)obj);
2703                        break;
2704                    case MSG_ROUTE_SELECTED:
2705                        mSystemProvider.onSyncRouteSelected((RouteInfo)obj);
2706                        break;
2707                }
2708            }
2709
2710            private void invokeCallback(CallbackRecord record, int what, Object obj) {
2711                final MediaRouter router = record.mRouter;
2712                final MediaRouter.Callback callback = record.mCallback;
2713                switch (what & MSG_TYPE_MASK) {
2714                    case MSG_TYPE_ROUTE: {
2715                        final RouteInfo route = (RouteInfo)obj;
2716                        if (!record.filterRouteEvent(route)) {
2717                            break;
2718                        }
2719                        switch (what) {
2720                            case MSG_ROUTE_ADDED:
2721                                callback.onRouteAdded(router, route);
2722                                break;
2723                            case MSG_ROUTE_REMOVED:
2724                                callback.onRouteRemoved(router, route);
2725                                break;
2726                            case MSG_ROUTE_CHANGED:
2727                                callback.onRouteChanged(router, route);
2728                                break;
2729                            case MSG_ROUTE_VOLUME_CHANGED:
2730                                callback.onRouteVolumeChanged(router, route);
2731                                break;
2732                            case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
2733                                callback.onRoutePresentationDisplayChanged(router, route);
2734                                break;
2735                            case MSG_ROUTE_SELECTED:
2736                                callback.onRouteSelected(router, route);
2737                                break;
2738                            case MSG_ROUTE_UNSELECTED:
2739                                callback.onRouteUnselected(router, route);
2740                                break;
2741                        }
2742                        break;
2743                    }
2744                    case MSG_TYPE_PROVIDER: {
2745                        final ProviderInfo provider = (ProviderInfo)obj;
2746                        switch (what) {
2747                            case MSG_PROVIDER_ADDED:
2748                                callback.onProviderAdded(router, provider);
2749                                break;
2750                            case MSG_PROVIDER_REMOVED:
2751                                callback.onProviderRemoved(router, provider);
2752                                break;
2753                            case MSG_PROVIDER_CHANGED:
2754                                callback.onProviderChanged(router, provider);
2755                                break;
2756                        }
2757                    }
2758                }
2759            }
2760        }
2761    }
2762}
2763