• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.support.v7.media;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Handler;
23 import android.os.Message;
24 import android.support.annotation.NonNull;
25 import android.support.annotation.Nullable;
26 import 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  */
57 public abstract class MediaRouteProvider {
58     private static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1;
59     private 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      */
MediaRouteProvider(@onNull Context context)78     public MediaRouteProvider(@NonNull Context context) {
79         this(context, null);
80     }
81 
MediaRouteProvider(Context context, ProviderMetadata metadata)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      */
getContext()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      */
getHandler()105     public final Handler getHandler() {
106         return mHandler;
107     }
108 
109     /**
110      * Gets some metadata about the provider's implementation.
111      */
getMetadata()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      */
setCallback(@ullable Callback callback)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
getDiscoveryRequest()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      */
setDiscoveryRequest(MediaRouteDiscoveryRequest request)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 
deliverDiscoveryRequestChanged()162     private 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      */
onDiscoveryRequestChanged(@ullable MediaRouteDiscoveryRequest request)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
getDescriptor()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      */
setDescriptor(@ullable MediaRouteProviderDescriptor descriptor)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 
deliverDescriptorChanged()233     private 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
onCreateRouteController(@onNull String routeId)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
onCreateRouteController(@onNull String routeId, @NonNull String routeGroupId)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 
ProviderMetadata(ComponentName componentName)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          */
getPackageName()305         public String getPackageName() {
306             return mComponentName.getPackageName();
307         }
308 
309         /**
310          * Gets the provider's component name.
311          */
getComponentName()312         public ComponentName getComponentName() {
313             return mComponentName;
314         }
315 
316         @Override
toString()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          */
onRelease()347         public void onRelease() {
348         }
349 
350         /**
351          * Selects the route.
352          */
onSelect()353         public void onSelect() {
354         }
355 
356         /**
357          * Unselects the route.
358          */
onUnselect()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          */
onUnselect(int reason)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          */
onSetVolume(int volume)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          */
onUpdateVolume(int delta)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          */
onControlRequest(Intent intent, @Nullable ControlRequestCallback callback)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          */
onDescriptorChanged(@onNull MediaRouteProvider provider, @Nullable MediaRouteProviderDescriptor descriptor)424         public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
425                 @Nullable MediaRouteProviderDescriptor descriptor) {
426         }
427     }
428 
429     private final class ProviderHandler extends Handler {
430         @Override
handleMessage(Message msg)431         public void handleMessage(Message msg) {
432             switch (msg.what) {
433                 case MSG_DELIVER_DESCRIPTOR_CHANGED:
434                     deliverDescriptorChanged();
435                     break;
436                 case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
437                     deliverDiscoveryRequestChanged();
438                     break;
439             }
440         }
441     }
442 }
443