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