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