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