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.os.Bundle;
20import android.support.annotation.NonNull;
21import android.support.annotation.Nullable;
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 capabilities of routes that applications would like to discover and use.
31 * <p>
32 * This object is immutable once created using a {@link Builder} instance.
33 * </p>
34 *
35 * <h3>Example</h3>
36 * <pre>
37 * MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
38 *         .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
39 *         .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
40 *         .build();
41 *
42 * MediaRouter router = MediaRouter.getInstance(context);
43 * router.addCallback(selector, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
44 * </pre>
45 */
46public final class MediaRouteSelector {
47    static final String KEY_CONTROL_CATEGORIES = "controlCategories";
48
49    private final Bundle mBundle;
50    List<String> mControlCategories;
51
52    /**
53     * An empty media route selector that will not match any routes.
54     */
55    public static final MediaRouteSelector EMPTY = new MediaRouteSelector(new Bundle(), null);
56
57    MediaRouteSelector(Bundle bundle, List<String> controlCategories) {
58        mBundle = bundle;
59        mControlCategories = controlCategories;
60    }
61
62    /**
63     * Gets the list of {@link MediaControlIntent media control categories} in the selector.
64     *
65     * @return The list of categories.
66     */
67    public List<String> getControlCategories() {
68        ensureControlCategories();
69        return mControlCategories;
70    }
71
72    void ensureControlCategories() {
73        if (mControlCategories == null) {
74            mControlCategories = mBundle.getStringArrayList(KEY_CONTROL_CATEGORIES);
75            if (mControlCategories == null || mControlCategories.isEmpty()) {
76                mControlCategories = Collections.<String>emptyList();
77            }
78        }
79    }
80
81    /**
82     * Returns true if the selector contains the specified category.
83     *
84     * @param category The category to check.
85     * @return True if the category is present.
86     */
87    public boolean hasControlCategory(String category) {
88        if (category != null) {
89            ensureControlCategories();
90            final int categoryCount = mControlCategories.size();
91            for (int i = 0; i < categoryCount; i++) {
92                if (mControlCategories.get(i).equals(category)) {
93                    return true;
94                }
95            }
96        }
97        return false;
98    }
99
100    /**
101     * Returns true if the selector matches at least one of the specified control filters.
102     *
103     * @param filters The list of control filters to consider.
104     * @return True if a match is found.
105     */
106    public boolean matchesControlFilters(List<IntentFilter> filters) {
107        if (filters != null) {
108            ensureControlCategories();
109            final int categoryCount = mControlCategories.size();
110            if (categoryCount != 0) {
111                final int filterCount = filters.size();
112                for (int i = 0; i < filterCount; i++) {
113                    final IntentFilter filter = filters.get(i);
114                    if (filter != null) {
115                        for (int j = 0; j < categoryCount; j++) {
116                            if (filter.hasCategory(mControlCategories.get(j))) {
117                                return true;
118                            }
119                        }
120                    }
121                }
122            }
123        }
124        return false;
125    }
126
127    /**
128     * Returns true if this selector contains all of the capabilities described
129     * by the specified selector.
130     *
131     * @param selector The selector to be examined.
132     * @return True if this selector contains all of the capabilities described
133     * by the specified selector.
134     */
135    public boolean contains(MediaRouteSelector selector) {
136        if (selector != null) {
137            ensureControlCategories();
138            selector.ensureControlCategories();
139            return mControlCategories.containsAll(selector.mControlCategories);
140        }
141        return false;
142    }
143
144    /**
145     * Returns true if the selector does not specify any capabilities.
146     */
147    public boolean isEmpty() {
148        ensureControlCategories();
149        return mControlCategories.isEmpty();
150    }
151
152    /**
153     * Returns true if the selector has all of the required fields.
154     */
155    public boolean isValid() {
156        ensureControlCategories();
157        if (mControlCategories.contains(null)) {
158            return false;
159        }
160        return true;
161    }
162
163    @Override
164    public boolean equals(Object o) {
165        if (o instanceof MediaRouteSelector) {
166            MediaRouteSelector other = (MediaRouteSelector)o;
167            ensureControlCategories();
168            other.ensureControlCategories();
169            return mControlCategories.equals(other.mControlCategories);
170        }
171        return false;
172    }
173
174    @Override
175    public int hashCode() {
176        ensureControlCategories();
177        return mControlCategories.hashCode();
178    }
179
180    @Override
181    public String toString() {
182        StringBuilder result = new StringBuilder();
183        result.append("MediaRouteSelector{ ");
184        result.append("controlCategories=").append(
185                Arrays.toString(getControlCategories().toArray()));
186        result.append(" }");
187        return result.toString();
188    }
189
190    /**
191     * Converts this object to a bundle for serialization.
192     *
193     * @return The contents of the object represented as a bundle.
194     */
195    public Bundle asBundle() {
196        return mBundle;
197    }
198
199    /**
200     * Creates an instance from a bundle.
201     *
202     * @param bundle The bundle, or null if none.
203     * @return The new instance, or null if the bundle was null.
204     */
205    public static MediaRouteSelector fromBundle(@Nullable Bundle bundle) {
206        return bundle != null ? new MediaRouteSelector(bundle, null) : null;
207    }
208
209    /**
210     * Builder for {@link MediaRouteSelector media route selectors}.
211     */
212    public static final class Builder {
213        private ArrayList<String> mControlCategories;
214
215        /**
216         * Creates an empty media route selector builder.
217         */
218        public Builder() {
219        }
220
221        /**
222         * Creates a media route selector descriptor builder whose initial contents are
223         * copied from an existing selector.
224         */
225        public Builder(@NonNull MediaRouteSelector selector) {
226            if (selector == null) {
227                throw new IllegalArgumentException("selector must not be null");
228            }
229
230            selector.ensureControlCategories();
231            if (!selector.mControlCategories.isEmpty()) {
232                mControlCategories = new ArrayList<String>(selector.mControlCategories);
233            }
234        }
235
236        /**
237         * Adds a {@link MediaControlIntent media control category} to the builder.
238         *
239         * @param category The category to add to the set of desired capabilities, such as
240         * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
241         * @return The builder instance for chaining.
242         */
243        @NonNull
244        public Builder addControlCategory(@NonNull String category) {
245            if (category == null) {
246                throw new IllegalArgumentException("category must not be null");
247            }
248
249            if (mControlCategories == null) {
250                mControlCategories = new ArrayList<String>();
251            }
252            if (!mControlCategories.contains(category)) {
253                mControlCategories.add(category);
254            }
255            return this;
256        }
257
258        /**
259         * Adds a list of {@link MediaControlIntent media control categories} to the builder.
260         *
261         * @param categories The list categories to add to the set of desired capabilities,
262         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
263         * @return The builder instance for chaining.
264         */
265        @NonNull
266        public Builder addControlCategories(@NonNull Collection<String> categories) {
267            if (categories == null) {
268                throw new IllegalArgumentException("categories must not be null");
269            }
270
271            if (!categories.isEmpty()) {
272                for (String category : categories) {
273                    addControlCategory(category);
274                }
275            }
276            return this;
277        }
278
279        /**
280         * Adds the contents of an existing media route selector to the builder.
281         *
282         * @param selector The media route selector whose contents are to be added.
283         * @return The builder instance for chaining.
284         */
285        @NonNull
286        public Builder addSelector(@NonNull MediaRouteSelector selector) {
287            if (selector == null) {
288                throw new IllegalArgumentException("selector must not be null");
289            }
290
291            addControlCategories(selector.getControlCategories());
292            return this;
293        }
294
295        /**
296         * Builds the {@link MediaRouteSelector media route selector}.
297         */
298        @NonNull
299        public MediaRouteSelector build() {
300            if (mControlCategories == null) {
301                return EMPTY;
302            }
303            Bundle bundle = new Bundle();
304            bundle.putStringArrayList(KEY_CONTROL_CATEGORIES, mControlCategories);
305            return new MediaRouteSelector(bundle, mControlCategories);
306        }
307    }
308}