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