1 /* 2 * Copyright (C) 2018 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.audiopolicy; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.annotation.TestApi; 23 import android.media.AudioAttributes; 24 import android.media.AudioSystem; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.Objects; 36 37 /** 38 * @hide 39 * A class to encapsulate a collection of attributes associated to a given product strategy 40 * (and for legacy reason, keep the association with the stream type). 41 */ 42 @SystemApi 43 public final class AudioProductStrategy implements Parcelable { 44 /** 45 * group value to use when introspection API fails. 46 * @hide 47 */ 48 public static final int DEFAULT_GROUP = -1; 49 50 51 private static final String TAG = "AudioProductStrategy"; 52 53 /** 54 * The audio flags that will affect product strategy selection. 55 */ 56 private static final int AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION = 57 AudioAttributes.FLAG_AUDIBILITY_ENFORCED 58 | AudioAttributes.FLAG_SCO 59 | AudioAttributes.FLAG_BEACON; 60 61 private final AudioAttributesGroup[] mAudioAttributesGroups; 62 private final String mName; 63 /** 64 * Unique identifier of a product strategy. 65 * This Id can be assimilated to Car Audio Usage and even more generally to usage. 66 * For legacy platforms, the product strategy id is the routing_strategy, which was hidden to 67 * upper layer but was transpiring in the {@link AudioAttributes#getUsage()}. 68 */ 69 private int mId; 70 71 private static final Object sLock = new Object(); 72 73 @GuardedBy("sLock") 74 private static List<AudioProductStrategy> sAudioProductStrategies; 75 76 /** 77 * @hide 78 * @return the list of AudioProductStrategy discovered from platform configuration file. 79 */ 80 @NonNull getAudioProductStrategies()81 public static List<AudioProductStrategy> getAudioProductStrategies() { 82 if (sAudioProductStrategies == null) { 83 synchronized (sLock) { 84 if (sAudioProductStrategies == null) { 85 sAudioProductStrategies = initializeAudioProductStrategies(); 86 } 87 } 88 } 89 return sAudioProductStrategies; 90 } 91 92 /** 93 * @hide 94 * Return the AudioProductStrategy object for the given strategy ID. 95 * @param id the ID of the strategy to find 96 * @return an AudioProductStrategy on which getId() would return id, null if no such strategy 97 * exists. 98 */ getAudioProductStrategyWithId(int id)99 public static @Nullable AudioProductStrategy getAudioProductStrategyWithId(int id) { 100 synchronized (sLock) { 101 if (sAudioProductStrategies == null) { 102 sAudioProductStrategies = initializeAudioProductStrategies(); 103 } 104 for (AudioProductStrategy strategy : sAudioProductStrategies) { 105 if (strategy.getId() == id) { 106 return strategy; 107 } 108 } 109 } 110 return null; 111 } 112 113 /** 114 * @hide 115 * Create an invalid AudioProductStrategy instance for testing 116 * @param id the ID for the invalid strategy, always use a different one than in use 117 * @return an invalid instance that cannot successfully be used for volume groups or routing 118 */ 119 @SystemApi createInvalidAudioProductStrategy(int id)120 public static @NonNull AudioProductStrategy createInvalidAudioProductStrategy(int id) { 121 return new AudioProductStrategy("dummy strategy", id, new AudioAttributesGroup[0]); 122 } 123 124 /** 125 * @hide 126 * @param streamType to match against AudioProductStrategy 127 * @return the AudioAttributes for the first strategy found with the associated stream type 128 * If no match is found, returns AudioAttributes with unknown content_type and usage 129 */ 130 @NonNull getAudioAttributesForStrategyWithLegacyStreamType( int streamType)131 public static AudioAttributes getAudioAttributesForStrategyWithLegacyStreamType( 132 int streamType) { 133 for (final AudioProductStrategy productStrategy : 134 AudioProductStrategy.getAudioProductStrategies()) { 135 AudioAttributes aa = productStrategy.getAudioAttributesForLegacyStreamType(streamType); 136 if (aa != null) { 137 return aa; 138 } 139 } 140 return DEFAULT_ATTRIBUTES; 141 } 142 143 /** 144 * @hide 145 * @param audioAttributes to identify {@link AudioProductStrategy} with 146 * @return legacy stream type associated with matched {@link AudioProductStrategy}. If no 147 * strategy found or found {@link AudioProductStrategy} does not have associated 148 * legacy stream (i.e. associated with {@link AudioSystem#STREAM_DEFAULT}) defaults 149 * to {@link AudioSystem#STREAM_MUSIC} 150 */ getLegacyStreamTypeForStrategyWithAudioAttributes( @onNull AudioAttributes audioAttributes)151 public static int getLegacyStreamTypeForStrategyWithAudioAttributes( 152 @NonNull AudioAttributes audioAttributes) { 153 Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null"); 154 for (final AudioProductStrategy productStrategy : 155 AudioProductStrategy.getAudioProductStrategies()) { 156 if (productStrategy.supportsAudioAttributes(audioAttributes)) { 157 int streamType = productStrategy.getLegacyStreamTypeForAudioAttributes( 158 audioAttributes); 159 if (streamType == AudioSystem.STREAM_DEFAULT) { 160 Log.w(TAG, "Attributes " + audioAttributes + " supported by strategy " 161 + productStrategy.getId() + " have no associated stream type, " 162 + "therefore falling back to STREAM_MUSIC"); 163 return AudioSystem.STREAM_MUSIC; 164 } 165 if (streamType < AudioSystem.getNumStreamTypes()) { 166 return streamType; 167 } 168 } 169 } 170 return AudioSystem.STREAM_MUSIC; 171 } 172 173 /** 174 * @hide 175 * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with 176 * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group 177 * associated to {@link AudioManager#STREAM_MUSIC}). 178 * @return volume group id associated with the given {@link AudioAttributes} if found, 179 * default volume group id if fallbackOnDefault is set 180 * <p>By convention, the product strategy with default attributes will be associated to the 181 * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC}) 182 * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found. 183 */ getVolumeGroupIdForAudioAttributes( @onNull AudioAttributes attributes, boolean fallbackOnDefault)184 public static int getVolumeGroupIdForAudioAttributes( 185 @NonNull AudioAttributes attributes, boolean fallbackOnDefault) { 186 Objects.requireNonNull(attributes, "attributes must not be null"); 187 int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes); 188 if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { 189 return volumeGroupId; 190 } 191 if (fallbackOnDefault) { 192 return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes()); 193 } 194 return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; 195 } 196 initializeAudioProductStrategies()197 private static List<AudioProductStrategy> initializeAudioProductStrategies() { 198 ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>(); 199 int status = native_list_audio_product_strategies(apsList); 200 if (status != AudioSystem.SUCCESS) { 201 Log.w(TAG, ": initializeAudioProductStrategies failed"); 202 } 203 return apsList; 204 } 205 native_list_audio_product_strategies( ArrayList<AudioProductStrategy> strategies)206 private static native int native_list_audio_product_strategies( 207 ArrayList<AudioProductStrategy> strategies); 208 209 @Override equals(@ullable Object o)210 public boolean equals(@Nullable Object o) { 211 if (this == o) return true; 212 if (o == null || getClass() != o.getClass()) return false; 213 214 AudioProductStrategy thatStrategy = (AudioProductStrategy) o; 215 216 return mId == thatStrategy.mId 217 && Objects.equals(mName, thatStrategy.mName) 218 && Arrays.equals(mAudioAttributesGroups, thatStrategy.mAudioAttributesGroups); 219 } 220 221 @Override hashCode()222 public int hashCode() { 223 return Objects.hash(mId, mName, Arrays.hashCode(mAudioAttributesGroups)); 224 } 225 226 /** 227 * @param name of the product strategy 228 * @param id of the product strategy 229 * @param aag {@link AudioAttributesGroup} associated to the given product strategy 230 */ AudioProductStrategy(@onNull String name, int id, @NonNull AudioAttributesGroup[] aag)231 private AudioProductStrategy(@NonNull String name, int id, 232 @NonNull AudioAttributesGroup[] aag) { 233 Objects.requireNonNull(name, "name must not be null"); 234 Objects.requireNonNull(aag, "AudioAttributesGroups must not be null"); 235 mName = name; 236 mId = id; 237 mAudioAttributesGroups = aag; 238 } 239 240 /** 241 * @hide 242 * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy 243 * routing_strategy linked to {@link AudioAttributes#getUsage()}). 244 */ 245 @SystemApi getId()246 public int getId() { 247 return mId; 248 } 249 250 /** 251 * @hide 252 * @return the product strategy name (which is the generalisation of Car Audio Usage / legacy 253 * routing_strategy linked to {@link AudioAttributes#getUsage()}). 254 */ 255 @SystemApi getName()256 @NonNull public String getName() { 257 return mName; 258 } 259 260 /** 261 * @hide 262 * @return first {@link AudioAttributes} associated to this product strategy. 263 */ 264 @SystemApi getAudioAttributes()265 public @NonNull AudioAttributes getAudioAttributes() { 266 // We need a choice, so take the first one 267 return mAudioAttributesGroups.length == 0 ? DEFAULT_ATTRIBUTES 268 : mAudioAttributesGroups[0].getAudioAttributes(); 269 } 270 271 /** 272 * @hide 273 * @param streamType legacy stream type used for volume operation only 274 * @return the {@link AudioAttributes} relevant for the given streamType. 275 * If none is found, it builds the default attributes. 276 */ getAudioAttributesForLegacyStreamType(int streamType)277 public @Nullable AudioAttributes getAudioAttributesForLegacyStreamType(int streamType) { 278 for (final AudioAttributesGroup aag : mAudioAttributesGroups) { 279 if (aag.supportsStreamType(streamType)) { 280 return aag.getAudioAttributes(); 281 } 282 } 283 return null; 284 } 285 286 /** 287 * @hide 288 * @param aa the {@link AudioAttributes} to be considered 289 * @return the legacy stream type relevant for the given {@link AudioAttributes}. 290 * If none is found, it return DEFAULT stream type. 291 */ 292 @TestApi getLegacyStreamTypeForAudioAttributes(@onNull AudioAttributes aa)293 public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) { 294 Objects.requireNonNull(aa, "AudioAttributes must not be null"); 295 for (final AudioAttributesGroup aag : mAudioAttributesGroups) { 296 if (aag.supportsAttributes(aa)) { 297 return aag.getStreamType(); 298 } 299 } 300 return AudioSystem.STREAM_DEFAULT; 301 } 302 303 /** 304 * @hide 305 * @param aa the {@link AudioAttributes} to be considered 306 * @return true if the {@link AudioProductStrategy} supports the given {@link AudioAttributes}, 307 * false otherwise. 308 */ 309 @SystemApi supportsAudioAttributes(@onNull AudioAttributes aa)310 public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) { 311 Objects.requireNonNull(aa, "AudioAttributes must not be null"); 312 for (final AudioAttributesGroup aag : mAudioAttributesGroups) { 313 if (aag.supportsAttributes(aa)) { 314 return true; 315 } 316 } 317 return false; 318 } 319 320 /** 321 * @hide 322 * @param streamType legacy stream type used for volume operation only 323 * @return the volume group id relevant for the given streamType. 324 * If none is found, {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} is returned. 325 */ 326 @TestApi getVolumeGroupIdForLegacyStreamType(int streamType)327 public int getVolumeGroupIdForLegacyStreamType(int streamType) { 328 for (final AudioAttributesGroup aag : mAudioAttributesGroups) { 329 if (aag.supportsStreamType(streamType)) { 330 return aag.getVolumeGroupId(); 331 } 332 } 333 return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; 334 } 335 336 /** 337 * @hide 338 * @param aa the {@link AudioAttributes} to be considered 339 * @return the volume group id associated with the given audio attributes if found, 340 * {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} otherwise. 341 */ 342 @TestApi getVolumeGroupIdForAudioAttributes(@onNull AudioAttributes aa)343 public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) { 344 Objects.requireNonNull(aa, "AudioAttributes must not be null"); 345 for (final AudioAttributesGroup aag : mAudioAttributesGroups) { 346 if (aag.supportsAttributes(aa)) { 347 return aag.getVolumeGroupId(); 348 } 349 } 350 return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; 351 } 352 getVolumeGroupIdForAudioAttributesInt(@onNull AudioAttributes attributes)353 private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) { 354 Objects.requireNonNull(attributes, "attributes must not be null"); 355 for (AudioProductStrategy productStrategy : getAudioProductStrategies()) { 356 int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes); 357 if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { 358 return volumeGroupId; 359 } 360 } 361 return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; 362 } 363 364 @Override describeContents()365 public int describeContents() { 366 return 0; 367 } 368 369 @Override writeToParcel(@onNull Parcel dest, int flags)370 public void writeToParcel(@NonNull Parcel dest, int flags) { 371 dest.writeString(mName); 372 dest.writeInt(mId); 373 dest.writeInt(mAudioAttributesGroups.length); 374 for (AudioAttributesGroup aag : mAudioAttributesGroups) { 375 aag.writeToParcel(dest, flags); 376 } 377 } 378 379 @NonNull 380 public static final Parcelable.Creator<AudioProductStrategy> CREATOR = 381 new Parcelable.Creator<AudioProductStrategy>() { 382 @Override 383 public AudioProductStrategy createFromParcel(@NonNull Parcel in) { 384 String name = in.readString(); 385 int id = in.readInt(); 386 int nbAttributesGroups = in.readInt(); 387 AudioAttributesGroup[] aag = new AudioAttributesGroup[nbAttributesGroups]; 388 for (int index = 0; index < nbAttributesGroups; index++) { 389 aag[index] = AudioAttributesGroup.CREATOR.createFromParcel(in); 390 } 391 return new AudioProductStrategy(name, id, aag); 392 } 393 394 @Override 395 public @NonNull AudioProductStrategy[] newArray(int size) { 396 return new AudioProductStrategy[size]; 397 } 398 }; 399 400 @NonNull 401 @Override toString()402 public String toString() { 403 StringBuilder s = new StringBuilder(); 404 s.append("\n Name: "); 405 s.append(mName); 406 s.append(" Id: "); 407 s.append(Integer.toString(mId)); 408 for (AudioAttributesGroup aag : mAudioAttributesGroups) { 409 s.append(aag.toString()); 410 } 411 return s.toString(); 412 } 413 414 /** 415 * @hide 416 * Default attributes, with default source to be aligned with native. 417 */ 418 private static final @NonNull AudioAttributes DEFAULT_ATTRIBUTES = 419 new AudioAttributes.Builder().build(); 420 421 /** 422 * @hide 423 */ 424 @TestApi getDefaultAttributes()425 public static @NonNull AudioAttributes getDefaultAttributes() { 426 return DEFAULT_ATTRIBUTES; 427 } 428 429 /** 430 * To avoid duplicating the logic in java and native, we shall make use of 431 * native API native_get_product_strategies_from_audio_attributes 432 * Keep in sync with frameworks/av/media/libaudioclient/AudioProductStrategy::attributesMatches 433 * @param refAttr {@link AudioAttributes} to be taken as the reference 434 * @param attr {@link AudioAttributes} of the requester. 435 */ attributesMatches(@onNull AudioAttributes refAttr, @NonNull AudioAttributes attr)436 private static boolean attributesMatches(@NonNull AudioAttributes refAttr, 437 @NonNull AudioAttributes attr) { 438 Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null"); 439 Objects.requireNonNull(attr, "requester's AudioAttributes must not be null"); 440 String refFormattedTags = TextUtils.join(";", refAttr.getTags()); 441 String cliFormattedTags = TextUtils.join(";", attr.getTags()); 442 if (refAttr.equals(DEFAULT_ATTRIBUTES)) { 443 return false; 444 } 445 return ((refAttr.getSystemUsage() == AudioAttributes.USAGE_UNKNOWN) 446 || (attr.getSystemUsage() == refAttr.getSystemUsage())) 447 && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN) 448 || (attr.getContentType() == refAttr.getContentType())) 449 && (((refAttr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) == 0) 450 || ((attr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) != 0 451 && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags())) 452 && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags)); 453 } 454 455 private static final class AudioAttributesGroup implements Parcelable { 456 private int mVolumeGroupId; 457 private int mLegacyStreamType; 458 private final AudioAttributes[] mAudioAttributes; 459 AudioAttributesGroup(int volumeGroupId, int streamType, @NonNull AudioAttributes[] audioAttributes)460 AudioAttributesGroup(int volumeGroupId, int streamType, 461 @NonNull AudioAttributes[] audioAttributes) { 462 mVolumeGroupId = volumeGroupId; 463 mLegacyStreamType = streamType; 464 mAudioAttributes = audioAttributes; 465 } 466 467 @Override equals(@ullable Object o)468 public boolean equals(@Nullable Object o) { 469 if (this == o) return true; 470 if (o == null || getClass() != o.getClass()) return false; 471 472 AudioAttributesGroup thatAag = (AudioAttributesGroup) o; 473 474 return mVolumeGroupId == thatAag.mVolumeGroupId 475 && mLegacyStreamType == thatAag.mLegacyStreamType 476 && Arrays.equals(mAudioAttributes, thatAag.mAudioAttributes); 477 } 478 479 @Override hashCode()480 public int hashCode() { 481 return Objects.hash(mVolumeGroupId, mLegacyStreamType, 482 Arrays.hashCode(mAudioAttributes)); 483 } 484 getStreamType()485 public int getStreamType() { 486 return mLegacyStreamType; 487 } 488 getVolumeGroupId()489 public int getVolumeGroupId() { 490 return mVolumeGroupId; 491 } 492 getAudioAttributes()493 public @NonNull AudioAttributes getAudioAttributes() { 494 // We need a choice, so take the first one 495 return mAudioAttributes.length == 0 ? DEFAULT_ATTRIBUTES : mAudioAttributes[0]; 496 } 497 498 /** 499 * Checks if a {@link AudioAttributes} is supported by this product strategy. 500 * @param {@link AudioAttributes} to check upon support 501 * @return true if the {@link AudioAttributes} follows this product strategy, 502 false otherwise. 503 */ supportsAttributes(@onNull AudioAttributes attributes)504 public boolean supportsAttributes(@NonNull AudioAttributes attributes) { 505 for (final AudioAttributes refAa : mAudioAttributes) { 506 if (refAa.equals(attributes) || attributesMatches(refAa, attributes)) { 507 return true; 508 } 509 } 510 return false; 511 } 512 supportsStreamType(int streamType)513 public boolean supportsStreamType(int streamType) { 514 return mLegacyStreamType == streamType; 515 } 516 517 @Override describeContents()518 public int describeContents() { 519 return 0; 520 } 521 522 @Override writeToParcel(@onNull Parcel dest, int flags)523 public void writeToParcel(@NonNull Parcel dest, int flags) { 524 dest.writeInt(mVolumeGroupId); 525 dest.writeInt(mLegacyStreamType); 526 dest.writeInt(mAudioAttributes.length); 527 for (AudioAttributes attributes : mAudioAttributes) { 528 attributes.writeToParcel(dest, flags | AudioAttributes.FLATTEN_TAGS/*flags*/); 529 } 530 } 531 532 public static final @android.annotation.NonNull Parcelable.Creator<AudioAttributesGroup> CREATOR = 533 new Parcelable.Creator<AudioAttributesGroup>() { 534 @Override 535 public AudioAttributesGroup createFromParcel(@NonNull Parcel in) { 536 int volumeGroupId = in.readInt(); 537 int streamType = in.readInt(); 538 int nbAttributes = in.readInt(); 539 AudioAttributes[] aa = new AudioAttributes[nbAttributes]; 540 for (int index = 0; index < nbAttributes; index++) { 541 aa[index] = AudioAttributes.CREATOR.createFromParcel(in); 542 } 543 return new AudioAttributesGroup(volumeGroupId, streamType, aa); 544 } 545 546 @Override 547 public @NonNull AudioAttributesGroup[] newArray(int size) { 548 return new AudioAttributesGroup[size]; 549 } 550 }; 551 552 553 @Override toString()554 public @NonNull String toString() { 555 StringBuilder s = new StringBuilder(); 556 s.append("\n Legacy Stream Type: "); 557 s.append(Integer.toString(mLegacyStreamType)); 558 s.append(" Volume Group Id: "); 559 s.append(Integer.toString(mVolumeGroupId)); 560 561 for (AudioAttributes attribute : mAudioAttributes) { 562 s.append("\n -"); 563 s.append(attribute.toString()); 564 } 565 return s.toString(); 566 } 567 } 568 } 569