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