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