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 android.media.MediaRouter2Utils.toUniqueId;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.TestApi;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.text.TextUtils;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * Describes the properties of a route.
40  */
41 public final class MediaRoute2Info implements Parcelable {
42     @NonNull
43     public static final Creator<MediaRoute2Info> CREATOR = new Creator<MediaRoute2Info>() {
44         @Override
45         public MediaRoute2Info createFromParcel(Parcel in) {
46             return new MediaRoute2Info(in);
47         }
48 
49         @Override
50         public MediaRoute2Info[] newArray(int size) {
51             return new MediaRoute2Info[size];
52         }
53     };
54 
55     /** @hide */
56     @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
57             CONNECTION_STATE_CONNECTED})
58     @Retention(RetentionPolicy.SOURCE)
59     public @interface ConnectionState {}
60 
61     /**
62      * The default connection state indicating the route is disconnected.
63      *
64      * @see #getConnectionState
65      */
66     public static final int CONNECTION_STATE_DISCONNECTED = 0;
67 
68     /**
69      * A connection state indicating the route is in the process of connecting and is not yet
70      * ready for use.
71      *
72      * @see #getConnectionState
73      */
74     public static final int CONNECTION_STATE_CONNECTING = 1;
75 
76     /**
77      * A connection state indicating the route is connected.
78      *
79      * @see #getConnectionState
80      */
81     public static final int CONNECTION_STATE_CONNECTED = 2;
82 
83     /** @hide */
84     @IntDef({PLAYBACK_VOLUME_FIXED, PLAYBACK_VOLUME_VARIABLE})
85     @Retention(RetentionPolicy.SOURCE)
86     public @interface PlaybackVolume {}
87 
88     /**
89      * Playback information indicating the playback volume is fixed, i&#46;e&#46; it cannot be
90      * controlled from this object. An example of fixed playback volume is a remote player,
91      * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
92      * than attenuate at the source.
93      *
94      * @see #getVolumeHandling()
95      */
96     public static final int PLAYBACK_VOLUME_FIXED = 0;
97     /**
98      * Playback information indicating the playback volume is variable and can be controlled
99      * from this object.
100      *
101      * @see #getVolumeHandling()
102      */
103     public static final int PLAYBACK_VOLUME_VARIABLE = 1;
104 
105     /** @hide */
106     @IntDef({
107             TYPE_UNKNOWN, TYPE_BUILTIN_SPEAKER, TYPE_WIRED_HEADSET,
108             TYPE_WIRED_HEADPHONES, TYPE_BLUETOOTH_A2DP, TYPE_HDMI, TYPE_USB_DEVICE,
109             TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID,
110             TYPE_REMOTE_TV, TYPE_REMOTE_SPEAKER, TYPE_GROUP})
111     @Retention(RetentionPolicy.SOURCE)
112     public @interface Type {}
113 
114     /**
115      * The default route type indicating the type is unknown.
116      *
117      * @see #getType
118      * @hide
119      */
120     public static final int TYPE_UNKNOWN = 0;
121 
122     /**
123      * A route type describing the speaker system (i.e. a mono speaker or stereo speakers) built
124      * in a device.
125      *
126      * @see #getType
127      * @hide
128      */
129     public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
130 
131     /**
132      * A route type describing a headset, which is the combination of a headphones and microphone.
133      *
134      * @see #getType
135      * @hide
136      */
137     public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET;
138 
139     /**
140      * A route type describing a pair of wired headphones.
141      *
142      * @see #getType
143      * @hide
144      */
145     public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
146 
147     /**
148      * A route type indicating the presentation of the media is happening
149      * on a bluetooth device such as a bluetooth speaker.
150      *
151      * @see #getType
152      * @hide
153      */
154     public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
155 
156     /**
157      * A route type describing an HDMI connection.
158      *
159      * @see #getType
160      * @hide
161      */
162     public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
163 
164     /**
165      * A route type describing a USB audio device.
166      *
167      * @see #getType
168      * @hide
169      */
170     public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE;
171 
172     /**
173      * A route type describing a USB audio device in accessory mode.
174      *
175      * @see #getType
176      * @hide
177      */
178     public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY;
179 
180     /**
181      * A route type describing the audio device associated with a dock.
182      *
183      * @see #getType
184      * @hide
185      */
186     public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK;
187 
188     /**
189      * A device type describing a USB audio headset.
190      *
191      * @see #getType
192      * @hide
193      */
194     public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET;
195 
196     /**
197      * A route type describing a Hearing Aid.
198      *
199      * @see #getType
200      * @hide
201      */
202     public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
203 
204     /**
205      * A route type indicating the presentation of the media is happening on a TV.
206      *
207      * @see #getType
208      * @hide
209      */
210     public static final int TYPE_REMOTE_TV = 1001;
211 
212     /**
213      * A route type indicating the presentation of the media is happening on a speaker.
214      *
215      * @see #getType
216      * @hide
217      */
218     public static final int TYPE_REMOTE_SPEAKER = 1002;
219 
220     /**
221      * A route type indicating the presentation of the media is happening on multiple devices.
222      *
223      * @see #getType
224      * @hide
225      */
226     public static final int TYPE_GROUP = 2000;
227 
228     /**
229      * Route feature: Live audio.
230      * <p>
231      * A route that supports live audio routing will allow the media audio stream
232      * to be sent to supported destinations.  This can include internal speakers or
233      * audio jacks on the device itself, A2DP devices, and more.
234      * </p><p>
235      * When a live audio route is selected, audio routing is transparent to the application.
236      * All audio played on the media stream will be routed to the selected destination.
237      * </p><p>
238      * Refer to the class documentation for details about live audio routes.
239      * </p>
240      */
241     public static final String FEATURE_LIVE_AUDIO = "android.media.route.feature.LIVE_AUDIO";
242 
243     /**
244      * Route feature: Live video.
245      * <p>
246      * A route that supports live video routing will allow a mirrored version
247      * of the device's primary display or a customized
248      * {@link android.app.Presentation Presentation} to be sent to supported
249      * destinations.
250      * </p><p>
251      * When a live video route is selected, audio and video routing is transparent
252      * to the application.  By default, audio and video is routed to the selected
253      * destination.  For certain live video routes, the application may also use a
254      * {@link android.app.Presentation Presentation} to replace the mirrored view
255      * on the external display with different content.
256      * </p><p>
257      * Refer to the class documentation for details about live video routes.
258      * </p>
259      *
260      * @see android.app.Presentation
261      */
262     public static final String FEATURE_LIVE_VIDEO = "android.media.route.feature.LIVE_VIDEO";
263 
264     /**
265      * Route feature: Local playback.
266      * @hide
267      */
268     public static final String FEATURE_LOCAL_PLAYBACK =
269             "android.media.route.feature.LOCAL_PLAYBACK";
270 
271     /**
272      * Route feature: Remote playback.
273      * <p>
274      * A route that supports remote playback routing will allow an application to send
275      * requests to play content remotely to supported destinations.
276      * A route may only support {@link #FEATURE_REMOTE_AUDIO_PLAYBACK audio playback} or
277      * {@link #FEATURE_REMOTE_VIDEO_PLAYBACK video playback}.
278      * </p><p>
279      * Remote playback routes destinations operate independently of the local device.
280      * When a remote playback route is selected, the application can control the content
281      * playing on the destination using {@link MediaRouter2.RoutingController#getControlHints()}.
282      * The application may also receive status updates from the route regarding remote playback.
283      * </p><p>
284      * Refer to the class documentation for details about remote playback routes.
285      * </p>
286      * @see #FEATURE_REMOTE_AUDIO_PLAYBACK
287      * @see #FEATURE_REMOTE_VIDEO_PLAYBACK
288      */
289     public static final String FEATURE_REMOTE_PLAYBACK =
290             "android.media.route.feature.REMOTE_PLAYBACK";
291 
292     /**
293      * Route feature: Remote audio playback.
294      * <p>
295      * A route that supports remote audio playback routing will allow an application to send
296      * requests to play audio content remotely to supported destinations.
297      *
298      * @see #FEATURE_REMOTE_PLAYBACK
299      * @see #FEATURE_REMOTE_VIDEO_PLAYBACK
300      */
301     public static final String FEATURE_REMOTE_AUDIO_PLAYBACK =
302             "android.media.route.feature.REMOTE_AUDIO_PLAYBACK";
303 
304     /**
305      * Route feature: Remote video playback.
306      * <p>
307      * A route that supports remote video playback routing will allow an application to send
308      * requests to play video content remotely to supported destinations.
309      *
310      * @see #FEATURE_REMOTE_PLAYBACK
311      * @see #FEATURE_REMOTE_AUDIO_PLAYBACK
312      */
313     public static final String FEATURE_REMOTE_VIDEO_PLAYBACK =
314             "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
315 
316     /**
317      * Route feature: Remote group playback.
318      * <p>
319      * @hide
320      */
321     public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
322             "android.media.route.feature.REMOTE_GROUP_PLAYBACK";
323 
324     final String mId;
325     final CharSequence mName;
326     final List<String> mFeatures;
327     @Type
328     final int mType;
329     final boolean mIsSystem;
330     final Uri mIconUri;
331     final CharSequence mDescription;
332     @ConnectionState
333     final int mConnectionState;
334     final String mClientPackageName;
335     final int mVolumeHandling;
336     final int mVolumeMax;
337     final int mVolume;
338     final String mAddress;
339     final Bundle mExtras;
340     final String mProviderId;
341 
MediaRoute2Info(@onNull Builder builder)342     MediaRoute2Info(@NonNull Builder builder) {
343         mId = builder.mId;
344         mName = builder.mName;
345         mFeatures = builder.mFeatures;
346         mType = builder.mType;
347         mIsSystem = builder.mIsSystem;
348         mIconUri = builder.mIconUri;
349         mDescription = builder.mDescription;
350         mConnectionState = builder.mConnectionState;
351         mClientPackageName = builder.mClientPackageName;
352         mVolumeHandling = builder.mVolumeHandling;
353         mVolumeMax = builder.mVolumeMax;
354         mVolume = builder.mVolume;
355         mAddress = builder.mAddress;
356         mExtras = builder.mExtras;
357         mProviderId = builder.mProviderId;
358     }
359 
MediaRoute2Info(@onNull Parcel in)360     MediaRoute2Info(@NonNull Parcel in) {
361         mId = in.readString();
362         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
363         mFeatures = in.createStringArrayList();
364         mType = in.readInt();
365         mIsSystem = in.readBoolean();
366         mIconUri = in.readParcelable(null);
367         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
368         mConnectionState = in.readInt();
369         mClientPackageName = in.readString();
370         mVolumeHandling = in.readInt();
371         mVolumeMax = in.readInt();
372         mVolume = in.readInt();
373         mAddress = in.readString();
374         mExtras = in.readBundle();
375         mProviderId = in.readString();
376     }
377 
378     /**
379      * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
380      * unique IDs.
381      * <p>
382      * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
383      * can be different from what was set in {@link MediaRoute2ProviderService}.
384      *
385      * @see Builder#Builder(String, CharSequence)
386      */
387     @NonNull
getId()388     public String getId() {
389         if (mProviderId != null) {
390             return toUniqueId(mProviderId, mId);
391         } else {
392             return mId;
393         }
394     }
395 
396     /**
397      * Gets the user-visible name of the route.
398      */
399     @NonNull
getName()400     public CharSequence getName() {
401         return mName;
402     }
403 
404     /**
405      * Gets the supported features of the route.
406      */
407     @NonNull
getFeatures()408     public List<String> getFeatures() {
409         return mFeatures;
410     }
411 
412     /**
413      * Gets the type of this route.
414      *
415      * @return The type of this route:
416      * {@link #TYPE_UNKNOWN},
417      * {@link #TYPE_BUILTIN_SPEAKER}, {@link #TYPE_WIRED_HEADSET}, {@link #TYPE_WIRED_HEADPHONES},
418      * {@link #TYPE_BLUETOOTH_A2DP}, {@link #TYPE_HDMI}, {@link #TYPE_DOCK},
419      * {@Link #TYPE_USB_DEVICE}, {@link #TYPE_USB_ACCESSORY}, {@link #TYPE_USB_HEADSET}
420      * {@link #TYPE_HEARING_AID},
421      * {@link #TYPE_REMOTE_TV}, {@link #TYPE_REMOTE_SPEAKER}, {@link #TYPE_GROUP}.
422      * @hide
423      */
424     @Type
getType()425     public int getType() {
426         return mType;
427     }
428 
429     /**
430      * Returns whether the route is a system route or not.
431      * <p>
432      * System routes are media routes directly controlled by the system
433      * such as phone speaker, wired headset, and Bluetooth devices.
434      * </p>
435      */
isSystemRoute()436     public boolean isSystemRoute() {
437         return mIsSystem;
438     }
439 
440     /**
441      * Gets the URI of the icon representing this route.
442      * <p>
443      * This icon will be used in picker UIs if available.
444      *
445      * @return The URI of the icon representing this route, or null if none.
446      */
447     @Nullable
getIconUri()448     public Uri getIconUri() {
449         return mIconUri;
450     }
451 
452     /**
453      * Gets the user-visible description of the route.
454      */
455     @Nullable
getDescription()456     public CharSequence getDescription() {
457         return mDescription;
458     }
459 
460     /**
461      * Gets the connection state of the route.
462      *
463      * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
464      * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
465      */
466     @ConnectionState
getConnectionState()467     public int getConnectionState() {
468         return mConnectionState;
469     }
470 
471     /**
472      * Gets the package name of the app using the route.
473      * Returns null if no apps are using this route.
474      */
475     @Nullable
getClientPackageName()476     public String getClientPackageName() {
477         return mClientPackageName;
478     }
479 
480     /**
481      * Gets information about how volume is handled on the route.
482      *
483      * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
484      */
485     @PlaybackVolume
getVolumeHandling()486     public int getVolumeHandling() {
487         return mVolumeHandling;
488     }
489 
490     /**
491      * Gets the maximum volume of the route.
492      */
getVolumeMax()493     public int getVolumeMax() {
494         return mVolumeMax;
495     }
496 
497     /**
498      * Gets the current volume of the route. This may be invalid if the route is not selected.
499      */
getVolume()500     public int getVolume() {
501         return mVolume;
502     }
503 
504     /**
505      * Gets the hardware address of the route if available.
506      * @hide
507      */
508     @Nullable
getAddress()509     public String getAddress() {
510         return mAddress;
511     }
512 
513     @Nullable
getExtras()514     public Bundle getExtras() {
515         return mExtras == null ? null : new Bundle(mExtras);
516     }
517 
518     /**
519      * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
520      * @hide
521      */
522     @NonNull
523     @TestApi
getOriginalId()524     public String getOriginalId() {
525         return mId;
526     }
527 
528     /**
529      * Gets the provider id of the route. It is assigned automatically by
530      * {@link com.android.server.media.MediaRouterService}.
531      *
532      * @return provider id of the route or null if it's not set.
533      * @hide
534      */
535     @Nullable
getProviderId()536     public String getProviderId() {
537         return mProviderId;
538     }
539 
540     /**
541      * Returns if the route has at least one of the specified route features.
542      *
543      * @param features the list of route features to consider
544      * @return true if the route has at least one feature in the list
545      * @hide
546      */
hasAnyFeatures(@onNull Collection<String> features)547     public boolean hasAnyFeatures(@NonNull Collection<String> features) {
548         Objects.requireNonNull(features, "features must not be null");
549         for (String feature : features) {
550             if (getFeatures().contains(feature)) {
551                 return true;
552             }
553         }
554         return false;
555     }
556 
557     /**
558      * Returns true if the route info has all of the required field.
559      * A route is valid if and only if it is obtained from
560      * {@link com.android.server.media.MediaRouterService}.
561      * @hide
562      */
isValid()563     public boolean isValid() {
564         if (TextUtils.isEmpty(getId()) || TextUtils.isEmpty(getName())
565                 || TextUtils.isEmpty(getProviderId())) {
566             return false;
567         }
568         return true;
569     }
570 
571     @Override
equals(Object obj)572     public boolean equals(Object obj) {
573         if (this == obj) {
574             return true;
575         }
576         if (!(obj instanceof MediaRoute2Info)) {
577             return false;
578         }
579         MediaRoute2Info other = (MediaRoute2Info) obj;
580 
581         // Note: mExtras is not included.
582         return Objects.equals(mId, other.mId)
583                 && Objects.equals(mName, other.mName)
584                 && Objects.equals(mFeatures, other.mFeatures)
585                 && (mType == other.mType)
586                 && (mIsSystem == other.mIsSystem)
587                 && Objects.equals(mIconUri, other.mIconUri)
588                 && Objects.equals(mDescription, other.mDescription)
589                 && (mConnectionState == other.mConnectionState)
590                 && Objects.equals(mClientPackageName, other.mClientPackageName)
591                 && (mVolumeHandling == other.mVolumeHandling)
592                 && (mVolumeMax == other.mVolumeMax)
593                 && (mVolume == other.mVolume)
594                 && Objects.equals(mAddress, other.mAddress)
595                 && Objects.equals(mProviderId, other.mProviderId);
596     }
597 
598     @Override
hashCode()599     public int hashCode() {
600         // Note: mExtras is not included.
601         return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
602                 mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
603                 mAddress, mProviderId);
604     }
605 
606     @Override
toString()607     public String toString() {
608         // Note: mExtras is not printed here.
609         StringBuilder result = new StringBuilder()
610                 .append("MediaRoute2Info{ ")
611                 .append("id=").append(getId())
612                 .append(", name=").append(getName())
613                 .append(", features=").append(getFeatures())
614                 .append(", iconUri=").append(getIconUri())
615                 .append(", description=").append(getDescription())
616                 .append(", connectionState=").append(getConnectionState())
617                 .append(", clientPackageName=").append(getClientPackageName())
618                 .append(", volumeHandling=").append(getVolumeHandling())
619                 .append(", volumeMax=").append(getVolumeMax())
620                 .append(", volume=").append(getVolume())
621                 .append(", providerId=").append(getProviderId())
622                 .append(" }");
623         return result.toString();
624     }
625 
626     @Override
describeContents()627     public int describeContents() {
628         return 0;
629     }
630 
631     @Override
writeToParcel(@onNull Parcel dest, int flags)632     public void writeToParcel(@NonNull Parcel dest, int flags) {
633         dest.writeString(mId);
634         TextUtils.writeToParcel(mName, dest, flags);
635         dest.writeStringList(mFeatures);
636         dest.writeInt(mType);
637         dest.writeBoolean(mIsSystem);
638         dest.writeParcelable(mIconUri, flags);
639         TextUtils.writeToParcel(mDescription, dest, flags);
640         dest.writeInt(mConnectionState);
641         dest.writeString(mClientPackageName);
642         dest.writeInt(mVolumeHandling);
643         dest.writeInt(mVolumeMax);
644         dest.writeInt(mVolume);
645         dest.writeString(mAddress);
646         dest.writeBundle(mExtras);
647         dest.writeString(mProviderId);
648     }
649 
650     /**
651      * Builder for {@link MediaRoute2Info media route info}.
652      */
653     public static final class Builder {
654         final String mId;
655         final CharSequence mName;
656         final List<String> mFeatures;
657 
658         @Type
659         int mType = TYPE_UNKNOWN;
660         boolean mIsSystem;
661         Uri mIconUri;
662         CharSequence mDescription;
663         @ConnectionState
664         int mConnectionState;
665         String mClientPackageName;
666         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
667         int mVolumeMax;
668         int mVolume;
669         String mAddress;
670         Bundle mExtras;
671         String mProviderId;
672 
673         /**
674          * Constructor for builder to create {@link MediaRoute2Info}.
675          * <p>
676          * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info
677          * obtained from {@link MediaRouter2} can be different from what was set in
678          * {@link MediaRoute2ProviderService}.
679          * </p>
680          * @param id The ID of the route. Must not be empty.
681          * @param name The user-visible name of the route.
682          */
Builder(@onNull String id, @NonNull CharSequence name)683         public Builder(@NonNull String id, @NonNull CharSequence name) {
684             if (TextUtils.isEmpty(id)) {
685                 throw new IllegalArgumentException("id must not be empty");
686             }
687             if (TextUtils.isEmpty(name)) {
688                 throw new IllegalArgumentException("name must not be empty");
689             }
690             mId = id;
691             mName = name;
692             mFeatures = new ArrayList<>();
693         }
694 
695         /**
696          * Constructor for builder to create {@link MediaRoute2Info} with existing
697          * {@link MediaRoute2Info} instance.
698          *
699          * @param routeInfo the existing instance to copy data from.
700          */
Builder(@onNull MediaRoute2Info routeInfo)701         public Builder(@NonNull MediaRoute2Info routeInfo) {
702             this(routeInfo.mId, routeInfo);
703         }
704 
705         /**
706          * Constructor for builder to create {@link MediaRoute2Info} with existing
707          * {@link MediaRoute2Info} instance and replace ID with the given {@code id}.
708          *
709          * @param id The ID of the new route. Must not be empty.
710          * @param routeInfo the existing instance to copy data from.
711          * @hide
712          */
Builder(@onNull String id, @NonNull MediaRoute2Info routeInfo)713         public Builder(@NonNull String id, @NonNull MediaRoute2Info routeInfo) {
714             if (TextUtils.isEmpty(id)) {
715                 throw new IllegalArgumentException("id must not be empty");
716             }
717             Objects.requireNonNull(routeInfo, "routeInfo must not be null");
718 
719             mId = id;
720             mName = routeInfo.mName;
721             mFeatures = new ArrayList<>(routeInfo.mFeatures);
722             mType = routeInfo.mType;
723             mIsSystem = routeInfo.mIsSystem;
724             mIconUri = routeInfo.mIconUri;
725             mDescription = routeInfo.mDescription;
726             mConnectionState = routeInfo.mConnectionState;
727             mClientPackageName = routeInfo.mClientPackageName;
728             mVolumeHandling = routeInfo.mVolumeHandling;
729             mVolumeMax = routeInfo.mVolumeMax;
730             mVolume = routeInfo.mVolume;
731             mAddress = routeInfo.mAddress;
732             if (routeInfo.mExtras != null) {
733                 mExtras = new Bundle(routeInfo.mExtras);
734             }
735             mProviderId = routeInfo.mProviderId;
736         }
737 
738         /**
739          * Adds a feature for the route.
740          * @param feature a feature that the route has. May be one of predefined features
741          *                such as {@link #FEATURE_LIVE_AUDIO}, {@link #FEATURE_LIVE_VIDEO} or
742          *                {@link #FEATURE_REMOTE_PLAYBACK} or a custom feature defined by
743          *                a provider.
744          *
745          * @see #addFeatures(Collection)
746          */
747         @NonNull
addFeature(@onNull String feature)748         public Builder addFeature(@NonNull String feature) {
749             if (TextUtils.isEmpty(feature)) {
750                 throw new IllegalArgumentException("feature must not be null or empty");
751             }
752             mFeatures.add(feature);
753             return this;
754         }
755 
756         /**
757          * Adds features for the route. A route must support at least one route type.
758          * @param features features that the route has. May include predefined features
759          *                such as {@link #FEATURE_LIVE_AUDIO}, {@link #FEATURE_LIVE_VIDEO} or
760          *                {@link #FEATURE_REMOTE_PLAYBACK} or custom features defined by
761          *                a provider.
762          *
763          * @see #addFeature(String)
764          */
765         @NonNull
addFeatures(@onNull Collection<String> features)766         public Builder addFeatures(@NonNull Collection<String> features) {
767             Objects.requireNonNull(features, "features must not be null");
768             for (String feature : features) {
769                 addFeature(feature);
770             }
771             return this;
772         }
773 
774         /**
775          * Clears the features of the route. A route must support at least one route type.
776          */
777         @NonNull
clearFeatures()778         public Builder clearFeatures() {
779             mFeatures.clear();
780             return this;
781         }
782 
783         /**
784          * Sets the route's type.
785          * @hide
786          */
787         @NonNull
setType(@ype int type)788         public Builder setType(@Type int type) {
789             mType = type;
790             return this;
791         }
792 
793         /**
794          * Sets whether the route is a system route or not.
795          * @hide
796          */
797         @NonNull
setSystemRoute(boolean isSystem)798         public Builder setSystemRoute(boolean isSystem) {
799             mIsSystem = isSystem;
800             return this;
801         }
802 
803         /**
804          * Sets the URI of the icon representing this route.
805          * <p>
806          * This icon will be used in picker UIs if available.
807          * </p><p>
808          * The URI must be one of the following formats:
809          * <ul>
810          * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
811          * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
812          * </li>
813          * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
814          * </ul>
815          * </p>
816          */
817         @NonNull
setIconUri(@ullable Uri iconUri)818         public Builder setIconUri(@Nullable Uri iconUri) {
819             mIconUri = iconUri;
820             return this;
821         }
822 
823         /**
824          * Sets the user-visible description of the route.
825          */
826         @NonNull
setDescription(@ullable CharSequence description)827         public Builder setDescription(@Nullable CharSequence description) {
828             mDescription = description;
829             return this;
830         }
831 
832         /**
833         * Sets the route's connection state.
834         *
835         * {@link #CONNECTION_STATE_DISCONNECTED},
836         * {@link #CONNECTION_STATE_CONNECTING}, or
837         * {@link #CONNECTION_STATE_CONNECTED}.
838         */
839         @NonNull
setConnectionState(@onnectionState int connectionState)840         public Builder setConnectionState(@ConnectionState int connectionState) {
841             mConnectionState = connectionState;
842             return this;
843         }
844 
845         /**
846          * Sets the package name of the app using the route.
847          */
848         @NonNull
setClientPackageName(@ullable String packageName)849         public Builder setClientPackageName(@Nullable String packageName) {
850             mClientPackageName = packageName;
851             return this;
852         }
853 
854         /**
855          * Sets the route's volume handling.
856          */
857         @NonNull
setVolumeHandling(@laybackVolume int volumeHandling)858         public Builder setVolumeHandling(@PlaybackVolume int volumeHandling) {
859             mVolumeHandling = volumeHandling;
860             return this;
861         }
862 
863         /**
864          * Sets the route's maximum volume, or 0 if unknown.
865          */
866         @NonNull
setVolumeMax(int volumeMax)867         public Builder setVolumeMax(int volumeMax) {
868             mVolumeMax = volumeMax;
869             return this;
870         }
871 
872         /**
873          * Sets the route's current volume, or 0 if unknown.
874          */
875         @NonNull
setVolume(int volume)876         public Builder setVolume(int volume) {
877             mVolume = volume;
878             return this;
879         }
880 
881         /**
882          * Sets the hardware address of the route.
883          * @hide
884          */
885         @NonNull
setAddress(String address)886         public Builder setAddress(String address) {
887             mAddress = address;
888             return this;
889         }
890 
891         /**
892          * Sets a bundle of extras for the route.
893          * <p>
894          * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
895          */
896         @NonNull
setExtras(@ullable Bundle extras)897         public Builder setExtras(@Nullable Bundle extras) {
898             if (extras == null) {
899                 mExtras = null;
900                 return this;
901             }
902             mExtras = new Bundle(extras);
903             return this;
904         }
905 
906         /**
907          * Sets the provider id of the route.
908          * @hide
909          */
910         @NonNull
setProviderId(@onNull String providerId)911         public Builder setProviderId(@NonNull String providerId) {
912             if (TextUtils.isEmpty(providerId)) {
913                 throw new IllegalArgumentException("providerId must not be null or empty");
914             }
915             mProviderId = providerId;
916             return this;
917         }
918 
919         /**
920          * Builds the {@link MediaRoute2Info media route info}.
921          *
922          * @throws IllegalArgumentException if no features are added.
923          */
924         @NonNull
build()925         public MediaRoute2Info build() {
926             if (mFeatures.isEmpty()) {
927                 throw new IllegalArgumentException("features must not be empty!");
928             }
929             return new MediaRoute2Info(this);
930         }
931     }
932 }
933