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 import com.android.internal.util.Preconditions;
33 
34 import java.util.ArrayList;
35 import java.util.List;
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     @TestApi
112     @SystemApi
createInvalidAudioProductStrategy(int id)113     public static @NonNull AudioProductStrategy createInvalidAudioProductStrategy(int id) {
114         return new AudioProductStrategy("dummy strategy", id, new AudioAttributesGroup[0]);
115     }
116 
117     /**
118      * @hide
119      * @param streamType to match against AudioProductStrategy
120      * @return the AudioAttributes for the first strategy found with the associated stream type
121      *          If no match is found, returns AudioAttributes with unknown content_type and usage
122      */
123     @NonNull
getAudioAttributesForStrategyWithLegacyStreamType( int streamType)124     public static AudioAttributes getAudioAttributesForStrategyWithLegacyStreamType(
125             int streamType) {
126         for (final AudioProductStrategy productStrategy :
127                 AudioProductStrategy.getAudioProductStrategies()) {
128             AudioAttributes aa = productStrategy.getAudioAttributesForLegacyStreamType(streamType);
129             if (aa != null) {
130                 return aa;
131             }
132         }
133         return new AudioAttributes.Builder()
134             .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
135             .setUsage(AudioAttributes.USAGE_UNKNOWN).build();
136     }
137 
138     /**
139      * @hide
140      * @param audioAttributes to identify AudioProductStrategy with
141      * @return legacy stream type associated with matched AudioProductStrategy
142      *              Defaults to STREAM_MUSIC if no match is found, or if matches is STREAM_DEFAULT
143      */
getLegacyStreamTypeForStrategyWithAudioAttributes( @onNull AudioAttributes audioAttributes)144     public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
145             @NonNull AudioAttributes audioAttributes) {
146         Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null");
147         for (final AudioProductStrategy productStrategy :
148                 AudioProductStrategy.getAudioProductStrategies()) {
149             if (productStrategy.supportsAudioAttributes(audioAttributes)) {
150                 int streamType = productStrategy.getLegacyStreamTypeForAudioAttributes(
151                         audioAttributes);
152                 if (streamType == AudioSystem.STREAM_DEFAULT) {
153                     Log.w(TAG, "Attributes " + audioAttributes.toString() + " ported by strategy "
154                             + productStrategy.getId() + " has no stream type associated, "
155                             + "DO NOT USE STREAM TO CONTROL THE VOLUME");
156                     return AudioSystem.STREAM_MUSIC;
157                 }
158                 if (streamType < AudioSystem.getNumStreamTypes()) {
159                     return streamType;
160                 }
161             }
162         }
163         return AudioSystem.STREAM_MUSIC;
164     }
165 
initializeAudioProductStrategies()166     private static List<AudioProductStrategy> initializeAudioProductStrategies() {
167         ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>();
168         int status = native_list_audio_product_strategies(apsList);
169         if (status != AudioSystem.SUCCESS) {
170             Log.w(TAG, ": initializeAudioProductStrategies failed");
171         }
172         return apsList;
173     }
174 
native_list_audio_product_strategies( ArrayList<AudioProductStrategy> strategies)175     private static native int native_list_audio_product_strategies(
176             ArrayList<AudioProductStrategy> strategies);
177 
178     @Override
equals(@ullable Object o)179     public boolean equals(@Nullable Object o) {
180         if (this == o) return true;
181         if (o == null || getClass() != o.getClass()) return false;
182 
183         AudioProductStrategy thatStrategy = (AudioProductStrategy) o;
184 
185         return mName == thatStrategy.mName && mId == thatStrategy.mId
186                 && mAudioAttributesGroups.equals(thatStrategy.mAudioAttributesGroups);
187     }
188 
189     /**
190      * @param name of the product strategy
191      * @param id of the product strategy
192      * @param aag {@link AudioAttributesGroup} associated to the given product strategy
193      */
AudioProductStrategy(@onNull String name, int id, @NonNull AudioAttributesGroup[] aag)194     private AudioProductStrategy(@NonNull String name, int id,
195             @NonNull AudioAttributesGroup[] aag) {
196         Preconditions.checkNotNull(name, "name must not be null");
197         Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null");
198         mName = name;
199         mId = id;
200         mAudioAttributesGroups = aag;
201     }
202 
203     /**
204      * @hide
205      * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy
206      *         routing_strategy linked to {@link AudioAttributes#getUsage()}).
207      */
208     @SystemApi
getId()209     public int getId() {
210         return mId;
211     }
212 
213     /**
214      * @hide
215      * @return first {@link AudioAttributes} associated to this product strategy.
216      */
217     @SystemApi
getAudioAttributes()218     public @NonNull AudioAttributes getAudioAttributes() {
219         // We need a choice, so take the first one
220         return mAudioAttributesGroups.length == 0 ? (new AudioAttributes.Builder().build())
221                 : mAudioAttributesGroups[0].getAudioAttributes();
222     }
223 
224     /**
225      * @hide
226      * @param streamType legacy stream type used for volume operation only
227      * @return the {@link AudioAttributes} relevant for the given streamType.
228      *         If none is found, it builds the default attributes.
229      */
getAudioAttributesForLegacyStreamType(int streamType)230     public @Nullable AudioAttributes getAudioAttributesForLegacyStreamType(int streamType) {
231         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
232             if (aag.supportsStreamType(streamType)) {
233                 return aag.getAudioAttributes();
234             }
235         }
236         return null;
237     }
238 
239     /**
240      * @hide
241      * @param aa the {@link AudioAttributes} to be considered
242      * @return the legacy stream type relevant for the given {@link AudioAttributes}.
243      *         If none is found, it return DEFAULT stream type.
244      */
getLegacyStreamTypeForAudioAttributes(@onNull AudioAttributes aa)245     public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
246         Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
247         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
248             if (aag.supportsAttributes(aa)) {
249                 return aag.getStreamType();
250             }
251         }
252         return AudioSystem.STREAM_DEFAULT;
253     }
254 
255     /**
256      * @hide
257      * @param aa the {@link AudioAttributes} to be considered
258      * @return true if the {@link AudioProductStrategy} supports the given {@link AudioAttributes},
259      *         false otherwise.
260      */
261     @SystemApi
supportsAudioAttributes(@onNull AudioAttributes aa)262     public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
263         Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
264         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
265             if (aag.supportsAttributes(aa)) {
266                 return true;
267             }
268         }
269         return false;
270     }
271 
272     /**
273      * @hide
274      * @param streamType legacy stream type used for volume operation only
275      * @return the volume group id relevant for the given streamType.
276      *         If none is found, {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} is returned.
277      */
getVolumeGroupIdForLegacyStreamType(int streamType)278     public int getVolumeGroupIdForLegacyStreamType(int streamType) {
279         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
280             if (aag.supportsStreamType(streamType)) {
281                 return aag.getVolumeGroupId();
282             }
283         }
284         return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
285     }
286 
287     /**
288      * @hide
289      * @param aa the {@link AudioAttributes} to be considered
290      * @return the volume group id associated with the given audio attributes if found,
291      *         {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} otherwise.
292      */
getVolumeGroupIdForAudioAttributes(@onNull AudioAttributes aa)293     public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
294         Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
295         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
296             if (aag.supportsAttributes(aa)) {
297                 return aag.getVolumeGroupId();
298             }
299         }
300         return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
301     }
302 
303     @Override
describeContents()304     public int describeContents() {
305         return 0;
306     }
307 
308     @Override
writeToParcel(@onNull Parcel dest, int flags)309     public void writeToParcel(@NonNull Parcel dest, int flags) {
310         dest.writeString(mName);
311         dest.writeInt(mId);
312         dest.writeInt(mAudioAttributesGroups.length);
313         for (AudioAttributesGroup aag : mAudioAttributesGroups) {
314             aag.writeToParcel(dest, flags);
315         }
316     }
317 
318     @NonNull
319     public static final Parcelable.Creator<AudioProductStrategy> CREATOR =
320             new Parcelable.Creator<AudioProductStrategy>() {
321                 @Override
322                 public AudioProductStrategy createFromParcel(@NonNull Parcel in) {
323                     String name = in.readString();
324                     int id = in.readInt();
325                     int nbAttributesGroups = in.readInt();
326                     AudioAttributesGroup[] aag = new AudioAttributesGroup[nbAttributesGroups];
327                     for (int index = 0; index < nbAttributesGroups; index++) {
328                         aag[index] = AudioAttributesGroup.CREATOR.createFromParcel(in);
329                     }
330                     return new AudioProductStrategy(name, id, aag);
331                 }
332 
333                 @Override
334                 public @NonNull AudioProductStrategy[] newArray(int size) {
335                     return new AudioProductStrategy[size];
336                 }
337             };
338 
339     @NonNull
340     @Override
toString()341     public String toString() {
342         StringBuilder s = new StringBuilder();
343         s.append("\n Name: ");
344         s.append(mName);
345         s.append(" Id: ");
346         s.append(Integer.toString(mId));
347         for (AudioAttributesGroup aag : mAudioAttributesGroups) {
348             s.append(aag.toString());
349         }
350         return s.toString();
351     }
352 
353     /**
354      * @hide
355      * Default attributes, with default source to be aligned with native.
356      */
357     public static final @NonNull AudioAttributes sDefaultAttributes =
358             new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.DEFAULT)
359                                          .build();
360 
361     /**
362      * To avoid duplicating the logic in java and native, we shall make use of
363      * native API native_get_product_strategies_from_audio_attributes
364      * Keep in sync with frameworks/av/media/libaudioclient/AudioProductStrategy::attributesMatches
365      * @param refAttr {@link AudioAttributes} to be taken as the reference
366      * @param attr {@link AudioAttributes} of the requester.
367      */
attributesMatches(@onNull AudioAttributes refAttr, @NonNull AudioAttributes attr)368     private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
369             @NonNull AudioAttributes attr) {
370         Preconditions.checkNotNull(refAttr, "refAttr must not be null");
371         Preconditions.checkNotNull(attr, "attr must not be null");
372         String refFormattedTags = TextUtils.join(";", refAttr.getTags());
373         String cliFormattedTags = TextUtils.join(";", attr.getTags());
374         if (refAttr.equals(sDefaultAttributes)) {
375             return false;
376         }
377         return ((refAttr.getSystemUsage() == AudioAttributes.USAGE_UNKNOWN)
378                 || (attr.getSystemUsage() == refAttr.getSystemUsage()))
379             && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
380                 || (attr.getContentType() == refAttr.getContentType()))
381             && ((refAttr.getAllFlags() == 0)
382                 || (attr.getAllFlags() != 0
383                 && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
384             && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
385     }
386 
387     private static final class AudioAttributesGroup implements Parcelable {
388         private int mVolumeGroupId;
389         private int mLegacyStreamType;
390         private final AudioAttributes[] mAudioAttributes;
391 
AudioAttributesGroup(int volumeGroupId, int streamType, @NonNull AudioAttributes[] audioAttributes)392         AudioAttributesGroup(int volumeGroupId, int streamType,
393                 @NonNull AudioAttributes[] audioAttributes) {
394             mVolumeGroupId = volumeGroupId;
395             mLegacyStreamType = streamType;
396             mAudioAttributes = audioAttributes;
397         }
398 
399         @Override
equals(@ullable Object o)400         public boolean equals(@Nullable Object o) {
401             if (this == o) return true;
402             if (o == null || getClass() != o.getClass()) return false;
403 
404             AudioAttributesGroup thatAag = (AudioAttributesGroup) o;
405 
406             return mVolumeGroupId == thatAag.mVolumeGroupId
407                     && mLegacyStreamType == thatAag.mLegacyStreamType
408                     && mAudioAttributes.equals(thatAag.mAudioAttributes);
409         }
410 
getStreamType()411         public int getStreamType() {
412             return mLegacyStreamType;
413         }
414 
getVolumeGroupId()415         public int getVolumeGroupId() {
416             return mVolumeGroupId;
417         }
418 
getAudioAttributes()419         public @NonNull AudioAttributes getAudioAttributes() {
420             // We need a choice, so take the first one
421             return mAudioAttributes.length == 0 ? (new AudioAttributes.Builder().build())
422                     : mAudioAttributes[0];
423         }
424 
425         /**
426          * Checks if a {@link AudioAttributes} is supported by this product strategy.
427          * @param {@link AudioAttributes} to check upon support
428          * @return true if the {@link AudioAttributes} follows this product strategy,
429                    false otherwise.
430          */
supportsAttributes(@onNull AudioAttributes attributes)431         public boolean supportsAttributes(@NonNull AudioAttributes attributes) {
432             for (final AudioAttributes refAa : mAudioAttributes) {
433                 if (refAa.equals(attributes) || attributesMatches(refAa, attributes)) {
434                     return true;
435                 }
436             }
437             return false;
438         }
439 
supportsStreamType(int streamType)440         public boolean supportsStreamType(int streamType) {
441             return mLegacyStreamType == streamType;
442         }
443 
444         @Override
describeContents()445         public int describeContents() {
446             return 0;
447         }
448 
449         @Override
writeToParcel(@onNull Parcel dest, int flags)450         public void writeToParcel(@NonNull Parcel dest, int flags) {
451             dest.writeInt(mVolumeGroupId);
452             dest.writeInt(mLegacyStreamType);
453             dest.writeInt(mAudioAttributes.length);
454             for (AudioAttributes attributes : mAudioAttributes) {
455                 attributes.writeToParcel(dest, flags | AudioAttributes.FLATTEN_TAGS/*flags*/);
456             }
457         }
458 
459         public static final @android.annotation.NonNull Parcelable.Creator<AudioAttributesGroup> CREATOR =
460                 new Parcelable.Creator<AudioAttributesGroup>() {
461                     @Override
462                     public AudioAttributesGroup createFromParcel(@NonNull Parcel in) {
463                         int volumeGroupId = in.readInt();
464                         int streamType = in.readInt();
465                         int nbAttributes = in.readInt();
466                         AudioAttributes[] aa = new AudioAttributes[nbAttributes];
467                         for (int index = 0; index < nbAttributes; index++) {
468                             aa[index] = AudioAttributes.CREATOR.createFromParcel(in);
469                         }
470                         return new AudioAttributesGroup(volumeGroupId, streamType, aa);
471                     }
472 
473                     @Override
474                     public @NonNull AudioAttributesGroup[] newArray(int size) {
475                         return new AudioAttributesGroup[size];
476                     }
477                 };
478 
479 
480         @Override
toString()481         public @NonNull String toString() {
482             StringBuilder s = new StringBuilder();
483             s.append("\n    Legacy Stream Type: ");
484             s.append(Integer.toString(mLegacyStreamType));
485             s.append(" Volume Group Id: ");
486             s.append(Integer.toString(mVolumeGroupId));
487 
488             for (AudioAttributes attribute : mAudioAttributes) {
489                 s.append("\n    -");
490                 s.append(attribute.toString());
491             }
492             return s.toString();
493         }
494     }
495 }
496