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 import android.util.Log;
23 
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.Objects;
27 
28 
29 /**
30  * @hide
31  *
32  * Here's an example of creating a mixing rule for all media playback:
33  * <pre>
34  * AudioAttributes mediaAttr = new AudioAttributes.Builder()
35  *         .setUsage(AudioAttributes.USAGE_MEDIA)
36  *         .build();
37  * AudioMixingRule mediaRule = new AudioMixingRule.Builder()
38  *         .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
39  *         .build();
40  * </pre>
41  */
42 @SystemApi
43 public class AudioMixingRule {
44 
AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria)45     private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria) {
46         mCriteria = criteria;
47         mTargetMixType = mixType;
48     }
49 
50     /**
51      * A rule requiring the usage information of the {@link AudioAttributes} to match.
52      * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or
53      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
54      * {@link AudioAttributes}.
55      */
56     @SystemApi
57     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
58     /**
59      * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
60      * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or
61      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
62      * {@link AudioAttributes}.
63      */
64     @SystemApi
65     public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
66     /**
67      * A rule requiring the UID of the audio stream to match that specified.
68      * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object
69      * parameter is an instance of {@link java.lang.Integer}.
70      */
71     @SystemApi
72     public static final int RULE_MATCH_UID = 0x1 << 2;
73 
74     private final static int RULE_EXCLUSION_MASK = 0x8000;
75     /**
76      * @hide
77      * A rule requiring the usage information of the {@link AudioAttributes} to differ.
78      */
79     public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
80             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
81     /**
82      * @hide
83      * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
84      */
85     public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
86             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
87     /**
88      * @hide
89      * A rule requiring the UID information to differ.
90      */
91     public static final int RULE_EXCLUDE_UID =
92             RULE_EXCLUSION_MASK | RULE_MATCH_UID;
93 
94     static final class AudioMixMatchCriterion {
95         final AudioAttributes mAttr;
96         final int mIntProp;
97         final int mRule;
98 
99         /** input parameters must be valid */
AudioMixMatchCriterion(AudioAttributes attributes, int rule)100         AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
101             mAttr = attributes;
102             mIntProp = Integer.MIN_VALUE;
103             mRule = rule;
104         }
105         /** input parameters must be valid */
AudioMixMatchCriterion(Integer intProp, int rule)106         AudioMixMatchCriterion(Integer intProp, int rule) {
107             mAttr = null;
108             mIntProp = intProp.intValue();
109             mRule = rule;
110         }
111 
112         @Override
hashCode()113         public int hashCode() {
114             return Objects.hash(mAttr, mIntProp, mRule);
115         }
116 
writeToParcel(Parcel dest)117         void writeToParcel(Parcel dest) {
118             dest.writeInt(mRule);
119             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
120             switch (match_rule) {
121             case RULE_MATCH_ATTRIBUTE_USAGE:
122                 dest.writeInt(mAttr.getUsage());
123                 break;
124             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
125                 dest.writeInt(mAttr.getCapturePreset());
126                 break;
127             case RULE_MATCH_UID:
128                 dest.writeInt(mIntProp);
129                 break;
130             default:
131                 Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule
132                         + " when writing to Parcel");
133                 dest.writeInt(-1);
134             }
135         }
136     }
137 
138     private final int mTargetMixType;
getTargetMixType()139     int getTargetMixType() { return mTargetMixType; }
140     private final ArrayList<AudioMixMatchCriterion> mCriteria;
getCriteria()141     ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
142 
143     @Override
hashCode()144     public int hashCode() {
145         return Objects.hash(mTargetMixType, mCriteria);
146     }
147 
isValidSystemApiRule(int rule)148     private static boolean isValidSystemApiRule(int rule) {
149         // API rules only expose the RULE_MATCH_* rules
150         switch (rule) {
151             case RULE_MATCH_ATTRIBUTE_USAGE:
152             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
153             case RULE_MATCH_UID:
154                 return true;
155             default:
156                 return false;
157         }
158     }
isValidAttributesSystemApiRule(int rule)159     private static boolean isValidAttributesSystemApiRule(int rule) {
160         // API rules only expose the RULE_MATCH_* rules
161         switch (rule) {
162             case RULE_MATCH_ATTRIBUTE_USAGE:
163             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
164                 return true;
165             default:
166                 return false;
167         }
168     }
169 
isValidRule(int rule)170     private static boolean isValidRule(int rule) {
171         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
172         switch (match_rule) {
173             case RULE_MATCH_ATTRIBUTE_USAGE:
174             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
175             case RULE_MATCH_UID:
176                 return true;
177             default:
178                 return false;
179         }
180     }
181 
isPlayerRule(int rule)182     private static boolean isPlayerRule(int rule) {
183         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
184         switch (match_rule) {
185         case RULE_MATCH_ATTRIBUTE_USAGE:
186         case RULE_MATCH_UID:
187             return true;
188         default:
189             return false;
190         }
191     }
192 
isAudioAttributeRule(int match_rule)193     private static boolean isAudioAttributeRule(int match_rule) {
194         switch(match_rule) {
195             case RULE_MATCH_ATTRIBUTE_USAGE:
196             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
197                 return true;
198             default:
199                 return false;
200         }
201     }
202 
203     /**
204      * Builder class for {@link AudioMixingRule} objects
205      */
206     @SystemApi
207     public static class Builder {
208         private ArrayList<AudioMixMatchCriterion> mCriteria;
209         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
210 
211         /**
212          * Constructs a new Builder with no rules.
213          */
214         @SystemApi
Builder()215         public Builder() {
216             mCriteria = new ArrayList<AudioMixMatchCriterion>();
217         }
218 
219         /**
220          * Add a rule for the selection of which streams are mixed together.
221          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
222          *     rule hasn't been set yet.
223          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
224          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
225          * @return the same Builder instance.
226          * @throws IllegalArgumentException
227          * @see {@link #excludeRule(AudioAttributes, int)}
228          */
229         @SystemApi
addRule(AudioAttributes attrToMatch, int rule)230         public Builder addRule(AudioAttributes attrToMatch, int rule)
231                 throws IllegalArgumentException {
232             if (!isValidAttributesSystemApiRule(rule)) {
233                 throw new IllegalArgumentException("Illegal rule value " + rule);
234             }
235             return checkAddRuleObjInternal(rule, attrToMatch);
236         }
237 
238         /**
239          * Add a rule by exclusion for the selection of which streams are mixed together.
240          * <br>For instance the following code
241          * <br><pre>
242          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
243          *         .setUsage(AudioAttributes.USAGE_MEDIA)
244          *         .build();
245          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
246          *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
247          *         .build();
248          * </pre>
249          * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
250          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
251          *     rule hasn't been set yet.
252          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
253          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
254          * @return the same Builder instance.
255          * @throws IllegalArgumentException
256          * @see {@link #addRule(AudioAttributes, int)}
257          */
258         @SystemApi
excludeRule(AudioAttributes attrToMatch, int rule)259         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
260                 throws IllegalArgumentException {
261             if (!isValidAttributesSystemApiRule(rule)) {
262                 throw new IllegalArgumentException("Illegal rule value " + rule);
263             }
264             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch);
265         }
266 
267         /**
268          * Add a rule for the selection of which streams are mixed together.
269          * The rule defines what the matching will be made on. It also determines the type of the
270          * property to match against.
271          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
272          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
273          *     {@link AudioMixingRule#RULE_MATCH_UID}.
274          * @param property see the definition of each rule for the type to use (either an
275          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
276          * @return the same Builder instance.
277          * @throws IllegalArgumentException
278          * @see {@link #excludeMixRule(int, Object)}
279          */
280         @SystemApi
addMixRule(int rule, Object property)281         public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
282             if (!isValidSystemApiRule(rule)) {
283                 throw new IllegalArgumentException("Illegal rule value " + rule);
284             }
285             return checkAddRuleObjInternal(rule, property);
286         }
287 
288         /**
289          * Add a rule by exclusion for the selection of which streams are mixed together.
290          * <br>For instance the following code
291          * <br><pre>
292          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
293          *         .setUsage(AudioAttributes.USAGE_MEDIA)
294          *         .build();
295          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
296          *         .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr)
297          *         .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude)
298          *         .build();
299          * </pre>
300          * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream
301          * coming from the specified UID.
302          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
303          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
304          *     {@link AudioMixingRule#RULE_MATCH_UID}.
305          * @param property see the definition of each rule for the type to use (either an
306          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
307          * @return the same Builder instance.
308          * @throws IllegalArgumentException
309          */
310         @SystemApi
excludeMixRule(int rule, Object property)311         public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException {
312             if (!isValidSystemApiRule(rule)) {
313                 throw new IllegalArgumentException("Illegal rule value " + rule);
314             }
315             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property);
316         }
317 
318         /**
319          * Add or exclude a rule for the selection of which streams are mixed together.
320          * Does error checking on the parameters.
321          * @param rule
322          * @param property
323          * @return the same Builder instance.
324          * @throws IllegalArgumentException
325          */
checkAddRuleObjInternal(int rule, Object property)326         private Builder checkAddRuleObjInternal(int rule, Object property)
327                 throws IllegalArgumentException {
328             if (property == null) {
329                 throw new IllegalArgumentException("Illegal null argument for mixing rule");
330             }
331             if (!isValidRule(rule)) {
332                 throw new IllegalArgumentException("Illegal rule value " + rule);
333             }
334             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
335             if (isAudioAttributeRule(match_rule)) {
336                 if (!(property instanceof AudioAttributes)) {
337                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
338                 }
339                 return addRuleInternal((AudioAttributes) property, null, rule);
340             } else {
341                 // implies integer match rule
342                 if (!(property instanceof Integer)) {
343                     throw new IllegalArgumentException("Invalid Integer argument");
344                 }
345                 return addRuleInternal(null, (Integer) property, rule);
346             }
347         }
348 
349         /**
350          * Add or exclude a rule on AudioAttributes or integer property for the selection of which
351          * streams are mixed together.
352          * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}.
353          * Exceptions are thrown only when incompatible rules are added.
354          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
355          *     rule hasn't been set yet, null if not used.
356          * @param intProp an integer property to match or exclude, null if not used.
357          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
358          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
359          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
360          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
361          *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
362          * @return the same Builder instance.
363          * @throws IllegalArgumentException
364          */
addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)365         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
366                 throws IllegalArgumentException {
367             // as rules are added to the Builder, we verify they are consistent with the type
368             // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
369             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
370                 if (isPlayerRule(rule)) {
371                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
372                 } else {
373                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
374                 }
375             } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
376                     || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
377             {
378                 throw new IllegalArgumentException("Incompatible rule for mix");
379             }
380             synchronized (mCriteria) {
381                 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
382                 final int match_rule = rule & ~RULE_EXCLUSION_MASK;
383                 while (crIterator.hasNext()) {
384                     final AudioMixMatchCriterion criterion = crIterator.next();
385                     switch (match_rule) {
386                         case RULE_MATCH_ATTRIBUTE_USAGE:
387                             // "usage"-based rule
388                             if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
389                                 if (criterion.mRule == rule) {
390                                     // rule already exists, we're done
391                                     return this;
392                                 } else {
393                                     // criterion already exists with a another rule,
394                                     // it is incompatible
395                                     throw new IllegalArgumentException("Contradictory rule exists"
396                                             + " for " + attrToMatch);
397                                 }
398                             }
399                             break;
400                         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
401                             // "capture preset"-base rule
402                             if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
403                                 if (criterion.mRule == rule) {
404                                     // rule already exists, we're done
405                                     return this;
406                                 } else {
407                                     // criterion already exists with a another rule,
408                                     // it is incompatible
409                                     throw new IllegalArgumentException("Contradictory rule exists"
410                                             + " for " + attrToMatch);
411                                 }
412                             }
413                             break;
414                         case RULE_MATCH_UID:
415                             // "usage"-based rule
416                             if (criterion.mIntProp == intProp.intValue()) {
417                                 if (criterion.mRule == rule) {
418                                     // rule already exists, we're done
419                                     return this;
420                                 } else {
421                                     // criterion already exists with a another rule,
422                                     // it is incompatible
423                                     throw new IllegalArgumentException("Contradictory rule exists"
424                                             + " for UID " + intProp);
425                                 }
426                             }
427                             break;
428                     }
429                 }
430                 // rule didn't exist, add it
431                 switch (match_rule) {
432                     case RULE_MATCH_ATTRIBUTE_USAGE:
433                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
434                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
435                         break;
436                     case RULE_MATCH_UID:
437                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
438                         break;
439                     default:
440                         throw new IllegalStateException("Unreachable code in addRuleInternal()");
441                 }
442             }
443             return this;
444         }
445 
addRuleFromParcel(Parcel in)446         Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
447             final int rule = in.readInt();
448             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
449             AudioAttributes attr = null;
450             Integer intProp = null;
451             switch (match_rule) {
452                 case RULE_MATCH_ATTRIBUTE_USAGE:
453                     int usage = in.readInt();
454                     attr = new AudioAttributes.Builder()
455                             .setUsage(usage).build();
456                     break;
457                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
458                     int preset = in.readInt();
459                     attr = new AudioAttributes.Builder()
460                             .setInternalCapturePreset(preset).build();
461                     break;
462                 case RULE_MATCH_UID:
463                     intProp = new Integer(in.readInt());
464                     break;
465                 default:
466                     // assume there was in int value to read as for now they come in pair
467                     in.readInt();
468                     throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
469             }
470             return addRuleInternal(attr, intProp, rule);
471         }
472 
473         /**
474          * Combines all of the matching and exclusion rules that have been set and return a new
475          * {@link AudioMixingRule} object.
476          * @return a new {@link AudioMixingRule} object
477          */
build()478         public AudioMixingRule build() {
479             return new AudioMixingRule(mTargetMixType, mCriteria);
480         }
481     }
482 }
483