MediaRouter.java revision c21f57ed68b81a77167f1df000b0e272e1598bc0
1c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown/*
2c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Copyright (C) 2013 The Android Open Source Project
3c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *
4c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Licensed under the Apache License, Version 2.0 (the "License");
5c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * you may not use this file except in compliance with the License.
6c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * You may obtain a copy of the License at
7c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *
8c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *      http://www.apache.org/licenses/LICENSE-2.0
9c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *
10c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Unless required by applicable law or agreed to in writing, software
11c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * distributed under the License is distributed on an "AS IS" BASIS,
12c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * See the License for the specific language governing permissions and
14c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * limitations under the License.
15c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown */
16c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
17c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownpackage android.support.v4.media;
18c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
19c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.ContentResolver;
20c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.Context;
21c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.Intent;
22c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.IntentFilter;
23c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.graphics.drawable.Drawable;
24c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.os.Bundle;
25c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.os.Handler;
26c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.os.Looper;
27c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.os.Message;
28c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.support.v4.hardware.display.DisplayManagerCompat;
29c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.support.v4.media.MediaRouteProvider.RouteDescriptor;
30c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.support.v4.media.MediaRouteProvider.RouteProviderDescriptor;
31c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.util.Log;
32c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.view.Display;
33c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
34c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.ArrayList;
35c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.Collections;
36c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.List;
37c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.WeakHashMap;
38c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.concurrent.CopyOnWriteArrayList;
39c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
40c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown/**
41c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * MediaRouter allows applications to control the routing of media channels
42c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * and streams from the current device to external speakers and destination devices.
43c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * <p>
44c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * A MediaRouter instance is retrieved through {@link #getInstance}.  Applications
45c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * can query the media router about the currently selected route and its capabilities
46c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * to determine how to send content to the route's destination.  Applications can
47c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * also {@link RouteInfo#sendControlRequest send control requests} to the route
48c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * to ask the route's destination to perform certain remote control functions
49c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * such as playing media.
50c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * </p><p>
51c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * See also {@link MediaRouteProvider} for information on how an application
52c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * can publish new media routes to the media router.
53c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * </p><p>
54c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * The media router API is not thread-safe; all interactions with it must be
55c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * done from the main thread of the process.
56c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * </p>
57c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown */
58c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownpublic final class MediaRouter {
59c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static final String TAG = "MediaRouter";
60c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
61c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    // Maintains global media router state for the process.
62c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    // This field is initialized in MediaRouter.getInstance() before any
63c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    // MediaRouter objects are instantiated so it is guaranteed to be
64c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    // valid whenever any instance method is invoked.
65c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static GlobalMediaRouter sGlobal;
66c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
67c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    // Context-bound state of the media router.
68c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    final Context mContext;
69c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    final CopyOnWriteArrayList<Callback> mCallbacks = new CopyOnWriteArrayList<Callback>();
70c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
71c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    MediaRouter(Context context) {
72c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        mContext = context;
73c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
74c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
75c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
76c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Gets an instance of the media router service from the context.
77c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
78c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static MediaRouter getInstance(Context context) {
79c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (context == null) {
80c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalArgumentException("context must not be null");
81c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
82c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
83c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
84c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (sGlobal == null) {
85c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            sGlobal = new GlobalMediaRouter(context.getApplicationContext());
86c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
87c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return sGlobal.getRouter(context);
88c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
89c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
90c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
91c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Gets the {@link MediaRouter.RouteInfo routes} currently known to this MediaRouter.
92c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
93c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public List<RouteInfo> getRoutes() {
94c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
95c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return sGlobal.getRoutes();
96c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
97c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
98c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
99c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Gets the default route for playing media content on the system.
100c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <p>
101c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * The system always provides a default route.
102c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * </p>
103c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
104c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @return The default route, which is guaranteed to never be null.
105c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
106c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public RouteInfo getDefaultRoute() {
107c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
108c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return sGlobal.getDefaultRoute();
109c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
110c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
111c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
112c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Gets the currently selected route.
113c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <p>
114c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * The application should examine the route's
115c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * {@link RouteInfo#getControlFilter media control intent filter} to assess the
116c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * capabilities of the route before attempting to use it.
117c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * </p>
118c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
119c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <h3>Example</h3>
120c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <pre>
121c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * public boolean playMovie() {
122c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     MediaRouter mediaRouter = MediaRouter.getInstance(context);
123c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
124c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
125c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     // First try using the remote playback interface, if supported.
126c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
127c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         // The route supports remote playback.
128c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         // Try to send it the Uri of the movie to play.
129c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
130c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
131c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
132c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         if (route.sendControlRequest(intent, null)) {
133c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *             return true; // successfully sent the request to play the movie
134c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         }
135c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     }
136c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
137c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     // If remote playback was not possible, then play locally.
138c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
139c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         // The route supports live video streaming.
140c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         // Prepare to play content locally in a window or in a presentation.
141c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *         return playMovieInWindow();
142c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     }
143c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
144c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     // Neither interface is supported, so we can't play the movie to this route.
145c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *     return false;
146c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * }
147c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * </pre>
148c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
149c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @return The selected route, which is guaranteed to never be null.
150c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
151c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see RouteInfo#getControlFilter
152c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see RouteInfo#supportsControlCategory
153c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see RouteInfo#supportsControlRequest
154c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
155c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public RouteInfo getSelectedRoute() {
156c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
157c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return sGlobal.getSelectedRoute();
158c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
159c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
160c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
161c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Selects the specified route.
162c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
163c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @param route The route to select.
164c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
165c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void selectRoute(RouteInfo route) {
166c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (route == null) {
167c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalArgumentException("route must not be null");
168c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
169c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
170c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
171c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        sGlobal.selectRoute(route);
172c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
173c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
174c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
175c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Adds a callback to listen to changes to media routes.
176c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
177c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @param callback The callback to add.
178c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
179c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void addCallback(Callback callback) {
180c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (callback == null) {
181c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalArgumentException("callback must not be null");
182c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
183c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
184c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
185c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (!mCallbacks.contains(callback)) {
186c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mCallbacks.add(callback);
187c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
188c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
189c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
190c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
191c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Removes the specified callback.  It will no longer receive information about
192c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * changes to media routes.
193c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
194c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @param callback The callback to remove.
195c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
196c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void removeCallback(Callback callback) {
197c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (callback == null) {
198c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalArgumentException("callback must not be null");
199c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
200c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
201c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
202c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        mCallbacks.remove(callback);
203c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
204c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
205c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
206c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Registers a media route provider globally for this application process.
207c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
208c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @param provider The media route provider to add.
209c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
210c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see MediaRouteProvider
211c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
212c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void addProvider(MediaRouteProvider provider) {
213c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (provider == null) {
214c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalArgumentException("provider must not be null");
215c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
216c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
217c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
218c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        sGlobal.addProvider(provider);
219c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
220c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
221c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
222c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Unregisters a media route provider globally for this application process.
223c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
224c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @param provider The media route provider to remove.
225c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
226c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see MediaRouteProvider
227c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
228c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void removeProvider(MediaRouteProvider provider) {
229c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (provider == null) {
230c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalArgumentException("provider must not be null");
231c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
232c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        checkCallingThread();
233c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
234c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        sGlobal.removeProvider(provider);
235c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
236c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
237c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
238c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Ensures that calls into the media router are on the correct thread.
239c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * It pays to be a little paranoid when global state invariants are at risk.
240c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
241c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static void checkCallingThread() {
242c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Looper.myLooper() != Looper.getMainLooper()) {
243c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            throw new IllegalStateException("The media router service must only be "
244c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + "accessed on the application's main thread.");
245c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
246c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
247c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
248c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static <T> boolean equal(T a, T b) {
249c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return a == b || (a != null && b != null && a.equals(b));
250c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
251c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
252c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
253c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Provides information about a media route.
254c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <p>
255c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Each media route has a {@link MediaControlIntent media control}
256c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * {@link #getControlFilter intent filter} that describes the capabilities of the
257c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * route and the manner in which it is used and controlled.
258c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * </p>
259c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
260c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static final class RouteInfo {
261c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final ProviderRecord mProviderRecord;
262c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final String mDescriptorId;
263c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private String mName;
264c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private String mStatus;
265c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private Drawable mIconDrawable;
266c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private boolean mEnabled;
267c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private IntentFilter mControlFilter;
268c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mPlaybackType;
269c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mPlaybackStream;
270c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mVolumeHandling;
271c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mVolume;
272c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mVolumeMax;
273c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private Display mPresentationDisplay;
274c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mPresentationDisplayId = -1;
275c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private Bundle mExtras;
276c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private RouteDescriptor mDescriptor;
277c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
278c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
279c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * The default playback type, "local", indicating the presentation of the media
280c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * is happening on the same device (e.g. a phone, a tablet) as where it is
281c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * controlled from.
282c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
283c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #getPlaybackType
284c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
285c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int PLAYBACK_TYPE_LOCAL = 0;
286c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
287c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
288c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * A playback type indicating the presentation of the media is happening on
289c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * a different device (i.e. the remote device) than where it is controlled from.
290c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
291c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #getPlaybackType
292c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
293c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int PLAYBACK_TYPE_REMOTE = 1;
294c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
295c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
296c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Playback information indicating the playback volume is fixed, i.e. it cannot be
297c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * controlled from this object. An example of fixed playback volume is a remote player,
298c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
299c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * than attenuate at the source.
300c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
301c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #getVolumeHandling
302c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
303c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int PLAYBACK_VOLUME_FIXED = 0;
304c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
305c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
306c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Playback information indicating the playback volume is variable and can be controlled
307c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * from this object.
308c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
309c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #getVolumeHandling
310c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
311c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int PLAYBACK_VOLUME_VARIABLE = 1;
312c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
313c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        static final int CHANGE_GENERAL = 1 << 0;
314c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        static final int CHANGE_VOLUME = 1 << 1;
315c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
316c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
317c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        RouteInfo(ProviderRecord providerRecord, String descriptorId) {
318c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mProviderRecord = providerRecord;
319c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mDescriptorId = descriptorId;
320c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
321c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
322c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        int updateDescriptor(RouteDescriptor descriptor) {
323c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int changes = 0;
324c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mDescriptor != descriptor) {
325c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mDescriptor = descriptor;
326c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (descriptor != null) {
327c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (!equal(mName, descriptor.getName())) {
328c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mName = descriptor.getName();
329c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
330c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
331c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (!equal(mStatus, descriptor.getStatus())) {
332c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mStatus = descriptor.getStatus();
333c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
334c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
335c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    // TODO: mIconDrawable, probably set this as a resource package name + id
336c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mEnabled != descriptor.isEnabled()) {
337c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mEnabled = descriptor.isEnabled();
338c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
339c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
340c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (!equal(mControlFilter, descriptor.getControlFilter())) {
341c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mControlFilter = descriptor.getControlFilter();
342c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
343c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
344c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mPlaybackType != descriptor.getPlaybackType()) {
345c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mPlaybackType = descriptor.getPlaybackType();
346c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
347c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
348c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mPlaybackStream != descriptor.getPlaybackStream()) {
349c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mPlaybackStream = descriptor.getPlaybackStream();
350c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
351c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
352c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mVolumeHandling != descriptor.getVolumeHandling()) {
353c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mVolumeHandling = descriptor.getVolumeHandling();
354c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
355c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
356c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mVolume != descriptor.getVolume()) {
357c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mVolume = descriptor.getVolume();
358c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
359c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
360c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mVolumeMax != descriptor.getVolumeMax()) {
361c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mVolumeMax = descriptor.getVolumeMax();
362c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL | CHANGE_VOLUME;
363c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
364c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
365c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mPresentationDisplayId = descriptor.getPresentationDisplayId();
366c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mPresentationDisplay = null;
367c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
368c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
369c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (!equal(mExtras, descriptor.getExtras())) {
370c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mExtras = descriptor.getExtras();
371c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        changes |= CHANGE_GENERAL;
372c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
373c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
374c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
375c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return changes;
376c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
377c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
378c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        MediaRouteProvider getProvider() {
379c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mProviderRecord.mProvider;
380c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
381c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
382c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        String getDescriptorId() {
383c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mDescriptorId;
384c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
385c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
386c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        void select() {
387c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            sGlobal.selectRoute(this);
388c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
389c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
390c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
391c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the name of this route.
392c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
393c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The user-friendly name of a media route. This is the string presented
394c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * to users who may select this as the active route.
395c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
396c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public String getName() {
397c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mName;
398c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
399c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
400c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
401c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the status of this route.
402c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
403c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The user-friendly status for a media route. This may include a description
404c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * of the currently playing media, if available.
405c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
406c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public String getStatus() {
407c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mStatus;
408c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
409c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
410c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
411c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Get the icon representing this route.
412c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This icon will be used in picker UIs if available.
413c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
414c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The icon representing this route or null if no icon is available
415c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
416c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public Drawable getIconDrawable() {
417c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mIconDrawable;
418c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
419c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
420c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
421c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Returns true if this route is enabled and may be selected.
422c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
423c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return true if this route is enabled and may be selected.
424c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
425c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public boolean isEnabled() {
426c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mEnabled;
427c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
428c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
429c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
430c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets a {@link MediaControlIntent media control intent} filter that describes
431c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * the capabilities of this route and the media control actions that it supports.
432c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
433c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return An intent filter that specifies the media control intents that
434c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * are supported by this route.
435c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
436c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see MediaControlIntent
437c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #supportsControlCategory
438c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #supportsControlRequest
439c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
440c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public IntentFilter getControlFilter() {
441c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mControlFilter;
442c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
443c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
444c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
445c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Returns true if the route supports the specified
446c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * {@link MediaControlIntent media control} category.
447c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
448c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Media control categories describe the capabilities of this route
449c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * such as whether it supports live audio streaming or remote playback.
450c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
451c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
452c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param category A {@link MediaControlIntent media control} category
453c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
454c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
455c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK} or a provider-defined
456c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * media control category.
457c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
458c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see MediaControlIntent
459c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #getControlFilter
460c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
461c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public boolean supportsControlCategory(String category) {
462c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (category == null) {
463c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                throw new IllegalArgumentException("category must not be null");
464c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
465c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
466c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mControlFilter.hasCategory(category);
467c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
468c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
469c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
470c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Returns true if the route supports the specified
471c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * {@link MediaControlIntent media control} request.
472c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
473c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Media control requests are used to request the route to perform
474c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * actions such as starting remote playback of a content stream.
475c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
476c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
477c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param intent A {@link MediaControlIntent media control intent}.
478c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return True if the route can handle the specified intent.
479c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
480c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see MediaControlIntent
481c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see #getControlFilter
482c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
483c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public boolean supportsControlRequest(Intent intent) {
484c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (intent == null) {
485c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                throw new IllegalArgumentException("intent must not be null");
486c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
487c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            checkCallingThread();
488c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
489c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            ContentResolver contentResolver = sGlobal.getContentResolver();
490c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mControlFilter.match(contentResolver, intent, true, TAG) >= 0;
491c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
492c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
493c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
494c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Sends a {@link MediaControlIntent media control} request to be performed
495c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * asynchronously by the route's destination.
496c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
497c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Media control requests are used to request the route to perform
498c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * actions such as starting remote playback of a content stream.
499c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p><p>
500c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This function may only be called on a selected route.  It will return
501c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <code>false</code> and have no effect if the route is currently unselected.
502c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
503c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
504c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param intent A {@link MediaControlIntent media control intent}.
505c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param callback A {@link ControlRequestCallback} to invoke with the result
506c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * of the request, or null if no result is required.
507c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return True if the control request was delivered to the route
508c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * which does not necessarily mean that the request succeeded.  Provide a
509c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * callback to obtain the result of the request.
510c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
511c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see MediaControlIntent
512c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
513c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public boolean sendControlRequest(Intent intent, ControlRequestCallback callback) {
514c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (intent == null) {
515c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                throw new IllegalArgumentException("intent must not be null");
516c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
517c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            checkCallingThread();
518c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
519c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return sGlobal.sendControlRequest(this, intent, callback);
520c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
521c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
522c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
523c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the type of playback associated with this route.
524c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
525c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
526c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * or {@link #PLAYBACK_TYPE_REMOTE}.
527c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
528c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public int getPlaybackType() {
529c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mPlaybackType;
530c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
531c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
532c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
533c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the the stream over which the playback associated with this route is performed.
534c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
535c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The stream over which the playback associated with this route is performed.
536c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
537c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public int getPlaybackStream() {
538c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mPlaybackStream;
539c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
540c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
541c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
542c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets information about how volume is handled on the route.
543c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
544c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
545c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * or {@link #PLAYBACK_VOLUME_VARIABLE}.
546c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
547c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public int getVolumeHandling() {
548c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mVolumeHandling;
549c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
550c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
551c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
552c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the current volume for this route. Depending on the route, this may only
553c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * be valid if the route is currently selected.
554c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
555c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The volume at which the playback associated with this route is performed.
556c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
557c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public int getVolume() {
558c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mVolume;
559c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
560c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
561c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
562c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the maximum volume at which the playback associated with this route is performed.
563c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
564c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The maximum volume at which the playback associated with
565c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * this route is performed.
566c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
567c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public int getVolumeMax() {
568c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mVolumeMax;
569c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
570c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
571c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
572c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Requests a volume change for this route asynchronously.
573c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
574c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This function may only be called on a selected route.  It will have
575c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * no effect if the route is currently unselected.
576c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
577c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
578c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param volume The new volume value between 0 and {@link #getVolumeMax}.
579c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
580c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void requestSetVolume(int volume) {
581c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            checkCallingThread();
582c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
583c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
584c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
585c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
586c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Requests an incremental volume update for this route asynchronously.
587c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
588c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This function may only be called on a selected route.  It will have
589c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * no effect if the route is currently unselected.
590c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
591c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
592c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param delta The delta to add to the current volume.
593c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
594c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void requestUpdateVolume(int delta) {
595c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            checkCallingThread();
596c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (delta != 0) {
597c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                sGlobal.requestUpdateVolume(this, delta);
598c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
599c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
600c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
601c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
602c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets the {@link Display} that should be used by the application to show
603c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * a {@link android.app.Presentation} on an external display when this route is selected.
604c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Depending on the route, this may only be valid if the route is currently
605c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * selected.
606c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
607c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * The preferred presentation display may change independently of the route
608c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * being selected or unselected.  For example, the presentation display
609c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * of the default system route may change when an external HDMI display is connected
610c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * or disconnected even though the route itself has not changed.
611c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p><p>
612c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This method may return null if there is no external display associated with
613c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * the route or if the display is not ready to show UI yet.
614c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p><p>
615c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * The application should listen for changes to the presentation display
616c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
617c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * show or dismiss its {@link android.app.Presentation} accordingly when the display
618c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * becomes available or is removed.
619c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p><p>
620c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This method only makes sense for
621c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
622c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
623c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
624c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @return The preferred presentation display to use when this route is
625c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * selected or null if none.
626c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
627c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
628c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see android.app.Presentation
629c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
630c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public Display getPresentationDisplay() {
631c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
632c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
633c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
634c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mPresentationDisplay;
635c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
636c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
637c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
638c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Gets a collection of extra properties about this route that were supplied
639c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * by its media route provider, or null if none.
640c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
641c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public Bundle getExtras() {
642c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mExtras;
643c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
644c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
645c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
646c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public String toString() {
647c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return "MediaRouter.RouteInfo{ name=" + mName
648c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", status=" + mStatus
649c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", enabled=" + mEnabled
650c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", controlFilter=" + mControlFilter
651c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", playbackType=" + mPlaybackType
652c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", playbackStream=" + mPlaybackStream
653c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", volumeHandling=" + mVolumeHandling
654c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", volume=" + mVolume
655c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", volumeMax=" + mVolumeMax
656c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", presentationDisplayId=" + mPresentationDisplayId
657c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + ", extras=" + mExtras
658c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    + " }";
659c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
660c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
661c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
662c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
663c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Interface for receiving events about media routing changes.
664c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * All methods of this interface will be called from the application's main thread.
665c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <p>
666c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * A Callback will only receive events relevant to routes that the callback
667c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * was registered for.
668c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * </p>
669c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
670c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see MediaRouter#addCallback(Callback)
671c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see MediaRouter#removeCallback(Callback)
672c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
673c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static abstract class Callback {
674c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
675c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when the supplied route becomes selected as the active route.
676c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
677c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
678c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route that has been selected.
679c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
680c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteSelected(MediaRouter router, RouteInfo route) {
681c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
682c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
683c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
684c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when the supplied route becomes unselected as the active route.
685c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
686c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
687c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route that has been unselected.
688c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
689c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
690c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
691c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
692c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
693c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when a route has been added.
694c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
695c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
696c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route that has become available for use.
697c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
698c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteAdded(MediaRouter router, RouteInfo route) {
699c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
700c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
701c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
702c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when a route has been removed.
703c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
704c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
705c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route that has been removed from availability.
706c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
707c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
708c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
709c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
710c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
711c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when a property of the indicated route has changed.
712c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
713c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
714c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route that was changed.
715c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
716c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteChanged(MediaRouter router, RouteInfo route) {
717c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
718c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
719c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
720c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when a route's volume changes.
721c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
722c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
723c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route whose volume changed.
724c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
725c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
726c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
727c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
728c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
729c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called when a route's presentation display changes.
730c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * <p>
731c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * This method is called whenever the route's presentation display becomes
732c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * available, is removes or has changes to some of its properties (such as its size).
733c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * </p>
734c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
735c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param router The MediaRouter reporting the event.
736c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param route The route whose presentation display changed.
737c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
738c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @see RouteInfo#getPresentationDisplay()
739c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
740c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
741c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
742c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
743c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
744c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
745c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Callback which is invoked with the result of a media control request.
746c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     *
747c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * @see RouteInfo#sendControlRequest
748c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
749c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public abstract class ControlRequestCallback {
750c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
751c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Result code: The requested media control action is not supported.
752c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
753c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int REQUEST_NOT_SUPPORTED = -2;
754c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
755c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
756c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Result code: The media control action failed.
757c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
758c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int REQUEST_FAILED = -1;
759c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
760c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
761c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Result code: The media control action succeeded.
762c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
763c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public static final int REQUEST_SUCCEEDED = 0;
764c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
765c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
766c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Called with the result of the media control request.
767c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         *
768c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param result The result code: {@link #REQUEST_NOT_SUPPORTED},
769c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * {@link #REQUEST_FAILED}, or {@link #REQUEST_SUCCEEDED}.
770c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * @param data Additional result data.  Contents depend on the media control action.
771c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
772c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onResult(int result, Bundle data) {
773c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
774c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
775c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
776c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
777c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * State associated with a media route provider.
778c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
779c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static final class ProviderRecord {
780c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public final MediaRouteProvider mProvider;
781c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
782c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public RouteProviderDescriptor mDescriptor;
783c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
784c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public ProviderRecord(MediaRouteProvider provider) {
785c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mProvider = provider;
786c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
787c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
788c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        boolean updateDescriptor(RouteProviderDescriptor descriptor) {
789c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mDescriptor != descriptor) {
790c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mDescriptor = descriptor;
791c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                return true;
792c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
793c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return false;
794c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
795c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
796c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        int findRouteByDescriptorId(String id) {
797c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mRoutes.size();
798c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
799c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mRoutes.get(i).mDescriptorId.equals(id)) {
800c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
801c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
802c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
803c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
804c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
805c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
806c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
807c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
808c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Global state for the media router.
809c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * <p>
810c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Media routes and media route providers are global to the process; their
811c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * state and the bulk of the media router implementation lives here.
812c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * </p>
813c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
814c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback {
815c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final Context mApplicationContext;
816c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final WeakHashMap<Context, MediaRouter> mRouters =
817c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                new WeakHashMap<Context, MediaRouter>();
818c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final ArrayList<ProviderRecord> mProviderRecords =
819c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                new ArrayList<ProviderRecord>();
820c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
821c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final ProviderCallback mProviderCallback = new ProviderCallback();
822c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final CallbackHandler mCallbackHandler = new CallbackHandler();
823c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final DisplayManagerCompat mDisplayManager;
824c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final SystemMediaRouteProvider mSystemProvider;
825c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
826c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private RouteInfo mDefaultRoute;
827c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private RouteInfo mSelectedRoute;
828c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private MediaRouteProvider.RouteController mSelectedRouteController;
829c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
830c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        GlobalMediaRouter(Context applicationContext) {
831c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mApplicationContext = applicationContext;
832c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
833c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
834c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            addDefaultProviders();
835c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
836c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
837c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public MediaRouter getRouter(Context context) {
838c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouter router = mRouters.get(context);
839c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (router == null) {
840c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                router = new MediaRouter(context);
841c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRouters.put(context, router);
842c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
843c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return router;
844c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
845c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
846c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public ContentResolver getContentResolver() {
847c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mApplicationContext.getContentResolver();
848c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
849c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
850c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public Display getDisplay(int displayId) {
851c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mDisplayManager.getDisplay(displayId);
852c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
853c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
854c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public boolean sendControlRequest(RouteInfo route,
855c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Intent intent, ControlRequestCallback callback) {
856c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (route == mSelectedRoute && mSelectedRouteController != null) {
857c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                return mSelectedRouteController.sendControlRequest(intent, callback);
858c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
859c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return false;
860c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
861c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
862c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void requestSetVolume(RouteInfo route, int volume) {
863c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (route == mSelectedRoute && mSelectedRouteController != null) {
864c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mSelectedRouteController.requestSetVolume(volume);
865c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
866c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
867c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
868c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void requestUpdateVolume(RouteInfo route, int delta) {
869c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (route == mSelectedRoute && mSelectedRouteController != null) {
870c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mSelectedRouteController.requestUpdateVolume(delta);
871c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
872c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
873c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
874c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public List<RouteInfo> getRoutes() {
875c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mRoutes;
876c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
877c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
878c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public RouteInfo getDefaultRoute() {
879c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mDefaultRoute == null) {
880c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // This should never happen once the media router has been fully
881c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // initialized but it is good to check for the error in case there
882c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // is a bug in provider initialization.
883c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                throw new IllegalStateException("There is no default route.  "
884c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        + "The media router has not yet been fully initialized.");
885c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
886c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mDefaultRoute;
887c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
888c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
889c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public RouteInfo getSelectedRoute() {
890c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mSelectedRoute == null) {
891c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // This should never happen once the media router has been fully
892c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // initialized but it is good to check for the error in case there
893c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // is a bug in provider initialization.
894c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                throw new IllegalStateException("There is no currently selected route.  "
895c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        + "The media router has not yet been fully initialized.");
896c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
897c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return mSelectedRoute;
898c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
899c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
900c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void selectRoute(RouteInfo route) {
901c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (!mRoutes.contains(route)) {
902c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Log.w(TAG, "Ignoring attempt to select removed route: " + route);
903c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                return;
904c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
905c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (!route.mEnabled) {
906c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
907c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                return;
908c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
909c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
910c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            setSelectedRouteInternal(route);
911c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
912c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
913c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void addProvider(MediaRouteProvider provider) {
914c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int index = findProviderRecord(provider);
915c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (index < 0) {
916c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 1. Add the provider to the list.
917c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                ProviderRecord providerRecord = new ProviderRecord(provider);
918c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mProviderRecords.add(providerRecord);
919c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 2. Create the provider's contents.
920c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateProviderContents(providerRecord, provider.getDescriptor());
921c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 3. Register the provider callback.
922c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                provider.addCallback(mProviderCallback);
923c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 4. Update the selected route if needed.
924c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateSelectedRoute();
925c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
926c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
927c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
928c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void removeProvider(MediaRouteProvider provider) {
929c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int index = findProviderRecord(provider);
930c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (index >= 0) {
931c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 1. Unregister the provider callback.
932c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                provider.removeCallback(mProviderCallback);
933c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 2. Delete the provider's contents.
934c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                ProviderRecord providerRecord = mProviderRecords.get(index);
935c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateProviderContents(providerRecord, null);
936c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 3. Remove the provider from the list.
937c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mProviderRecords.remove(index);
938c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 4. Update the selected route if needed.
939c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateSelectedRoute();
940c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
941c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
942c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
943c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void updateProviderDescriptor(MediaRouteProvider provider,
944c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                RouteProviderDescriptor descriptor) {
945c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int index = findProviderRecord(provider);
946c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (index >= 0) {
947c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 1. Update the provider's contents.
948c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                ProviderRecord providerRecord = mProviderRecords.get(index);
949c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateProviderContents(providerRecord, descriptor);
950c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // 2. Update the selected route if needed.
951c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateSelectedRoute();
952c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
953c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
954c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
955c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void addDefaultProviders() {
956c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            addProvider(mSystemProvider);
957c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
958c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
959c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int findProviderRecord(MediaRouteProvider provider) {
960c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mProviderRecords.size();
961c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
962c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mProviderRecords.get(i).mProvider == provider) {
963c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
964c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
965c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
966c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
967c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
968c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
969c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void updateProviderContents(ProviderRecord providerRecord,
970c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                RouteProviderDescriptor providerDescriptor) {
971c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (providerRecord.updateDescriptor(providerDescriptor)) {
972c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // Update all existing routes and reorder them to match
973c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // the order of their descriptors.
974c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int targetIndex = 0;
975c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (providerDescriptor != null) {
976c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    final RouteDescriptor[] routeDescriptors = providerDescriptor.getRoutes();
977c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    for (int i = 0; i < routeDescriptors.length; i++) {
978c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        final RouteDescriptor routeDescriptor = routeDescriptors[i];
979c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        if (routeDescriptor.isValid()) {
980c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            final String id = routeDescriptor.getId();
981c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            final int sourceIndex = providerRecord.findRouteByDescriptorId(id);
982c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            if (sourceIndex < 0) {
983c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                // 1. Add the route to the list.
984c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                RouteInfo route = new RouteInfo(providerRecord, id);
985c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                providerRecord.mRoutes.add(targetIndex++, route);
986c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                mRoutes.add(route);
987c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                // 2. Create the route's contents.
988c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                route.updateDescriptor(routeDescriptor);
989c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                // 3. Notify clients.
990c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
991c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            } else {
992c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                // 1. Reorder the route within the list.
993c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                RouteInfo route = providerRecord.mRoutes.get(sourceIndex);
994c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                Collections.swap(providerRecord.mRoutes,
995c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                        sourceIndex, targetIndex++);
996c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                // 2. Update the route's contents.
997c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                int changes = route.updateDescriptor(routeDescriptor);
998c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                // 3. Notify clients.
999c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
1000c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                    mCallbackHandler.post(
1001c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                            CallbackHandler.MSG_ROUTE_CHANGED, route);
1002c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                }
1003c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
1004c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                    mCallbackHandler.post(
1005c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                            CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
1006c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                }
1007c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
1008c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                    mCallbackHandler.post(
1009c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                            CallbackHandler.MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED,
1010c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                            route);
1011c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                }
1012c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            }
1013c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        } else {
1014c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            Log.w(TAG, "Ignoring invalid route descriptor: " + routeDescriptor);
1015c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        }
1016c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
1017c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1018c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1019c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // Dispose all remaining routes that do not have matching descriptors.
1020c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                for (int i = providerRecord.mRoutes.size() - 1; i >= targetIndex; i--) {
1021c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    // 1. Notify clients.
1022c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    RouteInfo route = providerRecord.mRoutes.get(i);
1023c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
1024c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    // 2. Delete the route's contents.
1025c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    route.updateDescriptor(null);
1026c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    // 3. Remove the route from the list.
1027c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mRoutes.remove(providerRecord);
1028c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    providerRecord.mRoutes.remove(i);
1029c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1030c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1031c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1032c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1033c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void updateSelectedRoute() {
1034c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Update the default route.
1035c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) {
1036c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Log.i(TAG, "Choosing a new default route because the current one "
1037c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        + "is no longer selectable: " + mDefaultRoute);
1038c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mDefaultRoute = null;
1039c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1040c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mDefaultRoute == null && !mRoutes.isEmpty()) {
1041c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                for (RouteInfo route : mRoutes) {
1042c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (isSystemDefaultRoute(route) && isRouteSelectable(route)) {
1043c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mDefaultRoute = route;
1044c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1045c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
1046c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1047c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1048c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1049c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Update the selected route.
1050c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) {
1051c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Log.i(TAG, "Choosing a new selected route because the current one "
1052c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        + "is no longer selectable: " + mSelectedRoute);
1053c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                setSelectedRouteInternal(null);
1054c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1055c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mSelectedRoute == null) {
1056c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                setSelectedRouteInternal(mDefaultRoute);
1057c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1058c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1059c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1060c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private boolean isRouteSelectable(RouteInfo route) {
1061c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // This tests whether the route is still valid and enabled.
1062c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // The route descriptor field is set to null when the route is removed.
1063c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return route.mDescriptor != null && route.mEnabled;
1064c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1065c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1066c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private boolean isSystemDefaultRoute(RouteInfo route) {
1067c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return route.getProvider() == mSystemProvider
1068c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    && route.mDescriptorId.equals(
1069c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
1070c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1071c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1072c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void setSelectedRouteInternal(RouteInfo route) {
1073c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (mSelectedRoute != route) {
1074c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mSelectedRoute != null) {
1075c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
1076c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mSelectedRouteController != null) {
1077c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSelectedRouteController.unselect();
1078c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSelectedRouteController.release();
1079c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSelectedRouteController = null;
1080c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
1081c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1082c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1083c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mSelectedRoute = route;
1084c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1085c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mSelectedRoute != null) {
1086c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mSelectedRouteController = route.getProvider().onCreateRouteController(
1087c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            route.mDescriptorId);
1088c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (mSelectedRouteController != null) {
1089c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSelectedRouteController.select();
1090c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
1091c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
1092c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1093c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1094c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1095c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1096c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
1097c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public RouteInfo getSystemRouteByDescriptorId(String id) {
1098c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int providerIndex = findProviderRecord(mSystemProvider);
1099c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (providerIndex >= 0) {
1100c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                ProviderRecord providerRecord = mProviderRecords.get(providerIndex);
1101c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int routeIndex = providerRecord.findRouteByDescriptorId(id);
1102c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (routeIndex >= 0) {
1103c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return providerRecord.mRoutes.get(routeIndex);
1104c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1105c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1106c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return null;
1107c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1108c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1109c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final class ProviderCallback extends MediaRouteProvider.Callback {
1110c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
1111c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public void onDescriptorChanged(MediaRouteProvider provider,
1112c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    RouteProviderDescriptor descriptor) {
1113c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateProviderDescriptor(provider, descriptor);
1114c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1115c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1116c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1117c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final class CallbackHandler extends Handler {
1118c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            private final ArrayList<MediaRouter> mTempMediaRouters =
1119c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    new ArrayList<MediaRouter>();
1120c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1121c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_ADDED = 1;
1122c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_REMOVED = 2;
1123c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_CHANGED = 3;
1124c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_VOLUME_CHANGED = 4;
1125c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = 5;
1126c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_SELECTED = 6;
1127c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final int MSG_ROUTE_UNSELECTED = 7;
1128c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1129c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public void post(int msg, RouteInfo route) {
1130c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                obtainMessage(msg, route).sendToTarget();
1131c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1132c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1133c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
1134c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public void handleMessage(Message msg) {
1135c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                final int what = msg.what;
1136c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                final RouteInfo route = (RouteInfo)msg.obj;
1137c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1138c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // Synchronize state with the system media router.
1139c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                syncWithSystemProvider(what, route);
1140c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1141c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // Invoke all registered callbacks.
1142c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mTempMediaRouters.addAll(mRouters.values());
1143c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                try {
1144c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    final int routerCount = mTempMediaRouters.size();
1145c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    for (int i = 0; i < routerCount; i++) {
1146c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        final MediaRouter router = mTempMediaRouters.get(i);
1147c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        if (!router.mCallbacks.isEmpty()) {
1148c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            for (MediaRouter.Callback callback : router.mCallbacks) {
1149c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                                invokeCallback(router, callback, what, route);
1150c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            }
1151c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        }
1152c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
1153c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                } finally {
1154c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mTempMediaRouters.clear();
1155c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1156c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1157c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1158c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            private void syncWithSystemProvider(int what, RouteInfo route) {
1159c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                switch (what) {
1160c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_ADDED:
1161c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSystemProvider.onSyncRouteAdded(route);
1162c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1163c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_REMOVED:
1164c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSystemProvider.onSyncRouteRemoved(route);
1165c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1166c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_CHANGED:
1167c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSystemProvider.onSyncRouteChanged(route);
1168c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1169c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_SELECTED:
1170c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mSystemProvider.onSyncRouteSelected(route);
1171c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1172c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1173c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1174c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
1175c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            private void invokeCallback(MediaRouter router, MediaRouter.Callback callback,
1176c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    int what, RouteInfo route) {
1177c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                switch (what) {
1178c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_ADDED:
1179c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRouteAdded(router, route);
1180c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1181c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_REMOVED:
1182c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRouteRemoved(router, route);
1183c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1184c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_CHANGED:
1185c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRouteChanged(router, route);
1186c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1187c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_VOLUME_CHANGED:
1188c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRouteVolumeChanged(router, route);
1189c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1190c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
1191c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRoutePresentationDisplayChanged(router, route);
1192c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1193c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_SELECTED:
1194c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRouteSelected(router, route);
1195c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1196c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    case MSG_ROUTE_UNSELECTED:
1197c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        callback.onRouteUnselected(router, route);
1198c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        break;
1199c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
1200c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
1201c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
1202c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
1203c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown}
1204