Home | History | Annotate | Download | only in media
      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 
     17 package android.support.v7.media;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.content.pm.PackageManager.NameNotFoundException;
     24 import android.content.res.Resources;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Looper;
     28 import android.os.Message;
     29 import android.support.v4.hardware.display.DisplayManagerCompat;
     30 import android.support.v7.media.MediaRouteProvider.ProviderMetadata;
     31 import android.util.Log;
     32 import android.view.Display;
     33 
     34 import java.lang.ref.WeakReference;
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.List;
     38 import java.util.Locale;
     39 
     40 /**
     41  * MediaRouter allows applications to control the routing of media channels
     42  * and streams from the current device to external speakers and destination devices.
     43  * <p>
     44  * A MediaRouter instance is retrieved through {@link #getInstance}.  Applications
     45  * can query the media router about the currently selected route and its capabilities
     46  * to determine how to send content to the route's destination.  Applications can
     47  * also {@link RouteInfo#sendControlRequest send control requests} to the route
     48  * to ask the route's destination to perform certain remote control functions
     49  * such as playing media.
     50  * </p><p>
     51  * See also {@link MediaRouteProvider} for information on how an application
     52  * can publish new media routes to the media router.
     53  * </p><p>
     54  * The media router API is not thread-safe; all interactions with it must be
     55  * done from the main thread of the process.
     56  * </p>
     57  */
     58 public final class MediaRouter {
     59     private static final String TAG = "MediaRouter";
     60     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     61 
     62     // Maintains global media router state for the process.
     63     // This field is initialized in MediaRouter.getInstance() before any
     64     // MediaRouter objects are instantiated so it is guaranteed to be
     65     // valid whenever any instance method is invoked.
     66     static GlobalMediaRouter sGlobal;
     67 
     68     // Context-bound state of the media router.
     69     final Context mContext;
     70     final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
     71 
     72     /**
     73      * Flag for {@link #addCallback}: Actively scan for routes while this callback
     74      * is registered.
     75      * <p>
     76      * When this flag is specified, the media router will actively scan for new
     77      * routes.  Certain routes, such as wifi display routes, may not be discoverable
     78      * except when actively scanning.  This flag is typically used when the route picker
     79      * dialog has been opened by the user to ensure that the route information is
     80      * up to date.
     81      * </p><p>
     82      * Active scanning may consume a significant amount of power and may have intrusive
     83      * effects on wireless connectivity.  Therefore it is important that active scanning
     84      * only be requested when it is actually needed to satisfy a user request to
     85      * discover and select a new route.
     86      * </p><p>
     87      * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing
     88      * active scans is much more expensive than a normal discovery request.
     89      * </p>
     90      *
     91      * @see #CALLBACK_FLAG_REQUEST_DISCOVERY
     92      */
     93     public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
     94 
     95     /**
     96      * Flag for {@link #addCallback}: Do not filter route events.
     97      * <p>
     98      * When this flag is specified, the callback will be invoked for events that affect any
     99      * route event if they do not match the callback's associated media route selector.
    100      * </p>
    101      */
    102     public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
    103 
    104     /**
    105      * Flag for {@link #addCallback}: Request that route discovery be performed while this
    106      * callback is registered.
    107      * <p>
    108      * When this flag is specified, the media router will try to discover routes.
    109      * Although route discovery is intended to be efficient, checking for new routes may
    110      * result in some network activity and could slowly drain the battery.  Therefore
    111      * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when
    112      * they are running in the foreground and would like to provide the user with the
    113      * option of connecting to new routes.
    114      * </p><p>
    115      * Applications should typically add a callback using this flag in the
    116      * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart}
    117      * method and remove it in the {@link android.app.Activity#onStop onStop} method.
    118      * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may
    119      * also be used for this purpose.
    120      * </p>
    121      *
    122      * @see android.support.v7.app.MediaRouteDiscoveryFragment
    123      */
    124     public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
    125 
    126     /**
    127      * Flag for {@link #isRouteAvailable}: Ignore the default route.
    128      * <p>
    129      * This flag is used to determine whether a matching non-default route is available.
    130      * This constraint may be used to decide whether to offer the route chooser dialog
    131      * to the user.  There is no point offering the chooser if there are no
    132      * non-default choices.
    133      * </p>
    134      */
    135     public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
    136 
    137     MediaRouter(Context context) {
    138         mContext = context;
    139     }
    140 
    141     /**
    142      * Gets an instance of the media router service associated with the context.
    143      * <p>
    144      * The application is responsible for holding a strong reference to the returned
    145      * {@link MediaRouter} instance, such as by storing the instance in a field of
    146      * the {@link android.app.Activity}, to ensure that the media router remains alive
    147      * as long as the application is using its features.
    148      * </p><p>
    149      * In other words, the support library only holds a {@link WeakReference weak reference}
    150      * to each media router instance.  When there are no remaining strong references to the
    151      * media router instance, all of its callbacks will be removed and route discovery
    152      * will no longer be performed on its behalf.
    153      * </p>
    154      *
    155      * @return The media router instance for the context.  The application must hold
    156      * a strong reference to this object as long as it is in use.
    157      */
    158     public static MediaRouter getInstance(Context context) {
    159         if (context == null) {
    160             throw new IllegalArgumentException("context must not be null");
    161         }
    162         checkCallingThread();
    163 
    164         if (sGlobal == null) {
    165             sGlobal = new GlobalMediaRouter(context.getApplicationContext());
    166             sGlobal.start();
    167         }
    168         return sGlobal.getRouter(context);
    169     }
    170 
    171     /**
    172      * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
    173      * this media router.
    174      */
    175     public List<RouteInfo> getRoutes() {
    176         checkCallingThread();
    177         return sGlobal.getRoutes();
    178     }
    179 
    180     /**
    181      * Gets information about the {@link MediaRouter.ProviderInfo route providers}
    182      * currently known to this media router.
    183      */
    184     public List<ProviderInfo> getProviders() {
    185         checkCallingThread();
    186         return sGlobal.getProviders();
    187     }
    188 
    189     /**
    190      * Gets the default route for playing media content on the system.
    191      * <p>
    192      * The system always provides a default route.
    193      * </p>
    194      *
    195      * @return The default route, which is guaranteed to never be null.
    196      */
    197     public RouteInfo getDefaultRoute() {
    198         checkCallingThread();
    199         return sGlobal.getDefaultRoute();
    200     }
    201 
    202     /**
    203      * Gets the currently selected route.
    204      * <p>
    205      * The application should examine the route's
    206      * {@link RouteInfo#getControlFilters media control intent filters} to assess the
    207      * capabilities of the route before attempting to use it.
    208      * </p>
    209      *
    210      * <h3>Example</h3>
    211      * <pre>
    212      * public boolean playMovie() {
    213      *     MediaRouter mediaRouter = MediaRouter.getInstance(context);
    214      *     MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
    215      *
    216      *     // First try using the remote playback interface, if supported.
    217      *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
    218      *         // The route supports remote playback.
    219      *         // Try to send it the Uri of the movie to play.
    220      *         Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
    221      *         intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
    222      *         intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
    223      *         if (route.supportsControlRequest(intent)) {
    224      *             route.sendControlRequest(intent, null);
    225      *             return true; // sent the request to play the movie
    226      *         }
    227      *     }
    228      *
    229      *     // If remote playback was not possible, then play locally.
    230      *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
    231      *         // The route supports live video streaming.
    232      *         // Prepare to play content locally in a window or in a presentation.
    233      *         return playMovieInWindow();
    234      *     }
    235      *
    236      *     // Neither interface is supported, so we can't play the movie to this route.
    237      *     return false;
    238      * }
    239      * </pre>
    240      *
    241      * @return The selected route, which is guaranteed to never be null.
    242      *
    243      * @see RouteInfo#getControlFilters
    244      * @see RouteInfo#supportsControlCategory
    245      * @see RouteInfo#supportsControlRequest
    246      */
    247     public RouteInfo getSelectedRoute() {
    248         checkCallingThread();
    249         return sGlobal.getSelectedRoute();
    250     }
    251 
    252     /**
    253      * Returns the selected route if it matches the specified selector, otherwise
    254      * selects the default route and returns it.
    255      *
    256      * @param selector The selector to match.
    257      * @return The previously selected route if it matched the selector, otherwise the
    258      * newly selected default route which is guaranteed to never be null.
    259      *
    260      * @see MediaRouteSelector
    261      * @see RouteInfo#matchesSelector
    262      * @see RouteInfo#isDefault
    263      */
    264     public RouteInfo updateSelectedRoute(MediaRouteSelector selector) {
    265         if (selector == null) {
    266             throw new IllegalArgumentException("selector must not be null");
    267         }
    268         checkCallingThread();
    269 
    270         if (DEBUG) {
    271             Log.d(TAG, "updateSelectedRoute: " + selector);
    272         }
    273         RouteInfo route = sGlobal.getSelectedRoute();
    274         if (!route.isDefault() && !route.matchesSelector(selector)) {
    275             route = sGlobal.getDefaultRoute();
    276             sGlobal.selectRoute(route);
    277         }
    278         return route;
    279     }
    280 
    281     /**
    282      * Selects the specified route.
    283      *
    284      * @param route The route to select.
    285      */
    286     public void selectRoute(RouteInfo route) {
    287         if (route == null) {
    288             throw new IllegalArgumentException("route must not be null");
    289         }
    290         checkCallingThread();
    291 
    292         if (DEBUG) {
    293             Log.d(TAG, "selectRoute: " + route);
    294         }
    295         sGlobal.selectRoute(route);
    296     }
    297 
    298     /**
    299      * Returns true if there is a route that matches the specified selector.
    300      * <p>
    301      * This method returns true if there are any available routes that match the selector
    302      * regardless of whether they are enabled or disabled.  If the
    303      * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
    304      * the method will only consider non-default routes.
    305      * </p>
    306      *
    307      * @param selector The selector to match.
    308      * @param flags Flags to control the determination of whether a route may be available.
    309      * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
    310      * @return True if a matching route may be available.
    311      */
    312     public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
    313         if (selector == null) {
    314             throw new IllegalArgumentException("selector must not be null");
    315         }
    316         checkCallingThread();
    317 
    318         return sGlobal.isRouteAvailable(selector, flags);
    319     }
    320 
    321     /**
    322      * Registers a callback to discover routes that match the selector and to receive
    323      * events when they change.
    324      * <p>
    325      * This is a convenience method that has the same effect as calling
    326      * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
    327      * </p>
    328      *
    329      * @param selector A route selector that indicates the kinds of routes that the
    330      * callback would like to discover.
    331      * @param callback The callback to add.
    332      * @see #removeCallback
    333      */
    334     public void addCallback(MediaRouteSelector selector, Callback callback) {
    335         addCallback(selector, callback, 0);
    336     }
    337 
    338     /**
    339      * Registers a callback to discover routes that match the selector and to receive
    340      * events when they change.
    341      * <p>
    342      * The selector describes the kinds of routes that the application wants to
    343      * discover.  For example, if the application wants to use
    344      * live audio routes then it should include the
    345      * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
    346      * in its selector when it adds a callback to the media router.
    347      * The selector may include any number of categories.
    348      * </p><p>
    349      * If the callback has already been registered, then the selector is added to
    350      * the set of selectors being monitored by the callback.
    351      * </p><p>
    352      * By default, the callback will only be invoked for events that affect routes
    353      * that match the specified selector.  Event filtering may be disabled by specifying
    354      * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
    355      * </p>
    356      *
    357      * <h3>Example</h3>
    358      * <pre>
    359      * public class MyActivity extends Activity {
    360      *     private MediaRouter mRouter;
    361      *     private MediaRouter.Callback mCallback;
    362      *     private MediaRouteSelector mSelector;
    363      *
    364      *     protected void onCreate(Bundle savedInstanceState) {
    365      *         super.onCreate(savedInstanceState);
    366      *
    367      *         mRouter = Mediarouter.getInstance(this);
    368      *         mCallback = new MyCallback();
    369      *         mSelector = new MediaRouteSelector.Builder()
    370      *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
    371      *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
    372      *                 .build();
    373      *     }
    374      *
    375      *     // Add the callback on start to tell the media router what kinds of routes
    376      *     // the application is interested in so that it can try to discover suitable ones.
    377      *     public void onStart() {
    378      *         super.onStart();
    379      *
    380      *         mediaRouter.addCallback(mSelector, mCallback,
    381      *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
    382      *
    383      *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
    384      *         // do something with the route...
    385      *     }
    386      *
    387      *     // Remove the selector on stop to tell the media router that it no longer
    388      *     // needs to invest effort trying to discover routes of these kinds for now.
    389      *     public void onStop() {
    390      *         super.onStop();
    391      *
    392      *         mediaRouter.removeCallback(mCallback);
    393      *     }
    394      *
    395      *     private final class MyCallback extends MediaRouter.Callback {
    396      *         // Implement callback methods as needed.
    397      *     }
    398      * }
    399      * </pre>
    400      *
    401      * @param selector A route selector that indicates the kinds of routes that the
    402      * callback would like to discover.
    403      * @param callback The callback to add.
    404      * @param flags Flags to control the behavior of the callback.
    405      * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
    406      * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
    407      * @see #removeCallback
    408      */
    409     public void addCallback(MediaRouteSelector selector, Callback callback, int flags) {
    410         if (selector == null) {
    411             throw new IllegalArgumentException("selector must not be null");
    412         }
    413         if (callback == null) {
    414             throw new IllegalArgumentException("callback must not be null");
    415         }
    416         checkCallingThread();
    417 
    418         if (DEBUG) {
    419             Log.d(TAG, "addCallback: selector=" + selector
    420                     + ", callback=" + callback + ", flags=" + Integer.toHexString(flags));
    421         }
    422 
    423         CallbackRecord record;
    424         int index = findCallbackRecord(callback);
    425         if (index < 0) {
    426             record = new CallbackRecord(this, callback);
    427             mCallbackRecords.add(record);
    428         } else {
    429             record = mCallbackRecords.get(index);
    430         }
    431         boolean updateNeeded = false;
    432         if ((flags & ~record.mFlags) != 0) {
    433             record.mFlags |= flags;
    434             updateNeeded = true;
    435         }
    436         if (!record.mSelector.contains(selector)) {
    437             record.mSelector = new MediaRouteSelector.Builder(record.mSelector)
    438                     .addSelector(selector)
    439                     .build();
    440             updateNeeded = true;
    441         }
    442         if (updateNeeded) {
    443             sGlobal.updateDiscoveryRequest();
    444         }
    445     }
    446 
    447     /**
    448      * Removes the specified callback.  It will no longer receive events about
    449      * changes to media routes.
    450      *
    451      * @param callback The callback to remove.
    452      * @see #addCallback
    453      */
    454     public void removeCallback(Callback callback) {
    455         if (callback == null) {
    456             throw new IllegalArgumentException("callback must not be null");
    457         }
    458         checkCallingThread();
    459 
    460         if (DEBUG) {
    461             Log.d(TAG, "removeCallback: callback=" + callback);
    462         }
    463 
    464         int index = findCallbackRecord(callback);
    465         if (index >= 0) {
    466             mCallbackRecords.remove(index);
    467             sGlobal.updateDiscoveryRequest();
    468         }
    469     }
    470 
    471     private int findCallbackRecord(Callback callback) {
    472         final int count = mCallbackRecords.size();
    473         for (int i = 0; i < count; i++) {
    474             if (mCallbackRecords.get(i).mCallback == callback) {
    475                 return i;
    476             }
    477         }
    478         return -1;
    479     }
    480 
    481     /**
    482      * Registers a media route provider within this application process.
    483      * <p>
    484      * The provider will be added to the list of providers that all {@link MediaRouter}
    485      * instances within this process can use to discover routes.
    486      * </p>
    487      *
    488      * @param providerInstance The media route provider instance to add.
    489      *
    490      * @see MediaRouteProvider
    491      * @see #removeCallback
    492      */
    493     public void addProvider(MediaRouteProvider providerInstance) {
    494         if (providerInstance == null) {
    495             throw new IllegalArgumentException("providerInstance must not be null");
    496         }
    497         checkCallingThread();
    498 
    499         if (DEBUG) {
    500             Log.d(TAG, "addProvider: " + providerInstance);
    501         }
    502         sGlobal.addProvider(providerInstance);
    503     }
    504 
    505     /**
    506      * Unregisters a media route provider within this application process.
    507      * <p>
    508      * The provider will be removed from the list of providers that all {@link MediaRouter}
    509      * instances within this process can use to discover routes.
    510      * </p>
    511      *
    512      * @param providerInstance The media route provider instance to remove.
    513      *
    514      * @see MediaRouteProvider
    515      * @see #addCallback
    516      */
    517     public void removeProvider(MediaRouteProvider providerInstance) {
    518         if (providerInstance == null) {
    519             throw new IllegalArgumentException("providerInstance must not be null");
    520         }
    521         checkCallingThread();
    522 
    523         if (DEBUG) {
    524             Log.d(TAG, "removeProvider: " + providerInstance);
    525         }
    526         sGlobal.removeProvider(providerInstance);
    527     }
    528 
    529     /**
    530      * Ensures that calls into the media router are on the correct thread.
    531      * It pays to be a little paranoid when global state invariants are at risk.
    532      */
    533     static void checkCallingThread() {
    534         if (Looper.myLooper() != Looper.getMainLooper()) {
    535             throw new IllegalStateException("The media router service must only be "
    536                     + "accessed on the application's main thread.");
    537         }
    538     }
    539 
    540     static <T> boolean equal(T a, T b) {
    541         return a == b || (a != null && b != null && a.equals(b));
    542     }
    543 
    544     /**
    545      * Provides information about a media route.
    546      * <p>
    547      * Each media route has a list of {@link MediaControlIntent media control}
    548      * {@link #getControlFilters intent filters} that describe the capabilities of the
    549      * route and the manner in which it is used and controlled.
    550      * </p>
    551      */
    552     public static final class RouteInfo {
    553         private final ProviderInfo mProvider;
    554         private final String mDescriptorId;
    555         private final String mUniqueId;
    556         private String mName;
    557         private String mDescription;
    558         private boolean mEnabled;
    559         private boolean mConnecting;
    560         private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>();
    561         private int mPlaybackType;
    562         private int mPlaybackStream;
    563         private int mVolumeHandling;
    564         private int mVolume;
    565         private int mVolumeMax;
    566         private Display mPresentationDisplay;
    567         private int mPresentationDisplayId = -1;
    568         private Bundle mExtras;
    569         private MediaRouteDescriptor mDescriptor;
    570 
    571         /**
    572          * The default playback type, "local", indicating the presentation of the media
    573          * is happening on the same device (e.g. a phone, a tablet) as where it is
    574          * controlled from.
    575          *
    576          * @see #getPlaybackType
    577          */
    578         public static final int PLAYBACK_TYPE_LOCAL = 0;
    579 
    580         /**
    581          * A playback type indicating the presentation of the media is happening on
    582          * a different device (i.e. the remote device) than where it is controlled from.
    583          *
    584          * @see #getPlaybackType
    585          */
    586         public static final int PLAYBACK_TYPE_REMOTE = 1;
    587 
    588         /**
    589          * Playback information indicating the playback volume is fixed, i.e. it cannot be
    590          * controlled from this object. An example of fixed playback volume is a remote player,
    591          * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
    592          * than attenuate at the source.
    593          *
    594          * @see #getVolumeHandling
    595          */
    596         public static final int PLAYBACK_VOLUME_FIXED = 0;
    597 
    598         /**
    599          * Playback information indicating the playback volume is variable and can be controlled
    600          * from this object.
    601          *
    602          * @see #getVolumeHandling
    603          */
    604         public static final int PLAYBACK_VOLUME_VARIABLE = 1;
    605 
    606         static final int CHANGE_GENERAL = 1 << 0;
    607         static final int CHANGE_VOLUME = 1 << 1;
    608         static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
    609 
    610         RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) {
    611             mProvider = provider;
    612             mDescriptorId = descriptorId;
    613             mUniqueId = uniqueId;
    614         }
    615 
    616         /**
    617          * Gets information about the provider of this media route.
    618          */
    619         public ProviderInfo getProvider() {
    620             return mProvider;
    621         }
    622 
    623         /**
    624          * Gets the unique id of the route.
    625          * <p>
    626          * The route unique id functions as a stable identifier by which the route is known.
    627          * For example, an application can use this id as a token to remember the
    628          * selected route across restarts or to communicate its identity to a service.
    629          * </p>
    630          *
    631          * @return The unique id of the route, never null.
    632          */
    633         public String getId() {
    634             return mUniqueId;
    635         }
    636 
    637         /**
    638          * Gets the user-visible name of the route.
    639          * <p>
    640          * The route name identifies the destination represented by the route.
    641          * It may be a user-supplied name, an alias, or device serial number.
    642          * </p>
    643          *
    644          * @return The user-visible name of a media route.  This is the string presented
    645          * to users who may select this as the active route.
    646          */
    647         public String getName() {
    648             return mName;
    649         }
    650 
    651         /**
    652          * Gets the user-visible description of the route.
    653          * <p>
    654          * The route description describes the kind of destination represented by the route.
    655          * It may be a user-supplied string, a model number or brand of device.
    656          * </p>
    657          *
    658          * @return The description of the route, or null if none.
    659          */
    660         public String getDescription() {
    661             return mDescription;
    662         }
    663 
    664         /**
    665          * Returns true if this route is enabled and may be selected.
    666          *
    667          * @return True if this route is enabled.
    668          */
    669         public boolean isEnabled() {
    670             return mEnabled;
    671         }
    672 
    673         /**
    674          * Returns true if the route is in the process of connecting and is not
    675          * yet ready for use.
    676          *
    677          * @return True if this route is in the process of connecting.
    678          */
    679         public boolean isConnecting() {
    680             return mConnecting;
    681         }
    682 
    683         /**
    684          * Returns true if this route is currently selected.
    685          *
    686          * @return True if this route is currently selected.
    687          *
    688          * @see MediaRouter#getSelectedRoute
    689          */
    690         public boolean isSelected() {
    691             checkCallingThread();
    692             return sGlobal.getSelectedRoute() == this;
    693         }
    694 
    695         /**
    696          * Returns true if this route is the default route.
    697          *
    698          * @return True if this route is the default route.
    699          *
    700          * @see MediaRouter#getDefaultRoute
    701          */
    702         public boolean isDefault() {
    703             checkCallingThread();
    704             return sGlobal.getDefaultRoute() == this;
    705         }
    706 
    707         /**
    708          * Gets a list of {@link MediaControlIntent media control intent} filters that
    709          * describe the capabilities of this route and the media control actions that
    710          * it supports.
    711          *
    712          * @return A list of intent filters that specifies the media control intents that
    713          * this route supports.
    714          *
    715          * @see MediaControlIntent
    716          * @see #supportsControlCategory
    717          * @see #supportsControlRequest
    718          */
    719         public List<IntentFilter> getControlFilters() {
    720             return mControlFilters;
    721         }
    722 
    723         /**
    724          * Returns true if the route supports at least one of the capabilities
    725          * described by a media route selector.
    726          *
    727          * @param selector The selector that specifies the capabilities to check.
    728          * @return True if the route supports at least one of the capabilities
    729          * described in the media route selector.
    730          */
    731         public boolean matchesSelector(MediaRouteSelector selector) {
    732             if (selector == null) {
    733                 throw new IllegalArgumentException("selector must not be null");
    734             }
    735             checkCallingThread();
    736             return selector.matchesControlFilters(mControlFilters);
    737         }
    738 
    739         /**
    740          * Returns true if the route supports the specified
    741          * {@link MediaControlIntent media control} category.
    742          * <p>
    743          * Media control categories describe the capabilities of this route
    744          * such as whether it supports live audio streaming or remote playback.
    745          * </p>
    746          *
    747          * @param category A {@link MediaControlIntent media control} category
    748          * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
    749          * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
    750          * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
    751          * media control category.
    752          * @return True if the route supports the specified intent category.
    753          *
    754          * @see MediaControlIntent
    755          * @see #getControlFilters
    756          */
    757         public boolean supportsControlCategory(String category) {
    758             if (category == null) {
    759                 throw new IllegalArgumentException("category must not be null");
    760             }
    761             checkCallingThread();
    762 
    763             int count = mControlFilters.size();
    764             for (int i = 0; i < count; i++) {
    765                 if (mControlFilters.get(i).hasCategory(category)) {
    766                     return true;
    767                 }
    768             }
    769             return false;
    770         }
    771 
    772         /**
    773          * Returns true if the route supports the specified
    774          * {@link MediaControlIntent media control} request.
    775          * <p>
    776          * Media control requests are used to request the route to perform
    777          * actions such as starting remote playback of a media item.
    778          * </p>
    779          *
    780          * @param intent A {@link MediaControlIntent media control intent}.
    781          * @return True if the route can handle the specified intent.
    782          *
    783          * @see MediaControlIntent
    784          * @see #getControlFilters
    785          */
    786         public boolean supportsControlRequest(Intent intent) {
    787             if (intent == null) {
    788                 throw new IllegalArgumentException("intent must not be null");
    789             }
    790             checkCallingThread();
    791 
    792             ContentResolver contentResolver = sGlobal.getContentResolver();
    793             int count = mControlFilters.size();
    794             for (int i = 0; i < count; i++) {
    795                 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
    796                     return true;
    797                 }
    798             }
    799             return false;
    800         }
    801 
    802         /**
    803          * Sends a {@link MediaControlIntent media control} request to be performed
    804          * asynchronously by the route's destination.
    805          * <p>
    806          * Media control requests are used to request the route to perform
    807          * actions such as starting remote playback of a media item.
    808          * </p><p>
    809          * This function may only be called on a selected route.  Control requests
    810          * sent to unselected routes will fail.
    811          * </p>
    812          *
    813          * @param intent A {@link MediaControlIntent media control intent}.
    814          * @param callback A {@link ControlRequestCallback} to invoke with the result
    815          * of the request, or null if no result is required.
    816          *
    817          * @see MediaControlIntent
    818          */
    819         public void sendControlRequest(Intent intent, ControlRequestCallback callback) {
    820             if (intent == null) {
    821                 throw new IllegalArgumentException("intent must not be null");
    822             }
    823             checkCallingThread();
    824 
    825             sGlobal.sendControlRequest(this, intent, callback);
    826         }
    827 
    828         /**
    829          * Gets the type of playback associated with this route.
    830          *
    831          * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
    832          * or {@link #PLAYBACK_TYPE_REMOTE}.
    833          */
    834         public int getPlaybackType() {
    835             return mPlaybackType;
    836         }
    837 
    838         /**
    839          * Gets the audio stream over which the playback associated with this route is performed.
    840          *
    841          * @return The stream over which the playback associated with this route is performed.
    842          */
    843         public int getPlaybackStream() {
    844             return mPlaybackStream;
    845         }
    846 
    847         /**
    848          * Gets information about how volume is handled on the route.
    849          *
    850          * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
    851          * or {@link #PLAYBACK_VOLUME_VARIABLE}.
    852          */
    853         public int getVolumeHandling() {
    854             return mVolumeHandling;
    855         }
    856 
    857         /**
    858          * Gets the current volume for this route. Depending on the route, this may only
    859          * be valid if the route is currently selected.
    860          *
    861          * @return The volume at which the playback associated with this route is performed.
    862          */
    863         public int getVolume() {
    864             return mVolume;
    865         }
    866 
    867         /**
    868          * Gets the maximum volume at which the playback associated with this route is performed.
    869          *
    870          * @return The maximum volume at which the playback associated with
    871          * this route is performed.
    872          */
    873         public int getVolumeMax() {
    874             return mVolumeMax;
    875         }
    876 
    877         /**
    878          * Requests a volume change for this route asynchronously.
    879          * <p>
    880          * This function may only be called on a selected route.  It will have
    881          * no effect if the route is currently unselected.
    882          * </p>
    883          *
    884          * @param volume The new volume value between 0 and {@link #getVolumeMax}.
    885          */
    886         public void requestSetVolume(int volume) {
    887             checkCallingThread();
    888             sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
    889         }
    890 
    891         /**
    892          * Requests an incremental volume update for this route asynchronously.
    893          * <p>
    894          * This function may only be called on a selected route.  It will have
    895          * no effect if the route is currently unselected.
    896          * </p>
    897          *
    898          * @param delta The delta to add to the current volume.
    899          */
    900         public void requestUpdateVolume(int delta) {
    901             checkCallingThread();
    902             if (delta != 0) {
    903                 sGlobal.requestUpdateVolume(this, delta);
    904             }
    905         }
    906 
    907         /**
    908          * Gets the {@link Display} that should be used by the application to show
    909          * a {@link android.app.Presentation} on an external display when this route is selected.
    910          * Depending on the route, this may only be valid if the route is currently
    911          * selected.
    912          * <p>
    913          * The preferred presentation display may change independently of the route
    914          * being selected or unselected.  For example, the presentation display
    915          * of the default system route may change when an external HDMI display is connected
    916          * or disconnected even though the route itself has not changed.
    917          * </p><p>
    918          * This method may return null if there is no external display associated with
    919          * the route or if the display is not ready to show UI yet.
    920          * </p><p>
    921          * The application should listen for changes to the presentation display
    922          * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
    923          * show or dismiss its {@link android.app.Presentation} accordingly when the display
    924          * becomes available or is removed.
    925          * </p><p>
    926          * This method only makes sense for
    927          * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
    928          * </p>
    929          *
    930          * @return The preferred presentation display to use when this route is
    931          * selected or null if none.
    932          *
    933          * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
    934          * @see android.app.Presentation
    935          */
    936         public Display getPresentationDisplay() {
    937             checkCallingThread();
    938             if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
    939                 mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
    940             }
    941             return mPresentationDisplay;
    942         }
    943 
    944         /**
    945          * Gets a collection of extra properties about this route that were supplied
    946          * by its media route provider, or null if none.
    947          */
    948         public Bundle getExtras() {
    949             return mExtras;
    950         }
    951 
    952         /**
    953          * Selects this media route.
    954          */
    955         public void select() {
    956             checkCallingThread();
    957             sGlobal.selectRoute(this);
    958         }
    959 
    960         @Override
    961         public String toString() {
    962             return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
    963                     + ", name=" + mName
    964                     + ", description=" + mDescription
    965                     + ", enabled=" + mEnabled
    966                     + ", connecting=" + mConnecting
    967                     + ", playbackType=" + mPlaybackType
    968                     + ", playbackStream=" + mPlaybackStream
    969                     + ", volumeHandling=" + mVolumeHandling
    970                     + ", volume=" + mVolume
    971                     + ", volumeMax=" + mVolumeMax
    972                     + ", presentationDisplayId=" + mPresentationDisplayId
    973                     + ", extras=" + mExtras
    974                     + ", providerPackageName=" + mProvider.getPackageName()
    975                     + " }";
    976         }
    977 
    978         int updateDescriptor(MediaRouteDescriptor descriptor) {
    979             int changes = 0;
    980             if (mDescriptor != descriptor) {
    981                 mDescriptor = descriptor;
    982                 if (descriptor != null) {
    983                     if (!equal(mName, descriptor.getName())) {
    984                         mName = descriptor.getName();
    985                         changes |= CHANGE_GENERAL;
    986                     }
    987                     if (!equal(mDescription, descriptor.getDescription())) {
    988                         mDescription = descriptor.getDescription();
    989                         changes |= CHANGE_GENERAL;
    990                     }
    991                     if (mEnabled != descriptor.isEnabled()) {
    992                         mEnabled = descriptor.isEnabled();
    993                         changes |= CHANGE_GENERAL;
    994                     }
    995                     if (mConnecting != descriptor.isConnecting()) {
    996                         mConnecting = descriptor.isConnecting();
    997                         changes |= CHANGE_GENERAL;
    998                     }
    999                     if (!mControlFilters.equals(descriptor.getControlFilters())) {
   1000                         mControlFilters.clear();
   1001                         mControlFilters.addAll(descriptor.getControlFilters());
   1002                         changes |= CHANGE_GENERAL;
   1003                     }
   1004                     if (mPlaybackType != descriptor.getPlaybackType()) {
   1005                         mPlaybackType = descriptor.getPlaybackType();
   1006                         changes |= CHANGE_GENERAL;
   1007                     }
   1008                     if (mPlaybackStream != descriptor.getPlaybackStream()) {
   1009                         mPlaybackStream = descriptor.getPlaybackStream();
   1010                         changes |= CHANGE_GENERAL;
   1011                     }
   1012                     if (mVolumeHandling != descriptor.getVolumeHandling()) {
   1013                         mVolumeHandling = descriptor.getVolumeHandling();
   1014                         changes |= CHANGE_GENERAL | CHANGE_VOLUME;
   1015                     }
   1016                     if (mVolume != descriptor.getVolume()) {
   1017                         mVolume = descriptor.getVolume();
   1018                         changes |= CHANGE_GENERAL | CHANGE_VOLUME;
   1019                     }
   1020                     if (mVolumeMax != descriptor.getVolumeMax()) {
   1021                         mVolumeMax = descriptor.getVolumeMax();
   1022                         changes |= CHANGE_GENERAL | CHANGE_VOLUME;
   1023                     }
   1024                     if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
   1025                         mPresentationDisplayId = descriptor.getPresentationDisplayId();
   1026                         mPresentationDisplay = null;
   1027                         changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
   1028                     }
   1029                     if (!equal(mExtras, descriptor.getExtras())) {
   1030                         mExtras = descriptor.getExtras();
   1031                         changes |= CHANGE_GENERAL;
   1032                     }
   1033                 }
   1034             }
   1035             return changes;
   1036         }
   1037 
   1038         String getDescriptorId() {
   1039             return mDescriptorId;
   1040         }
   1041 
   1042         MediaRouteProvider getProviderInstance() {
   1043             return mProvider.getProviderInstance();
   1044         }
   1045     }
   1046 
   1047     /**
   1048      * Provides information about a media route provider.
   1049      * <p>
   1050      * This object may be used to determine which media route provider has
   1051      * published a particular route.
   1052      * </p>
   1053      */
   1054     public static final class ProviderInfo {
   1055         private final MediaRouteProvider mProviderInstance;
   1056         private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
   1057 
   1058         private final ProviderMetadata mMetadata;
   1059         private MediaRouteProviderDescriptor mDescriptor;
   1060         private Resources mResources;
   1061         private boolean mResourcesNotAvailable;
   1062 
   1063         ProviderInfo(MediaRouteProvider provider) {
   1064             mProviderInstance = provider;
   1065             mMetadata = provider.getMetadata();
   1066         }
   1067 
   1068         /**
   1069          * Gets the provider's underlying {@link MediaRouteProvider} instance.
   1070          */
   1071         public MediaRouteProvider getProviderInstance() {
   1072             checkCallingThread();
   1073             return mProviderInstance;
   1074         }
   1075 
   1076         /**
   1077          * Gets the package name of the media route provider service.
   1078          */
   1079         public String getPackageName() {
   1080             return mMetadata.getPackageName();
   1081         }
   1082 
   1083         /**
   1084          * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
   1085          */
   1086         public List<RouteInfo> getRoutes() {
   1087             checkCallingThread();
   1088             return mRoutes;
   1089         }
   1090 
   1091         Resources getResources() {
   1092             if (mResources == null && !mResourcesNotAvailable) {
   1093                 String packageName = getPackageName();
   1094                 Context context = sGlobal.getProviderContext(packageName);
   1095                 if (context != null) {
   1096                     mResources = context.getResources();
   1097                 } else {
   1098                     Log.w(TAG, "Unable to obtain resources for route provider package: "
   1099                             + packageName);
   1100                     mResourcesNotAvailable = true;
   1101                 }
   1102             }
   1103             return mResources;
   1104         }
   1105 
   1106         boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
   1107             if (mDescriptor != descriptor) {
   1108                 mDescriptor = descriptor;
   1109                 return true;
   1110             }
   1111             return false;
   1112         }
   1113 
   1114         int findRouteByDescriptorId(String id) {
   1115             final int count = mRoutes.size();
   1116             for (int i = 0; i < count; i++) {
   1117                 if (mRoutes.get(i).mDescriptorId.equals(id)) {
   1118                     return i;
   1119                 }
   1120             }
   1121             return -1;
   1122         }
   1123 
   1124         @Override
   1125         public String toString() {
   1126             return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
   1127                     + " }";
   1128         }
   1129     }
   1130 
   1131     /**
   1132      * Interface for receiving events about media routing changes.
   1133      * All methods of this interface will be called from the application's main thread.
   1134      * <p>
   1135      * A Callback will only receive events relevant to routes that the callback
   1136      * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
   1137      * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
   1138      * </p>
   1139      *
   1140      * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
   1141      * @see MediaRouter#removeCallback(Callback)
   1142      */
   1143     public static abstract class Callback {
   1144         /**
   1145          * Called when the supplied media route becomes selected as the active route.
   1146          *
   1147          * @param router The media router reporting the event.
   1148          * @param route The route that has been selected.
   1149          */
   1150         public void onRouteSelected(MediaRouter router, RouteInfo route) {
   1151         }
   1152 
   1153         /**
   1154          * Called when the supplied media route becomes unselected as the active route.
   1155          *
   1156          * @param router The media router reporting the event.
   1157          * @param route The route that has been unselected.
   1158          */
   1159         public void onRouteUnselected(MediaRouter router, RouteInfo route) {
   1160         }
   1161 
   1162         /**
   1163          * Called when a media route has been added.
   1164          *
   1165          * @param router The media router reporting the event.
   1166          * @param route The route that has become available for use.
   1167          */
   1168         public void onRouteAdded(MediaRouter router, RouteInfo route) {
   1169         }
   1170 
   1171         /**
   1172          * Called when a media route has been removed.
   1173          *
   1174          * @param router The media router reporting the event.
   1175          * @param route The route that has been removed from availability.
   1176          */
   1177         public void onRouteRemoved(MediaRouter router, RouteInfo route) {
   1178         }
   1179 
   1180         /**
   1181          * Called when a property of the indicated media route has changed.
   1182          *
   1183          * @param router The media router reporting the event.
   1184          * @param route The route that was changed.
   1185          */
   1186         public void onRouteChanged(MediaRouter router, RouteInfo route) {
   1187         }
   1188 
   1189         /**
   1190          * Called when a media route's volume changes.
   1191          *
   1192          * @param router The media router reporting the event.
   1193          * @param route The route whose volume changed.
   1194          */
   1195         public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
   1196         }
   1197 
   1198         /**
   1199          * Called when a media route's presentation display changes.
   1200          * <p>
   1201          * This method is called whenever the route's presentation display becomes
   1202          * available, is removed or has changes to some of its properties (such as its size).
   1203          * </p>
   1204          *
   1205          * @param router The media router reporting the event.
   1206          * @param route The route whose presentation display changed.
   1207          *
   1208          * @see RouteInfo#getPresentationDisplay()
   1209          */
   1210         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
   1211         }
   1212 
   1213         /**
   1214          * Called when a media route provider has been added.
   1215          *
   1216          * @param router The media router reporting the event.
   1217          * @param provider The provider that has become available for use.
   1218          */
   1219         public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
   1220         }
   1221 
   1222         /**
   1223          * Called when a media route provider has been removed.
   1224          *
   1225          * @param router The media router reporting the event.
   1226          * @param provider The provider that has been removed from availability.
   1227          */
   1228         public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
   1229         }
   1230 
   1231         /**
   1232          * Called when a property of the indicated media route provider has changed.
   1233          *
   1234          * @param router The media router reporting the event.
   1235          * @param provider The provider that was changed.
   1236          */
   1237         public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
   1238         }
   1239     }
   1240 
   1241     /**
   1242      * Callback which is invoked with the result of a media control request.
   1243      *
   1244      * @see RouteInfo#sendControlRequest
   1245      */
   1246     public static abstract class ControlRequestCallback {
   1247         /**
   1248          * Called when a media control request succeeds.
   1249          *
   1250          * @param data Result data, or null if none.
   1251          * Contents depend on the {@link MediaControlIntent media control action}.
   1252          */
   1253         public void onResult(Bundle data) {
   1254         }
   1255 
   1256         /**
   1257          * Called when a media control request fails.
   1258          *
   1259          * @param error A localized error message which may be shown to the user, or null
   1260          * if the cause of the error is unclear.
   1261          * @param data Error data, or null if none.
   1262          * Contents depend on the {@link MediaControlIntent media control action}.
   1263          */
   1264         public void onError(String error, Bundle data) {
   1265         }
   1266     }
   1267 
   1268     private static final class CallbackRecord {
   1269         public final MediaRouter mRouter;
   1270         public final Callback mCallback;
   1271         public MediaRouteSelector mSelector;
   1272         public int mFlags;
   1273 
   1274         public CallbackRecord(MediaRouter router, Callback callback) {
   1275             mRouter = router;
   1276             mCallback = callback;
   1277             mSelector = MediaRouteSelector.EMPTY;
   1278         }
   1279 
   1280         public boolean filterRouteEvent(RouteInfo route) {
   1281             return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
   1282                     || route.matchesSelector(mSelector);
   1283         }
   1284     }
   1285 
   1286     /**
   1287      * Global state for the media router.
   1288      * <p>
   1289      * Media routes and media route providers are global to the process; their
   1290      * state and the bulk of the media router implementation lives here.
   1291      * </p>
   1292      */
   1293     private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback {
   1294         private final Context mApplicationContext;
   1295         private final MediaRouter mApplicationRouter;
   1296         private final ArrayList<WeakReference<MediaRouter>> mRouters =
   1297                 new ArrayList<WeakReference<MediaRouter>>();
   1298         private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
   1299         private final ArrayList<ProviderInfo> mProviders =
   1300                 new ArrayList<ProviderInfo>();
   1301         private final ProviderCallback mProviderCallback = new ProviderCallback();
   1302         private final CallbackHandler mCallbackHandler = new CallbackHandler();
   1303         private final DisplayManagerCompat mDisplayManager;
   1304         private final SystemMediaRouteProvider mSystemProvider;
   1305 
   1306         private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
   1307         private RouteInfo mDefaultRoute;
   1308         private RouteInfo mSelectedRoute;
   1309         private MediaRouteProvider.RouteController mSelectedRouteController;
   1310         private MediaRouteDiscoveryRequest mDiscoveryRequest;
   1311 
   1312         GlobalMediaRouter(Context applicationContext) {
   1313             mApplicationContext = applicationContext;
   1314             mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
   1315             mApplicationRouter = getRouter(applicationContext);
   1316 
   1317             // Add the system media route provider for interoperating with
   1318             // the framework media router.  This one is special and receives
   1319             // synchronization messages from the media router.
   1320             mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
   1321             addProvider(mSystemProvider);
   1322         }
   1323 
   1324         public void start() {
   1325             // Start watching for routes published by registered media route
   1326             // provider services.
   1327             mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
   1328                     mApplicationContext, mApplicationRouter);
   1329             mRegisteredProviderWatcher.start();
   1330         }
   1331 
   1332         public MediaRouter getRouter(Context context) {
   1333             MediaRouter router;
   1334             for (int i = mRouters.size(); --i >= 0; ) {
   1335                 router = mRouters.get(i).get();
   1336                 if (router == null) {
   1337                     mRouters.remove(i);
   1338                 } else if (router.mContext == context) {
   1339                     return router;
   1340                 }
   1341             }
   1342             router = new MediaRouter(context);
   1343             mRouters.add(new WeakReference<MediaRouter>(router));
   1344             return router;
   1345         }
   1346 
   1347         public ContentResolver getContentResolver() {
   1348             return mApplicationContext.getContentResolver();
   1349         }
   1350 
   1351         public Context getProviderContext(String packageName) {
   1352             if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
   1353                 return mApplicationContext;
   1354             }
   1355             try {
   1356                 return mApplicationContext.createPackageContext(
   1357                         packageName, Context.CONTEXT_RESTRICTED);
   1358             } catch (NameNotFoundException ex) {
   1359                 return null;
   1360             }
   1361         }
   1362 
   1363         public Display getDisplay(int displayId) {
   1364             return mDisplayManager.getDisplay(displayId);
   1365         }
   1366 
   1367         public void sendControlRequest(RouteInfo route,
   1368                 Intent intent, ControlRequestCallback callback) {
   1369             if (route == mSelectedRoute && mSelectedRouteController != null) {
   1370                 if (mSelectedRouteController.onControlRequest(intent, callback)) {
   1371                     return;
   1372                 }
   1373             }
   1374             if (callback != null) {
   1375                 callback.onError(null, null);
   1376             }
   1377         }
   1378 
   1379         public void requestSetVolume(RouteInfo route, int volume) {
   1380             if (route == mSelectedRoute && mSelectedRouteController != null) {
   1381                 mSelectedRouteController.onSetVolume(volume);
   1382             }
   1383         }
   1384 
   1385         public void requestUpdateVolume(RouteInfo route, int delta) {
   1386             if (route == mSelectedRoute && mSelectedRouteController != null) {
   1387                 mSelectedRouteController.onUpdateVolume(delta);
   1388             }
   1389         }
   1390 
   1391         public List<RouteInfo> getRoutes() {
   1392             return mRoutes;
   1393         }
   1394 
   1395         public List<ProviderInfo> getProviders() {
   1396             return mProviders;
   1397         }
   1398 
   1399         public RouteInfo getDefaultRoute() {
   1400             if (mDefaultRoute == null) {
   1401                 // This should never happen once the media router has been fully
   1402                 // initialized but it is good to check for the error in case there
   1403                 // is a bug in provider initialization.
   1404                 throw new IllegalStateException("There is no default route.  "
   1405                         + "The media router has not yet been fully initialized.");
   1406             }
   1407             return mDefaultRoute;
   1408         }
   1409 
   1410         public RouteInfo getSelectedRoute() {
   1411             if (mSelectedRoute == null) {
   1412                 // This should never happen once the media router has been fully
   1413                 // initialized but it is good to check for the error in case there
   1414                 // is a bug in provider initialization.
   1415                 throw new IllegalStateException("There is no currently selected route.  "
   1416                         + "The media router has not yet been fully initialized.");
   1417             }
   1418             return mSelectedRoute;
   1419         }
   1420 
   1421         public void selectRoute(RouteInfo route) {
   1422             if (!mRoutes.contains(route)) {
   1423                 Log.w(TAG, "Ignoring attempt to select removed route: " + route);
   1424                 return;
   1425             }
   1426             if (!route.mEnabled) {
   1427                 Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
   1428                 return;
   1429             }
   1430 
   1431             setSelectedRouteInternal(route);
   1432         }
   1433 
   1434         public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
   1435             // Check whether any existing routes match the selector.
   1436             final int routeCount = mRoutes.size();
   1437             for (int i = 0; i < routeCount; i++) {
   1438                 RouteInfo route = mRoutes.get(i);
   1439                 if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
   1440                         && route.isDefault()) {
   1441                     continue;
   1442                 }
   1443                 if (route.matchesSelector(selector)) {
   1444                     return true;
   1445                 }
   1446             }
   1447 
   1448             // It doesn't look like we can find a matching route right now.
   1449             return false;
   1450         }
   1451 
   1452         public void updateDiscoveryRequest() {
   1453             // Combine all of the callback selectors and active scan flags.
   1454             boolean discover = false;
   1455             boolean activeScan = false;
   1456             MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
   1457             for (int i = mRouters.size(); --i >= 0; ) {
   1458                 MediaRouter router = mRouters.get(i).get();
   1459                 if (router == null) {
   1460                     mRouters.remove(i);
   1461                 } else {
   1462                     final int count = router.mCallbackRecords.size();
   1463                     for (int j = 0; j < count; j++) {
   1464                         CallbackRecord callback = router.mCallbackRecords.get(j);
   1465                         builder.addSelector(callback.mSelector);
   1466                         if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
   1467                             activeScan = true;
   1468                             discover = true; // perform active scan implies request discovery
   1469                         }
   1470                         if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
   1471                             discover = true;
   1472                         }
   1473                     }
   1474                 }
   1475             }
   1476             MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
   1477 
   1478             // Create a new discovery request.
   1479             if (mDiscoveryRequest != null
   1480                     && mDiscoveryRequest.getSelector().equals(selector)
   1481                     && mDiscoveryRequest.isActiveScan() == activeScan) {
   1482                 return; // no change
   1483             }
   1484             if (selector.isEmpty() && !activeScan) {
   1485                 // Discovery is not needed.
   1486                 if (mDiscoveryRequest == null) {
   1487                     return; // no change
   1488                 }
   1489                 mDiscoveryRequest = null;
   1490             } else {
   1491                 // Discovery is needed.
   1492                 mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
   1493             }
   1494             if (DEBUG) {
   1495                 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
   1496             }
   1497 
   1498             // Notify providers.
   1499             final int providerCount = mProviders.size();
   1500             for (int i = 0; i < providerCount; i++) {
   1501                 mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
   1502             }
   1503         }
   1504 
   1505         public void addProvider(MediaRouteProvider providerInstance) {
   1506             int index = findProviderInfo(providerInstance);
   1507             if (index < 0) {
   1508                 // 1. Add the provider to the list.
   1509                 ProviderInfo provider = new ProviderInfo(providerInstance);
   1510                 mProviders.add(provider);
   1511                 if (DEBUG) {
   1512                     Log.d(TAG, "Provider added: " + provider);
   1513                 }
   1514                 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
   1515                 // 2. Create the provider's contents.
   1516                 updateProviderContents(provider, providerInstance.getDescriptor());
   1517                 // 3. Register the provider callback.
   1518                 providerInstance.setCallback(mProviderCallback);
   1519                 // 4. Set the discovery request.
   1520                 providerInstance.setDiscoveryRequest(mDiscoveryRequest);
   1521             }
   1522         }
   1523 
   1524         public void removeProvider(MediaRouteProvider providerInstance) {
   1525             int index = findProviderInfo(providerInstance);
   1526             if (index >= 0) {
   1527                 // 1. Unregister the provider callback.
   1528                 providerInstance.setCallback(null);
   1529                 // 2. Clear the discovery request.
   1530                 providerInstance.setDiscoveryRequest(null);
   1531                 // 3. Delete the provider's contents.
   1532                 ProviderInfo provider = mProviders.get(index);
   1533                 updateProviderContents(provider, null);
   1534                 // 4. Remove the provider from the list.
   1535                 if (DEBUG) {
   1536                     Log.d(TAG, "Provider removed: " + provider);
   1537                 }
   1538                 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
   1539                 mProviders.remove(index);
   1540             }
   1541         }
   1542 
   1543         private void updateProviderDescriptor(MediaRouteProvider providerInstance,
   1544                 MediaRouteProviderDescriptor descriptor) {
   1545             int index = findProviderInfo(providerInstance);
   1546             if (index >= 0) {
   1547                 // Update the provider's contents.
   1548                 ProviderInfo provider = mProviders.get(index);
   1549                 updateProviderContents(provider, descriptor);
   1550             }
   1551         }
   1552 
   1553         private int findProviderInfo(MediaRouteProvider providerInstance) {
   1554             final int count = mProviders.size();
   1555             for (int i = 0; i < count; i++) {
   1556                 if (mProviders.get(i).mProviderInstance == providerInstance) {
   1557                     return i;
   1558                 }
   1559             }
   1560             return -1;
   1561         }
   1562 
   1563         private void updateProviderContents(ProviderInfo provider,
   1564                 MediaRouteProviderDescriptor providerDescriptor) {
   1565             if (provider.updateDescriptor(providerDescriptor)) {
   1566                 // Update all existing routes and reorder them to match
   1567                 // the order of their descriptors.
   1568                 int targetIndex = 0;
   1569                 if (providerDescriptor != null) {
   1570                     if (providerDescriptor.isValid()) {
   1571                         final List<MediaRouteDescriptor> routeDescriptors =
   1572                                 providerDescriptor.getRoutes();
   1573                         final int routeCount = routeDescriptors.size();
   1574                         for (int i = 0; i < routeCount; i++) {
   1575                             final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
   1576                             final String id = routeDescriptor.getId();
   1577                             final int sourceIndex = provider.findRouteByDescriptorId(id);
   1578                             if (sourceIndex < 0) {
   1579                                 // 1. Add the route to the list.
   1580                                 String uniqueId = assignRouteUniqueId(provider, id);
   1581                                 RouteInfo route = new RouteInfo(provider, id, uniqueId);
   1582                                 provider.mRoutes.add(targetIndex++, route);
   1583                                 mRoutes.add(route);
   1584                                 // 2. Create the route's contents.
   1585                                 route.updateDescriptor(routeDescriptor);
   1586                                 // 3. Notify clients about addition.
   1587                                 if (DEBUG) {
   1588                                     Log.d(TAG, "Route added: " + route);
   1589                                 }
   1590                                 mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
   1591                             } else if (sourceIndex < targetIndex) {
   1592                                 Log.w(TAG, "Ignoring route descriptor with duplicate id: "
   1593                                         + routeDescriptor);
   1594                             } else {
   1595                                 // 1. Reorder the route within the list.
   1596                                 RouteInfo route = provider.mRoutes.get(sourceIndex);
   1597                                 Collections.swap(provider.mRoutes,
   1598                                         sourceIndex, targetIndex++);
   1599                                 // 2. Update the route's contents.
   1600                                 int changes = route.updateDescriptor(routeDescriptor);
   1601                                 // 3. Unselect route if needed before notifying about changes.
   1602                                 unselectRouteIfNeeded(route);
   1603                                 // 4. Notify clients about changes.
   1604                                 if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
   1605                                     if (DEBUG) {
   1606                                         Log.d(TAG, "Route changed: " + route);
   1607                                     }
   1608                                     mCallbackHandler.post(
   1609                                             CallbackHandler.MSG_ROUTE_CHANGED, route);
   1610                                 }
   1611                                 if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
   1612                                     if (DEBUG) {
   1613                                         Log.d(TAG, "Route volume changed: " + route);
   1614                                     }
   1615                                     mCallbackHandler.post(
   1616                                             CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
   1617                                 }
   1618                                 if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
   1619                                     if (DEBUG) {
   1620                                         Log.d(TAG, "Route presentation display changed: "
   1621                                                 + route);
   1622                                     }
   1623                                     mCallbackHandler.post(CallbackHandler.
   1624                                             MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
   1625                                 }
   1626                             }
   1627                         }
   1628                     } else {
   1629                         Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
   1630                     }
   1631                 }
   1632 
   1633                 // Dispose all remaining routes that do not have matching descriptors.
   1634                 for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
   1635                     // 1. Delete the route's contents.
   1636                     RouteInfo route = provider.mRoutes.get(i);
   1637                     route.updateDescriptor(null);
   1638                     // 2. Remove the route from the list.
   1639                     mRoutes.remove(route);
   1640                     provider.mRoutes.remove(i);
   1641                     // 3. Unselect route if needed before notifying about removal.
   1642                     unselectRouteIfNeeded(route);
   1643                     // 4. Notify clients about removal.
   1644                     if (DEBUG) {
   1645                         Log.d(TAG, "Route removed: " + route);
   1646                     }
   1647                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
   1648                 }
   1649 
   1650                 // Notify provider changed.
   1651                 if (DEBUG) {
   1652                     Log.d(TAG, "Provider changed: " + provider);
   1653                 }
   1654                 mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
   1655 
   1656                 // Choose a new selected route if needed.
   1657                 selectRouteIfNeeded();
   1658             }
   1659         }
   1660 
   1661         private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
   1662             // Although route descriptor ids are unique within a provider, it's
   1663             // possible for there to be two providers with the same package name.
   1664             // Therefore we must dedupe the composite id.
   1665             String uniqueId = provider.getPackageName() + ":" + routeDescriptorId;
   1666             if (findRouteByUniqueId(uniqueId) < 0) {
   1667                 return uniqueId;
   1668             }
   1669             for (int i = 2; ; i++) {
   1670                 String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
   1671                 if (findRouteByUniqueId(newUniqueId) < 0) {
   1672                     return newUniqueId;
   1673                 }
   1674             }
   1675         }
   1676 
   1677         private int findRouteByUniqueId(String uniqueId) {
   1678             final int count = mRoutes.size();
   1679             for (int i = 0; i < count; i++) {
   1680                 if (mRoutes.get(i).mUniqueId.equals(uniqueId)) {
   1681                     return i;
   1682                 }
   1683             }
   1684             return -1;
   1685         }
   1686 
   1687         private void unselectRouteIfNeeded(RouteInfo route) {
   1688             if (mDefaultRoute == route && !isRouteSelectable(route)) {
   1689                 Log.i(TAG, "Choosing a new default route because the current one "
   1690                         + "is no longer selectable: " + route);
   1691                 mDefaultRoute = null;
   1692             }
   1693             if (mSelectedRoute == route && !isRouteSelectable(route)) {
   1694                 Log.i(TAG, "Choosing a new selected route because the current one "
   1695                         + "is no longer selectable: " + route);
   1696                 setSelectedRouteInternal(null);
   1697             }
   1698         }
   1699 
   1700         private void selectRouteIfNeeded() {
   1701             if (mDefaultRoute == null && !mRoutes.isEmpty()) {
   1702                 for (RouteInfo route : mRoutes) {
   1703                     if (isSystemDefaultRoute(route) && isRouteSelectable(route)) {
   1704                         mDefaultRoute = route;
   1705                         break;
   1706                     }
   1707                 }
   1708             }
   1709             if (mSelectedRoute == null) {
   1710                 setSelectedRouteInternal(mDefaultRoute);
   1711             }
   1712         }
   1713 
   1714         private boolean isRouteSelectable(RouteInfo route) {
   1715             // This tests whether the route is still valid and enabled.
   1716             // The route descriptor field is set to null when the route is removed.
   1717             return route.mDescriptor != null && route.mEnabled;
   1718         }
   1719 
   1720         private boolean isSystemDefaultRoute(RouteInfo route) {
   1721             return route.getProviderInstance() == mSystemProvider
   1722                     && route.mDescriptorId.equals(
   1723                             SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
   1724         }
   1725 
   1726         private void setSelectedRouteInternal(RouteInfo route) {
   1727             if (mSelectedRoute != route) {
   1728                 if (mSelectedRoute != null) {
   1729                     if (DEBUG) {
   1730                         Log.d(TAG, "Route unselected: " + mSelectedRoute);
   1731                     }
   1732                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
   1733                     if (mSelectedRouteController != null) {
   1734                         mSelectedRouteController.onUnselect();
   1735                         mSelectedRouteController.onRelease();
   1736                         mSelectedRouteController = null;
   1737                     }
   1738                 }
   1739 
   1740                 mSelectedRoute = route;
   1741 
   1742                 if (mSelectedRoute != null) {
   1743                     mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
   1744                             route.mDescriptorId);
   1745                     if (mSelectedRouteController != null) {
   1746                         mSelectedRouteController.onSelect();
   1747                     }
   1748                     if (DEBUG) {
   1749                         Log.d(TAG, "Route selected: " + mSelectedRoute);
   1750                     }
   1751                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
   1752                 }
   1753             }
   1754         }
   1755 
   1756         @Override
   1757         public RouteInfo getSystemRouteByDescriptorId(String id) {
   1758             int providerIndex = findProviderInfo(mSystemProvider);
   1759             if (providerIndex >= 0) {
   1760                 ProviderInfo provider = mProviders.get(providerIndex);
   1761                 int routeIndex = provider.findRouteByDescriptorId(id);
   1762                 if (routeIndex >= 0) {
   1763                     return provider.mRoutes.get(routeIndex);
   1764                 }
   1765             }
   1766             return null;
   1767         }
   1768 
   1769         private final class ProviderCallback extends MediaRouteProvider.Callback {
   1770             @Override
   1771             public void onDescriptorChanged(MediaRouteProvider provider,
   1772                     MediaRouteProviderDescriptor descriptor) {
   1773                 updateProviderDescriptor(provider, descriptor);
   1774             }
   1775         }
   1776 
   1777         private final class CallbackHandler extends Handler {
   1778             private final ArrayList<CallbackRecord> mTempCallbackRecords =
   1779                     new ArrayList<CallbackRecord>();
   1780 
   1781             private static final int MSG_TYPE_MASK = 0xff00;
   1782             private static final int MSG_TYPE_ROUTE = 0x0100;
   1783             private static final int MSG_TYPE_PROVIDER = 0x0200;
   1784 
   1785             public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
   1786             public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
   1787             public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
   1788             public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
   1789             public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
   1790             public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
   1791             public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
   1792 
   1793             public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
   1794             public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
   1795             public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
   1796 
   1797             public void post(int msg, Object obj) {
   1798                 obtainMessage(msg, obj).sendToTarget();
   1799             }
   1800 
   1801             @Override
   1802             public void handleMessage(Message msg) {
   1803                 final int what = msg.what;
   1804                 final Object obj = msg.obj;
   1805 
   1806                 // Synchronize state with the system media router.
   1807                 syncWithSystemProvider(what, obj);
   1808 
   1809                 // Invoke all registered callbacks.
   1810                 // Build a list of callbacks before invoking them in case callbacks
   1811                 // are added or removed during dispatch.
   1812                 try {
   1813                     for (int i = mRouters.size(); --i >= 0; ) {
   1814                         MediaRouter router = mRouters.get(i).get();
   1815                         if (router == null) {
   1816                             mRouters.remove(i);
   1817                         } else {
   1818                             mTempCallbackRecords.addAll(router.mCallbackRecords);
   1819                         }
   1820                     }
   1821 
   1822                     final int callbackCount = mTempCallbackRecords.size();
   1823                     for (int i = 0; i < callbackCount; i++) {
   1824                         invokeCallback(mTempCallbackRecords.get(i), what, obj);
   1825                     }
   1826                 } finally {
   1827                     mTempCallbackRecords.clear();
   1828                 }
   1829             }
   1830 
   1831             private void syncWithSystemProvider(int what, Object obj) {
   1832                 switch (what) {
   1833                     case MSG_ROUTE_ADDED:
   1834                         mSystemProvider.onSyncRouteAdded((RouteInfo)obj);
   1835                         break;
   1836                     case MSG_ROUTE_REMOVED:
   1837                         mSystemProvider.onSyncRouteRemoved((RouteInfo)obj);
   1838                         break;
   1839                     case MSG_ROUTE_CHANGED:
   1840                         mSystemProvider.onSyncRouteChanged((RouteInfo)obj);
   1841                         break;
   1842                     case MSG_ROUTE_SELECTED:
   1843                         mSystemProvider.onSyncRouteSelected((RouteInfo)obj);
   1844                         break;
   1845                 }
   1846             }
   1847 
   1848             private void invokeCallback(CallbackRecord record, int what, Object obj) {
   1849                 final MediaRouter router = record.mRouter;
   1850                 final MediaRouter.Callback callback = record.mCallback;
   1851                 switch (what & MSG_TYPE_MASK) {
   1852                     case MSG_TYPE_ROUTE: {
   1853                         final RouteInfo route = (RouteInfo)obj;
   1854                         if (!record.filterRouteEvent(route)) {
   1855                             break;
   1856                         }
   1857                         switch (what) {
   1858                             case MSG_ROUTE_ADDED:
   1859                                 callback.onRouteAdded(router, route);
   1860                                 break;
   1861                             case MSG_ROUTE_REMOVED:
   1862                                 callback.onRouteRemoved(router, route);
   1863                                 break;
   1864                             case MSG_ROUTE_CHANGED:
   1865                                 callback.onRouteChanged(router, route);
   1866                                 break;
   1867                             case MSG_ROUTE_VOLUME_CHANGED:
   1868                                 callback.onRouteVolumeChanged(router, route);
   1869                                 break;
   1870                             case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
   1871                                 callback.onRoutePresentationDisplayChanged(router, route);
   1872                                 break;
   1873                             case MSG_ROUTE_SELECTED:
   1874                                 callback.onRouteSelected(router, route);
   1875                                 break;
   1876                             case MSG_ROUTE_UNSELECTED:
   1877                                 callback.onRouteUnselected(router, route);
   1878                                 break;
   1879                         }
   1880                         break;
   1881                     }
   1882                     case MSG_TYPE_PROVIDER: {
   1883                         final ProviderInfo provider = (ProviderInfo)obj;
   1884                         switch (what) {
   1885                             case MSG_PROVIDER_ADDED:
   1886                                 callback.onProviderAdded(router, provider);
   1887                                 break;
   1888                             case MSG_PROVIDER_REMOVED:
   1889                                 callback.onProviderRemoved(router, provider);
   1890                                 break;
   1891                             case MSG_PROVIDER_CHANGED:
   1892                                 callback.onProviderChanged(router, provider);
   1893                                 break;
   1894                         }
   1895                     }
   1896                 }
   1897             }
   1898         }
   1899     }
   1900 }
   1901