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 @NonNull 303 @Override toString()304 public String toString() { 305 StringBuilder s = new StringBuilder(); 306 s.append("\n Name: "); 307 s.append(mName); 308 s.append(" Id: "); 309 s.append(Integer.toString(mId)); 310 for (AudioAttributesGroup aag : mAudioAttributesGroups) { 311 s.append(aag.toString()); 312 } 313 return s.toString(); 314 } 315 316 /** 317 * @hide 318 * Default attributes, with default source to be aligned with native. 319 */ 320 public static final @NonNull AudioAttributes sDefaultAttributes = 321 new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.DEFAULT) 322 .build(); 323 324 /** 325 * To avoid duplicating the logic in java and native, we shall make use of 326 * native API native_get_product_strategies_from_audio_attributes 327 * @param refAttr {@link AudioAttributes} to be taken as the reference 328 * @param attr {@link AudioAttributes} of the requester. 329 */ attributesMatches(@onNull AudioAttributes refAttr, @NonNull AudioAttributes attr)330 private static boolean attributesMatches(@NonNull AudioAttributes refAttr, 331 @NonNull AudioAttributes attr) { 332 Preconditions.checkNotNull(refAttr, "refAttr must not be null"); 333 Preconditions.checkNotNull(attr, "attr must not be null"); 334 String refFormattedTags = TextUtils.join(";", refAttr.getTags()); 335 String cliFormattedTags = TextUtils.join(";", attr.getTags()); 336 if (refAttr.equals(sDefaultAttributes)) { 337 return false; 338 } 339 return ((refAttr.getSystemUsage() == AudioAttributes.USAGE_UNKNOWN) 340 || (attr.getSystemUsage() == refAttr.getSystemUsage())) 341 && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN) 342 || (attr.getContentType() == refAttr.getContentType())) 343 && ((refAttr.getAllFlags() == 0) 344 || (attr.getAllFlags() != 0 345 && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags())) 346 && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags)); 347 } 348 349 private static final class AudioAttributesGroup implements Parcelable { 350 private int mVolumeGroupId; 351 private int mLegacyStreamType; 352 private final AudioAttributes[] mAudioAttributes; 353 AudioAttributesGroup(int volumeGroupId, int streamType, @NonNull AudioAttributes[] audioAttributes)354 AudioAttributesGroup(int volumeGroupId, int streamType, 355 @NonNull AudioAttributes[] audioAttributes) { 356 mVolumeGroupId = volumeGroupId; 357 mLegacyStreamType = streamType; 358 mAudioAttributes = audioAttributes; 359 } 360 361 @Override equals(@ullable Object o)362 public boolean equals(@Nullable Object o) { 363 if (this == o) return true; 364 if (o == null || getClass() != o.getClass()) return false; 365 366 AudioAttributesGroup thatAag = (AudioAttributesGroup) o; 367 368 return mVolumeGroupId == thatAag.mVolumeGroupId 369 && mLegacyStreamType == thatAag.mLegacyStreamType 370 && mAudioAttributes.equals(thatAag.mAudioAttributes); 371 } 372 getStreamType()373 public int getStreamType() { 374 return mLegacyStreamType; 375 } 376 getVolumeGroupId()377 public int getVolumeGroupId() { 378 return mVolumeGroupId; 379 } 380 getAudioAttributes()381 public @NonNull AudioAttributes getAudioAttributes() { 382 // We need a choice, so take the first one 383 return mAudioAttributes.length == 0 ? (new AudioAttributes.Builder().build()) 384 : mAudioAttributes[0]; 385 } 386 387 /** 388 * Checks if a {@link AudioAttributes} is supported by this product strategy. 389 * @param {@link AudioAttributes} to check upon support 390 * @return true if the {@link AudioAttributes} follows this product strategy, 391 false otherwise. 392 */ supportsAttributes(@onNull AudioAttributes attributes)393 public boolean supportsAttributes(@NonNull AudioAttributes attributes) { 394 for (final AudioAttributes refAa : mAudioAttributes) { 395 if (refAa.equals(attributes) || attributesMatches(refAa, attributes)) { 396 return true; 397 } 398 } 399 return false; 400 } 401 supportsStreamType(int streamType)402 public boolean supportsStreamType(int streamType) { 403 return mLegacyStreamType == streamType; 404 } 405 406 @Override describeContents()407 public int describeContents() { 408 return 0; 409 } 410 411 @Override writeToParcel(@onNull Parcel dest, int flags)412 public void writeToParcel(@NonNull Parcel dest, int flags) { 413 dest.writeInt(mVolumeGroupId); 414 dest.writeInt(mLegacyStreamType); 415 dest.writeInt(mAudioAttributes.length); 416 for (AudioAttributes attributes : mAudioAttributes) { 417 attributes.writeToParcel(dest, flags | AudioAttributes.FLATTEN_TAGS/*flags*/); 418 } 419 } 420 421 public static final @android.annotation.NonNull Parcelable.Creator<AudioAttributesGroup> CREATOR = 422 new Parcelable.Creator<AudioAttributesGroup>() { 423 @Override 424 public AudioAttributesGroup createFromParcel(@NonNull Parcel in) { 425 int volumeGroupId = in.readInt(); 426 int streamType = in.readInt(); 427 int nbAttributes = in.readInt(); 428 AudioAttributes[] aa = new AudioAttributes[nbAttributes]; 429 for (int index = 0; index < nbAttributes; index++) { 430 aa[index] = AudioAttributes.CREATOR.createFromParcel(in); 431 } 432 return new AudioAttributesGroup(volumeGroupId, streamType, aa); 433 } 434 435 @Override 436 public @NonNull AudioAttributesGroup[] newArray(int size) { 437 return new AudioAttributesGroup[size]; 438 } 439 }; 440 441 442 @Override toString()443 public @NonNull String toString() { 444 StringBuilder s = new StringBuilder(); 445 s.append("\n Legacy Stream Type: "); 446 s.append(Integer.toString(mLegacyStreamType)); 447 s.append(" Volume Group Id: "); 448 s.append(Integer.toString(mVolumeGroupId)); 449 450 for (AudioAttributes attribute : mAudioAttributes) { 451 s.append("\n -"); 452 s.append(attribute.toString()); 453 } 454 return s.toString(); 455 } 456 } 457 } 458