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