1 /* 2 * Copyright 2019 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.media; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Bundle; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Objects; 31 32 /** 33 * Describes a routing session which is created when a media route is selected. 34 */ 35 public final class RoutingSessionInfo implements Parcelable { 36 @NonNull 37 public static final Creator<RoutingSessionInfo> CREATOR = 38 new Creator<RoutingSessionInfo>() { 39 @Override 40 public RoutingSessionInfo createFromParcel(Parcel in) { 41 return new RoutingSessionInfo(in); 42 } 43 @Override 44 public RoutingSessionInfo[] newArray(int size) { 45 return new RoutingSessionInfo[size]; 46 } 47 }; 48 49 private static final String TAG = "RoutingSessionInfo"; 50 51 final String mId; 52 final CharSequence mName; 53 final String mOwnerPackageName; 54 final String mClientPackageName; 55 @Nullable 56 final String mProviderId; 57 final List<String> mSelectedRoutes; 58 final List<String> mSelectableRoutes; 59 final List<String> mDeselectableRoutes; 60 final List<String> mTransferableRoutes; 61 62 final int mVolumeHandling; 63 final int mVolumeMax; 64 final int mVolume; 65 66 @Nullable 67 final Bundle mControlHints; 68 final boolean mIsSystemSession; 69 RoutingSessionInfo(@onNull Builder builder)70 RoutingSessionInfo(@NonNull Builder builder) { 71 Objects.requireNonNull(builder, "builder must not be null."); 72 73 mId = builder.mId; 74 mName = builder.mName; 75 mOwnerPackageName = builder.mOwnerPackageName; 76 mClientPackageName = builder.mClientPackageName; 77 mProviderId = builder.mProviderId; 78 79 mSelectedRoutes = Collections.unmodifiableList( 80 convertToUniqueRouteIds(builder.mSelectedRoutes)); 81 mSelectableRoutes = Collections.unmodifiableList( 82 convertToUniqueRouteIds(builder.mSelectableRoutes)); 83 mDeselectableRoutes = Collections.unmodifiableList( 84 convertToUniqueRouteIds(builder.mDeselectableRoutes)); 85 mTransferableRoutes = Collections.unmodifiableList( 86 convertToUniqueRouteIds(builder.mTransferableRoutes)); 87 88 mVolumeHandling = builder.mVolumeHandling; 89 mVolumeMax = builder.mVolumeMax; 90 mVolume = builder.mVolume; 91 92 mControlHints = builder.mControlHints; 93 mIsSystemSession = builder.mIsSystemSession; 94 } 95 RoutingSessionInfo(@onNull Parcel src)96 RoutingSessionInfo(@NonNull Parcel src) { 97 Objects.requireNonNull(src, "src must not be null."); 98 99 mId = ensureString(src.readString()); 100 mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); 101 mOwnerPackageName = src.readString(); 102 mClientPackageName = ensureString(src.readString()); 103 mProviderId = src.readString(); 104 105 mSelectedRoutes = ensureList(src.createStringArrayList()); 106 mSelectableRoutes = ensureList(src.createStringArrayList()); 107 mDeselectableRoutes = ensureList(src.createStringArrayList()); 108 mTransferableRoutes = ensureList(src.createStringArrayList()); 109 110 mVolumeHandling = src.readInt(); 111 mVolumeMax = src.readInt(); 112 mVolume = src.readInt(); 113 114 mControlHints = src.readBundle(); 115 mIsSystemSession = src.readBoolean(); 116 } 117 ensureString(String str)118 private static String ensureString(String str) { 119 return str != null ? str : ""; 120 } 121 ensureList(List<? extends T> list)122 private static <T> List<T> ensureList(List<? extends T> list) { 123 if (list != null) { 124 return Collections.unmodifiableList(list); 125 } 126 return Collections.emptyList(); 127 } 128 129 /** 130 * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have 131 * unique IDs. 132 * <p> 133 * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method 134 * can be different from what was set in {@link MediaRoute2ProviderService}. 135 * 136 * @see Builder#Builder(String, String) 137 */ 138 @NonNull getId()139 public String getId() { 140 if (mProviderId != null) { 141 return MediaRouter2Utils.toUniqueId(mProviderId, mId); 142 } else { 143 return mId; 144 } 145 } 146 147 /** 148 * Gets the user-visible name of the session. It may be {@code null}. 149 */ 150 @Nullable getName()151 public CharSequence getName() { 152 return mName; 153 } 154 155 /** 156 * Gets the original id set by {@link Builder#Builder(String, String)}. 157 * @hide 158 */ 159 @NonNull getOriginalId()160 public String getOriginalId() { 161 return mId; 162 } 163 164 /** 165 * Gets the package name of the session owner. 166 * @hide 167 */ 168 @Nullable getOwnerPackageName()169 public String getOwnerPackageName() { 170 return mOwnerPackageName; 171 } 172 173 /** 174 * Gets the client package name of the session 175 */ 176 @NonNull getClientPackageName()177 public String getClientPackageName() { 178 return mClientPackageName; 179 } 180 181 /** 182 * Gets the provider id of the session. 183 * @hide 184 */ 185 @Nullable getProviderId()186 public String getProviderId() { 187 return mProviderId; 188 } 189 190 /** 191 * Gets the list of IDs of selected routes for the session. It shouldn't be empty. 192 */ 193 @NonNull getSelectedRoutes()194 public List<String> getSelectedRoutes() { 195 return mSelectedRoutes; 196 } 197 198 /** 199 * Gets the list of IDs of selectable routes for the session. 200 */ 201 @NonNull getSelectableRoutes()202 public List<String> getSelectableRoutes() { 203 return mSelectableRoutes; 204 } 205 206 /** 207 * Gets the list of IDs of deselectable routes for the session. 208 */ 209 @NonNull getDeselectableRoutes()210 public List<String> getDeselectableRoutes() { 211 return mDeselectableRoutes; 212 } 213 214 /** 215 * Gets the list of IDs of transferable routes for the session. 216 */ 217 @NonNull getTransferableRoutes()218 public List<String> getTransferableRoutes() { 219 return mTransferableRoutes; 220 } 221 222 /** 223 * Gets the information about how volume is handled on the session. 224 * 225 * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or 226 * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}. 227 */ 228 @MediaRoute2Info.PlaybackVolume getVolumeHandling()229 public int getVolumeHandling() { 230 return mVolumeHandling; 231 } 232 233 /** 234 * Gets the maximum volume of the session. 235 */ getVolumeMax()236 public int getVolumeMax() { 237 return mVolumeMax; 238 } 239 240 /** 241 * Gets the current volume of the session. 242 * <p> 243 * When it's available, it represents the volume of routing session, which is a group 244 * of selected routes. To get the volume of each route, use {@link MediaRoute2Info#getVolume()}. 245 * </p> 246 * @see MediaRoute2Info#getVolume() 247 */ getVolume()248 public int getVolume() { 249 return mVolume; 250 } 251 252 /** 253 * Gets the control hints 254 */ 255 @Nullable getControlHints()256 public Bundle getControlHints() { 257 return mControlHints; 258 } 259 260 /** 261 * Gets whether this session is in system media route provider. 262 * @hide 263 */ 264 @Nullable isSystemSession()265 public boolean isSystemSession() { 266 return mIsSystemSession; 267 } 268 269 @Override describeContents()270 public int describeContents() { 271 return 0; 272 } 273 274 @Override writeToParcel(@onNull Parcel dest, int flags)275 public void writeToParcel(@NonNull Parcel dest, int flags) { 276 dest.writeString(mId); 277 dest.writeCharSequence(mName); 278 dest.writeString(mOwnerPackageName); 279 dest.writeString(mClientPackageName); 280 dest.writeString(mProviderId); 281 dest.writeStringList(mSelectedRoutes); 282 dest.writeStringList(mSelectableRoutes); 283 dest.writeStringList(mDeselectableRoutes); 284 dest.writeStringList(mTransferableRoutes); 285 dest.writeInt(mVolumeHandling); 286 dest.writeInt(mVolumeMax); 287 dest.writeInt(mVolume); 288 dest.writeBundle(mControlHints); 289 dest.writeBoolean(mIsSystemSession); 290 } 291 292 @Override equals(Object obj)293 public boolean equals(Object obj) { 294 if (this == obj) { 295 return true; 296 } 297 if (!(obj instanceof RoutingSessionInfo)) { 298 return false; 299 } 300 301 RoutingSessionInfo other = (RoutingSessionInfo) obj; 302 return Objects.equals(mId, other.mId) 303 && Objects.equals(mName, other.mName) 304 && Objects.equals(mOwnerPackageName, other.mOwnerPackageName) 305 && Objects.equals(mClientPackageName, other.mClientPackageName) 306 && Objects.equals(mProviderId, other.mProviderId) 307 && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) 308 && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) 309 && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes) 310 && Objects.equals(mTransferableRoutes, other.mTransferableRoutes) 311 && (mVolumeHandling == other.mVolumeHandling) 312 && (mVolumeMax == other.mVolumeMax) 313 && (mVolume == other.mVolume); 314 } 315 316 @Override hashCode()317 public int hashCode() { 318 return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId, 319 mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes, 320 mVolumeMax, mVolumeHandling, mVolume); 321 } 322 323 @Override toString()324 public String toString() { 325 StringBuilder result = new StringBuilder() 326 .append("RoutingSessionInfo{ ") 327 .append("sessionId=").append(getId()) 328 .append(", name=").append(getName()) 329 .append(", clientPackageName=").append(getClientPackageName()) 330 .append(", selectedRoutes={") 331 .append(String.join(",", getSelectedRoutes())) 332 .append("}") 333 .append(", selectableRoutes={") 334 .append(String.join(",", getSelectableRoutes())) 335 .append("}") 336 .append(", deselectableRoutes={") 337 .append(String.join(",", getDeselectableRoutes())) 338 .append("}") 339 .append(", transferableRoutes={") 340 .append(String.join(",", getTransferableRoutes())) 341 .append("}") 342 .append(", volumeHandling=").append(getVolumeHandling()) 343 .append(", volumeMax=").append(getVolumeMax()) 344 .append(", volume=").append(getVolume()) 345 .append(" }"); 346 return result.toString(); 347 } 348 convertToUniqueRouteIds(@onNull List<String> routeIds)349 private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) { 350 if (routeIds == null) { 351 Log.w(TAG, "routeIds is null. Returning an empty list"); 352 return Collections.emptyList(); 353 } 354 355 // mProviderId can be null if not set. Return the original list for this case. 356 if (mProviderId == null) { 357 return routeIds; 358 } 359 360 List<String> result = new ArrayList<>(); 361 for (String routeId : routeIds) { 362 result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId)); 363 } 364 return result; 365 } 366 367 /** 368 * Builder class for {@link RoutingSessionInfo}. 369 */ 370 public static final class Builder { 371 // TODO: Reorder these (important ones first) 372 final String mId; 373 CharSequence mName; 374 String mOwnerPackageName; 375 String mClientPackageName; 376 String mProviderId; 377 final List<String> mSelectedRoutes; 378 final List<String> mSelectableRoutes; 379 final List<String> mDeselectableRoutes; 380 final List<String> mTransferableRoutes; 381 int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED; 382 int mVolumeMax; 383 int mVolume; 384 Bundle mControlHints; 385 boolean mIsSystemSession; 386 387 /** 388 * Constructor for builder to create {@link RoutingSessionInfo}. 389 * <p> 390 * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of 391 * {@link RoutingSessionInfo#getId()} can be different from what was set in 392 * {@link MediaRoute2ProviderService}. 393 * </p> 394 * 395 * @param id ID of the session. Must not be empty. 396 * @param clientPackageName package name of the client app which uses this session. 397 * If is is unknown, then just use an empty string. 398 * @see MediaRoute2Info#getId() 399 */ Builder(@onNull String id, @NonNull String clientPackageName)400 public Builder(@NonNull String id, @NonNull String clientPackageName) { 401 if (TextUtils.isEmpty(id)) { 402 throw new IllegalArgumentException("id must not be empty"); 403 } 404 405 mId = id; 406 mClientPackageName = 407 Objects.requireNonNull(clientPackageName, "clientPackageName must not be null"); 408 mSelectedRoutes = new ArrayList<>(); 409 mSelectableRoutes = new ArrayList<>(); 410 mDeselectableRoutes = new ArrayList<>(); 411 mTransferableRoutes = new ArrayList<>(); 412 } 413 414 /** 415 * Constructor for builder to create {@link RoutingSessionInfo} with 416 * existing {@link RoutingSessionInfo} instance. 417 * 418 * @param sessionInfo the existing instance to copy data from. 419 */ Builder(@onNull RoutingSessionInfo sessionInfo)420 public Builder(@NonNull RoutingSessionInfo sessionInfo) { 421 Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); 422 423 mId = sessionInfo.mId; 424 mName = sessionInfo.mName; 425 mClientPackageName = sessionInfo.mClientPackageName; 426 mProviderId = sessionInfo.mProviderId; 427 428 mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); 429 mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes); 430 mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes); 431 mTransferableRoutes = new ArrayList<>(sessionInfo.mTransferableRoutes); 432 433 if (mProviderId != null) { 434 // They must have unique IDs. 435 mSelectedRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 436 mSelectableRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 437 mDeselectableRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 438 mTransferableRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 439 } 440 441 mVolumeHandling = sessionInfo.mVolumeHandling; 442 mVolumeMax = sessionInfo.mVolumeMax; 443 mVolume = sessionInfo.mVolume; 444 445 mControlHints = sessionInfo.mControlHints; 446 mIsSystemSession = sessionInfo.mIsSystemSession; 447 } 448 449 /** 450 * Sets the user-visible name of the session. 451 */ 452 @NonNull setName(@ullable CharSequence name)453 public Builder setName(@Nullable CharSequence name) { 454 mName = name; 455 return this; 456 } 457 458 /** 459 * Sets the package name of the session owner. It is expected to be called by the system. 460 * 461 * @hide 462 */ 463 @NonNull setOwnerPackageName(@ullable String packageName)464 public Builder setOwnerPackageName(@Nullable String packageName) { 465 mOwnerPackageName = packageName; 466 return this; 467 } 468 469 /** 470 * Sets the client package name of the session. 471 * 472 * @hide 473 */ 474 @NonNull setClientPackageName(@ullable String packageName)475 public Builder setClientPackageName(@Nullable String packageName) { 476 mClientPackageName = packageName; 477 return this; 478 } 479 480 /** 481 * Sets the provider ID of the session. 482 * 483 * @hide 484 */ 485 @NonNull setProviderId(@onNull String providerId)486 public Builder setProviderId(@NonNull String providerId) { 487 if (TextUtils.isEmpty(providerId)) { 488 throw new IllegalArgumentException("providerId must not be empty"); 489 } 490 mProviderId = providerId; 491 return this; 492 } 493 494 /** 495 * Clears the selected routes. 496 */ 497 @NonNull clearSelectedRoutes()498 public Builder clearSelectedRoutes() { 499 mSelectedRoutes.clear(); 500 return this; 501 } 502 503 /** 504 * Adds a route to the selected routes. The {@code routeId} must not be empty. 505 */ 506 @NonNull addSelectedRoute(@onNull String routeId)507 public Builder addSelectedRoute(@NonNull String routeId) { 508 if (TextUtils.isEmpty(routeId)) { 509 throw new IllegalArgumentException("routeId must not be empty"); 510 } 511 mSelectedRoutes.add(routeId); 512 return this; 513 } 514 515 /** 516 * Removes a route from the selected routes. The {@code routeId} must not be empty. 517 */ 518 @NonNull removeSelectedRoute(@onNull String routeId)519 public Builder removeSelectedRoute(@NonNull String routeId) { 520 if (TextUtils.isEmpty(routeId)) { 521 throw new IllegalArgumentException("routeId must not be empty"); 522 } 523 mSelectedRoutes.remove(routeId); 524 return this; 525 } 526 527 /** 528 * Clears the selectable routes. 529 */ 530 @NonNull clearSelectableRoutes()531 public Builder clearSelectableRoutes() { 532 mSelectableRoutes.clear(); 533 return this; 534 } 535 536 /** 537 * Adds a route to the selectable routes. The {@code routeId} must not be empty. 538 */ 539 @NonNull addSelectableRoute(@onNull String routeId)540 public Builder addSelectableRoute(@NonNull String routeId) { 541 if (TextUtils.isEmpty(routeId)) { 542 throw new IllegalArgumentException("routeId must not be empty"); 543 } 544 mSelectableRoutes.add(routeId); 545 return this; 546 } 547 548 /** 549 * Removes a route from the selectable routes. The {@code routeId} must not be empty. 550 */ 551 @NonNull removeSelectableRoute(@onNull String routeId)552 public Builder removeSelectableRoute(@NonNull String routeId) { 553 if (TextUtils.isEmpty(routeId)) { 554 throw new IllegalArgumentException("routeId must not be empty"); 555 } 556 mSelectableRoutes.remove(routeId); 557 return this; 558 } 559 560 /** 561 * Clears the deselectable routes. 562 */ 563 @NonNull clearDeselectableRoutes()564 public Builder clearDeselectableRoutes() { 565 mDeselectableRoutes.clear(); 566 return this; 567 } 568 569 /** 570 * Adds a route to the deselectable routes. The {@code routeId} must not be empty. 571 */ 572 @NonNull addDeselectableRoute(@onNull String routeId)573 public Builder addDeselectableRoute(@NonNull String routeId) { 574 if (TextUtils.isEmpty(routeId)) { 575 throw new IllegalArgumentException("routeId must not be empty"); 576 } 577 mDeselectableRoutes.add(routeId); 578 return this; 579 } 580 581 /** 582 * Removes a route from the deselectable routes. The {@code routeId} must not be empty. 583 */ 584 @NonNull removeDeselectableRoute(@onNull String routeId)585 public Builder removeDeselectableRoute(@NonNull String routeId) { 586 if (TextUtils.isEmpty(routeId)) { 587 throw new IllegalArgumentException("routeId must not be empty"); 588 } 589 mDeselectableRoutes.remove(routeId); 590 return this; 591 } 592 593 /** 594 * Clears the transferable routes. 595 */ 596 @NonNull clearTransferableRoutes()597 public Builder clearTransferableRoutes() { 598 mTransferableRoutes.clear(); 599 return this; 600 } 601 602 /** 603 * Adds a route to the transferable routes. The {@code routeId} must not be empty. 604 */ 605 @NonNull addTransferableRoute(@onNull String routeId)606 public Builder addTransferableRoute(@NonNull String routeId) { 607 if (TextUtils.isEmpty(routeId)) { 608 throw new IllegalArgumentException("routeId must not be empty"); 609 } 610 mTransferableRoutes.add(routeId); 611 return this; 612 } 613 614 /** 615 * Removes a route from the transferable routes. The {@code routeId} must not be empty. 616 */ 617 @NonNull removeTransferableRoute(@onNull String routeId)618 public Builder removeTransferableRoute(@NonNull String routeId) { 619 if (TextUtils.isEmpty(routeId)) { 620 throw new IllegalArgumentException("routeId must not be empty"); 621 } 622 mTransferableRoutes.remove(routeId); 623 return this; 624 } 625 626 /** 627 * Sets the session's volume handling. 628 * {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or 629 * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}. 630 */ 631 @NonNull setVolumeHandling( @ediaRoute2Info.PlaybackVolume int volumeHandling)632 public RoutingSessionInfo.Builder setVolumeHandling( 633 @MediaRoute2Info.PlaybackVolume int volumeHandling) { 634 mVolumeHandling = volumeHandling; 635 return this; 636 } 637 638 /** 639 * Sets the session's maximum volume, or 0 if unknown. 640 */ 641 @NonNull setVolumeMax(int volumeMax)642 public RoutingSessionInfo.Builder setVolumeMax(int volumeMax) { 643 mVolumeMax = volumeMax; 644 return this; 645 } 646 647 /** 648 * Sets the session's current volume, or 0 if unknown. 649 */ 650 @NonNull setVolume(int volume)651 public RoutingSessionInfo.Builder setVolume(int volume) { 652 mVolume = volume; 653 return this; 654 } 655 656 /** 657 * Sets control hints. 658 */ 659 @NonNull setControlHints(@ullable Bundle controlHints)660 public Builder setControlHints(@Nullable Bundle controlHints) { 661 mControlHints = controlHints; 662 return this; 663 } 664 665 /** 666 * Sets whether this session is in system media route provider. 667 * @hide 668 */ 669 @NonNull setSystemSession(boolean isSystemSession)670 public Builder setSystemSession(boolean isSystemSession) { 671 mIsSystemSession = isSystemSession; 672 return this; 673 } 674 675 /** 676 * Builds a routing session info. 677 * 678 * @throws IllegalArgumentException if no selected routes are added. 679 */ 680 @NonNull build()681 public RoutingSessionInfo build() { 682 if (mSelectedRoutes.isEmpty()) { 683 throw new IllegalArgumentException("selectedRoutes must not be empty"); 684 } 685 return new RoutingSessionInfo(this); 686 } 687 } 688 } 689