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