• 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.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