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