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