• 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      * @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