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 static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.res.Resources; 26 import android.os.Bundle; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.os.UserHandle; 30 import android.text.TextUtils; 31 32 import com.android.internal.util.Preconditions; 33 34 import java.io.FileDescriptor; 35 import java.io.PrintWriter; 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Objects; 42 43 /** 44 * Describes a routing session which is created when a media route is selected. 45 */ 46 public final class RoutingSessionInfo implements Parcelable { 47 @NonNull 48 public static final Creator<RoutingSessionInfo> CREATOR = 49 new Creator<RoutingSessionInfo>() { 50 @Override 51 public RoutingSessionInfo createFromParcel(Parcel in) { 52 return new RoutingSessionInfo(in); 53 } 54 @Override 55 public RoutingSessionInfo[] newArray(int size) { 56 return new RoutingSessionInfo[size]; 57 } 58 }; 59 60 private static final String TAG = "RoutingSessionInfo"; 61 62 private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE"; 63 private static final String KEY_VOLUME_HANDLING = "volumeHandling"; 64 65 /** 66 * Indicates that the transfer happened by the default logic without explicit system's or user's 67 * request. 68 * 69 * <p>For example, an automatically connected Bluetooth device will have this transfer reason. 70 */ 71 @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) 72 public static final int TRANSFER_REASON_FALLBACK = 0; 73 74 /** Indicates that the transfer happened from within a privileged application. */ 75 @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) 76 public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; 77 78 /** Indicates that the transfer happened from a non-privileged app. */ 79 @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) 80 public static final int TRANSFER_REASON_APP = 2; 81 82 /** 83 * Indicates the transfer reason. 84 * 85 * @hide 86 */ 87 @IntDef(value = {TRANSFER_REASON_FALLBACK, TRANSFER_REASON_SYSTEM_REQUEST, TRANSFER_REASON_APP}) 88 @Retention(RetentionPolicy.SOURCE) 89 public @interface TransferReason {} 90 91 @NonNull 92 final String mId; 93 @Nullable 94 final CharSequence mName; 95 @Nullable 96 final String mOwnerPackageName; 97 @NonNull 98 final String mClientPackageName; 99 @Nullable 100 final String mProviderId; 101 @NonNull 102 final List<String> mSelectedRoutes; 103 @NonNull 104 final List<String> mSelectableRoutes; 105 @NonNull 106 final List<String> mDeselectableRoutes; 107 @NonNull 108 final List<String> mTransferableRoutes; 109 110 @MediaRoute2Info.PlaybackVolume final int mVolumeHandling; 111 final int mVolumeMax; 112 final int mVolume; 113 114 @Nullable 115 final Bundle mControlHints; 116 final boolean mIsSystemSession; 117 118 @TransferReason final int mTransferReason; 119 120 @Nullable final UserHandle mTransferInitiatorUserHandle; 121 @Nullable final String mTransferInitiatorPackageName; 122 RoutingSessionInfo(@onNull Builder builder)123 RoutingSessionInfo(@NonNull Builder builder) { 124 Objects.requireNonNull(builder, "builder must not be null."); 125 126 mId = builder.mId; 127 mName = builder.mName; 128 mOwnerPackageName = builder.mOwnerPackageName; 129 mClientPackageName = builder.mClientPackageName; 130 mProviderId = builder.mProviderId; 131 132 mSelectedRoutes = Collections.unmodifiableList( 133 convertToUniqueRouteIds(builder.mSelectedRoutes)); 134 mSelectableRoutes = Collections.unmodifiableList( 135 convertToUniqueRouteIds(builder.mSelectableRoutes)); 136 mDeselectableRoutes = Collections.unmodifiableList( 137 convertToUniqueRouteIds(builder.mDeselectableRoutes)); 138 mTransferableRoutes = Collections.unmodifiableList( 139 convertToUniqueRouteIds(builder.mTransferableRoutes)); 140 141 mVolumeMax = builder.mVolumeMax; 142 mVolume = builder.mVolume; 143 144 mIsSystemSession = builder.mIsSystemSession; 145 146 boolean volumeAdjustmentForRemoteGroupSessions = Resources.getSystem().getBoolean( 147 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); 148 mVolumeHandling = 149 defineVolumeHandling( 150 mIsSystemSession, 151 builder.mVolumeHandling, 152 mSelectedRoutes, 153 volumeAdjustmentForRemoteGroupSessions); 154 155 mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling); 156 mTransferReason = builder.mTransferReason; 157 mTransferInitiatorUserHandle = builder.mTransferInitiatorUserHandle; 158 mTransferInitiatorPackageName = builder.mTransferInitiatorPackageName; 159 } 160 RoutingSessionInfo(@onNull Parcel src)161 RoutingSessionInfo(@NonNull Parcel src) { 162 mId = src.readString(); 163 Preconditions.checkArgument(!TextUtils.isEmpty(mId)); 164 165 mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); 166 mOwnerPackageName = src.readString(); 167 mClientPackageName = ensureString(src.readString()); 168 mProviderId = src.readString(); 169 170 mSelectedRoutes = ensureList(src.createStringArrayList()); 171 Preconditions.checkArgument(!mSelectedRoutes.isEmpty()); 172 173 mSelectableRoutes = ensureList(src.createStringArrayList()); 174 mDeselectableRoutes = ensureList(src.createStringArrayList()); 175 mTransferableRoutes = ensureList(src.createStringArrayList()); 176 177 mVolumeHandling = src.readInt(); 178 mVolumeMax = src.readInt(); 179 mVolume = src.readInt(); 180 181 mControlHints = src.readBundle(); 182 mIsSystemSession = src.readBoolean(); 183 mTransferReason = src.readInt(); 184 mTransferInitiatorUserHandle = UserHandle.readFromParcel(src); 185 mTransferInitiatorPackageName = src.readString(); 186 } 187 188 @Nullable updateVolumeHandlingInHints(@ullable Bundle controlHints, int volumeHandling)189 private static Bundle updateVolumeHandlingInHints(@Nullable Bundle controlHints, 190 int volumeHandling) { 191 // Workaround to preserve retro-compatibility with androidx. 192 // See b/228021646 for more details. 193 if (controlHints != null && controlHints.containsKey(KEY_GROUP_ROUTE)) { 194 Bundle groupRoute = controlHints.getBundle(KEY_GROUP_ROUTE); 195 196 if (groupRoute != null && groupRoute.containsKey(KEY_VOLUME_HANDLING) 197 && volumeHandling != groupRoute.getInt(KEY_VOLUME_HANDLING)) { 198 //Creating copy of controlHints with updated value. 199 Bundle newGroupRoute = new Bundle(groupRoute); 200 newGroupRoute.putInt(KEY_VOLUME_HANDLING, volumeHandling); 201 Bundle newControlHints = new Bundle(controlHints); 202 newControlHints.putBundle(KEY_GROUP_ROUTE, newGroupRoute); 203 return newControlHints; 204 } 205 } 206 //Return same Bundle. 207 return controlHints; 208 } 209 210 @MediaRoute2Info.PlaybackVolume defineVolumeHandling( boolean isSystemSession, @MediaRoute2Info.PlaybackVolume int volumeHandling, List<String> selectedRoutes, boolean volumeAdjustmentForRemoteGroupSessions)211 private static int defineVolumeHandling( 212 boolean isSystemSession, 213 @MediaRoute2Info.PlaybackVolume int volumeHandling, 214 List<String> selectedRoutes, 215 boolean volumeAdjustmentForRemoteGroupSessions) { 216 if (!isSystemSession 217 && !volumeAdjustmentForRemoteGroupSessions 218 && selectedRoutes.size() > 1) { 219 return MediaRoute2Info.PLAYBACK_VOLUME_FIXED; 220 } 221 return volumeHandling; 222 } 223 224 @NonNull ensureString(@ullable String str)225 private static String ensureString(@Nullable String str) { 226 return str != null ? str : ""; 227 } 228 229 @NonNull ensureList(@ullable List<? extends T> list)230 private static <T> List<T> ensureList(@Nullable List<? extends T> list) { 231 if (list != null) { 232 return Collections.unmodifiableList(list); 233 } 234 return Collections.emptyList(); 235 } 236 237 /** 238 * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have 239 * unique IDs. 240 * <p> 241 * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method 242 * can be different from what was set in {@link MediaRoute2ProviderService}. 243 * 244 * @see Builder#Builder(String, String) 245 */ 246 @NonNull getId()247 public String getId() { 248 if (!TextUtils.isEmpty(mProviderId)) { 249 return MediaRouter2Utils.toUniqueId(mProviderId, mId); 250 } else { 251 return mId; 252 } 253 } 254 255 /** 256 * Gets the user-visible name of the session. It may be {@code null}. 257 */ 258 @Nullable getName()259 public CharSequence getName() { 260 return mName; 261 } 262 263 /** 264 * Gets the original id set by {@link Builder#Builder(String, String)}. 265 * @hide 266 */ 267 @NonNull getOriginalId()268 public String getOriginalId() { 269 return mId; 270 } 271 272 /** 273 * Gets the package name of the session owner. 274 * @hide 275 */ 276 @Nullable getOwnerPackageName()277 public String getOwnerPackageName() { 278 return mOwnerPackageName; 279 } 280 281 /** 282 * Gets the client package name of the session 283 */ 284 @NonNull getClientPackageName()285 public String getClientPackageName() { 286 return mClientPackageName; 287 } 288 289 /** 290 * Gets the provider id of the session. 291 * @hide 292 */ 293 @Nullable getProviderId()294 public String getProviderId() { 295 return mProviderId; 296 } 297 298 /** 299 * Gets the list of IDs of selected routes for the session. It shouldn't be empty. 300 */ 301 @NonNull getSelectedRoutes()302 public List<String> getSelectedRoutes() { 303 return mSelectedRoutes; 304 } 305 306 /** 307 * Gets the list of IDs of selectable routes for the session. 308 */ 309 @NonNull getSelectableRoutes()310 public List<String> getSelectableRoutes() { 311 return mSelectableRoutes; 312 } 313 314 /** 315 * Gets the list of IDs of deselectable routes for the session. 316 */ 317 @NonNull getDeselectableRoutes()318 public List<String> getDeselectableRoutes() { 319 return mDeselectableRoutes; 320 } 321 322 /** 323 * Gets the list of IDs of transferable routes for the session. 324 */ 325 @NonNull getTransferableRoutes()326 public List<String> getTransferableRoutes() { 327 return mTransferableRoutes; 328 } 329 330 /** 331 * Gets the information about how volume is handled on the session. 332 * 333 * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or 334 * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}. 335 */ 336 @MediaRoute2Info.PlaybackVolume getVolumeHandling()337 public int getVolumeHandling() { 338 return mVolumeHandling; 339 } 340 341 /** 342 * Gets the maximum volume of the session. 343 */ getVolumeMax()344 public int getVolumeMax() { 345 return mVolumeMax; 346 } 347 348 /** 349 * Gets the current volume of the session. 350 * <p> 351 * When it's available, it represents the volume of routing session, which is a group 352 * of selected routes. To get the volume of each route, use {@link MediaRoute2Info#getVolume()}. 353 * </p> 354 * @see MediaRoute2Info#getVolume() 355 */ getVolume()356 public int getVolume() { 357 return mVolume; 358 } 359 360 /** 361 * Gets the control hints 362 */ 363 @Nullable getControlHints()364 public Bundle getControlHints() { 365 return mControlHints; 366 } 367 368 /** 369 * Gets whether this session is in system media route provider. 370 * @hide 371 */ 372 @Nullable isSystemSession()373 public boolean isSystemSession() { 374 return mIsSystemSession; 375 } 376 377 /** Returns the transfer reason for this routing session. */ 378 @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) 379 @TransferReason getTransferReason()380 public int getTransferReason() { 381 return mTransferReason; 382 } 383 384 /** @hide */ 385 @Nullable getTransferInitiatorUserHandle()386 public UserHandle getTransferInitiatorUserHandle() { 387 return mTransferInitiatorUserHandle; 388 } 389 390 /** @hide */ 391 @Nullable getTransferInitiatorPackageName()392 public String getTransferInitiatorPackageName() { 393 return mTransferInitiatorPackageName; 394 } 395 396 @Override describeContents()397 public int describeContents() { 398 return 0; 399 } 400 401 @Override writeToParcel(@onNull Parcel dest, int flags)402 public void writeToParcel(@NonNull Parcel dest, int flags) { 403 dest.writeString(mId); 404 dest.writeCharSequence(mName); 405 dest.writeString(mOwnerPackageName); 406 dest.writeString(mClientPackageName); 407 dest.writeString(mProviderId); 408 dest.writeStringList(mSelectedRoutes); 409 dest.writeStringList(mSelectableRoutes); 410 dest.writeStringList(mDeselectableRoutes); 411 dest.writeStringList(mTransferableRoutes); 412 dest.writeInt(mVolumeHandling); 413 dest.writeInt(mVolumeMax); 414 dest.writeInt(mVolume); 415 dest.writeBundle(mControlHints); 416 dest.writeBoolean(mIsSystemSession); 417 dest.writeInt(mTransferReason); 418 UserHandle.writeToParcel(mTransferInitiatorUserHandle, dest); 419 dest.writeString(mTransferInitiatorPackageName); 420 } 421 422 /** 423 * Dumps current state of the instance. Use with {@code dumpsys}. 424 * 425 * See {@link android.os.Binder#dump(FileDescriptor, PrintWriter, String[])}. 426 * 427 * @hide 428 */ dump(@onNull PrintWriter pw, @NonNull String prefix)429 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 430 pw.println(prefix + "RoutingSessionInfo"); 431 432 String indent = prefix + " "; 433 434 pw.println(indent + "mId=" + mId); 435 pw.println(indent + "mName=" + mName); 436 pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName); 437 pw.println(indent + "mClientPackageName=" + mClientPackageName); 438 pw.println(indent + "mProviderId=" + mProviderId); 439 pw.println(indent + "mSelectedRoutes=" + mSelectedRoutes); 440 pw.println(indent + "mSelectableRoutes=" + mSelectableRoutes); 441 pw.println(indent + "mDeselectableRoutes=" + mDeselectableRoutes); 442 pw.println(indent + "mTransferableRoutes=" + mTransferableRoutes); 443 pw.println(indent + MediaRoute2Info.getVolumeString(mVolume, mVolumeMax, mVolumeHandling)); 444 pw.println(indent + "mControlHints=" + mControlHints); 445 pw.println(indent + "mIsSystemSession=" + mIsSystemSession); 446 pw.println(indent + "mTransferReason=" + mTransferReason); 447 pw.println(indent + "mtransferInitiatorUserHandle=" + mTransferInitiatorUserHandle); 448 pw.println(indent + "mtransferInitiatorPackageName=" + mTransferInitiatorPackageName); 449 } 450 451 @Override equals(@ullable Object obj)452 public boolean equals(@Nullable Object obj) { 453 if (obj == null) { 454 return false; 455 } 456 457 if (this == obj) { 458 return true; 459 } 460 if (getClass() != obj.getClass()) { 461 return false; 462 } 463 464 RoutingSessionInfo other = (RoutingSessionInfo) obj; 465 return Objects.equals(mId, other.mId) 466 && Objects.equals(mName, other.mName) 467 && Objects.equals(mOwnerPackageName, other.mOwnerPackageName) 468 && Objects.equals(mClientPackageName, other.mClientPackageName) 469 && Objects.equals(mProviderId, other.mProviderId) 470 && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) 471 && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) 472 && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes) 473 && Objects.equals(mTransferableRoutes, other.mTransferableRoutes) 474 && (mVolumeHandling == other.mVolumeHandling) 475 && (mVolumeMax == other.mVolumeMax) 476 && (mVolume == other.mVolume) 477 && (mTransferReason == other.mTransferReason) 478 && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle) 479 && Objects.equals( 480 mTransferInitiatorPackageName, other.mTransferInitiatorPackageName); 481 } 482 483 @Override hashCode()484 public int hashCode() { 485 return Objects.hash( 486 mId, 487 mName, 488 mOwnerPackageName, 489 mClientPackageName, 490 mProviderId, 491 mSelectedRoutes, 492 mSelectableRoutes, 493 mDeselectableRoutes, 494 mTransferableRoutes, 495 mVolumeMax, 496 mVolumeHandling, 497 mVolume, 498 mTransferReason, 499 mTransferInitiatorUserHandle, 500 mTransferInitiatorPackageName); 501 } 502 503 @Override toString()504 public String toString() { 505 return new StringBuilder() 506 .append("RoutingSessionInfo{ ") 507 .append("sessionId=") 508 .append(getId()) 509 .append(", name=") 510 .append(getName()) 511 .append(", clientPackageName=") 512 .append(getClientPackageName()) 513 .append(", selectedRoutes={") 514 .append(String.join(",", getSelectedRoutes())) 515 .append("}") 516 .append(", selectableRoutes={") 517 .append(String.join(",", getSelectableRoutes())) 518 .append("}") 519 .append(", deselectableRoutes={") 520 .append(String.join(",", getDeselectableRoutes())) 521 .append("}") 522 .append(", transferableRoutes={") 523 .append(String.join(",", getTransferableRoutes())) 524 .append("}") 525 .append(", ") 526 .append(MediaRoute2Info.getVolumeString(mVolume, mVolumeMax, mVolumeHandling)) 527 .append(", transferReason=") 528 .append(getTransferReason()) 529 .append(", transferInitiatorUserHandle=") 530 .append(getTransferInitiatorUserHandle()) 531 .append(", transferInitiatorPackageName=") 532 .append(getTransferInitiatorPackageName()) 533 .append(" }") 534 .toString(); 535 } 536 537 /** 538 * Provides a new list with unique route IDs if {@link #mProviderId} is set, or the original IDs 539 * otherwise. 540 * 541 * @param routeIds list of route IDs to convert 542 * @return new list with unique IDs or original IDs 543 */ 544 545 @NonNull convertToUniqueRouteIds(@onNull List<String> routeIds)546 private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) { 547 Objects.requireNonNull(routeIds, "RouteIds cannot be null."); 548 549 // mProviderId can be null if not set. Return the original list for this case. 550 if (TextUtils.isEmpty(mProviderId)) { 551 return new ArrayList<>(routeIds); 552 } 553 554 List<String> result = new ArrayList<>(); 555 for (String routeId : routeIds) { 556 result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId)); 557 } 558 return result; 559 } 560 561 /** 562 * Builder class for {@link RoutingSessionInfo}. 563 */ 564 public static final class Builder { 565 @NonNull 566 private final String mId; 567 @Nullable 568 private CharSequence mName; 569 @Nullable 570 private String mOwnerPackageName; 571 @NonNull 572 private String mClientPackageName; 573 @Nullable 574 private String mProviderId; 575 @NonNull 576 private final List<String> mSelectedRoutes; 577 @NonNull 578 private final List<String> mSelectableRoutes; 579 @NonNull 580 private final List<String> mDeselectableRoutes; 581 @NonNull 582 private final List<String> mTransferableRoutes; 583 @MediaRoute2Info.PlaybackVolume 584 private int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED; 585 private int mVolumeMax; 586 private int mVolume; 587 @Nullable 588 private Bundle mControlHints; 589 private boolean mIsSystemSession; 590 @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK; 591 @Nullable private UserHandle mTransferInitiatorUserHandle; 592 @Nullable private String mTransferInitiatorPackageName; 593 594 /** 595 * Constructor for builder to create {@link RoutingSessionInfo}. 596 * <p> 597 * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of 598 * {@link RoutingSessionInfo#getId()} can be different from what was set in 599 * {@link MediaRoute2ProviderService}. 600 * </p> 601 * 602 * @param id ID of the session. Must not be empty. 603 * @param clientPackageName package name of the client app which uses this session. 604 * If is is unknown, then just use an empty string. 605 * @see MediaRoute2Info#getId() 606 */ Builder(@onNull String id, @NonNull String clientPackageName)607 public Builder(@NonNull String id, @NonNull String clientPackageName) { 608 if (TextUtils.isEmpty(id)) { 609 throw new IllegalArgumentException("id must not be empty"); 610 } 611 612 mId = id; 613 mClientPackageName = 614 Objects.requireNonNull(clientPackageName, "clientPackageName must not be null"); 615 mSelectedRoutes = new ArrayList<>(); 616 mSelectableRoutes = new ArrayList<>(); 617 mDeselectableRoutes = new ArrayList<>(); 618 mTransferableRoutes = new ArrayList<>(); 619 } 620 621 /** 622 * Constructor for builder to create {@link RoutingSessionInfo} with 623 * existing {@link RoutingSessionInfo} instance. 624 * 625 * @param sessionInfo the existing instance to copy data from. 626 */ Builder(@onNull RoutingSessionInfo sessionInfo)627 public Builder(@NonNull RoutingSessionInfo sessionInfo) { 628 Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); 629 630 mId = sessionInfo.mId; 631 mName = sessionInfo.mName; 632 mClientPackageName = sessionInfo.mClientPackageName; 633 mProviderId = sessionInfo.mProviderId; 634 635 mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); 636 mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes); 637 mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes); 638 mTransferableRoutes = new ArrayList<>(sessionInfo.mTransferableRoutes); 639 640 if (mProviderId != null) { 641 // They must have unique IDs. 642 mSelectedRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 643 mSelectableRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 644 mDeselectableRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 645 mTransferableRoutes.replaceAll(MediaRouter2Utils::getOriginalId); 646 } 647 648 mVolumeHandling = sessionInfo.mVolumeHandling; 649 mVolumeMax = sessionInfo.mVolumeMax; 650 mVolume = sessionInfo.mVolume; 651 652 mControlHints = sessionInfo.mControlHints; 653 mIsSystemSession = sessionInfo.mIsSystemSession; 654 mTransferReason = sessionInfo.mTransferReason; 655 mTransferInitiatorUserHandle = sessionInfo.mTransferInitiatorUserHandle; 656 mTransferInitiatorPackageName = sessionInfo.mTransferInitiatorPackageName; 657 } 658 659 /** 660 * Sets the user-visible name of the session. 661 */ 662 @NonNull setName(@ullable CharSequence name)663 public Builder setName(@Nullable CharSequence name) { 664 mName = name; 665 return this; 666 } 667 668 /** 669 * Sets the package name of the session owner. It is expected to be called by the system. 670 * 671 * @hide 672 */ 673 @NonNull setOwnerPackageName(@ullable String packageName)674 public Builder setOwnerPackageName(@Nullable String packageName) { 675 mOwnerPackageName = packageName; 676 return this; 677 } 678 679 /** 680 * Sets the client package name of the session. 681 * 682 * @hide 683 */ 684 @NonNull setClientPackageName(@ullable String packageName)685 public Builder setClientPackageName(@Nullable String packageName) { 686 mClientPackageName = packageName; 687 return this; 688 } 689 690 /** 691 * Sets the provider ID of the session. 692 * 693 * @hide 694 */ 695 @NonNull setProviderId(@onNull String providerId)696 public Builder setProviderId(@NonNull String providerId) { 697 if (TextUtils.isEmpty(providerId)) { 698 throw new IllegalArgumentException("providerId must not be empty"); 699 } 700 mProviderId = providerId; 701 return this; 702 } 703 704 /** 705 * Clears the selected routes. 706 */ 707 @NonNull clearSelectedRoutes()708 public Builder clearSelectedRoutes() { 709 mSelectedRoutes.clear(); 710 return this; 711 } 712 713 /** 714 * Adds a route to the selected routes. The {@code routeId} must not be empty. 715 */ 716 @NonNull addSelectedRoute(@onNull String routeId)717 public Builder addSelectedRoute(@NonNull String routeId) { 718 if (TextUtils.isEmpty(routeId)) { 719 throw new IllegalArgumentException("routeId must not be empty"); 720 } 721 mSelectedRoutes.add(routeId); 722 return this; 723 } 724 725 /** 726 * Removes a route from the selected routes. The {@code routeId} must not be empty. 727 */ 728 @NonNull removeSelectedRoute(@onNull String routeId)729 public Builder removeSelectedRoute(@NonNull String routeId) { 730 if (TextUtils.isEmpty(routeId)) { 731 throw new IllegalArgumentException("routeId must not be empty"); 732 } 733 mSelectedRoutes.remove(routeId); 734 return this; 735 } 736 737 /** 738 * Clears the selectable routes. 739 */ 740 @NonNull clearSelectableRoutes()741 public Builder clearSelectableRoutes() { 742 mSelectableRoutes.clear(); 743 return this; 744 } 745 746 /** 747 * Adds a route to the selectable routes. The {@code routeId} must not be empty. 748 */ 749 @NonNull addSelectableRoute(@onNull String routeId)750 public Builder addSelectableRoute(@NonNull String routeId) { 751 if (TextUtils.isEmpty(routeId)) { 752 throw new IllegalArgumentException("routeId must not be empty"); 753 } 754 mSelectableRoutes.add(routeId); 755 return this; 756 } 757 758 /** 759 * Removes a route from the selectable routes. The {@code routeId} must not be empty. 760 */ 761 @NonNull removeSelectableRoute(@onNull String routeId)762 public Builder removeSelectableRoute(@NonNull String routeId) { 763 if (TextUtils.isEmpty(routeId)) { 764 throw new IllegalArgumentException("routeId must not be empty"); 765 } 766 mSelectableRoutes.remove(routeId); 767 return this; 768 } 769 770 /** 771 * Clears the deselectable routes. 772 */ 773 @NonNull clearDeselectableRoutes()774 public Builder clearDeselectableRoutes() { 775 mDeselectableRoutes.clear(); 776 return this; 777 } 778 779 /** 780 * Adds a route to the deselectable routes. The {@code routeId} must not be empty. 781 */ 782 @NonNull addDeselectableRoute(@onNull String routeId)783 public Builder addDeselectableRoute(@NonNull String routeId) { 784 if (TextUtils.isEmpty(routeId)) { 785 throw new IllegalArgumentException("routeId must not be empty"); 786 } 787 mDeselectableRoutes.add(routeId); 788 return this; 789 } 790 791 /** 792 * Removes a route from the deselectable routes. The {@code routeId} must not be empty. 793 */ 794 @NonNull removeDeselectableRoute(@onNull String routeId)795 public Builder removeDeselectableRoute(@NonNull String routeId) { 796 if (TextUtils.isEmpty(routeId)) { 797 throw new IllegalArgumentException("routeId must not be empty"); 798 } 799 mDeselectableRoutes.remove(routeId); 800 return this; 801 } 802 803 /** 804 * Clears the transferable routes. 805 */ 806 @NonNull clearTransferableRoutes()807 public Builder clearTransferableRoutes() { 808 mTransferableRoutes.clear(); 809 return this; 810 } 811 812 /** 813 * Adds a route to the transferable routes. The {@code routeId} must not be empty. 814 */ 815 @NonNull addTransferableRoute(@onNull String routeId)816 public Builder addTransferableRoute(@NonNull String routeId) { 817 if (TextUtils.isEmpty(routeId)) { 818 throw new IllegalArgumentException("routeId must not be empty"); 819 } 820 mTransferableRoutes.add(routeId); 821 return this; 822 } 823 824 /** 825 * Removes a route from the transferable routes. The {@code routeId} must not be empty. 826 */ 827 @NonNull removeTransferableRoute(@onNull String routeId)828 public Builder removeTransferableRoute(@NonNull String routeId) { 829 if (TextUtils.isEmpty(routeId)) { 830 throw new IllegalArgumentException("routeId must not be empty"); 831 } 832 mTransferableRoutes.remove(routeId); 833 return this; 834 } 835 836 /** 837 * Sets the session's volume handling. 838 * {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or 839 * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}. 840 */ 841 @NonNull setVolumeHandling( @ediaRoute2Info.PlaybackVolume int volumeHandling)842 public RoutingSessionInfo.Builder setVolumeHandling( 843 @MediaRoute2Info.PlaybackVolume int volumeHandling) { 844 mVolumeHandling = volumeHandling; 845 return this; 846 } 847 848 /** 849 * Sets the session's maximum volume, or 0 if unknown. 850 */ 851 @NonNull setVolumeMax(int volumeMax)852 public RoutingSessionInfo.Builder setVolumeMax(int volumeMax) { 853 mVolumeMax = volumeMax; 854 return this; 855 } 856 857 /** 858 * Sets the session's current volume, or 0 if unknown. 859 */ 860 @NonNull setVolume(int volume)861 public RoutingSessionInfo.Builder setVolume(int volume) { 862 mVolume = volume; 863 return this; 864 } 865 866 /** 867 * Sets control hints. 868 */ 869 @NonNull setControlHints(@ullable Bundle controlHints)870 public Builder setControlHints(@Nullable Bundle controlHints) { 871 mControlHints = controlHints; 872 return this; 873 } 874 875 /** 876 * Sets whether this session is in system media route provider. 877 * @hide 878 */ 879 @NonNull setSystemSession(boolean isSystemSession)880 public Builder setSystemSession(boolean isSystemSession) { 881 mIsSystemSession = isSystemSession; 882 return this; 883 } 884 885 /** 886 * Sets transfer reason for the current session. 887 * 888 * <p>By default the transfer reason is set to {@link 889 * RoutingSessionInfo#TRANSFER_REASON_FALLBACK}. 890 */ 891 @NonNull 892 @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) setTransferReason(@ransferReason int transferReason)893 public Builder setTransferReason(@TransferReason int transferReason) { 894 mTransferReason = transferReason; 895 return this; 896 } 897 898 /** 899 * Sets the user handle and package name of the process that initiated the transfer. 900 * 901 * <p>By default the transfer initiation user handle and package name are set to {@code 902 * null}. 903 */ 904 // The UserHandleName warning suggests the name should be "doFooAsUser". But the UserHandle 905 // parameter of this function is stored in a field, and not used to execute an operation on 906 // a specific user. 907 // The MissingGetterMatchingBuilder requires a getTransferInitiator function. But said 908 // getter is not included because the returned package name and user handle is always either 909 // null or the values that correspond to the calling app, and that information is obtainable 910 // via RoutingController#wasTransferInitiatedBySelf. 911 @SuppressWarnings({"UserHandleName", "MissingGetterMatchingBuilder"}) 912 @NonNull 913 @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) setTransferInitiator( @ullable UserHandle transferInitiatorUserHandle, @Nullable String transferInitiatorPackageName)914 public Builder setTransferInitiator( 915 @Nullable UserHandle transferInitiatorUserHandle, 916 @Nullable String transferInitiatorPackageName) { 917 mTransferInitiatorUserHandle = transferInitiatorUserHandle; 918 mTransferInitiatorPackageName = transferInitiatorPackageName; 919 return this; 920 } 921 922 /** 923 * Builds a routing session info. 924 * 925 * @throws IllegalArgumentException if no selected routes are added. 926 */ 927 @NonNull build()928 public RoutingSessionInfo build() { 929 if (mSelectedRoutes.isEmpty()) { 930 throw new IllegalArgumentException("selectedRoutes must not be empty"); 931 } 932 return new RoutingSessionInfo(this); 933 } 934 } 935 } 936