1 /*
2  * Copyright (C) 2014 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.SystemApi;
20 import android.media.AudioAttributes;
21 import android.os.Parcel;
22 
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.Objects;
26 
27 
28 /**
29  * @hide
30  *
31  * Here's an example of creating a mixing rule for all media playback:
32  * <pre>
33  * AudioAttributes mediaAttr = new AudioAttributes.Builder()
34  *         .setUsage(AudioAttributes.USAGE_MEDIA)
35  *         .build();
36  * AudioMixingRule mediaRule = new AudioMixingRule.Builder()
37  *         .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
38  *         .build();
39  * </pre>
40  */
41 @SystemApi
42 public class AudioMixingRule {
43 
AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria)44     private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) {
45         mCriteria = criteria;
46         mTargetMixType = mixType;
47     }
48 
49     /**
50      * A rule requiring the usage information of the {@link AudioAttributes} to match.
51      */
52     @SystemApi
53     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
54     /**
55      * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
56      */
57     @SystemApi
58     public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
59 
60     private final static int RULE_EXCLUSION_MASK = 0x8000;
61     /**
62      * @hide
63      * A rule requiring the usage information of the {@link AudioAttributes} to differ.
64      */
65     public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
66             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
67     /**
68      * @hide
69      * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
70      */
71     public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
72             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
73 
74     static final class AttributeMatchCriterion {
75         AudioAttributes mAttr;
76         int mRule;
77 
78         /** input parameters must be valid */
AttributeMatchCriterion(AudioAttributes attributes, int rule)79         AttributeMatchCriterion(AudioAttributes attributes, int rule) {
80             mAttr = attributes;
81             mRule = rule;
82         }
83 
84         @Override
hashCode()85         public int hashCode() {
86             return Objects.hash(mAttr, mRule);
87         }
88 
writeToParcel(Parcel dest)89         void writeToParcel(Parcel dest) {
90             dest.writeInt(mRule);
91             if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
92                 dest.writeInt(mAttr.getUsage());
93             } else {
94                 // capture preset rule
95                 dest.writeInt(mAttr.getCapturePreset());
96             }
97         }
98     }
99 
100     private final int mTargetMixType;
getTargetMixType()101     int getTargetMixType() { return mTargetMixType; }
102     private final ArrayList<AttributeMatchCriterion> mCriteria;
getCriteria()103     ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }
104 
105     @Override
hashCode()106     public int hashCode() {
107         return Objects.hash(mTargetMixType, mCriteria);
108     }
109 
isValidSystemApiRule(int rule)110     private static boolean isValidSystemApiRule(int rule) {
111         switch(rule) {
112             case RULE_MATCH_ATTRIBUTE_USAGE:
113             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
114                 return true;
115             default:
116                 return false;
117         }
118     }
119 
isValidIntRule(int rule)120     private static boolean isValidIntRule(int rule) {
121         switch(rule) {
122             case RULE_MATCH_ATTRIBUTE_USAGE:
123             case RULE_EXCLUDE_ATTRIBUTE_USAGE:
124             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
125             case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
126                 return true;
127             default:
128                 return false;
129         }
130     }
131 
isPlayerRule(int rule)132     private static boolean isPlayerRule(int rule) {
133         return ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
134                 || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE));
135     }
136 
137     /**
138      * Builder class for {@link AudioMixingRule} objects
139      */
140     @SystemApi
141     public static class Builder {
142         private ArrayList<AttributeMatchCriterion> mCriteria;
143         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
144 
145         /**
146          * Constructs a new Builder with no rules.
147          */
148         @SystemApi
Builder()149         public Builder() {
150             mCriteria = new ArrayList<AttributeMatchCriterion>();
151         }
152 
153         /**
154          * Add a rule for the selection of which streams are mixed together.
155          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
156          *     rule hasn't been set yet.
157          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
158          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
159          * @return the same Builder instance.
160          * @throws IllegalArgumentException
161          */
162         @SystemApi
addRule(AudioAttributes attrToMatch, int rule)163         public Builder addRule(AudioAttributes attrToMatch, int rule)
164                 throws IllegalArgumentException {
165             if (!isValidSystemApiRule(rule)) {
166                 throw new IllegalArgumentException("Illegal rule value " + rule);
167             }
168             return addRuleInt(attrToMatch, rule);
169         }
170 
171         /**
172          * Add a rule by exclusion for the selection of which streams are mixed together.
173          * <br>For instance the following code
174          * <br><pre>
175          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
176          *         .setUsage(AudioAttributes.USAGE_MEDIA)
177          *         .build();
178          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
179          *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
180          *         .build();
181          * </pre>
182          * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
183          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
184          *     rule hasn't been set yet.
185          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
186          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
187          * @return the same Builder instance.
188          * @throws IllegalArgumentException
189          */
190         @SystemApi
excludeRule(AudioAttributes attrToMatch, int rule)191         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
192                 throws IllegalArgumentException {
193             if (!isValidSystemApiRule(rule)) {
194                 throw new IllegalArgumentException("Illegal rule value " + rule);
195             }
196             return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK);
197         }
198 
199         /**
200          * Add or exclude a rule for the selection of which streams are mixed together.
201          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
202          *     rule hasn't been set yet.
203          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
204          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
205          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
206          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}.
207          * @return the same Builder instance.
208          * @throws IllegalArgumentException
209          */
addRuleInt(AudioAttributes attrToMatch, int rule)210         Builder addRuleInt(AudioAttributes attrToMatch, int rule)
211                 throws IllegalArgumentException {
212             if (attrToMatch == null) {
213                 throw new IllegalArgumentException("Illegal null AudioAttributes argument");
214             }
215             if (!isValidIntRule(rule)) {
216                 throw new IllegalArgumentException("Illegal rule value " + rule);
217             } else {
218                 // as rules are added to the Builder, we verify they are consistent with the type
219                 // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
220                 if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
221                     if (isPlayerRule(rule)) {
222                         mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
223                     } else {
224                         mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
225                     }
226                 } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
227                         || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
228                 {
229                     throw new IllegalArgumentException("Incompatible rule for mix");
230                 }
231             }
232             synchronized (mCriteria) {
233                 Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
234                 while (crIterator.hasNext()) {
235                     final AttributeMatchCriterion criterion = crIterator.next();
236                     if ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
237                             || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
238                         // "usage"-based rule
239                         if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
240                             if (criterion.mRule == rule) {
241                                 // rule already exists, we're done
242                                 return this;
243                             } else {
244                                 // criterion already exists with a another rule, it is incompatible
245                                 throw new IllegalArgumentException("Contradictory rule exists for "
246                                         + attrToMatch);
247                             }
248                         }
249                     } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
250                             || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
251                         // "capture preset"-base rule
252                         if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
253                             if (criterion.mRule == rule) {
254                              // rule already exists, we're done
255                                 return this;
256                             } else {
257                                 // criterion already exists with a another rule, it is incompatible
258                                 throw new IllegalArgumentException("Contradictory rule exists for "
259                                         + attrToMatch);
260                             }
261                         }
262                     }
263                 }
264                 // rule didn't exist, add it
265                 mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule));
266             }
267             return this;
268         }
269 
addRuleFromParcel(Parcel in)270         Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
271             int rule = in.readInt();
272             AudioAttributes attr;
273             if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
274                 int usage = in.readInt();
275                 attr = new AudioAttributes.Builder()
276                         .setUsage(usage).build();
277             } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
278                     || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
279                 int preset = in.readInt();
280                 attr = new AudioAttributes.Builder()
281                         .setInternalCapturePreset(preset).build();
282             } else {
283                 in.readInt(); // assume there was in int value to read as for now they come in pair
284                 throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
285             }
286             return addRuleInt(attr, rule);
287         }
288 
289         /**
290          * Combines all of the matching and exclusion rules that have been set and return a new
291          * {@link AudioMixingRule} object.
292          * @return a new {@link AudioMixingRule} object
293          */
build()294         public AudioMixingRule build() {
295             return new AudioMixingRule(mTargetMixType, mCriteria);
296         }
297     }
298 }
299