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