• 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(String routeId)253     public RouteController onCreateRouteController(String routeId) {
254         return null;
255     }
256 
257     /**
258      * Describes properties of the route provider's implementation.
259      * <p>
260      * This object is immutable once created.
261      * </p>
262      */
263     public static final class ProviderMetadata {
264         private final ComponentName mComponentName;
265 
ProviderMetadata(ComponentName componentName)266         ProviderMetadata(ComponentName componentName) {
267             if (componentName == null) {
268                 throw new IllegalArgumentException("componentName must not be null");
269             }
270             mComponentName = componentName;
271         }
272 
273         /**
274          * Gets the provider's package name.
275          */
getPackageName()276         public String getPackageName() {
277             return mComponentName.getPackageName();
278         }
279 
280         /**
281          * Gets the provider's component name.
282          */
getComponentName()283         public ComponentName getComponentName() {
284             return mComponentName;
285         }
286 
287         @Override
toString()288         public String toString() {
289             return "ProviderMetadata{ componentName="
290                     + mComponentName.flattenToShortString() + " }";
291         }
292     }
293 
294     /**
295      * Provides control over a particular route.
296      * <p>
297      * The media router obtains a route controller for a route whenever it needs
298      * to control a route.  When a route is selected, the media router invokes
299      * the {@link #onSelect} method of its route controller.  While selected,
300      * the media router may call other methods of the route controller to
301      * request that it perform certain actions to the route.  When a route is
302      * unselected, the media router invokes the {@link #onUnselect} method of its
303      * route controller.  When the media route no longer needs the route controller
304      * it will invoke the {@link #onRelease} method to allow the route controller
305      * to free its resources.
306      * </p><p>
307      * There may be multiple route controllers simultaneously active for the
308      * same route.  Each route controller will be released separately.
309      * </p><p>
310      * All operations on the route controller are asynchronous and
311      * results are communicated via callbacks.
312      * </p>
313      */
314     public static abstract class RouteController {
315         /**
316          * Releases the route controller, allowing it to free its resources.
317          */
onRelease()318         public void onRelease() {
319         }
320 
321         /**
322          * Selects the route.
323          */
onSelect()324         public void onSelect() {
325         }
326 
327         /**
328          * Unselects the route.
329          */
onUnselect()330         public void onUnselect() {
331         }
332 
333         /**
334          * Unselects the route and provides a reason. The default implementation
335          * calls {@link #onUnselect()}.
336          * <p>
337          * The reason provided will be one of the following:
338          * <ul>
339          * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
340          * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
341          * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
342          * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
343          * </ul>
344          *
345          * @param reason The reason for unselecting the route.
346          */
onUnselect(int reason)347         public void onUnselect(int reason) {
348             onUnselect();
349         }
350 
351         /**
352          * Requests to set the volume of the route.
353          *
354          * @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
355          */
onSetVolume(int volume)356         public void onSetVolume(int volume) {
357         }
358 
359         /**
360          * Requests an incremental volume update for the route.
361          *
362          * @param delta The delta to add to the current volume.
363          */
onUpdateVolume(int delta)364         public void onUpdateVolume(int delta) {
365         }
366 
367         /**
368          * Performs a {@link MediaControlIntent media control} request
369          * asynchronously on behalf of the route.
370          *
371          * @param intent A {@link MediaControlIntent media control intent}.
372          * @param callback A {@link ControlRequestCallback} to invoke with the result
373          * of the request, or null if no result is required.
374          * @return True if the controller intends to handle the request and will
375          * invoke the callback when finished.  False if the controller will not
376          * handle the request and will not invoke the callback.
377          *
378          * @see MediaControlIntent
379          */
onControlRequest(Intent intent, @Nullable ControlRequestCallback callback)380         public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
381             return false;
382         }
383     }
384 
385     /**
386      * Callback which is invoked when route information becomes available or changes.
387      */
388     public static abstract class Callback {
389         /**
390          * Called when information about a route provider and its routes changes.
391          *
392          * @param provider The media route provider that changed, never null.
393          * @param descriptor The new media route provider descriptor, or null if none.
394          */
onDescriptorChanged(@onNull MediaRouteProvider provider, @Nullable MediaRouteProviderDescriptor descriptor)395         public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
396                 @Nullable MediaRouteProviderDescriptor descriptor) {
397         }
398     }
399 
400     private final class ProviderHandler extends Handler {
401         @Override
handleMessage(Message msg)402         public void handleMessage(Message msg) {
403             switch (msg.what) {
404                 case MSG_DELIVER_DESCRIPTOR_CHANGED:
405                     deliverDescriptorChanged();
406                     break;
407                 case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
408                     deliverDiscoveryRequestChanged();
409                     break;
410             }
411         }
412     }
413 }
414