MediaRouteProvider.java revision 11417b1cfde8f1749905f2d735623af9214148af
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.media;
18
19import android.content.Context;
20import android.content.Intent;
21import android.os.Handler;
22import android.os.Message;
23import android.support.v7.media.MediaRouter.ControlRequestCallback;
24import android.text.TextUtils;
25
26/**
27 * Media route providers are used to publish additional media routes for
28 * use within an application.  Media route providers may also be declared
29 * as a service to publish additional media routes to all applications
30 * in the system.
31 * <p>
32 * The purpose of a media route provider is to discover media routes that satisfy
33 * the criteria specified by the current {@link MediaRouteDiscoveryRequest} and publish a
34 * {@link MediaRouteProviderDescriptor} with information about each route by calling
35 * {@link #setDescriptor} to notify the currently registered {@link Callback}.
36 * </p><p>
37 * The provider should watch for changes to the discovery request by implementing
38 * {@link #onDiscoveryRequestChanged} and updating the set of routes that it is
39 * attempting to discover.  It should also handle route control requests such
40 * as volume changes or {@link MediaControlIntent media control intents}
41 * by implementing {@link #onCreateRouteController} to return a {@link RouteController}
42 * for a particular route.
43 * </p><p>
44 * A media route provider may be used privately within the scope of a single
45 * application process by calling {@link MediaRouter#addProvider MediaRouter.addProvider}
46 * to add it to the local {@link MediaRouter}.  A media route provider may also be made
47 * available globally to all applications by registering a {@link MediaRouteProviderService}
48 * in the provider's manifest.  When the media route provider is registered
49 * as a service, all applications that use the media router API will be able to
50 * discover and used the provider's routes without having to install anything else.
51 * </p><p>
52 * This object must only be accessed on the main thread.
53 * </p>
54 */
55public abstract class MediaRouteProvider {
56    private static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1;
57    private static final int MSG_DELIVER_DISCOVERY_REQUEST_CHANGED = 2;
58
59    private final Context mContext;
60    private final ProviderMetadata mMetadata;
61    private final ProviderHandler mHandler = new ProviderHandler();
62
63    private Callback mCallback;
64
65    private MediaRouteDiscoveryRequest mDiscoveryRequest;
66    private boolean mPendingDiscoveryRequestChange;
67
68    private MediaRouteProviderDescriptor mDescriptor;
69    private boolean mPendingDescriptorChange;
70
71    /**
72     * Creates a media route provider.
73     *
74     * @param context The context.
75     */
76    public MediaRouteProvider(Context context) {
77        this(context, null);
78    }
79
80    MediaRouteProvider(Context context, ProviderMetadata metadata) {
81        if (context == null) {
82            throw new IllegalArgumentException("context must not be null");
83        }
84
85        mContext = context;
86        if (metadata == null) {
87            mMetadata = new ProviderMetadata(context.getPackageName());
88        } else {
89            mMetadata = metadata;
90        }
91    }
92
93    /**
94     * Gets the context of the media route provider.
95     */
96    public final Context getContext() {
97        return mContext;
98    }
99
100    /**
101     * Gets the provider's handler which is associated with the main thread.
102     */
103    public final Handler getHandler() {
104        return mHandler;
105    }
106
107    /**
108     * Gets some metadata about the provider's implementation.
109     */
110    public final ProviderMetadata getMetadata() {
111        return mMetadata;
112    }
113
114    /**
115     * Sets a callback to invoke when the provider's descriptor changes.
116     *
117     * @param callback The callback to use, or null if none.
118     */
119    public final void setCallback(Callback callback) {
120        MediaRouter.checkCallingThread();
121        mCallback = callback;
122    }
123
124    /**
125     * Gets the current discovery request which informs the provider about the
126     * kinds of routes to discover and whether to perform active scanning.
127     *
128     * @return The current discovery request, or null if no discovery is needed at this time.
129     *
130     * @see #onDiscoveryRequestChanged
131     */
132    public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
133        return mDiscoveryRequest;
134    }
135
136    /**
137     * Sets a discovery request to inform the provider about the kinds of
138     * routes that its clients would like to discover and whether to perform active scanning.
139     *
140     * @param request The discovery request, or null if no discovery is needed at this time.
141     *
142     * @see #onDiscoveryRequestChanged
143     */
144    public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
145        MediaRouter.checkCallingThread();
146
147        if (mDiscoveryRequest == request
148                || (mDiscoveryRequest != null && mDiscoveryRequest.equals(request))) {
149            return;
150        }
151
152        mDiscoveryRequest = request;
153        if (!mPendingDiscoveryRequestChange) {
154            mPendingDiscoveryRequestChange = true;
155            mHandler.sendEmptyMessage(MSG_DELIVER_DISCOVERY_REQUEST_CHANGED);
156        }
157    }
158
159    private void deliverDiscoveryRequestChanged() {
160        mPendingDiscoveryRequestChange = false;
161        onDiscoveryRequestChanged(mDiscoveryRequest);
162    }
163
164    /**
165     * Called by the media router when the {@link MediaRouteDiscoveryRequest discovery request}
166     * has changed.
167     * <p>
168     * Whenever an applications calls {@link MediaRouter#addCallback} to register
169     * a callback, it also provides a selector to specify the kinds of routes that
170     * it is interested in.  The media router combines all of these selectors together
171     * to generate a {@link MediaRouteDiscoveryRequest} and notifies each provider when a change
172     * occurs by calling {@link #setDiscoveryRequest} which posts a message to invoke
173     * this method asynchronously.
174     * </p><p>
175     * The provider should examine the {@link MediaControlIntent media control categories}
176     * in the discovery request's {@link MediaRouteSelector selector} to determine what
177     * kinds of routes it should try to discover and whether it should perform active
178     * or passive scans.  In many cases, the provider may be able to save power by
179     * determining that the selector does not contain any categories that it supports
180     * and it can therefore avoid performing any scans at all.
181     * </p>
182     *
183     * @param request The new discovery request, or null if no discovery is needed at this time.
184     *
185     * @see MediaRouter#addCallback
186     */
187    public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
188    }
189
190    /**
191     * Gets the provider's descriptor.
192     * <p>
193     * The descriptor describes the state of the media route provider and
194     * the routes that it publishes.  Watch for changes to the descriptor
195     * by registering a {@link Callback callback} with {@link #setCallback}.
196     * </p>
197     *
198     * @return The media route provider descriptor, or null if none.
199     *
200     * @see Callback#onDescriptorChanged
201     */
202    public final MediaRouteProviderDescriptor getDescriptor() {
203        return mDescriptor;
204    }
205
206    /**
207     * Sets the provider's descriptor.
208     * <p>
209     * The provider must call this method to notify the currently registered
210     * {@link Callback callback} about the change to the provider's descriptor.
211     * </p>
212     *
213     * @param descriptor The updated route provider descriptor, or null if none.
214     *
215     * @see Callback#onDescriptorChanged
216     */
217    public final void setDescriptor(MediaRouteProviderDescriptor descriptor) {
218        MediaRouter.checkCallingThread();
219
220        if (mDescriptor != descriptor) {
221            mDescriptor = descriptor;
222            if (!mPendingDescriptorChange) {
223                mPendingDescriptorChange = true;
224                mHandler.sendEmptyMessage(MSG_DELIVER_DESCRIPTOR_CHANGED);
225            }
226        }
227    }
228
229    private void deliverDescriptorChanged() {
230        mPendingDescriptorChange = false;
231
232        if (mCallback != null) {
233            mCallback.onDescriptorChanged(this, mDescriptor);
234        }
235    }
236
237    /**
238     * Called by the media router to obtain a route controller for a particular route.
239     * <p>
240     * The media router will invoke the {@link RouteController#onRelease} method of the route
241     * controller when it is no longer needed to allow it to free its resources.
242     * </p>
243     *
244     * @param routeId The unique id of the route.
245     * @return The route controller.  Returns null if there is no such route or if the route
246     * cannot be controlled using the route controller interface.
247     */
248    public RouteController onCreateRouteController(String routeId) {
249        return null;
250    }
251
252    /**
253     * Describes properties of the route provider's implementation.
254     * <p>
255     * This object is immutable once created.
256     * </p>
257     */
258    public static final class ProviderMetadata {
259        private final String mPackageName;
260
261        /**
262         * Creates a provider metadata object.
263         *
264         * @param packageName The provider application's package name.
265         */
266        public ProviderMetadata(String packageName) {
267            if (TextUtils.isEmpty(packageName)) {
268                throw new IllegalArgumentException("packageName must not be null or empty");
269            }
270            mPackageName = packageName;
271        }
272
273        /**
274         * Gets the provider application's package name.
275         */
276        public String getPackageName() {
277            return mPackageName;
278        }
279
280        @Override
281        public String toString() {
282            return "ProviderMetadata{ packageName=" + mPackageName + " }";
283        }
284    }
285
286    /**
287     * Provides control over a particular route.
288     * <p>
289     * The media router obtains a route controller for a route whenever it needs
290     * to control a route.  When a route is selected, the media router invokes
291     * the {@link #onSelect} method of its route controller.  While selected,
292     * the media router may call other methods of the route controller to
293     * request that it perform certain actions to the route.  When a route is
294     * unselected, the media router invokes the {@link #onUnselect} method of its
295     * route controller.  When the media route no longer needs the route controller
296     * it will invoke the {@link #onRelease} method to allow the route controller
297     * to free its resources.
298     * </p><p>
299     * There may be multiple route controllers simultaneously active for the
300     * same route.  Each route controller will be released separately.
301     * </p><p>
302     * All operations on the route controller are asynchronous and
303     * results are communicated via callbacks.
304     * </p>
305     */
306    public static abstract class RouteController {
307        /**
308         * Releases the route controller, allowing it to free its resources.
309         */
310        public void onRelease() {
311        }
312
313        /**
314         * Selects the route.
315         */
316        public void onSelect() {
317        }
318
319        /**
320         * Unselects the route.
321         */
322        public void onUnselect() {
323        }
324
325        /**
326         * Requests to set the volume of the route.
327         *
328         * @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
329         */
330        public void onSetVolume(int volume) {
331        }
332
333        /**
334         * Requests an incremental volume update for the route.
335         *
336         * @param delta The delta to add to the current volume.
337         */
338        public void onUpdateVolume(int delta) {
339        }
340
341        /**
342         * Performs a {@link MediaControlIntent media control} request
343         * asynchronously on behalf of the route.
344         *
345         * @param intent A {@link MediaControlIntent media control intent}.
346         * @param callback A {@link ControlRequestCallback} to invoke with the result
347         * of the request, or null if no result is required.
348         * @return True if the controller intends to handle the request and will
349         * invoke the callback when finished.  False if the controller will not
350         * handle the request and will not invoke the callback.
351         *
352         * @see MediaControlIntent
353         */
354        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
355            return false;
356        }
357    }
358
359    /**
360     * Callback which is invoked when route information becomes available or changes.
361     */
362    public static abstract class Callback {
363        /**
364         * Called when information about a route provider and its routes changes.
365         *
366         * @param provider The media route provider that changed, never null.
367         * @param descriptor The new media route provider descriptor, or null if none.
368         */
369        public void onDescriptorChanged(MediaRouteProvider provider,
370                MediaRouteProviderDescriptor descriptor) {
371        }
372    }
373
374    private final class ProviderHandler extends Handler {
375        @Override
376        public void handleMessage(Message msg) {
377            switch (msg.what) {
378                case MSG_DELIVER_DESCRIPTOR_CHANGED:
379                    deliverDescriptorChanged();
380                    break;
381                case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
382                    deliverDiscoveryRequestChanged();
383                    break;
384            }
385        }
386    }
387}
388