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