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