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