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