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