MediaRouter.java revision 11417b1cfde8f1749905f2d735623af9214148af
144a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org/*
244a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * Copyright (C) 2013 The Android Open Source Project
344a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org *
444a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * Licensed under the Apache License, Version 2.0 (the "License");
544a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * you may not use this file except in compliance with the License.
644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * You may obtain a copy of the License at
744a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org *
844a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org *      http://www.apache.org/licenses/LICENSE-2.0
944a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org *
1044a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * Unless required by applicable law or agreed to in writing, software
1144a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * distributed under the License is distributed on an "AS IS" BASIS,
1244a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1344a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * See the License for the specific language governing permissions and
140fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org * limitations under the License.
1544a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org */
1644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org
1744a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgpackage android.support.v7.media;
1844a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org
1944a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.content.ContentResolver;
2048d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.orgimport android.content.Context;
2148d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.orgimport android.content.Intent;
2244a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.content.IntentFilter;
2344a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.content.pm.PackageManager.NameNotFoundException;
2444a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.content.res.Resources;
253a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.orgimport android.graphics.drawable.Drawable;
2644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.os.Bundle;
2744a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.os.Handler;
2848d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.orgimport android.os.Looper;
2948d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.orgimport android.os.Message;
3044a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.support.v4.hardware.display.DisplayManagerCompat;
3144a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.support.v7.media.MediaRouteProvider.ProviderMetadata;
3244a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.util.Log;
3344a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport android.view.Display;
3444a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org
3544a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport java.util.ArrayList;
3644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport java.util.Collections;
3744a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport java.util.List;
3844a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport java.util.WeakHashMap;
3944a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.orgimport java.util.concurrent.CopyOnWriteArrayList;
4080bd0c995d9ec6a70330d6dc3f2f8dd387701d1dcommit-bot@chromium.org
410fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org/**
420fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org * MediaRouter allows applications to control the routing of media channels
439bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * and streams from the current device to external speakers and destination devices.
4480bd0c995d9ec6a70330d6dc3f2f8dd387701d1dcommit-bot@chromium.org * <p>
4580bd0c995d9ec6a70330d6dc3f2f8dd387701d1dcommit-bot@chromium.org * A MediaRouter instance is retrieved through {@link #getInstance}.  Applications
4644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org * can query the media router about the currently selected route and its capabilities
473a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org * to determine how to send content to the route's destination.  Applications can
489bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * also {@link RouteInfo#sendControlRequest send control requests} to the route
499bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * to ask the route's destination to perform certain remote control functions
509bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * such as playing media.
519bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * </p><p>
529bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * See also {@link MediaRouteProvider} for information on how an application
533a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org * can publish new media routes to the media router.
543a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org * </p><p>
553a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org * The media router API is not thread-safe; all interactions with it must be
569bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org * done from the main thread of the process.
573a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org * </p>
583a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org */
593a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.orgpublic final class MediaRouter {
603a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    private static final String TAG = "MediaRouter";
613a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    private static final boolean DEBUG = false;
623a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
633a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    // Maintains global media router state for the process.
643a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    // This field is initialized in MediaRouter.getInstance() before any
653a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    // MediaRouter objects are instantiated so it is guaranteed to be
663a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    // valid whenever any instance method is invoked.
673a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    static GlobalMediaRouter sGlobal;
683a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
699bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    // Context-bound state of the media router.
709bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    final Context mContext;
713a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
723a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org            new CopyOnWriteArrayList<CallbackRecord>();
733a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
743a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    /**
759bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * Flag for {@link #addCallback}: Actively scan for routes while this callback
763a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * is registered.
773a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * <p>
783a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * When this flag is specified, the media router will actively scan for new
793a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * routes.  Certain routes, such as wifi display routes, may not be discoverable
803a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * except when actively scanning.  This flag is typically used when the route picker
813a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * dialog has been opened by the user to ensure that the route information is
823a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * up to date.
833a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * </p><p>
843a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * Active scanning may consume a significant amount of power and may have intrusive
853a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * effects on wireless connectivity.  Therefore it is important that active scanning
863a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * only be requested when it is actually needed to satisfy a user request to
873a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * discover and select a new route.
883a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * </p>
893a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     */
903a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    public static final int CALLBACK_FLAG_ACTIVE_SCAN = 1 << 0;
913a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
923a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    /**
933a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * Flag for {@link #addCallback}: Do not filter route events.
943a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * <p>
953a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * When this flag is specified, the callback will be invoked for events that affect any
963a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * route event if they do not match the callback's associated media route selector.
973a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * </p>
983a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     */
993a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
1003a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
1013a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    /**
1023a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * Flag for {@link #isRouteAvailable}: Ignore the default route.
1033a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * <p>
1043a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * This flag is used to determine whether a matching non-default route is available.
1053a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * This constraint may be used to decide whether to offer the route chooser dialog
1063a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * to the user.  There is no point offering the chooser if there are no
1073a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * non-default choices.
1083a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * </p>
1093a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     */
1103a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
1113a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
1129bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    /**
1139bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * Flag for {@link #isRouteAvailable}: Consider whether matching routes
1143a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * might be discovered if an active scan were performed.
1159bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * <p>
1169bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * If no existing routes match the route selector, then this flag is used to
1179bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * determine whether to consider whether any route providers that require active
1180fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * scans might discover matching routes if an active scan were actually performed.
1199bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * </p><p>
1209bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * This flag may be used to decide whether to offer the route chooser dialog to the user.
1219bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * When the dialog is opened, an active scan will be performed which may cause
1229bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * additional routes to be discovered by any providers that require active scans.
1239bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * </p>
1249bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     */
1259bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    public static final int AVAILABILITY_FLAG_CONSIDER_ACTIVE_SCAN = 1 << 1;
1263a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
1270fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    MediaRouter(Context context) {
1280fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        mContext = context;
1293a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    }
1303a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org
1313a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org    /**
1329bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * Gets an instance of the media router service from the context.
1339bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     */
1340fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    public static MediaRouter getInstance(Context context) {
1350fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        if (context == null) {
1363a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org            throw new IllegalArgumentException("context must not be null");
1379bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        }
1389bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        checkCallingThread();
1399bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org
1409bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        if (sGlobal == null) {
1419bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org            sGlobal = new GlobalMediaRouter(context.getApplicationContext());
1429bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org            sGlobal.start();
1439bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        }
1449bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        return sGlobal.getRouter(context);
1459bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    }
1460fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org
1479bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    /**
1489bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
1499bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * this media router.
1509bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     */
1519bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    public List<RouteInfo> getRoutes() {
1520fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        checkCallingThread();
1539bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        return sGlobal.getRoutes();
1549bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    }
1550fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org
15644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org    /**
1579bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * Gets information about the {@link MediaRouter.ProviderInfo route providers}
1589bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * currently known to this media router.
15944a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org     */
1609bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    public List<ProviderInfo> getProviders() {
1619bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        checkCallingThread();
1620fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        return sGlobal.getProviders();
16380bd0c995d9ec6a70330d6dc3f2f8dd387701d1dcommit-bot@chromium.org    }
1649bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org
1659bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    /**
16644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org     * Gets the default route for playing media content on the system.
1679bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * <p>
1689bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * The system always provides a default route.
1699bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * </p>
1709bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *
1719bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * @return The default route, which is guaranteed to never be null.
1729bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     */
1739bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    public RouteInfo getDefaultRoute() {
1740fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        checkCallingThread();
1759bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        return sGlobal.getDefaultRoute();
1769bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    }
1779bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org
1789bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    /**
1790fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * Gets the currently selected route.
1809bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * <p>
1819bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * The application should examine the route's
1820fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * {@link RouteInfo#getControlFilters media control intent filters} to assess the
1839bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * capabilities of the route before attempting to use it.
1849bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * </p>
1859bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *
1869bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * <h3>Example</h3>
1879bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * <pre>
1889bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * public boolean playMovie() {
1899bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     MediaRouter mediaRouter = MediaRouter.getInstance(context);
1909bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
1919bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *
1929bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     // First try using the remote playback interface, if supported.
1939bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
1949bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         // The route supports remote playback.
1959bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         // Try to send it the Uri of the movie to play.
1969bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
1979bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1980fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     *         intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
1999bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         if (route.supportsControlRequest(intent)) {
2009bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *             route.sendControlRequest(intent, null);
2019bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *             return true; // sent the request to play the movie
2029bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         }
2039bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     }
2049bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *
2050fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     *     // If remote playback was not possible, then play locally.
2069bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
2079bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         // The route supports live video streaming.
2089bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         // Prepare to play content locally in a window or in a presentation.
2099bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *         return playMovieInWindow();
2109bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     }
2119bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *
2120fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     *     // Neither interface is supported, so we can't play the movie to this route.
2139bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *     return false;
2149bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * }
2159bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * </pre>
21680bd0c995d9ec6a70330d6dc3f2f8dd387701d1dcommit-bot@chromium.org     *
21780bd0c995d9ec6a70330d6dc3f2f8dd387701d1dcommit-bot@chromium.org     * @return The selected route, which is guaranteed to never be null.
2180fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     *
2190fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @see RouteInfo#getControlFilters
2209bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * @see RouteInfo#supportsControlCategory
2219bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * @see RouteInfo#supportsControlRequest
2220fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     */
2239bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    public RouteInfo getSelectedRoute() {
2240fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        checkCallingThread();
2250fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        return sGlobal.getSelectedRoute();
22644a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org    }
2279bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org
2289bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    /**
22944a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org     * Returns the selected route if it matches the specified selector, otherwise
2309bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * selects the default route and returns it.
23144a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org     *
2320fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @param selector The selector to match.
2330fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @return The previously selected route if it matched the selector, otherwise the
2349bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * newly selected default route which is guaranteed to never be null.
2359bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     *
2369bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * @see MediaRouteSelector
2373a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org     * @see RouteInfo#matchesSelector
2389bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * @see RouteInfo#isDefault
2399bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     */
24044a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org    public RouteInfo updateSelectedRoute(MediaRouteSelector selector) {
2419bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        if (selector == null) {
2429bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org            throw new IllegalArgumentException("selector must not be null");
24344a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org        }
2449bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        checkCallingThread();
2459bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org
2460fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        if (DEBUG) {
2479bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org            Log.d(TAG, "updateSelectedRoute: " + selector);
2489bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        }
2499bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        RouteInfo route = sGlobal.getSelectedRoute();
2509bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org        if (!route.isDefault() && !route.matchesSelector(selector)) {
2510fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org            route = sGlobal.getDefaultRoute();
2520fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org            sGlobal.selectRoute(route);
2530fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        }
2540fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        return route;
2550fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    }
2560fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org
2570fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    /**
2580fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * Selects the specified route.
2590fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     *
2600fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @param route The route to select.
2610fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     */
2620fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    public void selectRoute(RouteInfo route) {
2630fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        if (route == null) {
2640fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org            throw new IllegalArgumentException("route must not be null");
2650fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        }
2660fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        checkCallingThread();
2670fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org
2680fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        if (DEBUG) {
2690fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org            Log.d(TAG, "selectRoute: " + route);
2700fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        }
2710fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        sGlobal.selectRoute(route);
2720fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    }
2730fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org
2740fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    /**
2750fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * Returns true if there is a route that matches the specified selector
2760fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * or, depending on the specified availability flags, if it is possible to discover one.
2770fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * <p>
2780fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * This method first considers whether there are any available
2790fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * routes that match the selector regardless of whether they are enabled or
2800fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * disabled.  If not and the {@link #AVAILABILITY_FLAG_CONSIDER_ACTIVE_SCAN} flag
2810fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * was specifies, then it considers whether any of the route providers
2820fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * could discover a matching route if an active scan were performed.
2830fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * </p>
2840fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     *
2850fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @param selector The selector to match.
2869bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * @param flags Flags to control the determination of whether a route may be available.
2879bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * May be zero or a combination of
2889bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
28948d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * {@link #AVAILABILITY_FLAG_CONSIDER_ACTIVE_SCAN}.
29048d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * @return True if a matching route may be available.
2919bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     */
2929bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org    public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
2930fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        if (selector == null) {
29448d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org            throw new IllegalArgumentException("selector must not be null");
2953a6143d91f06d01f07b3b0e8adfa703a574d2beccommit-bot@chromium.org        }
29648d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org        checkCallingThread();
29748d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org
29848d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org        return sGlobal.isRouteAvailable(selector, flags);
29948d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org    }
30048d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org
30148d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org    /**
30248d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * Registers a callback to discover routes that match the selector and to receive
30348d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * events when they change.
30448d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * <p>
30548d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * This is a convenience method that has the same effect as calling
30648d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
30748d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * </p>
30848d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     *
30948d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * @param selector A route selector that indicates the kinds of routes that the
31048d94b8ce51a4b75fe1fe996b135fcd5b2d34779commit-bot@chromium.org     * callback would like to discover.
3110fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @param callback The callback to add.
3120fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * @see #removeCallback
3130fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     */
3140fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    public void addCallback(MediaRouteSelector selector, Callback callback) {
3150fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org        addCallback(selector, callback, 0);
3160fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    }
3170fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org
3180fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org    /**
3199bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * Registers a callback to discover routes that match the selector and to receive
3209bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * events when they change.
3219bde13c1b27b3704a45992341855192032e966b2commit-bot@chromium.org     * <p>
3220fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * The selector describes the kinds of routes that the application wants to
3230fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * discover.  For example, if the application wants to use
3240fc0dea333f313a579558beb7ba498db0711780ecommit-bot@chromium.org     * live audio routes then it should include the
32544a3877442805f48158c0d1e936b9c3970ddd3afcommit-bot@chromium.org     * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
326     * in its selector when it adds a callback to the media router.
327     * The selector may include any number of categories.
328     * </p><p>
329     * If the callback has already been registered, then the selector is added to
330     * the set of selectors being monitored by the callback.
331     * </p><p>
332     * By default, the callback will only be invoked for events that affect routes
333     * that match the specified selector.  Event filtering may be disabled by specifying
334     * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
335     * </p>
336     *
337     * <h3>Example</h3>
338     * <pre>
339     * public class MyActivity extends Activity {
340     *     private MediaRouter mRouter;
341     *     private MediaRouter.Callback mCallback;
342     *     private MediaRouteSelector mSelector;
343     *
344     *     protected void onCreate(Bundle savedInstanceState) {
345     *         super.onCreate(savedInstanceState);
346     *
347     *         mRouter = Mediarouter.getInstance(this);
348     *         mCallback = new MyCallback();
349     *         mSelector = new MediaRouteSelector.Builder()
350     *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
351     *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
352     *                 .build();
353     *     }
354     *
355     *     // Add the callback on resume to tell the media router what kinds of routes
356     *     // the application is interested in so that it can try to discover suitable ones.
357     *     public void onResume() {
358     *         super.onResume();
359     *
360     *         mediaRouter.addCallback(mSelector, mCallback);
361     *
362     *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
363     *         // do something with the route...
364     *     }
365     *
366     *     // Remove the selector on pause to tell the media router that it no longer
367     *     // needs to invest effort trying to discover routes of these kinds for now.
368     *     public void onPause() {
369     *         super.onPause();
370     *
371     *         mediaRouter.removeCallback(mCallback);
372     *     }
373     *
374     *     private final class MyCallback extends MediaRouter.Callback {
375     *         // Implement callback methods as needed.
376     *     }
377     * }
378     * </pre>
379     *
380     * @param selector A route selector that indicates the kinds of routes that the
381     * callback would like to discover.
382     * @param callback The callback to add.
383     * @param flags Flags to control the behavior of the callback.
384     * May be zero or a combination of {@link #CALLBACK_FLAG_ACTIVE_SCAN} and
385     * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
386     * @see #removeCallback
387     */
388    public void addCallback(MediaRouteSelector selector, Callback callback, int flags) {
389        if (selector == null) {
390            throw new IllegalArgumentException("selector must not be null");
391        }
392        if (callback == null) {
393            throw new IllegalArgumentException("callback must not be null");
394        }
395        checkCallingThread();
396
397        if (DEBUG) {
398            Log.d(TAG, "addCallback: selector=" + selector
399                    + ", callback=" + callback + ", flags=" + Integer.toHexString(flags));
400        }
401
402        CallbackRecord record;
403        int index = findCallbackRecord(callback);
404        if (index < 0) {
405            record = new CallbackRecord(callback);
406            mCallbackRecords.add(record);
407        } else {
408            record = mCallbackRecords.get(index);
409        }
410        boolean updateNeeded = false;
411        if ((flags & ~record.mFlags) != 0) {
412            record.mFlags |= flags;
413            updateNeeded = true;
414        }
415        if (!record.mSelector.contains(selector)) {
416            record.mSelector = new MediaRouteSelector.Builder(record.mSelector)
417                    .addSelector(selector)
418                    .build();
419            updateNeeded = true;
420        }
421        if (updateNeeded) {
422            sGlobal.updateDiscoveryRequest();
423        }
424    }
425
426    /**
427     * Removes the specified callback.  It will no longer receive events about
428     * changes to media routes.
429     *
430     * @param callback The callback to remove.
431     * @see #addCallback
432     */
433    public void removeCallback(Callback callback) {
434        if (callback == null) {
435            throw new IllegalArgumentException("callback must not be null");
436        }
437        checkCallingThread();
438
439        if (DEBUG) {
440            Log.d(TAG, "removeCallback: callback=" + callback);
441        }
442
443        int index = findCallbackRecord(callback);
444        if (index >= 0) {
445            mCallbackRecords.remove(index);
446            sGlobal.updateDiscoveryRequest();
447        }
448    }
449
450    private int findCallbackRecord(Callback callback) {
451        final int count = mCallbackRecords.size();
452        for (int i = 0; i < count; i++) {
453            if (mCallbackRecords.get(i).mCallback == callback) {
454                return i;
455            }
456        }
457        return -1;
458    }
459
460    /**
461     * Registers a media route provider globally for this application process.
462     *
463     * @param providerInstance The media route provider instance to add.
464     *
465     * @see MediaRouteProvider
466     * @see #removeCallback
467     */
468    public void addProvider(MediaRouteProvider providerInstance) {
469        if (providerInstance == null) {
470            throw new IllegalArgumentException("providerInstance must not be null");
471        }
472        checkCallingThread();
473
474        if (DEBUG) {
475            Log.d(TAG, "addProvider: " + providerInstance);
476        }
477        sGlobal.addProvider(providerInstance);
478    }
479
480    /**
481     * Unregisters a media route provider globally for this application process.
482     *
483     * @param providerInstance The media route provider instance to remove.
484     *
485     * @see MediaRouteProvider
486     * @see #addCallback
487     */
488    public void removeProvider(MediaRouteProvider providerInstance) {
489        if (providerInstance == null) {
490            throw new IllegalArgumentException("providerInstance must not be null");
491        }
492        checkCallingThread();
493
494        if (DEBUG) {
495            Log.d(TAG, "removeProvider: " + providerInstance);
496        }
497        sGlobal.removeProvider(providerInstance);
498    }
499
500    /**
501     * Ensures that calls into the media router are on the correct thread.
502     * It pays to be a little paranoid when global state invariants are at risk.
503     */
504    static void checkCallingThread() {
505        if (Looper.myLooper() != Looper.getMainLooper()) {
506            throw new IllegalStateException("The media router service must only be "
507                    + "accessed on the application's main thread.");
508        }
509    }
510
511    static <T> boolean equal(T a, T b) {
512        return a == b || (a != null && b != null && a.equals(b));
513    }
514
515    /**
516     * Provides information about a media route.
517     * <p>
518     * Each media route has a list of {@link MediaControlIntent media control}
519     * {@link #getControlFilters intent filters} that describe the capabilities of the
520     * route and the manner in which it is used and controlled.
521     * </p>
522     */
523    public static final class RouteInfo {
524        private final ProviderInfo mProvider;
525        private final String mDescriptorId;
526        private String mName;
527        private String mStatus;
528        private Drawable mIconDrawable;
529        private int mIconResource;
530        private boolean mEnabled;
531        private boolean mConnecting;
532        private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>();
533        private int mPlaybackType;
534        private int mPlaybackStream;
535        private int mVolumeHandling;
536        private int mVolume;
537        private int mVolumeMax;
538        private Display mPresentationDisplay;
539        private int mPresentationDisplayId = -1;
540        private Bundle mExtras;
541        private MediaRouteDescriptor mDescriptor;
542
543        /**
544         * The default playback type, "local", indicating the presentation of the media
545         * is happening on the same device (e.g. a phone, a tablet) as where it is
546         * controlled from.
547         *
548         * @see #getPlaybackType
549         */
550        public static final int PLAYBACK_TYPE_LOCAL = 0;
551
552        /**
553         * A playback type indicating the presentation of the media is happening on
554         * a different device (i.e. the remote device) than where it is controlled from.
555         *
556         * @see #getPlaybackType
557         */
558        public static final int PLAYBACK_TYPE_REMOTE = 1;
559
560        /**
561         * Playback information indicating the playback volume is fixed, i.e. it cannot be
562         * controlled from this object. An example of fixed playback volume is a remote player,
563         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
564         * than attenuate at the source.
565         *
566         * @see #getVolumeHandling
567         */
568        public static final int PLAYBACK_VOLUME_FIXED = 0;
569
570        /**
571         * Playback information indicating the playback volume is variable and can be controlled
572         * from this object.
573         *
574         * @see #getVolumeHandling
575         */
576        public static final int PLAYBACK_VOLUME_VARIABLE = 1;
577
578        static final int CHANGE_GENERAL = 1 << 0;
579        static final int CHANGE_VOLUME = 1 << 1;
580        static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
581
582        RouteInfo(ProviderInfo provider, String descriptorId) {
583            mProvider = provider;
584            mDescriptorId = descriptorId;
585        }
586
587        /**
588         * Gets information about the provider of this media route.
589         */
590        public ProviderInfo getProvider() {
591            return mProvider;
592        }
593
594        /**
595         * Gets the name of this route.
596         *
597         * @return The user-friendly name of a media route. This is the string presented
598         * to users who may select this as the active route.
599         */
600        public String getName() {
601            return mName;
602        }
603
604        /**
605         * Gets the status of this route.
606         *
607         * @return The user-friendly status for a media route. This may include a description
608         * of the currently playing media, if available.
609         */
610        public String getStatus() {
611            return mStatus;
612        }
613
614        /**
615         * Get the icon representing this route.
616         * This icon will be used in picker UIs if available.
617         *
618         * @return The icon representing this route or null if no icon is available.
619         */
620        public Drawable getIconDrawable() {
621            checkCallingThread();
622            if (mIconDrawable == null) {
623                if (mIconResource != 0) {
624                    Resources resources = mProvider.getResources();
625                    if (resources != null) {
626                        try {
627                            mIconDrawable = resources.getDrawable(mIconResource);
628                        } catch (Resources.NotFoundException ex) {
629                            Log.w(TAG, "Unable to load media route icon drawable resource "
630                                    + "from provider.", ex);
631                        }
632                    }
633                }
634            }
635            return mIconDrawable;
636        }
637
638        /**
639         * Returns true if this route is enabled and may be selected.
640         *
641         * @return True if this route is enabled.
642         */
643        public boolean isEnabled() {
644            return mEnabled;
645        }
646
647        /**
648         * Returns true if the route is in the process of connecting and is not
649         * yet ready for use.
650         *
651         * @return True if this route is in the process of connecting.
652         */
653        public boolean isConnecting() {
654            return mConnecting;
655        }
656
657        /**
658         * Returns true if this route is currently selected.
659         *
660         * @return True if this route is currently selected.
661         *
662         * @see MediaRouter#getSelectedRoute
663         */
664        public boolean isSelected() {
665            checkCallingThread();
666            return sGlobal.getSelectedRoute() == this;
667        }
668
669        /**
670         * Returns true if this route is the default route.
671         *
672         * @return True if this route is the default route.
673         *
674         * @see MediaRouter#getDefaultRoute
675         */
676        public boolean isDefault() {
677            checkCallingThread();
678            return sGlobal.getDefaultRoute() == this;
679        }
680
681        /**
682         * Gets a list of {@link MediaControlIntent media control intent} filters that
683         * describe the capabilities of this route and the media control actions that
684         * it supports.
685         *
686         * @return A list of intent filters that specifies the media control intents that
687         * this route supports.
688         *
689         * @see MediaControlIntent
690         * @see #supportsControlCategory
691         * @see #supportsControlRequest
692         */
693        public List<IntentFilter> getControlFilters() {
694            return mControlFilters;
695        }
696
697        /**
698         * Returns true if the route supports at least one of the capabilities
699         * described by a media route selector.
700         *
701         * @param selector The selector that specifies the capabilities to check.
702         * @return True if the route supports at least one of the capabilities
703         * described in the media route selector.
704         */
705        public boolean matchesSelector(MediaRouteSelector selector) {
706            if (selector == null) {
707                throw new IllegalArgumentException("selector must not be null");
708            }
709            checkCallingThread();
710            return selector.matchesControlFilters(mControlFilters);
711        }
712
713        /**
714         * Returns true if the route supports the specified
715         * {@link MediaControlIntent media control} category.
716         * <p>
717         * Media control categories describe the capabilities of this route
718         * such as whether it supports live audio streaming or remote playback.
719         * </p>
720         *
721         * @param category A {@link MediaControlIntent media control} category
722         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
723         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
724         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
725         * media control category.
726         * @return True if the route supports the specified intent category.
727         *
728         * @see MediaControlIntent
729         * @see #getControlFilters
730         */
731        public boolean supportsControlCategory(String category) {
732            if (category == null) {
733                throw new IllegalArgumentException("category must not be null");
734            }
735            checkCallingThread();
736
737            int count = mControlFilters.size();
738            for (int i = 0; i < count; i++) {
739                if (mControlFilters.get(i).hasCategory(category)) {
740                    return true;
741                }
742            }
743            return false;
744        }
745
746        /**
747         * Returns true if the route supports the specified
748         * {@link MediaControlIntent media control} request.
749         * <p>
750         * Media control requests are used to request the route to perform
751         * actions such as starting remote playback of a media item.
752         * </p>
753         *
754         * @param intent A {@link MediaControlIntent media control intent}.
755         * @return True if the route can handle the specified intent.
756         *
757         * @see MediaControlIntent
758         * @see #getControlFilters
759         */
760        public boolean supportsControlRequest(Intent intent) {
761            if (intent == null) {
762                throw new IllegalArgumentException("intent must not be null");
763            }
764            checkCallingThread();
765
766            ContentResolver contentResolver = sGlobal.getContentResolver();
767            int count = mControlFilters.size();
768            for (int i = 0; i < count; i++) {
769                if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
770                    return true;
771                }
772            }
773            return false;
774        }
775
776        /**
777         * Sends a {@link MediaControlIntent media control} request to be performed
778         * asynchronously by the route's destination.
779         * <p>
780         * Media control requests are used to request the route to perform
781         * actions such as starting remote playback of a media item.
782         * </p><p>
783         * This function may only be called on a selected route.  Control requests
784         * sent to unselected routes will fail.
785         * </p>
786         *
787         * @param intent A {@link MediaControlIntent media control intent}.
788         * @param callback A {@link ControlRequestCallback} to invoke with the result
789         * of the request, or null if no result is required.
790         *
791         * @see MediaControlIntent
792         */
793        public void sendControlRequest(Intent intent, ControlRequestCallback callback) {
794            if (intent == null) {
795                throw new IllegalArgumentException("intent must not be null");
796            }
797            checkCallingThread();
798
799            sGlobal.sendControlRequest(this, intent, callback);
800        }
801
802        /**
803         * Gets the type of playback associated with this route.
804         *
805         * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
806         * or {@link #PLAYBACK_TYPE_REMOTE}.
807         */
808        public int getPlaybackType() {
809            return mPlaybackType;
810        }
811
812        /**
813         * Gets the audio stream over which the playback associated with this route is performed.
814         *
815         * @return The stream over which the playback associated with this route is performed.
816         */
817        public int getPlaybackStream() {
818            return mPlaybackStream;
819        }
820
821        /**
822         * Gets information about how volume is handled on the route.
823         *
824         * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
825         * or {@link #PLAYBACK_VOLUME_VARIABLE}.
826         */
827        public int getVolumeHandling() {
828            return mVolumeHandling;
829        }
830
831        /**
832         * Gets the current volume for this route. Depending on the route, this may only
833         * be valid if the route is currently selected.
834         *
835         * @return The volume at which the playback associated with this route is performed.
836         */
837        public int getVolume() {
838            return mVolume;
839        }
840
841        /**
842         * Gets the maximum volume at which the playback associated with this route is performed.
843         *
844         * @return The maximum volume at which the playback associated with
845         * this route is performed.
846         */
847        public int getVolumeMax() {
848            return mVolumeMax;
849        }
850
851        /**
852         * Requests a volume change for this route asynchronously.
853         * <p>
854         * This function may only be called on a selected route.  It will have
855         * no effect if the route is currently unselected.
856         * </p>
857         *
858         * @param volume The new volume value between 0 and {@link #getVolumeMax}.
859         */
860        public void requestSetVolume(int volume) {
861            checkCallingThread();
862            sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
863        }
864
865        /**
866         * Requests an incremental volume update for this route asynchronously.
867         * <p>
868         * This function may only be called on a selected route.  It will have
869         * no effect if the route is currently unselected.
870         * </p>
871         *
872         * @param delta The delta to add to the current volume.
873         */
874        public void requestUpdateVolume(int delta) {
875            checkCallingThread();
876            if (delta != 0) {
877                sGlobal.requestUpdateVolume(this, delta);
878            }
879        }
880
881        /**
882         * Gets the {@link Display} that should be used by the application to show
883         * a {@link android.app.Presentation} on an external display when this route is selected.
884         * Depending on the route, this may only be valid if the route is currently
885         * selected.
886         * <p>
887         * The preferred presentation display may change independently of the route
888         * being selected or unselected.  For example, the presentation display
889         * of the default system route may change when an external HDMI display is connected
890         * or disconnected even though the route itself has not changed.
891         * </p><p>
892         * This method may return null if there is no external display associated with
893         * the route or if the display is not ready to show UI yet.
894         * </p><p>
895         * The application should listen for changes to the presentation display
896         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
897         * show or dismiss its {@link android.app.Presentation} accordingly when the display
898         * becomes available or is removed.
899         * </p><p>
900         * This method only makes sense for
901         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
902         * </p>
903         *
904         * @return The preferred presentation display to use when this route is
905         * selected or null if none.
906         *
907         * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
908         * @see android.app.Presentation
909         */
910        public Display getPresentationDisplay() {
911            checkCallingThread();
912            if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
913                mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
914            }
915            return mPresentationDisplay;
916        }
917
918        /**
919         * Gets a collection of extra properties about this route that were supplied
920         * by its media route provider, or null if none.
921         */
922        public Bundle getExtras() {
923            return mExtras;
924        }
925
926        /**
927         * Selects this media route.
928         */
929        public void select() {
930            checkCallingThread();
931            sGlobal.selectRoute(this);
932        }
933
934        @Override
935        public String toString() {
936            return "MediaRouter.RouteInfo{ name=" + mName
937                    + ", status=" + mStatus
938                    + ", enabled=" + mEnabled
939                    + ", connecting=" + mConnecting
940                    + ", playbackType=" + mPlaybackType
941                    + ", playbackStream=" + mPlaybackStream
942                    + ", volumeHandling=" + mVolumeHandling
943                    + ", volume=" + mVolume
944                    + ", volumeMax=" + mVolumeMax
945                    + ", presentationDisplayId=" + mPresentationDisplayId
946                    + ", extras=" + mExtras
947                    + ", providerPackageName=" + mProvider.getPackageName()
948                    + " }";
949        }
950
951        int updateDescriptor(MediaRouteDescriptor descriptor) {
952            int changes = 0;
953            if (mDescriptor != descriptor) {
954                mDescriptor = descriptor;
955                if (descriptor != null) {
956                    if (!equal(mName, descriptor.getName())) {
957                        mName = descriptor.getName();
958                        changes |= CHANGE_GENERAL;
959                    }
960                    if (!equal(mStatus, descriptor.getStatus())) {
961                        mStatus = descriptor.getStatus();
962                        changes |= CHANGE_GENERAL;
963                    }
964                    if (mIconResource != descriptor.getIconResource()) {
965                        mIconResource = descriptor.getIconResource();
966                        mIconDrawable = null;
967                        changes |= CHANGE_GENERAL;
968                    }
969                    if (mIconResource == 0
970                            && mIconDrawable != descriptor.getIconDrawable()) {
971                        mIconDrawable = descriptor.getIconDrawable();
972                        changes |= CHANGE_GENERAL;
973                    }
974                    if (mEnabled != descriptor.isEnabled()) {
975                        mEnabled = descriptor.isEnabled();
976                        changes |= CHANGE_GENERAL;
977                    }
978                    if (mConnecting != descriptor.isConnecting()) {
979                        mConnecting = descriptor.isConnecting();
980                        changes |= CHANGE_GENERAL;
981                    }
982                    if (!mControlFilters.equals(descriptor.getControlFilters())) {
983                        mControlFilters.clear();
984                        mControlFilters.addAll(descriptor.getControlFilters());
985                        changes |= CHANGE_GENERAL;
986                    }
987                    if (mPlaybackType != descriptor.getPlaybackType()) {
988                        mPlaybackType = descriptor.getPlaybackType();
989                        changes |= CHANGE_GENERAL;
990                    }
991                    if (mPlaybackStream != descriptor.getPlaybackStream()) {
992                        mPlaybackStream = descriptor.getPlaybackStream();
993                        changes |= CHANGE_GENERAL;
994                    }
995                    if (mVolumeHandling != descriptor.getVolumeHandling()) {
996                        mVolumeHandling = descriptor.getVolumeHandling();
997                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
998                    }
999                    if (mVolume != descriptor.getVolume()) {
1000                        mVolume = descriptor.getVolume();
1001                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1002                    }
1003                    if (mVolumeMax != descriptor.getVolumeMax()) {
1004                        mVolumeMax = descriptor.getVolumeMax();
1005                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
1006                    }
1007                    if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
1008                        mPresentationDisplayId = descriptor.getPresentationDisplayId();
1009                        mPresentationDisplay = null;
1010                        changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
1011                    }
1012                    if (!equal(mExtras, descriptor.getExtras())) {
1013                        mExtras = descriptor.getExtras();
1014                        changes |= CHANGE_GENERAL;
1015                    }
1016                }
1017            }
1018            return changes;
1019        }
1020
1021        String getDescriptorId() {
1022            return mDescriptorId;
1023        }
1024
1025        MediaRouteProvider getProviderInstance() {
1026            return mProvider.getProviderInstance();
1027        }
1028    }
1029
1030    /**
1031     * Provides information about a media route provider.
1032     * <p>
1033     * This object may be used to determine which media route provider has
1034     * published a particular route.
1035     * </p>
1036     */
1037    public static final class ProviderInfo {
1038        private final MediaRouteProvider mProviderInstance;
1039        private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
1040        private final ArrayList<IntentFilter> mDiscoverableControlFilters =
1041                new ArrayList<IntentFilter>();
1042
1043        private final ProviderMetadata mMetadata;
1044        private MediaRouteProviderDescriptor mDescriptor;
1045        private Resources mResources;
1046        private boolean mResourcesNotAvailable;
1047
1048        ProviderInfo(MediaRouteProvider provider) {
1049            mProviderInstance = provider;
1050            mMetadata = provider.getMetadata();
1051        }
1052
1053        /**
1054         * Gets the provider's underlying {@link MediaRouteProvider} instance.
1055         */
1056        public MediaRouteProvider getProviderInstance() {
1057            checkCallingThread();
1058            return mProviderInstance;
1059        }
1060
1061        /**
1062         * Gets the package name of the media route provider service.
1063         */
1064        public String getPackageName() {
1065            return mMetadata.getPackageName();
1066        }
1067
1068        /**
1069         * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
1070         */
1071        public List<RouteInfo> getRoutes() {
1072            checkCallingThread();
1073            return mRoutes;
1074        }
1075
1076        /**
1077         * Returns true if the provider requires active scans to discover routes.
1078         * <p>
1079         * To provide the best user experience, a media route provider should passively
1080         * discover and publish changes to route descriptors in the background.
1081         * However, for some providers, scanning for routes may use a significant
1082         * amount of power or may interfere with wireless network connectivity.
1083         * If this is the case, then the provider will indicate that it requires
1084         * active scans to discover routes by setting this flag.  Active scans
1085         * will be performed when the user opens the route chooser dialog.
1086         * </p>
1087         */
1088        public boolean isActiveScanRequired() {
1089            checkCallingThread();
1090            return mDescriptor != null && mDescriptor.isActiveScanRequired();
1091        }
1092
1093        /**
1094         * Gets a list of {@link MediaControlIntent media route control filters} that
1095         * describe the union of capabilities of all routes that this provider can
1096         * possibly discover.
1097         * <p>
1098         * Because a route provider may not know what to look for until an
1099         * application actually asks for it, the contents of the discoverable control
1100         * filter list may change depending on the route selectors that applications have
1101         * actually specified when {@link MediaRouter#addCallback registering callbacks}
1102         * on the media router to discover routes.
1103         * </p>
1104         */
1105        public List<IntentFilter> getDiscoverableControlFilters() {
1106            checkCallingThread();
1107            return mDiscoverableControlFilters;
1108        }
1109
1110        Resources getResources() {
1111            if (mResources == null && !mResourcesNotAvailable) {
1112                String packageName = getPackageName();
1113                Context context = sGlobal.getProviderContext(packageName);
1114                if (context != null) {
1115                    mResources = context.getResources();
1116                } else {
1117                    Log.w(TAG, "Unable to obtain resources for route provider package: "
1118                            + packageName);
1119                    mResourcesNotAvailable = true;
1120                }
1121            }
1122            return mResources;
1123        }
1124
1125        boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
1126            if (mDescriptor != descriptor) {
1127                mDescriptor = descriptor;
1128
1129                if (!mDiscoverableControlFilters.equals(
1130                        descriptor.getDiscoverableControlFilters())) {
1131                    mDiscoverableControlFilters.clear();
1132                    mDiscoverableControlFilters.addAll(descriptor.getDiscoverableControlFilters());
1133                }
1134                return true;
1135            }
1136            return false;
1137        }
1138
1139        int findRouteByDescriptorId(String id) {
1140            final int count = mRoutes.size();
1141            for (int i = 0; i < count; i++) {
1142                if (mRoutes.get(i).mDescriptorId.equals(id)) {
1143                    return i;
1144                }
1145            }
1146            return -1;
1147        }
1148
1149        @Override
1150        public String toString() {
1151            return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
1152                    + ", isActiveScanRequired=" + isActiveScanRequired()
1153                    + " }";
1154        }
1155    }
1156
1157    /**
1158     * Interface for receiving events about media routing changes.
1159     * All methods of this interface will be called from the application's main thread.
1160     * <p>
1161     * A Callback will only receive events relevant to routes that the callback
1162     * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
1163     * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
1164     * </p>
1165     *
1166     * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
1167     * @see MediaRouter#removeCallback(Callback)
1168     */
1169    public static abstract class Callback {
1170        /**
1171         * Called when the supplied media route becomes selected as the active route.
1172         *
1173         * @param router The media router reporting the event.
1174         * @param route The route that has been selected.
1175         */
1176        public void onRouteSelected(MediaRouter router, RouteInfo route) {
1177        }
1178
1179        /**
1180         * Called when the supplied media route becomes unselected as the active route.
1181         *
1182         * @param router The media router reporting the event.
1183         * @param route The route that has been unselected.
1184         */
1185        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
1186        }
1187
1188        /**
1189         * Called when a media route has been added.
1190         *
1191         * @param router The media router reporting the event.
1192         * @param route The route that has become available for use.
1193         */
1194        public void onRouteAdded(MediaRouter router, RouteInfo route) {
1195        }
1196
1197        /**
1198         * Called when a media route has been removed.
1199         *
1200         * @param router The media router reporting the event.
1201         * @param route The route that has been removed from availability.
1202         */
1203        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
1204        }
1205
1206        /**
1207         * Called when a property of the indicated media route has changed.
1208         *
1209         * @param router The media router reporting the event.
1210         * @param route The route that was changed.
1211         */
1212        public void onRouteChanged(MediaRouter router, RouteInfo route) {
1213        }
1214
1215        /**
1216         * Called when a media route's volume changes.
1217         *
1218         * @param router The media router reporting the event.
1219         * @param route The route whose volume changed.
1220         */
1221        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
1222        }
1223
1224        /**
1225         * Called when a media route's presentation display changes.
1226         * <p>
1227         * This method is called whenever the route's presentation display becomes
1228         * available, is removed or has changes to some of its properties (such as its size).
1229         * </p>
1230         *
1231         * @param router The media router reporting the event.
1232         * @param route The route whose presentation display changed.
1233         *
1234         * @see RouteInfo#getPresentationDisplay()
1235         */
1236        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
1237        }
1238
1239        /**
1240         * Called when a media route provider has been added.
1241         *
1242         * @param router The media router reporting the event.
1243         * @param provider The provider that has become available for use.
1244         */
1245        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
1246        }
1247
1248        /**
1249         * Called when a media route provider has been removed.
1250         *
1251         * @param router The media router reporting the event.
1252         * @param provider The provider that has been removed from availability.
1253         */
1254        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
1255        }
1256
1257        /**
1258         * Called when a property of the indicated media route provider has changed.
1259         *
1260         * @param router The media router reporting the event.
1261         * @param provider The provider that was changed.
1262         */
1263        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
1264        }
1265    }
1266
1267    /**
1268     * Callback which is invoked with the result of a media control request.
1269     *
1270     * @see RouteInfo#sendControlRequest
1271     */
1272    public static abstract class ControlRequestCallback {
1273        /**
1274         * Result code: The media control action succeeded.
1275         */
1276        public static final int REQUEST_SUCCEEDED = 0;
1277
1278        /**
1279         * Result code: The media control action failed.
1280         */
1281        public static final int REQUEST_FAILED = -1;
1282
1283        /**
1284         * Called with the result of the media control request.
1285         *
1286         * @param result The result code: {@link #REQUEST_SUCCEEDED}, or {@link #REQUEST_FAILED}.
1287         * @param data Additional result data.  Contents depend on the media control action.
1288         */
1289        public void onResult(int result, Bundle data) {
1290        }
1291    }
1292
1293    private static final class CallbackRecord {
1294        public final Callback mCallback;
1295        public MediaRouteSelector mSelector;
1296        public int mFlags;
1297
1298        public CallbackRecord(Callback callback) {
1299            mCallback = callback;
1300            mSelector = MediaRouteSelector.EMPTY;
1301        }
1302
1303        public boolean filterRouteEvent(RouteInfo route) {
1304            return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
1305                    || route.matchesSelector(mSelector);
1306        }
1307    }
1308
1309    /**
1310     * Global state for the media router.
1311     * <p>
1312     * Media routes and media route providers are global to the process; their
1313     * state and the bulk of the media router implementation lives here.
1314     * </p>
1315     */
1316    private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback {
1317        private final Context mApplicationContext;
1318        private final MediaRouter mApplicationRouter;
1319        private final WeakHashMap<Context, MediaRouter> mRouters =
1320                new WeakHashMap<Context, MediaRouter>();
1321        private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
1322        private final ArrayList<ProviderInfo> mProviders =
1323                new ArrayList<ProviderInfo>();
1324        private final ProviderCallback mProviderCallback = new ProviderCallback();
1325        private final CallbackHandler mCallbackHandler = new CallbackHandler();
1326        private final DisplayManagerCompat mDisplayManager;
1327        private final SystemMediaRouteProvider mSystemProvider;
1328
1329        private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
1330        private RouteInfo mDefaultRoute;
1331        private RouteInfo mSelectedRoute;
1332        private MediaRouteProvider.RouteController mSelectedRouteController;
1333        private MediaRouteDiscoveryRequest mDiscoveryRequest;
1334
1335        GlobalMediaRouter(Context applicationContext) {
1336            mApplicationContext = applicationContext;
1337            mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
1338            mApplicationRouter = getRouter(applicationContext);
1339
1340            // Add the system media route provider for interoperating with
1341            // the framework media router.  This one is special and receives
1342            // synchronization messages from the media router.
1343            mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
1344            addProvider(mSystemProvider);
1345        }
1346
1347        public void start() {
1348            // Start watching for routes published by registered media route
1349            // provider services.
1350            mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
1351                    mApplicationContext, mApplicationRouter);
1352            mRegisteredProviderWatcher.start();
1353        }
1354
1355        public MediaRouter getRouter(Context context) {
1356            MediaRouter router = mRouters.get(context);
1357            if (router == null) {
1358                router = new MediaRouter(context);
1359                mRouters.put(context, router);
1360            }
1361            return router;
1362        }
1363
1364        public ContentResolver getContentResolver() {
1365            return mApplicationContext.getContentResolver();
1366        }
1367
1368        public Context getProviderContext(String packageName) {
1369            if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
1370                return mApplicationContext;
1371            }
1372            try {
1373                return mApplicationContext.createPackageContext(
1374                        packageName, Context.CONTEXT_RESTRICTED);
1375            } catch (NameNotFoundException ex) {
1376                return null;
1377            }
1378        }
1379
1380        public Display getDisplay(int displayId) {
1381            return mDisplayManager.getDisplay(displayId);
1382        }
1383
1384        public void sendControlRequest(RouteInfo route,
1385                Intent intent, ControlRequestCallback callback) {
1386            if (route == mSelectedRoute && mSelectedRouteController != null) {
1387                if (mSelectedRouteController.onControlRequest(intent, callback)) {
1388                    return;
1389                }
1390            }
1391            if (callback != null) {
1392                callback.onResult(ControlRequestCallback.REQUEST_FAILED, null);
1393            }
1394        }
1395
1396        public void requestSetVolume(RouteInfo route, int volume) {
1397            if (route == mSelectedRoute && mSelectedRouteController != null) {
1398                mSelectedRouteController.onSetVolume(volume);
1399            }
1400        }
1401
1402        public void requestUpdateVolume(RouteInfo route, int delta) {
1403            if (route == mSelectedRoute && mSelectedRouteController != null) {
1404                mSelectedRouteController.onUpdateVolume(delta);
1405            }
1406        }
1407
1408        public List<RouteInfo> getRoutes() {
1409            return mRoutes;
1410        }
1411
1412        public List<ProviderInfo> getProviders() {
1413            return mProviders;
1414        }
1415
1416        public RouteInfo getDefaultRoute() {
1417            if (mDefaultRoute == null) {
1418                // This should never happen once the media router has been fully
1419                // initialized but it is good to check for the error in case there
1420                // is a bug in provider initialization.
1421                throw new IllegalStateException("There is no default route.  "
1422                        + "The media router has not yet been fully initialized.");
1423            }
1424            return mDefaultRoute;
1425        }
1426
1427        public RouteInfo getSelectedRoute() {
1428            if (mSelectedRoute == null) {
1429                // This should never happen once the media router has been fully
1430                // initialized but it is good to check for the error in case there
1431                // is a bug in provider initialization.
1432                throw new IllegalStateException("There is no currently selected route.  "
1433                        + "The media router has not yet been fully initialized.");
1434            }
1435            return mSelectedRoute;
1436        }
1437
1438        public void selectRoute(RouteInfo route) {
1439            if (!mRoutes.contains(route)) {
1440                Log.w(TAG, "Ignoring attempt to select removed route: " + route);
1441                return;
1442            }
1443            if (!route.mEnabled) {
1444                Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
1445                return;
1446            }
1447
1448            setSelectedRouteInternal(route);
1449        }
1450
1451        public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
1452            // Check whether any existing routes match the selector.
1453            final int routeCount = mRoutes.size();
1454            for (int i = 0; i < routeCount; i++) {
1455                RouteInfo route = mRoutes.get(i);
1456                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
1457                        && route.isDefault()) {
1458                    continue;
1459                }
1460                if (route.matchesSelector(selector)) {
1461                    return true;
1462                }
1463            }
1464
1465            // Check whether any provider could possibly discover a matching route
1466            // if a required active scan were performed.
1467            if ((flags & AVAILABILITY_FLAG_CONSIDER_ACTIVE_SCAN) != 0) {
1468                final int providerCount = mProviders.size();
1469                for (int i = 0; i < providerCount; i++) {
1470                    ProviderInfo provider = mProviders.get(i);
1471                    if (provider.isActiveScanRequired() && selector.matchesControlFilters(
1472                            provider.getDiscoverableControlFilters())) {
1473                        return true;
1474                    }
1475                }
1476            }
1477
1478            // It doesn't look like we can find a matching route right now.
1479            return false;
1480        }
1481
1482        public void updateDiscoveryRequest() {
1483            // Combine all of the callback selectors and active scan flags.
1484            boolean activeScan = false;
1485            MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
1486            for (MediaRouter router : mRouters.values()) {
1487                final int count = router.mCallbackRecords.size();
1488                for (int i = 0; i < count; i++) {
1489                    CallbackRecord callback = router.mCallbackRecords.get(i);
1490                    builder.addSelector(callback.mSelector);
1491                    if ((callback.mFlags & CALLBACK_FLAG_ACTIVE_SCAN) != 0) {
1492                        activeScan = true;
1493                    }
1494                }
1495            }
1496            MediaRouteSelector selector = builder.build();
1497
1498            // Create a new discovery request.
1499            if (mDiscoveryRequest != null
1500                    && mDiscoveryRequest.getSelector().equals(selector)
1501                    && mDiscoveryRequest.isActiveScan() == activeScan) {
1502                return; // no change
1503            }
1504            if (selector.isEmpty() && !activeScan) {
1505                // Discovery is not needed.
1506                if (mDiscoveryRequest == null) {
1507                    return; // no change
1508                }
1509                mDiscoveryRequest = null;
1510            } else {
1511                // Discovery is needed.
1512                mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
1513            }
1514            if (DEBUG) {
1515                Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
1516            }
1517
1518            // Notify providers.
1519            final int providerCount = mProviders.size();
1520            for (int i = 0; i < providerCount; i++) {
1521                mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
1522            }
1523        }
1524
1525        public void addProvider(MediaRouteProvider providerInstance) {
1526            int index = findProviderInfo(providerInstance);
1527            if (index < 0) {
1528                // 1. Add the provider to the list.
1529                ProviderInfo provider = new ProviderInfo(providerInstance);
1530                mProviders.add(provider);
1531                if (DEBUG) {
1532                    Log.d(TAG, "Provider added: " + provider);
1533                }
1534                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
1535                // 2. Create the provider's contents.
1536                updateProviderContents(provider, providerInstance.getDescriptor());
1537                // 3. Register the provider callback.
1538                providerInstance.setCallback(mProviderCallback);
1539                // 4. Set the discovery request.
1540                providerInstance.setDiscoveryRequest(mDiscoveryRequest);
1541            }
1542        }
1543
1544        public void removeProvider(MediaRouteProvider providerInstance) {
1545            int index = findProviderInfo(providerInstance);
1546            if (index >= 0) {
1547                // 1. Unregister the provider callback.
1548                providerInstance.setCallback(null);
1549                // 2. Clear the discovery request.
1550                providerInstance.setDiscoveryRequest(null);
1551                // 3. Delete the provider's contents.
1552                ProviderInfo provider = mProviders.get(index);
1553                updateProviderContents(provider, null);
1554                // 4. Remove the provider from the list.
1555                if (DEBUG) {
1556                    Log.d(TAG, "Provider removed: " + provider);
1557                }
1558                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
1559                mProviders.remove(index);
1560            }
1561        }
1562
1563        private void updateProviderDescriptor(MediaRouteProvider providerInstance,
1564                MediaRouteProviderDescriptor descriptor) {
1565            int index = findProviderInfo(providerInstance);
1566            if (index >= 0) {
1567                // Update the provider's contents.
1568                ProviderInfo provider = mProviders.get(index);
1569                updateProviderContents(provider, descriptor);
1570            }
1571        }
1572
1573        private int findProviderInfo(MediaRouteProvider providerInstance) {
1574            final int count = mProviders.size();
1575            for (int i = 0; i < count; i++) {
1576                if (mProviders.get(i).mProviderInstance == providerInstance) {
1577                    return i;
1578                }
1579            }
1580            return -1;
1581        }
1582
1583        private void updateProviderContents(ProviderInfo provider,
1584                MediaRouteProviderDescriptor providerDescriptor) {
1585            if (provider.updateDescriptor(providerDescriptor)) {
1586                // Update all existing routes and reorder them to match
1587                // the order of their descriptors.
1588                int targetIndex = 0;
1589                if (providerDescriptor != null) {
1590                    if (providerDescriptor.isValid()) {
1591                        final List<MediaRouteDescriptor> routeDescriptors =
1592                                providerDescriptor.getRoutes();
1593                        final int routeCount = routeDescriptors.size();
1594                        for (int i = 0; i < routeCount; i++) {
1595                            final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
1596                            final String id = routeDescriptor.getId();
1597                            final int sourceIndex = provider.findRouteByDescriptorId(id);
1598                            if (sourceIndex < 0) {
1599                                // 1. Add the route to the list.
1600                                RouteInfo route = new RouteInfo(provider, id);
1601                                provider.mRoutes.add(targetIndex++, route);
1602                                mRoutes.add(route);
1603                                // 2. Create the route's contents.
1604                                route.updateDescriptor(routeDescriptor);
1605                                // 3. Notify clients about addition.
1606                                if (DEBUG) {
1607                                    Log.d(TAG, "Route added: " + route);
1608                                }
1609                                mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
1610                            } else if (sourceIndex < targetIndex) {
1611                                Log.w(TAG, "Ignoring route descriptor with duplicate id: "
1612                                        + routeDescriptor);
1613                            } else {
1614                                // 1. Reorder the route within the list.
1615                                RouteInfo route = provider.mRoutes.get(sourceIndex);
1616                                Collections.swap(provider.mRoutes,
1617                                        sourceIndex, targetIndex++);
1618                                // 2. Update the route's contents.
1619                                int changes = route.updateDescriptor(routeDescriptor);
1620                                // 3. Unselect route if needed before notifying about changes.
1621                                unselectRouteIfNeeded(route);
1622                                // 4. Notify clients about changes.
1623                                if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
1624                                    if (DEBUG) {
1625                                        Log.d(TAG, "Route changed: " + route);
1626                                    }
1627                                    mCallbackHandler.post(
1628                                            CallbackHandler.MSG_ROUTE_CHANGED, route);
1629                                }
1630                                if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
1631                                    if (DEBUG) {
1632                                        Log.d(TAG, "Route volume changed: " + route);
1633                                    }
1634                                    mCallbackHandler.post(
1635                                            CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
1636                                }
1637                                if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
1638                                    if (DEBUG) {
1639                                        Log.d(TAG, "Route presentation display changed: "
1640                                                + route);
1641                                    }
1642                                    mCallbackHandler.post(CallbackHandler.
1643                                            MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
1644                                }
1645                            }
1646                        }
1647                    } else {
1648                        Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
1649                    }
1650                }
1651
1652                // Dispose all remaining routes that do not have matching descriptors.
1653                for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
1654                    // 1. Delete the route's contents.
1655                    RouteInfo route = provider.mRoutes.get(i);
1656                    route.updateDescriptor(null);
1657                    // 2. Remove the route from the list.
1658                    mRoutes.remove(route);
1659                    provider.mRoutes.remove(i);
1660                    // 3. Unselect route if needed before notifying about removal.
1661                    unselectRouteIfNeeded(route);
1662                    // 4. Notify clients about removal.
1663                    if (DEBUG) {
1664                        Log.d(TAG, "Route removed: " + route);
1665                    }
1666                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
1667                }
1668
1669                // Notify provider changed.
1670                if (DEBUG) {
1671                    Log.d(TAG, "Provider changed: " + provider);
1672                }
1673                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
1674
1675                // Choose a new selected route if needed.
1676                selectRouteIfNeeded();
1677            }
1678        }
1679
1680        private void unselectRouteIfNeeded(RouteInfo route) {
1681            if (mDefaultRoute == route && !isRouteSelectable(route)) {
1682                Log.i(TAG, "Choosing a new default route because the current one "
1683                        + "is no longer selectable: " + route);
1684                mDefaultRoute = null;
1685            }
1686            if (mSelectedRoute == route && !isRouteSelectable(route)) {
1687                Log.i(TAG, "Choosing a new selected route because the current one "
1688                        + "is no longer selectable: " + route);
1689                setSelectedRouteInternal(null);
1690            }
1691        }
1692
1693        private void selectRouteIfNeeded() {
1694            if (mDefaultRoute == null && !mRoutes.isEmpty()) {
1695                for (RouteInfo route : mRoutes) {
1696                    if (isSystemDefaultRoute(route) && isRouteSelectable(route)) {
1697                        mDefaultRoute = route;
1698                        break;
1699                    }
1700                }
1701            }
1702            if (mSelectedRoute == null) {
1703                setSelectedRouteInternal(mDefaultRoute);
1704            }
1705        }
1706
1707        private boolean isRouteSelectable(RouteInfo route) {
1708            // This tests whether the route is still valid and enabled.
1709            // The route descriptor field is set to null when the route is removed.
1710            return route.mDescriptor != null && route.mEnabled;
1711        }
1712
1713        private boolean isSystemDefaultRoute(RouteInfo route) {
1714            return route.getProviderInstance() == mSystemProvider
1715                    && route.mDescriptorId.equals(
1716                            SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
1717        }
1718
1719        private void setSelectedRouteInternal(RouteInfo route) {
1720            if (mSelectedRoute != route) {
1721                if (mSelectedRoute != null) {
1722                    if (DEBUG) {
1723                        Log.d(TAG, "Route unselected: " + mSelectedRoute);
1724                    }
1725                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
1726                    if (mSelectedRouteController != null) {
1727                        mSelectedRouteController.onUnselect();
1728                        mSelectedRouteController.onRelease();
1729                        mSelectedRouteController = null;
1730                    }
1731                }
1732
1733                mSelectedRoute = route;
1734
1735                if (mSelectedRoute != null) {
1736                    mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
1737                            route.mDescriptorId);
1738                    if (mSelectedRouteController != null) {
1739                        mSelectedRouteController.onSelect();
1740                    }
1741                    if (DEBUG) {
1742                        Log.d(TAG, "Route selected: " + mSelectedRoute);
1743                    }
1744                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
1745                }
1746            }
1747        }
1748
1749        @Override
1750        public RouteInfo getSystemRouteByDescriptorId(String id) {
1751            int providerIndex = findProviderInfo(mSystemProvider);
1752            if (providerIndex >= 0) {
1753                ProviderInfo provider = mProviders.get(providerIndex);
1754                int routeIndex = provider.findRouteByDescriptorId(id);
1755                if (routeIndex >= 0) {
1756                    return provider.mRoutes.get(routeIndex);
1757                }
1758            }
1759            return null;
1760        }
1761
1762        private final class ProviderCallback extends MediaRouteProvider.Callback {
1763            @Override
1764            public void onDescriptorChanged(MediaRouteProvider provider,
1765                    MediaRouteProviderDescriptor descriptor) {
1766                updateProviderDescriptor(provider, descriptor);
1767            }
1768        }
1769
1770        private final class CallbackHandler extends Handler {
1771            private final ArrayList<MediaRouter> mTempMediaRouters =
1772                    new ArrayList<MediaRouter>();
1773
1774            private static final int MSG_TYPE_MASK = 0xff00;
1775            private static final int MSG_TYPE_ROUTE = 0x0100;
1776            private static final int MSG_TYPE_PROVIDER = 0x0200;
1777
1778            public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
1779            public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
1780            public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
1781            public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
1782            public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
1783            public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
1784            public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
1785
1786            public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
1787            public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
1788            public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
1789
1790            public void post(int msg, Object obj) {
1791                obtainMessage(msg, obj).sendToTarget();
1792            }
1793
1794            @Override
1795            public void handleMessage(Message msg) {
1796                final int what = msg.what;
1797                final Object obj = msg.obj;
1798
1799                // Synchronize state with the system media router.
1800                syncWithSystemProvider(what, obj);
1801
1802                // Invoke all registered callbacks.
1803                mTempMediaRouters.addAll(mRouters.values());
1804                try {
1805                    final int routerCount = mTempMediaRouters.size();
1806                    for (int i = 0; i < routerCount; i++) {
1807                        final MediaRouter router = mTempMediaRouters.get(i);
1808                        if (!router.mCallbackRecords.isEmpty()) {
1809                            for (CallbackRecord record : router.mCallbackRecords) {
1810                                invokeCallback(router, record, what, obj);
1811                            }
1812                        }
1813                    }
1814                } finally {
1815                    mTempMediaRouters.clear();
1816                }
1817            }
1818
1819            private void syncWithSystemProvider(int what, Object obj) {
1820                switch (what) {
1821                    case MSG_ROUTE_ADDED:
1822                        mSystemProvider.onSyncRouteAdded((RouteInfo)obj);
1823                        break;
1824                    case MSG_ROUTE_REMOVED:
1825                        mSystemProvider.onSyncRouteRemoved((RouteInfo)obj);
1826                        break;
1827                    case MSG_ROUTE_CHANGED:
1828                        mSystemProvider.onSyncRouteChanged((RouteInfo)obj);
1829                        break;
1830                    case MSG_ROUTE_SELECTED:
1831                        mSystemProvider.onSyncRouteSelected((RouteInfo)obj);
1832                        break;
1833                }
1834            }
1835
1836            private void invokeCallback(MediaRouter router, CallbackRecord record,
1837                    int what, Object obj) {
1838                final MediaRouter.Callback callback = record.mCallback;
1839                switch (what & MSG_TYPE_MASK) {
1840                    case MSG_TYPE_ROUTE: {
1841                        final RouteInfo route = (RouteInfo)obj;
1842                        if (!record.filterRouteEvent(route)) {
1843                            break;
1844                        }
1845                        switch (what) {
1846                            case MSG_ROUTE_ADDED:
1847                                callback.onRouteAdded(router, route);
1848                                break;
1849                            case MSG_ROUTE_REMOVED:
1850                                callback.onRouteRemoved(router, route);
1851                                break;
1852                            case MSG_ROUTE_CHANGED:
1853                                callback.onRouteChanged(router, route);
1854                                break;
1855                            case MSG_ROUTE_VOLUME_CHANGED:
1856                                callback.onRouteVolumeChanged(router, route);
1857                                break;
1858                            case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
1859                                callback.onRoutePresentationDisplayChanged(router, route);
1860                                break;
1861                            case MSG_ROUTE_SELECTED:
1862                                callback.onRouteSelected(router, route);
1863                                break;
1864                            case MSG_ROUTE_UNSELECTED:
1865                                callback.onRouteUnselected(router, route);
1866                                break;
1867                        }
1868                        break;
1869                    }
1870                    case MSG_TYPE_PROVIDER: {
1871                        final ProviderInfo provider = (ProviderInfo)obj;
1872                        switch (what) {
1873                            case MSG_PROVIDER_ADDED:
1874                                callback.onProviderAdded(router, provider);
1875                                break;
1876                            case MSG_PROVIDER_REMOVED:
1877                                callback.onProviderRemoved(router, provider);
1878                                break;
1879                            case MSG_PROVIDER_CHANGED:
1880                                callback.onProviderChanged(router, provider);
1881                                break;
1882                        }
1883                    }
1884                }
1885            }
1886        }
1887    }
1888}
1889