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 */
16package android.support.v7.media;
17
18import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
19
20import android.content.IntentFilter;
21import android.content.IntentSender;
22import android.net.Uri;
23import android.os.Bundle;
24import android.support.annotation.RestrictTo;
25import android.text.TextUtils;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Collection;
30import java.util.Collections;
31import 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 */
42public 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
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     */
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)
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
191    public List<IntentFilter> getControlFilters() {
192        ensureControlFilters();
193        return mControlFilters;
194    }
195
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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)
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)
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     */
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
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     */
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     */
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         */
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         */
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         */
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)
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)
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         */
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         */
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         */
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         */
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
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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)
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)
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         */
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