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