MediaRouteProvider.java revision b507e525a61ed761eecfc2eaaf19af7e8db5dca5
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.Context; 20import android.content.Intent; 21import android.content.IntentFilter; 22import android.graphics.drawable.Drawable; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Message; 26import android.os.Parcelable; 27import android.support.v7.media.MediaRouter.ControlRequestCallback; 28import android.text.TextUtils; 29 30import java.util.concurrent.CopyOnWriteArrayList; 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 * Applications and services should extend this class to publish additional media routes 39 * to the {@link MediaRouter}. To make additional media routes available within 40 * your application, call {@link MediaRouter#addProvider} to add your provider to 41 * the media router. To make additional media routes available to all applications 42 * in the system, register a media route provider service in your manifest. 43 * </p><p> 44 * This object must only be accessed on the main thread. 45 * </p> 46 */ 47public abstract class MediaRouteProvider { 48 private static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1; 49 50 private final Context mContext; 51 private final ProviderMetadata mMetadata; 52 private final ProviderHandler mHandler = new ProviderHandler(); 53 private final CopyOnWriteArrayList<Callback> mCallbacks = 54 new CopyOnWriteArrayList<Callback>(); 55 56 private ProviderDescriptor mDescriptor; 57 private boolean mPendingDescriptorChange; 58 59 /** 60 * Creates a media route provider. 61 * 62 * @param context The context. 63 */ 64 public MediaRouteProvider(Context context) { 65 this(context, null); 66 } 67 68 MediaRouteProvider(Context context, ProviderMetadata metadata) { 69 if (context == null) { 70 throw new IllegalArgumentException("context must not be null"); 71 } 72 73 mContext = context; 74 if (metadata == null) { 75 mMetadata = new ProviderMetadata(context.getPackageName()); 76 } else { 77 mMetadata = metadata; 78 } 79 } 80 81 /** 82 * Gets the context of the media route provider. 83 */ 84 public final Context getContext() { 85 return mContext; 86 } 87 88 final ProviderMetadata getMetadata() { 89 return mMetadata; 90 } 91 92 /** 93 * Gets the provider's descriptor. 94 * <p> 95 * The descriptor describes the state of the media route provider and 96 * the routes that it publishes. Watch for changes to the descriptor 97 * by registering a {@link Callback callback} with {@link #addCallback}. 98 * </p> 99 * 100 * @return The media route provider descriptor, or null if none. This object 101 * and all of its contents should be treated as if it were immutable so that it is 102 * safe for clients to cache it. 103 */ 104 public final ProviderDescriptor getDescriptor() { 105 return mDescriptor; 106 } 107 108 /** 109 * Sets the provider's descriptor. 110 * <p> 111 * Asynchronously notifies all registered {@link Callback callbacks} about the change. 112 * </p> 113 * 114 * @param descriptor The updated route provider descriptor, or null if none. 115 * This object and all of its contents should be treated as if it were immutable 116 * so that it is safe for clients to cache it. 117 */ 118 public final void setDescriptor(ProviderDescriptor descriptor) { 119 MediaRouter.checkCallingThread(); 120 121 if (mDescriptor != descriptor) { 122 mDescriptor = descriptor; 123 if (!mPendingDescriptorChange) { 124 mPendingDescriptorChange = true; 125 mHandler.sendEmptyMessage(MSG_DELIVER_DESCRIPTOR_CHANGED); 126 } 127 } 128 } 129 130 private void deliverDescriptorChanged() { 131 mPendingDescriptorChange = false; 132 133 if (!mCallbacks.isEmpty()) { 134 final ProviderDescriptor currentDescriptor = mDescriptor; 135 for (Callback callback : mCallbacks) { 136 callback.onDescriptorChanged(this, currentDescriptor); 137 } 138 } 139 } 140 141 /** 142 * Adds a callback to be invoked on the main thread when information about a route 143 * provider and its routes changes. 144 * 145 * @param callback The callback to add. 146 */ 147 public final void addCallback(Callback callback) { 148 if (callback == null) { 149 throw new IllegalArgumentException("callback"); 150 } 151 152 if (!mCallbacks.contains(callback)) { 153 mCallbacks.add(callback); 154 } 155 } 156 157 /** 158 * Removes a callback. 159 * 160 * @param callback The callback to remove. 161 */ 162 public final void removeCallback(Callback callback) { 163 if (callback == null) { 164 throw new IllegalArgumentException("callback"); 165 } 166 167 mCallbacks.remove(callback); 168 } 169 170 /** 171 * Called by the media router to obtain a route controller for a particular route. 172 * <p> 173 * The media router will invoke the {@link RouteController#release} method of the route 174 * controller when it is no longer needed to allow it to free its resources. 175 * </p> 176 * 177 * @param routeId The unique id of the route. 178 * @return The route controller. Returns null if there is no such route or if the route 179 * cannot be controlled using the route controller interface. 180 */ 181 public RouteController onCreateRouteController(String routeId) { 182 return null; 183 } 184 185 /** 186 * Describes immutable properties of the route provider itself. 187 */ 188 static final class ProviderMetadata { 189 private final String mPackageName; 190 191 public ProviderMetadata(String packageName) { 192 if (TextUtils.isEmpty(packageName)) { 193 throw new IllegalArgumentException("packageName must not be null or empty"); 194 } 195 mPackageName = packageName; 196 } 197 198 public String getPackageName() { 199 return mPackageName; 200 } 201 } 202 203 /** 204 * Describes the state of a media route provider and the routes that it publishes. 205 */ 206 public static final class ProviderDescriptor { 207 private static final String KEY_ROUTES = "routes"; 208 209 private final Bundle mBundle; 210 private RouteDescriptor[] mRoutes; 211 212 /** 213 * Creates a route provider descriptor. 214 */ 215 public ProviderDescriptor() { 216 mBundle = new Bundle(); 217 } 218 219 /** 220 * Creates a copy of another route provider descriptor. 221 */ 222 public ProviderDescriptor(ProviderDescriptor other) { 223 mBundle = new Bundle(other.mBundle); 224 } 225 226 ProviderDescriptor(Bundle bundle) { 227 mBundle = bundle; 228 } 229 230 /** 231 * Gets the list of all routes that this provider has published. 232 */ 233 public RouteDescriptor[] getRoutes() { 234 if (mRoutes == null) { 235 mRoutes = RouteDescriptor.fromParcelableArray( 236 mBundle.getParcelableArray(KEY_ROUTES)); 237 } 238 return mRoutes; 239 } 240 241 /** 242 * Sets the list of all routes that this provider has published. 243 */ 244 public void setRoutes(RouteDescriptor[] routes) { 245 if (routes == null) { 246 throw new IllegalArgumentException("routes must not be null"); 247 } 248 mRoutes = routes; 249 mBundle.putParcelableArray(KEY_ROUTES, RouteDescriptor.toParcelableArray(routes)); 250 } 251 252 /** 253 * Returns true if the route provider descriptor and all of the routes that 254 * it contains have all of the required fields. 255 * <p> 256 * This verification is deep. If the provider descriptor is known to be 257 * valid then it is not necessary to call {@link #isValid} on each of its routes. 258 * </p> 259 */ 260 public boolean isValid() { 261 for (RouteDescriptor route : getRoutes()) { 262 if (route == null || !route.isValid()) { 263 return false; 264 } 265 } 266 return true; 267 } 268 269 @Override 270 public String toString() { 271 return "RouteProviderDescriptor{" + mBundle.toString() + "}"; 272 } 273 274 Bundle asBundle() { 275 return mBundle; 276 } 277 278 static ProviderDescriptor fromBundle(Bundle bundle) { 279 return bundle != null ? new ProviderDescriptor(bundle) : null; 280 } 281 } 282 283 /** 284 * Describes the properties of a route. 285 * <p> 286 * Each route is uniquely identified by an opaque id string. This token 287 * may take any form as long as it is unique within the media route provider. 288 * </p> 289 */ 290 public static final class RouteDescriptor { 291 static final RouteDescriptor[] EMPTY_ROUTE_ARRAY = new RouteDescriptor[0]; 292 static final IntentFilter[] EMTPY_FILTER_ARRAY = new IntentFilter[0]; 293 294 private static final String KEY_ID = "id"; 295 private static final String KEY_NAME = "name"; 296 private static final String KEY_STATUS = "status"; 297 private static final String KEY_ICON_RESOURCE = "iconId"; 298 private static final String KEY_ENABLED = "enabled"; 299 private static final String KEY_CONTROL_FILTERS = "controlFilters"; 300 private static final String KEY_PLAYBACK_TYPE = "playbackType"; 301 private static final String KEY_PLAYBACK_STREAM = "playbackStream"; 302 private static final String KEY_VOLUME = "volume"; 303 private static final String KEY_VOLUME_MAX = "volumeMax"; 304 private static final String KEY_VOLUME_HANDLING = "volumeHandling"; 305 private static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId"; 306 private static final String KEY_EXTRAS = "extras"; 307 308 private final Bundle mBundle; 309 private IntentFilter[] mControlFilters; 310 private Drawable mIconDrawable; 311 312 /** 313 * Creates a route descriptor. 314 * 315 * @param id The unique id of the route. 316 * @param name The user-friendly name of the route. 317 */ 318 public RouteDescriptor(String id, String name) { 319 mBundle = new Bundle(); 320 setId(id); 321 setName(name); 322 } 323 324 /** 325 * Creates a copy of another route descriptor. 326 */ 327 public RouteDescriptor(RouteDescriptor other) { 328 mBundle = new Bundle(other.mBundle); 329 } 330 331 RouteDescriptor(Bundle bundle) { 332 mBundle = bundle; 333 } 334 335 /** 336 * Gets the unique id of the route. 337 */ 338 public String getId() { 339 return mBundle.getString(KEY_ID); 340 } 341 342 /** 343 * Sets the unique id of the route. 344 */ 345 public void setId(String id) { 346 mBundle.putString(KEY_ID, id); 347 } 348 349 /** 350 * Gets the user-friendly name of the route. 351 */ 352 public String getName() { 353 return mBundle.getString(KEY_NAME); 354 } 355 356 /** 357 * Sets the user-friendly name of the route. 358 */ 359 public void setName(String name) { 360 mBundle.putString(KEY_NAME, name); 361 } 362 363 /** 364 * Gets the user-friendly status of the route. 365 */ 366 public String getStatus() { 367 return mBundle.getString(KEY_STATUS); 368 } 369 370 /** 371 * Sets the user-friendly status of the route. 372 */ 373 public void setStatus(String status) { 374 mBundle.putString(KEY_STATUS, status); 375 } 376 377 /** 378 * Gets a drawable to display as the route's icon. 379 * <p> 380 * Because drawables cannot be transferred to other processes, this method may 381 * only be used by media route providers that reside in the same process 382 * as the application. When implementing a media route provider service, use 383 * {@link #getIconResource} instead. 384 * </p> 385 */ 386 public Drawable getIconDrawable() { 387 return mIconDrawable; 388 } 389 390 /** 391 * Sets a drawable to display as the route's icon. 392 * <p> 393 * Because drawables cannot be transferred to other processes, this method may 394 * only be used by media route providers that reside in the same process 395 * as the application. When implementing a media route provider service, use 396 * {@link #setIconResource} instead. 397 * </p> 398 */ 399 public void setIconDrawable(Drawable drawable) { 400 mIconDrawable = drawable; 401 } 402 403 /** 404 * Gets the id of a drawable resource to display as the route's icon. 405 * <p> 406 * The specified drawable resource id will be loaded from the media route 407 * provider's package. 408 * </p> 409 */ 410 public int getIconResource() { 411 return mBundle.getInt(KEY_ICON_RESOURCE); 412 } 413 414 /** 415 * Sets the id of a drawable resource to display as the route's icon. 416 * <p> 417 * The specified drawable resource id will be loaded from the media route 418 * provider's package. 419 * </p> 420 */ 421 public void setIconResource(int id) { 422 mBundle.putInt(KEY_ICON_RESOURCE, id); 423 } 424 425 /** 426 * Gets whether the route is enabled. 427 */ 428 public boolean isEnabled() { 429 return mBundle.getBoolean(KEY_ENABLED, true); 430 } 431 432 /** 433 * Sets whether the route is enabled. 434 */ 435 public void setEnabled(boolean enabled) { 436 mBundle.putBoolean(KEY_ENABLED, enabled); 437 } 438 439 /** 440 * Gets the route's {@link MediaControlIntent media control intent} filters. 441 */ 442 public IntentFilter[] getControlFilters() { 443 if (mControlFilters == null) { 444 Parcelable[] filters = mBundle.getParcelableArray(KEY_CONTROL_FILTERS); 445 if (filters instanceof IntentFilter[]) { 446 mControlFilters = (IntentFilter[])filters; 447 } else if (filters != null && filters.length > 0) { 448 mControlFilters = new IntentFilter[filters.length]; 449 System.arraycopy(filters, 0, mControlFilters, 0, filters.length); 450 } else { 451 mControlFilters = EMTPY_FILTER_ARRAY; 452 } 453 } 454 return mControlFilters; 455 } 456 457 /** 458 * Sets the route's {@link MediaControlIntent media control intent} filters. 459 */ 460 public void setControlFilters(IntentFilter[] controlFilters) { 461 if (controlFilters == null) { 462 throw new IllegalArgumentException("controlFilters must not be null"); 463 } 464 mControlFilters = controlFilters; 465 mBundle.putParcelableArray(KEY_CONTROL_FILTERS, controlFilters); 466 } 467 468 /** 469 * Gets the route's playback type. 470 */ 471 public int getPlaybackType() { 472 return mBundle.getInt(KEY_PLAYBACK_TYPE, MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE); 473 } 474 475 /** 476 * Sets the route's playback type. 477 */ 478 public void setPlaybackType(int playbackType) { 479 mBundle.putInt(KEY_PLAYBACK_TYPE, playbackType); 480 } 481 482 /** 483 * Gets the route's playback stream. 484 */ 485 public int getPlaybackStream() { 486 return mBundle.getInt(KEY_PLAYBACK_STREAM, -1); 487 } 488 489 /** 490 * Sets the route's playback stream. 491 */ 492 public void setPlaybackStream(int playbackStream) { 493 mBundle.putInt(KEY_PLAYBACK_STREAM, playbackStream); 494 } 495 496 /** 497 * Gets the route's current volume, or 0 if unknown. 498 */ 499 public int getVolume() { 500 return mBundle.getInt(KEY_VOLUME); 501 } 502 503 /** 504 * Sets the route's current volume, or 0 if unknown. 505 */ 506 public void setVolume(int volume) { 507 mBundle.putInt(KEY_VOLUME, volume); 508 } 509 510 /** 511 * Gets the route's maximum volume, or 0 if unknown. 512 */ 513 public int getVolumeMax() { 514 return mBundle.getInt(KEY_VOLUME_MAX); 515 } 516 517 /** 518 * Sets the route's maximum volume, or 0 if unknown. 519 */ 520 public void setVolumeMax(int volumeMax) { 521 mBundle.putInt(KEY_VOLUME_MAX, volumeMax); 522 } 523 524 /** 525 * Gets the route's volume handling. 526 */ 527 public int getVolumeHandling() { 528 return mBundle.getInt(KEY_VOLUME_HANDLING, 529 MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED); 530 } 531 532 /** 533 * Sets the route's volume handling. 534 */ 535 public void setVolumeHandling(int volumeHandling) { 536 mBundle.putInt(KEY_VOLUME_HANDLING, volumeHandling); 537 } 538 539 /** 540 * Gets the route's presentation display id, or -1 if none. 541 */ 542 public int getPresentationDisplayId() { 543 return mBundle.getInt(KEY_PRESENTATION_DISPLAY_ID, -1); 544 } 545 546 /** 547 * Sets the route's presentation display id, or -1 if none. 548 */ 549 public void setPresentationDisplayId(int presentationDisplayId) { 550 mBundle.putInt(KEY_PRESENTATION_DISPLAY_ID, presentationDisplayId); 551 } 552 553 /** 554 * Gets a bundle of extras for this route descriptor. 555 * The extras will be ignored by the media router but they may be used 556 * by applications. 557 */ 558 public Bundle getExtras() { 559 return mBundle.getBundle(KEY_EXTRAS); 560 } 561 562 /** 563 * Sets a bundle of extras for this route descriptor. 564 * The extras will be ignored by the media router but they may be used 565 * by applications. 566 */ 567 public void setExtras(Bundle extras) { 568 mBundle.putBundle(KEY_EXTRAS, extras); 569 } 570 571 /** 572 * Returns true if the route descriptor has all of the required fields. 573 */ 574 public boolean isValid() { 575 if (TextUtils.isEmpty(getId()) 576 || TextUtils.isEmpty(getName())) { 577 return false; 578 } 579 for (IntentFilter filter : getControlFilters()) { 580 if (filter == null) { 581 return false; 582 } 583 } 584 return true; 585 } 586 587 @Override 588 public String toString() { 589 return "RouteDescriptor{" + mBundle.toString() + "}"; 590 } 591 592 Bundle asBundle() { 593 return mBundle; 594 } 595 596 static Parcelable[] toParcelableArray(RouteDescriptor[] descriptors) { 597 if (descriptors != null && descriptors.length > 0) { 598 Parcelable[] bundles = new Parcelable[descriptors.length]; 599 for (int i = 0; i < descriptors.length; i++) { 600 bundles[i] = descriptors[i].asBundle(); 601 } 602 return bundles; 603 } 604 return null; 605 } 606 607 static RouteDescriptor[] fromParcelableArray(Parcelable[] bundles) { 608 if (bundles != null && bundles.length > 0) { 609 RouteDescriptor[] descriptors = new RouteDescriptor[bundles.length]; 610 for (int i = 0; i < bundles.length; i++) { 611 descriptors[i] = new RouteDescriptor((Bundle)bundles[i]); 612 } 613 return descriptors; 614 } 615 return EMPTY_ROUTE_ARRAY; 616 } 617 } 618 619 /** 620 * Provides control over a particular route. 621 * <p> 622 * The media router obtains a route controller for a route whenever it needs 623 * to control a route. When a route is selected, the media router invokes 624 * the {@link #select} method of its route controller. While selected, 625 * the media router may call other methods of the route controller to 626 * request that it perform certain actions to the route. When a route is 627 * unselected, the media router invokes the {@link #unselect} method of its 628 * route controller. When the media route no longer needs the route controller 629 * it will invoke the {@link #release} method to allow the route controller 630 * to free its resources. 631 * </p><p> 632 * There may be multiple route controllers simultaneously active for the 633 * same route. Each route controller will be released separately. 634 * </p><p> 635 * All operations on the route controller are asynchronous and 636 * results are communicated via callbacks. 637 * </p> 638 */ 639 public static abstract class RouteController { 640 /** 641 * Releases the route controller, allowing it to free its resources. 642 */ 643 public void release() { 644 } 645 646 /** 647 * Selects the route. 648 */ 649 public void select() { 650 } 651 652 /** 653 * Unselects the route. 654 */ 655 public void unselect() { 656 } 657 658 /** 659 * Requests to set the volume of the route. 660 * 661 * @param volume The new volume value between 0 and {@link RouteDescriptor#getVolumeMax}. 662 */ 663 public void setVolume(int volume) { 664 } 665 666 /** 667 * Requests an incremental volume update for the route. 668 * 669 * @param delta The delta to add to the current volume. 670 */ 671 public void updateVolume(int delta) { 672 } 673 674 /** 675 * Sends a {@link MediaControlIntent media control} request to be performed 676 * asynchronously by the route's destination. 677 * 678 * @param intent A {@link MediaControlIntent media control intent}. 679 * @param callback A {@link ControlRequestCallback} to invoke with the result 680 * of the request, or null if no result is required. 681 * @return True if the controller intends to handle the request and will 682 * invoke the callback when finished. False if the contorller will not 683 * handle the request and will not invoke the callback. 684 * 685 * @see MediaControlIntent 686 */ 687 public boolean sendControlRequest(Intent intent, ControlRequestCallback callback) { 688 return false; 689 } 690 } 691 692 /** 693 * Callback which is invoked when route information becomes available or changes. 694 */ 695 public static abstract class Callback { 696 /** 697 * Called when information about a route provider and its routes changes. 698 * 699 * @param provider The media route provider that changed. 700 * and all of its contents should be treated as if it were immutable so that it is 701 * safe for clients to cache it. 702 */ 703 public void onDescriptorChanged(MediaRouteProvider provider, 704 ProviderDescriptor descriptor) { 705 } 706 } 707 708 private final class ProviderHandler extends Handler { 709 @Override 710 public void handleMessage(Message msg) { 711 switch (msg.what) { 712 case MSG_DELIVER_DESCRIPTOR_CHANGED: 713 deliverDescriptorChanged(); 714 break; 715 } 716 } 717 } 718} 719