MediaRouteProvider.java revision 2f829125aef3796ca674d0ca5fccf9bd37b8417b
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 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 */ 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 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 */ 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 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 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 @Override 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