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