1 /*
2  * Copyright 2020 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 
17 package android.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.os.Bundle;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.text.TextUtils;
26 
27 import java.io.FileDescriptor;
28 import java.io.PrintWriter;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36 
37 /**
38  * A media route discovery preference describing the features of routes that media router
39  * would like to discover and whether to perform active scanning.
40  * <p>
41  * When {@link MediaRouter2} instances set discovery preferences by calling
42  * {@link MediaRouter2#registerRouteCallback}, they are merged into a single discovery preference
43  * and it is delivered to call {@link MediaRoute2ProviderService#onDiscoveryPreferenceChanged}.
44  * </p><p>
45  * According to the given discovery preference, {@link MediaRoute2ProviderService} discovers
46  * routes and publishes them.
47  * </p>
48  *
49  * @see MediaRouter2#registerRouteCallback
50  */
51 public final class RouteDiscoveryPreference implements Parcelable {
52     @NonNull
53     public static final Creator<RouteDiscoveryPreference> CREATOR =
54             new Creator<RouteDiscoveryPreference>() {
55                 @Override
56                 public RouteDiscoveryPreference createFromParcel(Parcel in) {
57                     return new RouteDiscoveryPreference(in);
58                 }
59 
60                 @Override
61                 public RouteDiscoveryPreference[] newArray(int size) {
62                     return new RouteDiscoveryPreference[size];
63                 }
64             };
65 
66     @NonNull
67     private final List<String> mPreferredFeatures;
68     @NonNull
69     private final List<String> mPackageOrder;
70     @NonNull
71     private final List<String> mAllowedPackages;
72 
73     private final boolean mShouldPerformActiveScan;
74     @Nullable
75     private final Bundle mExtras;
76 
77     /**
78      * An empty discovery preference.
79      * @hide
80      */
81     @SystemApi
82     public static final RouteDiscoveryPreference EMPTY =
83             new Builder(Collections.emptyList(), false).build();
84 
RouteDiscoveryPreference(@onNull Builder builder)85     RouteDiscoveryPreference(@NonNull Builder builder) {
86         mPreferredFeatures = builder.mPreferredFeatures;
87         mPackageOrder = builder.mPackageOrder;
88         mAllowedPackages = builder.mAllowedPackages;
89         mShouldPerformActiveScan = builder.mActiveScan;
90         mExtras = builder.mExtras;
91     }
92 
RouteDiscoveryPreference(@onNull Parcel in)93     RouteDiscoveryPreference(@NonNull Parcel in) {
94         mPreferredFeatures = in.createStringArrayList();
95         mPackageOrder = in.createStringArrayList();
96         mAllowedPackages = in.createStringArrayList();
97         mShouldPerformActiveScan = in.readBoolean();
98         mExtras = in.readBundle();
99     }
100 
101     /**
102      * Gets the features of routes that media router would like to discover.
103      * <p>
104      * Routes that have at least one of the features will be discovered.
105      * They may include predefined features such as
106      * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
107      * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
108      * </p>
109      */
110     @NonNull
getPreferredFeatures()111     public List<String> getPreferredFeatures() {
112         return mPreferredFeatures;
113     }
114 
115     /**
116      * Gets the ordered list of package names used to remove duplicate routes.
117      * <p>
118      * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated
119      * based on their {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. If two routes
120      * have a deduplication ID in common, only the route from the provider whose package name is
121      * first in the provided list will remain.
122      *
123      * @see #shouldRemoveDuplicates()
124      * @hide
125      */
126     @NonNull
getDeduplicationPackageOrder()127     public List<String> getDeduplicationPackageOrder() {
128         return mPackageOrder;
129     }
130 
131     /**
132      * Gets the list of allowed packages.
133      * <p>
134      * If it's not empty, it will only discover routes from the provider whose package name
135      * belongs to the list.
136      * @hide
137      */
138     @NonNull
getAllowedPackages()139     public List<String> getAllowedPackages() {
140         return mAllowedPackages;
141     }
142 
143     /**
144      * Gets whether active scanning should be performed.
145      * <p>
146      * If any of discovery preferences sets this as {@code true}, active scanning will
147      * be performed regardless of other discovery preferences.
148      * </p>
149      */
shouldPerformActiveScan()150     public boolean shouldPerformActiveScan() {
151         return mShouldPerformActiveScan;
152     }
153 
154     /**
155      * Gets whether duplicate routes removal is enabled.
156      *
157      * @see #getDeduplicationPackageOrder()
158      * @hide
159      */
shouldRemoveDuplicates()160     public boolean shouldRemoveDuplicates() {
161         return !mPackageOrder.isEmpty();
162     }
163 
164     /**
165      * @hide
166      */
getExtras()167     public Bundle getExtras() {
168         return mExtras;
169     }
170 
171     @Override
describeContents()172     public int describeContents() {
173         return 0;
174     }
175 
176     @Override
writeToParcel(@onNull Parcel dest, int flags)177     public void writeToParcel(@NonNull Parcel dest, int flags) {
178         dest.writeStringList(mPreferredFeatures);
179         dest.writeStringList(mPackageOrder);
180         dest.writeStringList(mAllowedPackages);
181         dest.writeBoolean(mShouldPerformActiveScan);
182         dest.writeBundle(mExtras);
183     }
184 
185     /**
186      * Dumps current state of the instance. Use with {@code dumpsys}.
187      *
188      * See {@link android.os.Binder#dump(FileDescriptor, PrintWriter, String[])}.
189      *
190      * @hide
191      */
dump(@onNull PrintWriter pw, @NonNull String prefix)192     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
193         pw.println(prefix + "RouteDiscoveryPreference");
194 
195         String indent = prefix + "  ";
196 
197         pw.println(indent + "mShouldPerformActiveScan=" + mShouldPerformActiveScan);
198         pw.println(indent + "mPreferredFeatures=" + mPreferredFeatures);
199         pw.println(indent + "mPackageOrder=" + mPackageOrder);
200         pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
201         pw.println(indent + "mExtras=" + mExtras);
202     }
203 
204     @Override
toString()205     public String toString() {
206         StringBuilder result = new StringBuilder()
207                 .append("RouteDiscoveryRequest{ ")
208                 .append("preferredFeatures={")
209                 .append(String.join(", ", mPreferredFeatures))
210                 .append("}")
211                 .append(", activeScan=")
212                 .append(mShouldPerformActiveScan)
213                 .append(" }");
214 
215         return result.toString();
216     }
217 
218     @Override
equals(Object o)219     public boolean equals(Object o) {
220         if (this == o) {
221             return true;
222         }
223         if (!(o instanceof RouteDiscoveryPreference)) {
224             return false;
225         }
226         RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
227         return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
228                 && Objects.equals(mPackageOrder, other.mPackageOrder)
229                 && Objects.equals(mAllowedPackages, other.mAllowedPackages)
230                 && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
231     }
232 
233     @Override
hashCode()234     public int hashCode() {
235         return Objects.hash(mPreferredFeatures, mPackageOrder, mAllowedPackages,
236                 mShouldPerformActiveScan);
237     }
238 
239     /**
240      * Builder for {@link RouteDiscoveryPreference}.
241      */
242     public static final class Builder {
243         List<String> mPreferredFeatures;
244         List<String> mPackageOrder;
245         List<String> mAllowedPackages;
246 
247         boolean mActiveScan;
248 
249         Bundle mExtras;
250 
Builder(@onNull List<String> preferredFeatures, boolean activeScan)251         public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
252             Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
253             mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
254                     .collect(Collectors.toList());
255             mPackageOrder = List.of();
256             mAllowedPackages = List.of();
257             mActiveScan = activeScan;
258         }
259 
Builder(@onNull RouteDiscoveryPreference preference)260         public Builder(@NonNull RouteDiscoveryPreference preference) {
261             Objects.requireNonNull(preference, "preference must not be null");
262 
263             mPreferredFeatures = preference.getPreferredFeatures();
264             mPackageOrder = preference.getDeduplicationPackageOrder();
265             mAllowedPackages = preference.getAllowedPackages();
266             mActiveScan = preference.shouldPerformActiveScan();
267             mExtras = preference.getExtras();
268         }
269 
270         /**
271          * A constructor to combine multiple preferences into a single preference.
272          * It ignores extras of preferences.
273          *
274          * @hide
275          */
Builder(@onNull Collection<RouteDiscoveryPreference> preferences)276         public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) {
277             Objects.requireNonNull(preferences, "preferences must not be null");
278 
279             Set<String> preferredFeatures = new HashSet<>();
280             Set<String> allowedPackages = new HashSet<>();
281             mPackageOrder = List.of();
282             boolean activeScan = false;
283             for (RouteDiscoveryPreference preference : preferences) {
284                 preferredFeatures.addAll(preference.mPreferredFeatures);
285 
286                 allowedPackages.addAll(preference.mAllowedPackages);
287                 activeScan |= preference.mShouldPerformActiveScan;
288                 // Choose one of either
289                 if (mPackageOrder.isEmpty() && !preference.mPackageOrder.isEmpty()) {
290                     mPackageOrder = List.copyOf(preference.mPackageOrder);
291                 }
292             }
293             mPreferredFeatures = List.copyOf(preferredFeatures);
294             mAllowedPackages = List.copyOf(allowedPackages);
295             mActiveScan = activeScan;
296         }
297 
298         /**
299          * Sets preferred route features to discover.
300          * @param preferredFeatures features of routes that media router would like to discover.
301          *                          May include predefined features
302          *                          such as {@link MediaRoute2Info#FEATURE_LIVE_AUDIO},
303          *                          {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
304          *                          or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK}
305          *                          or custom features defined by a provider.
306          */
307         @NonNull
setPreferredFeatures(@onNull List<String> preferredFeatures)308         public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
309             Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
310             mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
311                     .collect(Collectors.toList());
312             return this;
313         }
314 
315         /**
316          * Sets the list of package names of providers that media router would like to discover.
317          * <p>
318          * If it's non-empty, media router only discovers route from the provider in the list.
319          * The default value is empty, which discovers routes from all providers.
320          * @hide
321          */
322         @NonNull
setAllowedPackages(@onNull List<String> allowedPackages)323         public Builder setAllowedPackages(@NonNull List<String> allowedPackages) {
324             Objects.requireNonNull(allowedPackages, "allowedPackages must not be null");
325             mAllowedPackages = List.copyOf(allowedPackages);
326             return this;
327         }
328 
329         /**
330          * Sets if active scanning should be performed.
331          * <p>
332          * Since active scanning uses more system resources, set this as {@code true} only
333          * when it's necessary.
334          * </p>
335          */
336         @NonNull
setShouldPerformActiveScan(boolean activeScan)337         public Builder setShouldPerformActiveScan(boolean activeScan) {
338             mActiveScan = activeScan;
339             return this;
340         }
341 
342         /**
343          * Sets the order of packages to use when removing duplicate routes.
344          * <p>
345          * Routes are deduplicated based on their
346          * {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}.
347          * If two routes have a deduplication ID in common, only the route from the provider whose
348          * package name is first in the provided list will remain.
349          *
350          * @param packageOrder ordered list of package names used to remove duplicate routes, or an
351          *                     empty list if deduplication should not be enabled.
352          * @hide
353          */
354         @NonNull
setDeduplicationPackageOrder(@onNull List<String> packageOrder)355         public Builder setDeduplicationPackageOrder(@NonNull List<String> packageOrder) {
356             Objects.requireNonNull(packageOrder, "packageOrder must not be null");
357             mPackageOrder = List.copyOf(packageOrder);
358             return this;
359         }
360 
361         /**
362          * Sets the extras of the route.
363          * @hide
364          */
365         @NonNull
setExtras(@ullable Bundle extras)366         public Builder setExtras(@Nullable Bundle extras) {
367             mExtras = extras;
368             return this;
369         }
370 
371         /**
372          * Builds the {@link RouteDiscoveryPreference}.
373          */
374         @NonNull
build()375         public RouteDiscoveryPreference build() {
376             return new RouteDiscoveryPreference(this);
377         }
378     }
379 }
380