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