• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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