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 */ 16package android.support.v7.media; 17 18import android.content.IntentFilter; 19import android.content.IntentSender; 20import android.net.Uri; 21import android.os.Bundle; 22import android.text.TextUtils; 23 24import java.util.ArrayList; 25import java.util.Arrays; 26import java.util.Collection; 27import java.util.Collections; 28import java.util.List; 29 30/** 31 * Describes the properties of a route. 32 * <p> 33 * Each route is uniquely identified by an opaque id string. This token 34 * may take any form as long as it is unique within the media route provider. 35 * </p><p> 36 * This object is immutable once created using a {@link Builder} instance. 37 * </p> 38 */ 39public final class MediaRouteDescriptor { 40 private static final String KEY_ID = "id"; 41 private static final String KEY_GROUP_MEMBER_IDS = "groupMemberIds"; 42 private static final String KEY_NAME = "name"; 43 private static final String KEY_DESCRIPTION = "status"; 44 private static final String KEY_ICON_URI = "iconUri"; 45 private static final String KEY_ENABLED = "enabled"; 46 private static final String KEY_CONNECTING = "connecting"; 47 private static final String KEY_CONNECTION_STATE = "connectionState"; 48 private static final String KEY_CONTROL_FILTERS = "controlFilters"; 49 private static final String KEY_PLAYBACK_TYPE = "playbackType"; 50 private static final String KEY_PLAYBACK_STREAM = "playbackStream"; 51 private static final String KEY_DEVICE_TYPE = "deviceType"; 52 private static final String KEY_VOLUME = "volume"; 53 private static final String KEY_VOLUME_MAX = "volumeMax"; 54 private static final String KEY_VOLUME_HANDLING = "volumeHandling"; 55 private static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId"; 56 private static final String KEY_EXTRAS = "extras"; 57 private static final String KEY_CAN_DISCONNECT = "canDisconnect"; 58 private static final String KEY_SETTINGS_INTENT = "settingsIntent"; 59 60 private final Bundle mBundle; 61 private List<IntentFilter> mControlFilters; 62 63 private MediaRouteDescriptor(Bundle bundle, List<IntentFilter> controlFilters) { 64 mBundle = bundle; 65 mControlFilters = controlFilters; 66 } 67 68 /** 69 * Gets the unique id of the route. 70 * <p> 71 * The route id associated with a route descriptor functions as a stable 72 * identifier for the route and must be unique among all routes offered 73 * by the provider. 74 * </p> 75 */ 76 public String getId() { 77 return mBundle.getString(KEY_ID); 78 } 79 80 /** 81 * Gets the group member ids of the route. 82 * <p> 83 * A route descriptor that has one or more group member route ids 84 * represents a route group. A member route may belong to another group. 85 * </p> 86 * @hide 87 */ 88 public List<String> getGroupMemberIds() { 89 return mBundle.getStringArrayList(KEY_GROUP_MEMBER_IDS); 90 } 91 92 /** 93 * Gets the user-visible name of the route. 94 * <p> 95 * The route name identifies the destination represented by the route. 96 * It may be a user-supplied name, an alias, or device serial number. 97 * </p> 98 */ 99 public String getName() { 100 return mBundle.getString(KEY_NAME); 101 } 102 103 /** 104 * Gets the user-visible description of the route. 105 * <p> 106 * The route description describes the kind of destination represented by the route. 107 * It may be a user-supplied string, a model number or brand of device. 108 * </p> 109 */ 110 public String getDescription() { 111 return mBundle.getString(KEY_DESCRIPTION); 112 } 113 114 /** 115 * Gets the URI of the icon representing this route. 116 * <p> 117 * This icon will be used in picker UIs if available. 118 * </p> 119 */ 120 public Uri getIconUri() { 121 String iconUri = mBundle.getString(KEY_ICON_URI); 122 return iconUri == null ? null : Uri.parse(iconUri); 123 } 124 125 /** 126 * Gets whether the route is enabled. 127 */ 128 public boolean isEnabled() { 129 return mBundle.getBoolean(KEY_ENABLED, true); 130 } 131 132 /** 133 * Gets whether the route is connecting. 134 * @deprecated Use {@link #getConnectionState} instead 135 */ 136 @Deprecated 137 public boolean isConnecting() { 138 return mBundle.getBoolean(KEY_CONNECTING, false); 139 } 140 141 /** 142 * Gets the connection state of the route. 143 * 144 * @return The connection state of this route: 145 * {@link MediaRouter.RouteInfo#CONNECTION_STATE_DISCONNECTED}, 146 * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTING}, or 147 * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTED}. 148 */ 149 public int getConnectionState() { 150 return mBundle.getInt(KEY_CONNECTION_STATE, 151 MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED); 152 } 153 154 /** 155 * Gets whether the route can be disconnected without stopping playback. 156 * <p> 157 * The route can normally be disconnected without stopping playback when 158 * the destination device on the route is connected to two or more source 159 * devices. The route provider should update the route immediately when the 160 * number of connected devices changes. 161 * </p><p> 162 * To specify that the route should disconnect without stopping use 163 * {@link MediaRouter#unselect(int)} with 164 * {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}. 165 * </p> 166 */ 167 public boolean canDisconnectAndKeepPlaying() { 168 return mBundle.getBoolean(KEY_CAN_DISCONNECT, false); 169 } 170 171 /** 172 * Gets an {@link IntentSender} for starting a settings activity for this 173 * route. The activity may have specific route settings or general settings 174 * for the connected device or route provider. 175 * 176 * @return An {@link IntentSender} to start a settings activity. 177 */ 178 public IntentSender getSettingsActivity() { 179 return mBundle.getParcelable(KEY_SETTINGS_INTENT); 180 } 181 182 /** 183 * Gets the route's {@link MediaControlIntent media control intent} filters. 184 */ 185 public List<IntentFilter> getControlFilters() { 186 ensureControlFilters(); 187 return mControlFilters; 188 } 189 190 private void ensureControlFilters() { 191 if (mControlFilters == null) { 192 mControlFilters = mBundle.<IntentFilter>getParcelableArrayList(KEY_CONTROL_FILTERS); 193 if (mControlFilters == null) { 194 mControlFilters = Collections.<IntentFilter>emptyList(); 195 } 196 } 197 } 198 199 /** 200 * Gets the type of playback associated with this route. 201 * 202 * @return The type of playback associated with this route: 203 * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_LOCAL} or 204 * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_REMOTE}. 205 */ 206 public int getPlaybackType() { 207 return mBundle.getInt(KEY_PLAYBACK_TYPE, MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE); 208 } 209 210 /** 211 * Gets the route's playback stream. 212 */ 213 public int getPlaybackStream() { 214 return mBundle.getInt(KEY_PLAYBACK_STREAM, -1); 215 } 216 217 /** 218 * Gets the type of the receiver device associated with this route. 219 * 220 * @return The type of the receiver device associated with this route: 221 * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or 222 * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}. 223 */ 224 public int getDeviceType() { 225 return mBundle.getInt(KEY_DEVICE_TYPE); 226 } 227 228 /** 229 * Gets the route's current volume, or 0 if unknown. 230 */ 231 public int getVolume() { 232 return mBundle.getInt(KEY_VOLUME); 233 } 234 235 /** 236 * Gets the route's maximum volume, or 0 if unknown. 237 */ 238 public int getVolumeMax() { 239 return mBundle.getInt(KEY_VOLUME_MAX); 240 } 241 242 /** 243 * Gets information about how volume is handled on the route. 244 * 245 * @return How volume is handled on the route: 246 * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_FIXED} or 247 * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_VARIABLE}. 248 */ 249 public int getVolumeHandling() { 250 return mBundle.getInt(KEY_VOLUME_HANDLING, 251 MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED); 252 } 253 254 /** 255 * Gets the route's presentation display id, or -1 if none. 256 */ 257 public int getPresentationDisplayId() { 258 return mBundle.getInt( 259 KEY_PRESENTATION_DISPLAY_ID, MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE); 260 } 261 262 /** 263 * Gets a bundle of extras for this route descriptor. 264 * The extras will be ignored by the media router but they may be used 265 * by applications. 266 */ 267 public Bundle getExtras() { 268 return mBundle.getBundle(KEY_EXTRAS); 269 } 270 271 /** 272 * Returns true if the route descriptor has all of the required fields. 273 */ 274 public boolean isValid() { 275 ensureControlFilters(); 276 if (TextUtils.isEmpty(getId()) 277 || TextUtils.isEmpty(getName()) 278 || mControlFilters.contains(null)) { 279 return false; 280 } 281 return true; 282 } 283 284 @Override 285 public String toString() { 286 StringBuilder result = new StringBuilder(); 287 result.append("MediaRouteDescriptor{ "); 288 result.append("id=").append(getId()); 289 result.append(", groupMemberIds=").append(getGroupMemberIds()); 290 result.append(", name=").append(getName()); 291 result.append(", description=").append(getDescription()); 292 result.append(", iconUri=").append(getIconUri()); 293 result.append(", isEnabled=").append(isEnabled()); 294 result.append(", isConnecting=").append(isConnecting()); 295 result.append(", connectionState=").append(getConnectionState()); 296 result.append(", controlFilters=").append(Arrays.toString(getControlFilters().toArray())); 297 result.append(", playbackType=").append(getPlaybackType()); 298 result.append(", playbackStream=").append(getPlaybackStream()); 299 result.append(", deviceType=").append(getDeviceType()); 300 result.append(", volume=").append(getVolume()); 301 result.append(", volumeMax=").append(getVolumeMax()); 302 result.append(", volumeHandling=").append(getVolumeHandling()); 303 result.append(", presentationDisplayId=").append(getPresentationDisplayId()); 304 result.append(", extras=").append(getExtras()); 305 result.append(", isValid=").append(isValid()); 306 result.append(" }"); 307 return result.toString(); 308 } 309 310 /** 311 * Converts this object to a bundle for serialization. 312 * 313 * @return The contents of the object represented as a bundle. 314 */ 315 public Bundle asBundle() { 316 return mBundle; 317 } 318 319 /** 320 * Creates an instance from a bundle. 321 * 322 * @param bundle The bundle, or null if none. 323 * @return The new instance, or null if the bundle was null. 324 */ 325 public static MediaRouteDescriptor fromBundle(Bundle bundle) { 326 return bundle != null ? new MediaRouteDescriptor(bundle, null) : null; 327 } 328 329 /** 330 * Builder for {@link MediaRouteDescriptor media route descriptors}. 331 */ 332 public static final class Builder { 333 private final Bundle mBundle; 334 private ArrayList<String> mGroupMemberIds; 335 private ArrayList<IntentFilter> mControlFilters; 336 337 /** 338 * Creates a media route descriptor builder. 339 * 340 * @param id The unique id of the route. 341 * @param name The user-visible name of the route. 342 */ 343 public Builder(String id, String name) { 344 mBundle = new Bundle(); 345 setId(id); 346 setName(name); 347 } 348 349 /** 350 * Creates a media route descriptor builder whose initial contents are 351 * copied from an existing descriptor. 352 */ 353 public Builder(MediaRouteDescriptor descriptor) { 354 if (descriptor == null) { 355 throw new IllegalArgumentException("descriptor must not be null"); 356 } 357 358 mBundle = new Bundle(descriptor.mBundle); 359 360 descriptor.ensureControlFilters(); 361 if (!descriptor.mControlFilters.isEmpty()) { 362 mControlFilters = new ArrayList<IntentFilter>(descriptor.mControlFilters); 363 } 364 } 365 366 /** 367 * Sets the unique id of the route. 368 * <p> 369 * The route id associated with a route descriptor functions as a stable 370 * identifier for the route and must be unique among all routes offered 371 * by the provider. 372 * </p> 373 */ 374 public Builder setId(String id) { 375 mBundle.putString(KEY_ID, id); 376 return this; 377 } 378 379 /** 380 * Adds a group member id of the route. 381 * <p> 382 * A route descriptor that has one or more group member route ids 383 * represents a route group. A member route may belong to another group. 384 * </p> 385 * @hide 386 */ 387 public Builder addGroupMemberId(String groupMemberId) { 388 if (TextUtils.isEmpty(groupMemberId)) { 389 throw new IllegalArgumentException("groupMemberId must not be empty"); 390 } 391 392 if (mGroupMemberIds == null) { 393 mGroupMemberIds = new ArrayList<>(); 394 } 395 if (!mGroupMemberIds.contains(groupMemberId)) { 396 mGroupMemberIds.add(groupMemberId); 397 } 398 return this; 399 } 400 401 /** 402 * Adds a list of group member ids of the route. 403 * <p> 404 * A route descriptor that has one or more group member route ids 405 * represents a route group. A member route may belong to another group. 406 * </p> 407 * @hide 408 */ 409 public Builder addGroupMemberIds(Collection<String> groupMemberIds) { 410 if (groupMemberIds == null) { 411 throw new IllegalArgumentException("groupMemberIds must not be null"); 412 } 413 414 if (!groupMemberIds.isEmpty()) { 415 for (String groupMemberId : groupMemberIds) { 416 addGroupMemberId(groupMemberId); 417 } 418 } 419 return this; 420 } 421 422 /** 423 * Sets the user-visible name of the route. 424 * <p> 425 * The route name identifies the destination represented by the route. 426 * It may be a user-supplied name, an alias, or device serial number. 427 * </p> 428 */ 429 public Builder setName(String name) { 430 mBundle.putString(KEY_NAME, name); 431 return this; 432 } 433 434 /** 435 * Sets the user-visible description of the route. 436 * <p> 437 * The route description describes the kind of destination represented by the route. 438 * It may be a user-supplied string, a model number or brand of device. 439 * </p> 440 */ 441 public Builder setDescription(String description) { 442 mBundle.putString(KEY_DESCRIPTION, description); 443 return this; 444 } 445 446 /** 447 * Sets the URI of the icon representing this route. 448 * <p> 449 * This icon will be used in picker UIs if available. 450 * </p><p> 451 * The URI must be one of the following formats: 452 * <ul> 453 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 454 * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) 455 * </li> 456 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 457 * </ul> 458 * </p> 459 */ 460 public Builder setIconUri(Uri iconUri) { 461 if (iconUri == null) { 462 throw new IllegalArgumentException("iconUri must not be null"); 463 } 464 mBundle.putString(KEY_ICON_URI, iconUri.toString()); 465 return this; 466 } 467 468 /** 469 * Sets whether the route is enabled. 470 * <p> 471 * Disabled routes represent routes that a route provider knows about, such as paired 472 * Wifi Display receivers, but that are not currently available for use. 473 * </p> 474 */ 475 public Builder setEnabled(boolean enabled) { 476 mBundle.putBoolean(KEY_ENABLED, enabled); 477 return this; 478 } 479 480 /** 481 * Sets whether the route is in the process of connecting and is not yet 482 * ready for use. 483 * @deprecated Use {@link #setConnectionState} instead. 484 */ 485 @Deprecated 486 public Builder setConnecting(boolean connecting) { 487 mBundle.putBoolean(KEY_CONNECTING, connecting); 488 return this; 489 } 490 491 /** 492 * Sets the route's connection state. 493 * 494 * @param connectionState The connection state of the route: 495 * {@link MediaRouter.RouteInfo#CONNECTION_STATE_DISCONNECTED}, 496 * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTING}, or 497 * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTED}. 498 */ 499 public Builder setConnectionState(int connectionState) { 500 mBundle.putInt(KEY_CONNECTION_STATE, connectionState); 501 return this; 502 } 503 504 /** 505 * Sets whether the route can be disconnected without stopping playback. 506 */ 507 public Builder setCanDisconnect(boolean canDisconnect) { 508 mBundle.putBoolean(KEY_CAN_DISCONNECT, canDisconnect); 509 return this; 510 } 511 512 /** 513 * Sets an intent sender for launching the settings activity for this 514 * route. 515 */ 516 public Builder setSettingsActivity(IntentSender is) { 517 mBundle.putParcelable(KEY_SETTINGS_INTENT, is); 518 return this; 519 } 520 521 /** 522 * Adds a {@link MediaControlIntent media control intent} filter for the route. 523 */ 524 public Builder addControlFilter(IntentFilter filter) { 525 if (filter == null) { 526 throw new IllegalArgumentException("filter must not be null"); 527 } 528 529 if (mControlFilters == null) { 530 mControlFilters = new ArrayList<IntentFilter>(); 531 } 532 if (!mControlFilters.contains(filter)) { 533 mControlFilters.add(filter); 534 } 535 return this; 536 } 537 538 /** 539 * Adds a list of {@link MediaControlIntent media control intent} filters for the route. 540 */ 541 public Builder addControlFilters(Collection<IntentFilter> filters) { 542 if (filters == null) { 543 throw new IllegalArgumentException("filters must not be null"); 544 } 545 546 if (!filters.isEmpty()) { 547 for (IntentFilter filter : filters) { 548 addControlFilter(filter); 549 } 550 } 551 return this; 552 } 553 554 /** 555 * Sets the route's playback type. 556 * 557 * @param playbackType The playback type of the route: 558 * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_LOCAL} or 559 * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_REMOTE}. 560 */ 561 public Builder setPlaybackType(int playbackType) { 562 mBundle.putInt(KEY_PLAYBACK_TYPE, playbackType); 563 return this; 564 } 565 566 /** 567 * Sets the route's playback stream. 568 */ 569 public Builder setPlaybackStream(int playbackStream) { 570 mBundle.putInt(KEY_PLAYBACK_STREAM, playbackStream); 571 return this; 572 } 573 574 /** 575 * Sets the route's receiver device type. 576 * 577 * @param deviceType The receive device type of the route: 578 * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or 579 * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}. 580 */ 581 public Builder setDeviceType(int deviceType) { 582 mBundle.putInt(KEY_DEVICE_TYPE, deviceType); 583 return this; 584 } 585 586 /** 587 * Sets the route's current volume, or 0 if unknown. 588 */ 589 public Builder setVolume(int volume) { 590 mBundle.putInt(KEY_VOLUME, volume); 591 return this; 592 } 593 594 /** 595 * Sets the route's maximum volume, or 0 if unknown. 596 */ 597 public Builder setVolumeMax(int volumeMax) { 598 mBundle.putInt(KEY_VOLUME_MAX, volumeMax); 599 return this; 600 } 601 602 /** 603 * Sets the route's volume handling. 604 * 605 * @param volumeHandling how volume is handled on the route: 606 * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_FIXED} or 607 * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_VARIABLE}. 608 */ 609 public Builder setVolumeHandling(int volumeHandling) { 610 mBundle.putInt(KEY_VOLUME_HANDLING, volumeHandling); 611 return this; 612 } 613 614 /** 615 * Sets the route's presentation display id, or -1 if none. 616 */ 617 public Builder setPresentationDisplayId(int presentationDisplayId) { 618 mBundle.putInt(KEY_PRESENTATION_DISPLAY_ID, presentationDisplayId); 619 return this; 620 } 621 622 /** 623 * Sets a bundle of extras for this route descriptor. 624 * The extras will be ignored by the media router but they may be used 625 * by applications. 626 */ 627 public Builder setExtras(Bundle extras) { 628 mBundle.putBundle(KEY_EXTRAS, extras); 629 return this; 630 } 631 632 /** 633 * Builds the {@link MediaRouteDescriptor media route descriptor}. 634 */ 635 public MediaRouteDescriptor build() { 636 if (mControlFilters != null) { 637 mBundle.putParcelableArrayList(KEY_CONTROL_FILTERS, mControlFilters); 638 } 639 if (mGroupMemberIds != null) { 640 mBundle.putStringArrayList(KEY_GROUP_MEMBER_IDS, mGroupMemberIds); 641 } 642 return new MediaRouteDescriptor(mBundle, mControlFilters); 643 } 644 } 645}