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