• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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