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 android.content.IntentFilter;
19import android.content.IntentSender;
20import android.os.Bundle;
21import android.text.TextUtils;
22
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.List;
28
29/**
30 * Describes the properties of a route.
31 * <p>
32 * Each route is uniquely identified by an opaque id string.  This token
33 * may take any form as long as it is unique within the media route provider.
34 * </p><p>
35 * This object is immutable once created using a {@link Builder} instance.
36 * </p>
37 */
38public final class MediaRouteDescriptor {
39    private static final String KEY_ID = "id";
40    private static final String KEY_NAME = "name";
41    private static final String KEY_DESCRIPTION = "status";
42    private static final String KEY_ENABLED = "enabled";
43    private static final String KEY_CONNECTING = "connecting";
44    private static final String KEY_CONTROL_FILTERS = "controlFilters";
45    private static final String KEY_PLAYBACK_TYPE = "playbackType";
46    private static final String KEY_PLAYBACK_STREAM = "playbackStream";
47    private static final String KEY_VOLUME = "volume";
48    private static final String KEY_VOLUME_MAX = "volumeMax";
49    private static final String KEY_VOLUME_HANDLING = "volumeHandling";
50    private static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId";
51    private static final String KEY_EXTRAS = "extras";
52    private static final String KEY_CAN_DISCONNECT = "canDisconnect";
53    private static final String KEY_SETTINGS_INTENT = "settingsIntent";
54
55    private final Bundle mBundle;
56    private List<IntentFilter> mControlFilters;
57
58    private MediaRouteDescriptor(Bundle bundle, List<IntentFilter> controlFilters) {
59        mBundle = bundle;
60        mControlFilters = controlFilters;
61    }
62
63    /**
64     * Gets the unique id of the route.
65     * <p>
66     * The route id associated with a route descriptor functions as a stable
67     * identifier for the route and must be unique among all routes offered
68     * by the provider.
69     * </p>
70     */
71    public String getId() {
72        return mBundle.getString(KEY_ID);
73    }
74
75    /**
76     * Gets the user-visible name of the route.
77     * <p>
78     * The route name identifies the destination represented by the route.
79     * It may be a user-supplied name, an alias, or device serial number.
80     * </p>
81     */
82    public String getName() {
83        return mBundle.getString(KEY_NAME);
84    }
85
86    /**
87     * Gets the user-visible description of the route.
88     * <p>
89     * The route description describes the kind of destination represented by the route.
90     * It may be a user-supplied string, a model number or brand of device.
91     * </p>
92     */
93    public String getDescription() {
94        return mBundle.getString(KEY_DESCRIPTION);
95    }
96
97    /**
98     * Gets whether the route is enabled.
99     */
100    public boolean isEnabled() {
101        return mBundle.getBoolean(KEY_ENABLED, true);
102    }
103
104    /**
105     * Gets whether the route is connecting.
106     */
107    public boolean isConnecting() {
108        return mBundle.getBoolean(KEY_CONNECTING, false);
109    }
110
111    /**
112     * Gets whether the route can be disconnected without stopping playback. To
113     * specify that the route should disconnect without stopping use
114     * {@link MediaRouter#unselect(int)} with
115     * {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}.
116     */
117    public boolean canDisconnectAndKeepPlaying() {
118        return mBundle.getBoolean(KEY_CAN_DISCONNECT, false);
119    }
120
121    /**
122     * Gets an {@link IntentSender} for starting a settings activity for this
123     * route. The activity may have specific route settings or general settings
124     * for the connected device or route provider.
125     *
126     * @return An {@link IntentSender} to start a settings activity.
127     */
128    public IntentSender getSettingsActivity() {
129        return mBundle.getParcelable(KEY_SETTINGS_INTENT);
130    }
131
132    /**
133     * Gets the route's {@link MediaControlIntent media control intent} filters.
134     */
135    public List<IntentFilter> getControlFilters() {
136        ensureControlFilters();
137        return mControlFilters;
138    }
139
140    private void ensureControlFilters() {
141        if (mControlFilters == null) {
142            mControlFilters = mBundle.<IntentFilter>getParcelableArrayList(KEY_CONTROL_FILTERS);
143            if (mControlFilters == null) {
144                mControlFilters = Collections.<IntentFilter>emptyList();
145            }
146        }
147    }
148
149    /**
150     * Gets the route's playback type.
151     */
152    public int getPlaybackType() {
153        return mBundle.getInt(KEY_PLAYBACK_TYPE, MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE);
154    }
155
156    /**
157     * Gets the route's playback stream.
158     */
159    public int getPlaybackStream() {
160        return mBundle.getInt(KEY_PLAYBACK_STREAM, -1);
161    }
162
163    /**
164     * Gets the route's current volume, or 0 if unknown.
165     */
166    public int getVolume() {
167        return mBundle.getInt(KEY_VOLUME);
168    }
169
170    /**
171     * Gets the route's maximum volume, or 0 if unknown.
172     */
173    public int getVolumeMax() {
174        return mBundle.getInt(KEY_VOLUME_MAX);
175    }
176
177    /**
178     * Gets the route's volume handling.
179     */
180    public int getVolumeHandling() {
181        return mBundle.getInt(KEY_VOLUME_HANDLING,
182                MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED);
183    }
184
185    /**
186     * Gets the route's presentation display id, or -1 if none.
187     */
188    public int getPresentationDisplayId() {
189        return mBundle.getInt(KEY_PRESENTATION_DISPLAY_ID, -1);
190    }
191
192    /**
193     * Gets a bundle of extras for this route descriptor.
194     * The extras will be ignored by the media router but they may be used
195     * by applications.
196     */
197    public Bundle getExtras() {
198        return mBundle.getBundle(KEY_EXTRAS);
199    }
200
201    /**
202     * Returns true if the route descriptor has all of the required fields.
203     */
204    public boolean isValid() {
205        ensureControlFilters();
206        if (TextUtils.isEmpty(getId())
207                || TextUtils.isEmpty(getName())
208                || mControlFilters.contains(null)) {
209            return false;
210        }
211        return true;
212    }
213
214    @Override
215    public String toString() {
216        StringBuilder result = new StringBuilder();
217        result.append("MediaRouteDescriptor{ ");
218        result.append("id=").append(getId());
219        result.append(", name=").append(getName());
220        result.append(", description=").append(getDescription());
221        result.append(", isEnabled=").append(isEnabled());
222        result.append(", isConnecting=").append(isConnecting());
223        result.append(", controlFilters=").append(Arrays.toString(getControlFilters().toArray()));
224        result.append(", playbackType=").append(getPlaybackType());
225        result.append(", playbackStream=").append(getPlaybackStream());
226        result.append(", volume=").append(getVolume());
227        result.append(", volumeMax=").append(getVolumeMax());
228        result.append(", volumeHandling=").append(getVolumeHandling());
229        result.append(", presentationDisplayId=").append(getPresentationDisplayId());
230        result.append(", extras=").append(getExtras());
231        result.append(", isValid=").append(isValid());
232        result.append(" }");
233        return result.toString();
234    }
235
236    /**
237     * Converts this object to a bundle for serialization.
238     *
239     * @return The contents of the object represented as a bundle.
240     */
241    public Bundle asBundle() {
242        return mBundle;
243    }
244
245    /**
246     * Creates an instance from a bundle.
247     *
248     * @param bundle The bundle, or null if none.
249     * @return The new instance, or null if the bundle was null.
250     */
251    public static MediaRouteDescriptor fromBundle(Bundle bundle) {
252        return bundle != null ? new MediaRouteDescriptor(bundle, null) : null;
253    }
254
255    /**
256     * Builder for {@link MediaRouteDescriptor media route descriptors}.
257     */
258    public static final class Builder {
259        private final Bundle mBundle;
260        private ArrayList<IntentFilter> mControlFilters;
261
262        /**
263         * Creates a media route descriptor builder.
264         *
265         * @param id The unique id of the route.
266         * @param name The user-visible name of the route.
267         */
268        public Builder(String id, String name) {
269            mBundle = new Bundle();
270            setId(id);
271            setName(name);
272        }
273
274        /**
275         * Creates a media route descriptor builder whose initial contents are
276         * copied from an existing descriptor.
277         */
278        public Builder(MediaRouteDescriptor descriptor) {
279            if (descriptor == null) {
280                throw new IllegalArgumentException("descriptor must not be null");
281            }
282
283            mBundle = new Bundle(descriptor.mBundle);
284
285            descriptor.ensureControlFilters();
286            if (!descriptor.mControlFilters.isEmpty()) {
287                mControlFilters = new ArrayList<IntentFilter>(descriptor.mControlFilters);
288            }
289        }
290
291        /**
292         * Sets the unique id of the route.
293         * <p>
294         * The route id associated with a route descriptor functions as a stable
295         * identifier for the route and must be unique among all routes offered
296         * by the provider.
297         * </p>
298         */
299        public Builder setId(String id) {
300            mBundle.putString(KEY_ID, id);
301            return this;
302        }
303
304        /**
305         * Sets the user-visible name of the route.
306         * <p>
307         * The route name identifies the destination represented by the route.
308         * It may be a user-supplied name, an alias, or device serial number.
309         * </p>
310         */
311        public Builder setName(String name) {
312            mBundle.putString(KEY_NAME, name);
313            return this;
314        }
315
316        /**
317         * Sets the user-visible description of the route.
318         * <p>
319         * The route description describes the kind of destination represented by the route.
320         * It may be a user-supplied string, a model number or brand of device.
321         * </p>
322         */
323        public Builder setDescription(String description) {
324            mBundle.putString(KEY_DESCRIPTION, description);
325            return this;
326        }
327
328        /**
329         * Sets whether the route is enabled.
330         * <p>
331         * Disabled routes represent routes that a route provider knows about, such as paired
332         * Wifi Display receivers, but that are not currently available for use.
333         * </p>
334         */
335        public Builder setEnabled(boolean enabled) {
336            mBundle.putBoolean(KEY_ENABLED, enabled);
337            return this;
338        }
339
340        /**
341         * Sets whether the route is in the process of connecting and is not yet
342         * ready for use.
343         */
344        public Builder setConnecting(boolean connecting) {
345            mBundle.putBoolean(KEY_CONNECTING, connecting);
346            return this;
347        }
348
349        /**
350         * Sets whether the route can be disconnected without stopping playback.
351         */
352        public Builder setCanDisconnect(boolean canDisconnect) {
353            mBundle.putBoolean(KEY_CAN_DISCONNECT, canDisconnect);
354            return this;
355        }
356
357        /**
358         * Sets an intent sender for launching the settings activity for this
359         * route.
360         */
361        public Builder setSettingsActivity(IntentSender is) {
362            mBundle.putParcelable(KEY_SETTINGS_INTENT, is);
363            return this;
364        }
365
366        /**
367         * Adds a {@link MediaControlIntent media control intent} filter for the route.
368         */
369        public Builder addControlFilter(IntentFilter filter) {
370            if (filter == null) {
371                throw new IllegalArgumentException("filter must not be null");
372            }
373
374            if (mControlFilters == null) {
375                mControlFilters = new ArrayList<IntentFilter>();
376            }
377            if (!mControlFilters.contains(filter)) {
378                mControlFilters.add(filter);
379            }
380            return this;
381        }
382
383        /**
384         * Adds a list of {@link MediaControlIntent media control intent} filters for the route.
385         */
386        public Builder addControlFilters(Collection<IntentFilter> filters) {
387            if (filters == null) {
388                throw new IllegalArgumentException("filters must not be null");
389            }
390
391            if (!filters.isEmpty()) {
392                for (IntentFilter filter : filters) {
393                    addControlFilter(filter);
394                }
395            }
396            return this;
397        }
398
399        /**
400         * Sets the route's playback type.
401         */
402        public Builder setPlaybackType(int playbackType) {
403            mBundle.putInt(KEY_PLAYBACK_TYPE, playbackType);
404            return this;
405        }
406
407        /**
408         * Sets the route's playback stream.
409         */
410        public Builder setPlaybackStream(int playbackStream) {
411            mBundle.putInt(KEY_PLAYBACK_STREAM, playbackStream);
412            return this;
413        }
414
415        /**
416         * Sets the route's current volume, or 0 if unknown.
417         */
418        public Builder setVolume(int volume) {
419            mBundle.putInt(KEY_VOLUME, volume);
420            return this;
421        }
422
423        /**
424         * Sets the route's maximum volume, or 0 if unknown.
425         */
426        public Builder setVolumeMax(int volumeMax) {
427            mBundle.putInt(KEY_VOLUME_MAX, volumeMax);
428            return this;
429        }
430
431        /**
432         * Sets the route's volume handling.
433         */
434        public Builder setVolumeHandling(int volumeHandling) {
435            mBundle.putInt(KEY_VOLUME_HANDLING, volumeHandling);
436            return this;
437        }
438
439        /**
440         * Sets the route's presentation display id, or -1 if none.
441         */
442        public Builder setPresentationDisplayId(int presentationDisplayId) {
443            mBundle.putInt(KEY_PRESENTATION_DISPLAY_ID, presentationDisplayId);
444            return this;
445        }
446
447        /**
448         * Sets a bundle of extras for this route descriptor.
449         * The extras will be ignored by the media router but they may be used
450         * by applications.
451         */
452        public Builder setExtras(Bundle extras) {
453            mBundle.putBundle(KEY_EXTRAS, extras);
454            return this;
455        }
456
457        /**
458         * Builds the {@link MediaRouteDescriptor media route descriptor}.
459         */
460        public MediaRouteDescriptor build() {
461            if (mControlFilters != null) {
462                mBundle.putParcelableArrayList(KEY_CONTROL_FILTERS, mControlFilters);
463            }
464            return new MediaRouteDescriptor(mBundle, mControlFilters);
465        }
466    }
467}