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