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