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     @NonNull
303     @Override
toString()304     public String toString() {
305         StringBuilder s = new StringBuilder();
306         s.append("\n Name: ");
307         s.append(mName);
308         s.append(" Id: ");
309         s.append(Integer.toString(mId));
310         for (AudioAttributesGroup aag : mAudioAttributesGroups) {
311             s.append(aag.toString());
312         }
313         return s.toString();
314     }
315 
316     /**
317      * @hide
318      * Default attributes, with default source to be aligned with native.
319      */
320     public static final @NonNull AudioAttributes sDefaultAttributes =
321             new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.DEFAULT)
322                                          .build();
323 
324     /**
325      * To avoid duplicating the logic in java and native, we shall make use of
326      * native API native_get_product_strategies_from_audio_attributes
327      * @param refAttr {@link AudioAttributes} to be taken as the reference
328      * @param attr {@link AudioAttributes} of the requester.
329      */
attributesMatches(@onNull AudioAttributes refAttr, @NonNull AudioAttributes attr)330     private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
331             @NonNull AudioAttributes attr) {
332         Preconditions.checkNotNull(refAttr, "refAttr must not be null");
333         Preconditions.checkNotNull(attr, "attr must not be null");
334         String refFormattedTags = TextUtils.join(";", refAttr.getTags());
335         String cliFormattedTags = TextUtils.join(";", attr.getTags());
336         if (refAttr.equals(sDefaultAttributes)) {
337             return false;
338         }
339         return ((refAttr.getSystemUsage() == AudioAttributes.USAGE_UNKNOWN)
340                 || (attr.getSystemUsage() == refAttr.getSystemUsage()))
341             && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
342                 || (attr.getContentType() == refAttr.getContentType()))
343             && ((refAttr.getAllFlags() == 0)
344                 || (attr.getAllFlags() != 0
345                 && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
346             && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
347     }
348 
349     private static final class AudioAttributesGroup implements Parcelable {
350         private int mVolumeGroupId;
351         private int mLegacyStreamType;
352         private final AudioAttributes[] mAudioAttributes;
353 
AudioAttributesGroup(int volumeGroupId, int streamType, @NonNull AudioAttributes[] audioAttributes)354         AudioAttributesGroup(int volumeGroupId, int streamType,
355                 @NonNull AudioAttributes[] audioAttributes) {
356             mVolumeGroupId = volumeGroupId;
357             mLegacyStreamType = streamType;
358             mAudioAttributes = audioAttributes;
359         }
360 
361         @Override
equals(@ullable Object o)362         public boolean equals(@Nullable Object o) {
363             if (this == o) return true;
364             if (o == null || getClass() != o.getClass()) return false;
365 
366             AudioAttributesGroup thatAag = (AudioAttributesGroup) o;
367 
368             return mVolumeGroupId == thatAag.mVolumeGroupId
369                     && mLegacyStreamType == thatAag.mLegacyStreamType
370                     && mAudioAttributes.equals(thatAag.mAudioAttributes);
371         }
372 
getStreamType()373         public int getStreamType() {
374             return mLegacyStreamType;
375         }
376 
getVolumeGroupId()377         public int getVolumeGroupId() {
378             return mVolumeGroupId;
379         }
380 
getAudioAttributes()381         public @NonNull AudioAttributes getAudioAttributes() {
382             // We need a choice, so take the first one
383             return mAudioAttributes.length == 0 ? (new AudioAttributes.Builder().build())
384                     : mAudioAttributes[0];
385         }
386 
387         /**
388          * Checks if a {@link AudioAttributes} is supported by this product strategy.
389          * @param {@link AudioAttributes} to check upon support
390          * @return true if the {@link AudioAttributes} follows this product strategy,
391                    false otherwise.
392          */
supportsAttributes(@onNull AudioAttributes attributes)393         public boolean supportsAttributes(@NonNull AudioAttributes attributes) {
394             for (final AudioAttributes refAa : mAudioAttributes) {
395                 if (refAa.equals(attributes) || attributesMatches(refAa, attributes)) {
396                     return true;
397                 }
398             }
399             return false;
400         }
401 
supportsStreamType(int streamType)402         public boolean supportsStreamType(int streamType) {
403             return mLegacyStreamType == streamType;
404         }
405 
406         @Override
describeContents()407         public int describeContents() {
408             return 0;
409         }
410 
411         @Override
writeToParcel(@onNull Parcel dest, int flags)412         public void writeToParcel(@NonNull Parcel dest, int flags) {
413             dest.writeInt(mVolumeGroupId);
414             dest.writeInt(mLegacyStreamType);
415             dest.writeInt(mAudioAttributes.length);
416             for (AudioAttributes attributes : mAudioAttributes) {
417                 attributes.writeToParcel(dest, flags | AudioAttributes.FLATTEN_TAGS/*flags*/);
418             }
419         }
420 
421         public static final @android.annotation.NonNull Parcelable.Creator<AudioAttributesGroup> CREATOR =
422                 new Parcelable.Creator<AudioAttributesGroup>() {
423                     @Override
424                     public AudioAttributesGroup createFromParcel(@NonNull Parcel in) {
425                         int volumeGroupId = in.readInt();
426                         int streamType = in.readInt();
427                         int nbAttributes = in.readInt();
428                         AudioAttributes[] aa = new AudioAttributes[nbAttributes];
429                         for (int index = 0; index < nbAttributes; index++) {
430                             aa[index] = AudioAttributes.CREATOR.createFromParcel(in);
431                         }
432                         return new AudioAttributesGroup(volumeGroupId, streamType, aa);
433                     }
434 
435                     @Override
436                     public @NonNull AudioAttributesGroup[] newArray(int size) {
437                         return new AudioAttributesGroup[size];
438                     }
439                 };
440 
441 
442         @Override
toString()443         public @NonNull String toString() {
444             StringBuilder s = new StringBuilder();
445             s.append("\n    Legacy Stream Type: ");
446             s.append(Integer.toString(mLegacyStreamType));
447             s.append(" Volume Group Id: ");
448             s.append(Integer.toString(mVolumeGroupId));
449 
450             for (AudioAttributes attribute : mAudioAttributes) {
451                 s.append("\n    -");
452                 s.append(attribute.toString());
453             }
454             return s.toString();
455         }
456     }
457 }
458