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 
isAffectingUsage(int usage)138     boolean isAffectingUsage(int usage) {
139         for (AudioMixMatchCriterion criterion : mCriteria) {
140             if ((criterion.mRule & RULE_MATCH_ATTRIBUTE_USAGE) != 0
141                     && criterion.mAttr != null
142                     && criterion.mAttr.getUsage() == usage) {
143                 return true;
144             }
145         }
146         return false;
147     }
148 
areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1, ArrayList<AudioMixMatchCriterion> cr2)149     private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
150             ArrayList<AudioMixMatchCriterion> cr2) {
151         if (cr1 == null || cr2 == null) return false;
152         if (cr1 == cr2) return true;
153         if (cr1.size() != cr2.size()) return false;
154         //TODO iterate over rules to check they contain the same criterion
155         return (cr1.hashCode() == cr2.hashCode());
156     }
157 
158     private final int mTargetMixType;
getTargetMixType()159     int getTargetMixType() { return mTargetMixType; }
160     private final ArrayList<AudioMixMatchCriterion> mCriteria;
getCriteria()161     ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
162 
163     /** @hide */
164     @Override
equals(Object o)165     public boolean equals(Object o) {
166         if (this == o) return true;
167         if (o == null || getClass() != o.getClass()) return false;
168 
169         final AudioMixingRule that = (AudioMixingRule) o;
170         return (this.mTargetMixType == that.mTargetMixType)
171                 && (areCriteriaEquivalent(this.mCriteria, that.mCriteria));
172     }
173 
174     @Override
hashCode()175     public int hashCode() {
176         return Objects.hash(mTargetMixType, mCriteria);
177     }
178 
isValidSystemApiRule(int rule)179     private static boolean isValidSystemApiRule(int rule) {
180         // API rules only expose the RULE_MATCH_* rules
181         switch (rule) {
182             case RULE_MATCH_ATTRIBUTE_USAGE:
183             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
184             case RULE_MATCH_UID:
185                 return true;
186             default:
187                 return false;
188         }
189     }
isValidAttributesSystemApiRule(int rule)190     private static boolean isValidAttributesSystemApiRule(int rule) {
191         // API rules only expose the RULE_MATCH_* rules
192         switch (rule) {
193             case RULE_MATCH_ATTRIBUTE_USAGE:
194             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
195                 return true;
196             default:
197                 return false;
198         }
199     }
200 
isValidRule(int rule)201     private static boolean isValidRule(int rule) {
202         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
203         switch (match_rule) {
204             case RULE_MATCH_ATTRIBUTE_USAGE:
205             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
206             case RULE_MATCH_UID:
207                 return true;
208             default:
209                 return false;
210         }
211     }
212 
isPlayerRule(int rule)213     private static boolean isPlayerRule(int rule) {
214         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
215         switch (match_rule) {
216         case RULE_MATCH_ATTRIBUTE_USAGE:
217         case RULE_MATCH_UID:
218             return true;
219         default:
220             return false;
221         }
222     }
223 
isAudioAttributeRule(int match_rule)224     private static boolean isAudioAttributeRule(int match_rule) {
225         switch(match_rule) {
226             case RULE_MATCH_ATTRIBUTE_USAGE:
227             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
228                 return true;
229             default:
230                 return false;
231         }
232     }
233 
234     /**
235      * Builder class for {@link AudioMixingRule} objects
236      */
237     @SystemApi
238     public static class Builder {
239         private ArrayList<AudioMixMatchCriterion> mCriteria;
240         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
241 
242         /**
243          * Constructs a new Builder with no rules.
244          */
245         @SystemApi
Builder()246         public Builder() {
247             mCriteria = new ArrayList<AudioMixMatchCriterion>();
248         }
249 
250         /**
251          * Add a rule for the selection of which streams are mixed together.
252          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
253          *     rule hasn't been set yet.
254          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
255          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
256          * @return the same Builder instance.
257          * @throws IllegalArgumentException
258          * @see #excludeRule(AudioAttributes, int)
259          */
260         @SystemApi
addRule(AudioAttributes attrToMatch, int rule)261         public Builder addRule(AudioAttributes attrToMatch, int rule)
262                 throws IllegalArgumentException {
263             if (!isValidAttributesSystemApiRule(rule)) {
264                 throw new IllegalArgumentException("Illegal rule value " + rule);
265             }
266             return checkAddRuleObjInternal(rule, attrToMatch);
267         }
268 
269         /**
270          * Add a rule by exclusion for the selection of which streams are mixed together.
271          * <br>For instance the following code
272          * <br><pre>
273          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
274          *         .setUsage(AudioAttributes.USAGE_MEDIA)
275          *         .build();
276          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
277          *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
278          *         .build();
279          * </pre>
280          * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
281          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
282          *     rule hasn't been set yet.
283          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
284          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
285          * @return the same Builder instance.
286          * @throws IllegalArgumentException
287          * @see #addRule(AudioAttributes, int)
288          */
289         @SystemApi
excludeRule(AudioAttributes attrToMatch, int rule)290         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
291                 throws IllegalArgumentException {
292             if (!isValidAttributesSystemApiRule(rule)) {
293                 throw new IllegalArgumentException("Illegal rule value " + rule);
294             }
295             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch);
296         }
297 
298         /**
299          * Add a rule for the selection of which streams are mixed together.
300          * The rule defines what the matching will be made on. It also determines the type of the
301          * property to match against.
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          * @see #excludeMixRule(int, Object)
310          */
311         @SystemApi
addMixRule(int rule, Object property)312         public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
313             if (!isValidSystemApiRule(rule)) {
314                 throw new IllegalArgumentException("Illegal rule value " + rule);
315             }
316             return checkAddRuleObjInternal(rule, property);
317         }
318 
319         /**
320          * Add a rule by exclusion for the selection of which streams are mixed together.
321          * <br>For instance the following code
322          * <br><pre>
323          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
324          *         .setUsage(AudioAttributes.USAGE_MEDIA)
325          *         .build();
326          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
327          *         .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr)
328          *         .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude)
329          *         .build();
330          * </pre>
331          * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream
332          * coming from the specified UID.
333          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
334          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
335          *     {@link AudioMixingRule#RULE_MATCH_UID}.
336          * @param property see the definition of each rule for the type to use (either an
337          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
338          * @return the same Builder instance.
339          * @throws IllegalArgumentException
340          */
341         @SystemApi
excludeMixRule(int rule, Object property)342         public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException {
343             if (!isValidSystemApiRule(rule)) {
344                 throw new IllegalArgumentException("Illegal rule value " + rule);
345             }
346             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property);
347         }
348 
349         /**
350          * Add or exclude a rule for the selection of which streams are mixed together.
351          * Does error checking on the parameters.
352          * @param rule
353          * @param property
354          * @return the same Builder instance.
355          * @throws IllegalArgumentException
356          */
checkAddRuleObjInternal(int rule, Object property)357         private Builder checkAddRuleObjInternal(int rule, Object property)
358                 throws IllegalArgumentException {
359             if (property == null) {
360                 throw new IllegalArgumentException("Illegal null argument for mixing rule");
361             }
362             if (!isValidRule(rule)) {
363                 throw new IllegalArgumentException("Illegal rule value " + rule);
364             }
365             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
366             if (isAudioAttributeRule(match_rule)) {
367                 if (!(property instanceof AudioAttributes)) {
368                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
369                 }
370                 return addRuleInternal((AudioAttributes) property, null, rule);
371             } else {
372                 // implies integer match rule
373                 if (!(property instanceof Integer)) {
374                     throw new IllegalArgumentException("Invalid Integer argument");
375                 }
376                 return addRuleInternal(null, (Integer) property, rule);
377             }
378         }
379 
380         /**
381          * Add or exclude a rule on AudioAttributes or integer property for the selection of which
382          * streams are mixed together.
383          * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}.
384          * Exceptions are thrown only when incompatible rules are added.
385          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
386          *     rule hasn't been set yet, null if not used.
387          * @param intProp an integer property to match or exclude, null if not used.
388          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
389          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
390          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
391          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
392          *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
393          * @return the same Builder instance.
394          * @throws IllegalArgumentException
395          */
addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)396         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
397                 throws IllegalArgumentException {
398             // as rules are added to the Builder, we verify they are consistent with the type
399             // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
400             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
401                 if (isPlayerRule(rule)) {
402                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
403                 } else {
404                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
405                 }
406             } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
407                     || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
408             {
409                 throw new IllegalArgumentException("Incompatible rule for mix");
410             }
411             synchronized (mCriteria) {
412                 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
413                 final int match_rule = rule & ~RULE_EXCLUSION_MASK;
414                 while (crIterator.hasNext()) {
415                     final AudioMixMatchCriterion criterion = crIterator.next();
416                     switch (match_rule) {
417                         case RULE_MATCH_ATTRIBUTE_USAGE:
418                             // "usage"-based rule
419                             if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
420                                 if (criterion.mRule == rule) {
421                                     // rule already exists, we're done
422                                     return this;
423                                 } else {
424                                     // criterion already exists with a another rule,
425                                     // it is incompatible
426                                     throw new IllegalArgumentException("Contradictory rule exists"
427                                             + " for " + attrToMatch);
428                                 }
429                             }
430                             break;
431                         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
432                             // "capture preset"-base rule
433                             if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
434                                 if (criterion.mRule == rule) {
435                                     // rule already exists, we're done
436                                     return this;
437                                 } else {
438                                     // criterion already exists with a another rule,
439                                     // it is incompatible
440                                     throw new IllegalArgumentException("Contradictory rule exists"
441                                             + " for " + attrToMatch);
442                                 }
443                             }
444                             break;
445                         case RULE_MATCH_UID:
446                             // "usage"-based rule
447                             if (criterion.mIntProp == intProp.intValue()) {
448                                 if (criterion.mRule == rule) {
449                                     // rule already exists, we're done
450                                     return this;
451                                 } else {
452                                     // criterion already exists with a another rule,
453                                     // it is incompatible
454                                     throw new IllegalArgumentException("Contradictory rule exists"
455                                             + " for UID " + intProp);
456                                 }
457                             }
458                             break;
459                     }
460                 }
461                 // rule didn't exist, add it
462                 switch (match_rule) {
463                     case RULE_MATCH_ATTRIBUTE_USAGE:
464                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
465                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
466                         break;
467                     case RULE_MATCH_UID:
468                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
469                         break;
470                     default:
471                         throw new IllegalStateException("Unreachable code in addRuleInternal()");
472                 }
473             }
474             return this;
475         }
476 
addRuleFromParcel(Parcel in)477         Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
478             final int rule = in.readInt();
479             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
480             AudioAttributes attr = null;
481             Integer intProp = null;
482             switch (match_rule) {
483                 case RULE_MATCH_ATTRIBUTE_USAGE:
484                     int usage = in.readInt();
485                     attr = new AudioAttributes.Builder()
486                             .setUsage(usage).build();
487                     break;
488                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
489                     int preset = in.readInt();
490                     attr = new AudioAttributes.Builder()
491                             .setInternalCapturePreset(preset).build();
492                     break;
493                 case RULE_MATCH_UID:
494                     intProp = new Integer(in.readInt());
495                     break;
496                 default:
497                     // assume there was in int value to read as for now they come in pair
498                     in.readInt();
499                     throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
500             }
501             return addRuleInternal(attr, intProp, rule);
502         }
503 
504         /**
505          * Combines all of the matching and exclusion rules that have been set and return a new
506          * {@link AudioMixingRule} object.
507          * @return a new {@link AudioMixingRule} object
508          */
build()509         public AudioMixingRule build() {
510             return new AudioMixingRule(mTargetMixType, mCriteria);
511         }
512     }
513 }
514