• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static java.lang.annotation.RetentionPolicy.SOURCE;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.SystemApi;
24 import android.compat.annotation.UnsupportedAppUsage;
25 import android.media.AudioAttributes;
26 import android.media.MediaRecorder;
27 import android.os.Build;
28 import android.os.Parcel;
29 import android.util.Log;
30 
31 import java.lang.annotation.Retention;
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 import java.util.Objects;
35 
36 
37 /**
38  * @hide
39  *
40  * Here's an example of creating a mixing rule for all media playback:
41  * <pre>
42  * AudioAttributes mediaAttr = new AudioAttributes.Builder()
43  *         .setUsage(AudioAttributes.USAGE_MEDIA)
44  *         .build();
45  * AudioMixingRule mediaRule = new AudioMixingRule.Builder()
46  *         .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
47  *         .build();
48  * </pre>
49  */
50 @SystemApi
51 public class AudioMixingRule {
52 
AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria, boolean allowPrivilegedMediaPlaybackCapture, boolean voiceCommunicationCaptureAllowed)53     private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria,
54                             boolean allowPrivilegedMediaPlaybackCapture,
55                             boolean voiceCommunicationCaptureAllowed) {
56         mCriteria = criteria;
57         mTargetMixType = mixType;
58         mAllowPrivilegedPlaybackCapture = allowPrivilegedMediaPlaybackCapture;
59         mVoiceCommunicationCaptureAllowed = voiceCommunicationCaptureAllowed;
60     }
61 
62     /**
63      * A rule requiring the usage information of the {@link AudioAttributes} to match.
64      * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or
65      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
66      * {@link AudioAttributes}.
67      */
68     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
69     /**
70      * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
71      * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or
72      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
73      * {@link AudioAttributes}.
74      */
75     public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
76     /**
77      * A rule requiring the UID of the audio stream to match that specified.
78      * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object
79      * parameter is an instance of {@link java.lang.Integer}.
80      */
81     public static final int RULE_MATCH_UID = 0x1 << 2;
82     /**
83      * A rule requiring the userId of the audio stream to match that specified.
84      * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object
85      * parameter is an instance of {@link java.lang.Integer}.
86      */
87     public static final int RULE_MATCH_USERID = 0x1 << 3;
88 
89     private final static int RULE_EXCLUSION_MASK = 0x8000;
90     /**
91      * @hide
92      * A rule requiring the usage information of the {@link AudioAttributes} to differ.
93      */
94     public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
95             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
96     /**
97      * @hide
98      * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
99      */
100     public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
101             RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
102     /**
103      * @hide
104      * A rule requiring the UID information to differ.
105      */
106     public static final int RULE_EXCLUDE_UID =
107             RULE_EXCLUSION_MASK | RULE_MATCH_UID;
108 
109     /**
110      * @hide
111      * A rule requiring the userId information to differ.
112      */
113     public static final int RULE_EXCLUDE_USERID =
114             RULE_EXCLUSION_MASK | RULE_MATCH_USERID;
115 
116     /** @hide */
117     public static final class AudioMixMatchCriterion {
118         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
119         final AudioAttributes mAttr;
120         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
121         final int mIntProp;
122         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
123         final int mRule;
124 
125         /** input parameters must be valid */
AudioMixMatchCriterion(AudioAttributes attributes, int rule)126         AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
127             mAttr = attributes;
128             mIntProp = Integer.MIN_VALUE;
129             mRule = rule;
130         }
131         /** input parameters must be valid */
AudioMixMatchCriterion(Integer intProp, int rule)132         AudioMixMatchCriterion(Integer intProp, int rule) {
133             mAttr = null;
134             mIntProp = intProp.intValue();
135             mRule = rule;
136         }
137 
138         @Override
hashCode()139         public int hashCode() {
140             return Objects.hash(mAttr, mIntProp, mRule);
141         }
142 
writeToParcel(Parcel dest)143         void writeToParcel(Parcel dest) {
144             dest.writeInt(mRule);
145             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
146             switch (match_rule) {
147                 case RULE_MATCH_ATTRIBUTE_USAGE:
148                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
149                     mAttr.writeToParcel(dest, AudioAttributes.FLATTEN_TAGS/*flags*/);
150                     break;
151                 case RULE_MATCH_UID:
152                 case RULE_MATCH_USERID:
153                     dest.writeInt(mIntProp);
154                     break;
155                 default:
156                     Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule
157                             + " when writing to Parcel");
158                     dest.writeInt(-1);
159             }
160         }
161 
getAudioAttributes()162         public AudioAttributes getAudioAttributes() { return mAttr; }
getIntProp()163         public int getIntProp() { return mIntProp; }
getRule()164         public int getRule() { return mRule; }
165     }
166 
isAffectingUsage(int usage)167     boolean isAffectingUsage(int usage) {
168         for (AudioMixMatchCriterion criterion : mCriteria) {
169             if ((criterion.mRule & RULE_MATCH_ATTRIBUTE_USAGE) != 0
170                     && criterion.mAttr != null
171                     && criterion.mAttr.getSystemUsage() == usage) {
172                 return true;
173             }
174         }
175         return false;
176     }
177 
178     /**
179       * Returns {@code true} if this rule contains a RULE_MATCH_ATTRIBUTE_USAGE criterion for
180       * the given usage
181       *
182       * @hide
183       */
containsMatchAttributeRuleForUsage(int usage)184     boolean containsMatchAttributeRuleForUsage(int usage) {
185         for (AudioMixMatchCriterion criterion : mCriteria) {
186             if (criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
187                     && criterion.mAttr != null
188                     && criterion.mAttr.getSystemUsage() == usage) {
189                 return true;
190             }
191         }
192         return false;
193     }
194 
areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1, ArrayList<AudioMixMatchCriterion> cr2)195     private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
196             ArrayList<AudioMixMatchCriterion> cr2) {
197         if (cr1 == null || cr2 == null) return false;
198         if (cr1 == cr2) return true;
199         if (cr1.size() != cr2.size()) return false;
200         //TODO iterate over rules to check they contain the same criterion
201         return (cr1.hashCode() == cr2.hashCode());
202     }
203 
204     private final int mTargetMixType;
getTargetMixType()205     int getTargetMixType() {
206         return mTargetMixType;
207     }
208 
209     /**
210      * Captures an audio signal from one or more playback streams.
211      */
212     public static final int MIX_ROLE_PLAYERS = AudioMix.MIX_TYPE_PLAYERS;
213     /**
214      * Injects an audio signal into the framework to replace a recording source.
215      */
216     public static final int MIX_ROLE_INJECTOR = AudioMix.MIX_TYPE_RECORDERS;
217 
218     /** @hide */
219     @IntDef({MIX_ROLE_PLAYERS, MIX_ROLE_INJECTOR})
220     @Retention(SOURCE)
221     public @interface MixRole {}
222 
223     /**
224      * Gets target mix role of this mixing rule.
225      *
226      * <p>The mix role indicates playback streams will be captured or recording source will be
227      * injected.
228      *
229      * @return integer value of {@link #MIX_ROLE_PLAYERS} or {@link #MIX_ROLE_INJECTOR}
230      */
getTargetMixRole()231     public @MixRole int getTargetMixRole() {
232         return mTargetMixType == AudioMix.MIX_TYPE_RECORDERS ? MIX_ROLE_INJECTOR : MIX_ROLE_PLAYERS;
233     }
234 
235     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
236     private final ArrayList<AudioMixMatchCriterion> mCriteria;
237     /** @hide */
getCriteria()238     public ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
239     /** Indicates that this rule is intended to capture media or game playback by a system component
240       * with permission CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT.
241       */
242     //TODO b/177061175: rename to mAllowPrivilegedMediaPlaybackCapture
243     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
244     private boolean mAllowPrivilegedPlaybackCapture = false;
245     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
246     private boolean mVoiceCommunicationCaptureAllowed = false;
247 
248     /** @hide */
allowPrivilegedMediaPlaybackCapture()249     public boolean allowPrivilegedMediaPlaybackCapture() {
250         return mAllowPrivilegedPlaybackCapture;
251     }
252 
253     /** @hide */
voiceCommunicationCaptureAllowed()254     public boolean voiceCommunicationCaptureAllowed() {
255         return mVoiceCommunicationCaptureAllowed;
256     }
257 
258     /** @hide */
setVoiceCommunicationCaptureAllowed(boolean allowed)259     public void setVoiceCommunicationCaptureAllowed(boolean allowed) {
260         mVoiceCommunicationCaptureAllowed = allowed;
261     }
262 
263     /** @hide */
isForCallRedirection()264     public boolean isForCallRedirection() {
265         for (AudioMixMatchCriterion criterion : mCriteria) {
266             if (criterion.mAttr != null
267                     && criterion.mAttr.isForCallRedirection()
268                     && ((criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
269                         && (criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION
270                             || criterion.mAttr.getUsage()
271                                 == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING))
272                     || (criterion.mRule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET
273                         && (criterion.mAttr.getCapturePreset()
274                             == MediaRecorder.AudioSource.VOICE_COMMUNICATION)))) {
275                 return true;
276             }
277         }
278         return false;
279     }
280 
281     /** @hide */
282     @Override
equals(Object o)283     public boolean equals(Object o) {
284         if (this == o) return true;
285         if (o == null || getClass() != o.getClass()) return false;
286 
287         final AudioMixingRule that = (AudioMixingRule) o;
288         return (this.mTargetMixType == that.mTargetMixType)
289                 && (areCriteriaEquivalent(this.mCriteria, that.mCriteria)
290                 && this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture
291                 && this.mVoiceCommunicationCaptureAllowed
292                     == that.mVoiceCommunicationCaptureAllowed);
293     }
294 
295     @Override
hashCode()296     public int hashCode() {
297         return Objects.hash(
298             mTargetMixType,
299             mCriteria,
300             mAllowPrivilegedPlaybackCapture,
301             mVoiceCommunicationCaptureAllowed);
302     }
303 
isValidSystemApiRule(int rule)304     private static boolean isValidSystemApiRule(int rule) {
305         // API rules only expose the RULE_MATCH_* rules
306         switch (rule) {
307             case RULE_MATCH_ATTRIBUTE_USAGE:
308             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
309             case RULE_MATCH_UID:
310             case RULE_MATCH_USERID:
311                 return true;
312             default:
313                 return false;
314         }
315     }
isValidAttributesSystemApiRule(int rule)316     private static boolean isValidAttributesSystemApiRule(int rule) {
317         // API rules only expose the RULE_MATCH_* rules
318         switch (rule) {
319             case RULE_MATCH_ATTRIBUTE_USAGE:
320             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
321                 return true;
322             default:
323                 return false;
324         }
325     }
326 
isValidRule(int rule)327     private static boolean isValidRule(int rule) {
328         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
329         switch (match_rule) {
330             case RULE_MATCH_ATTRIBUTE_USAGE:
331             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
332             case RULE_MATCH_UID:
333             case RULE_MATCH_USERID:
334                 return true;
335             default:
336                 return false;
337         }
338     }
339 
isPlayerRule(int rule)340     private static boolean isPlayerRule(int rule) {
341         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
342         switch (match_rule) {
343             case RULE_MATCH_ATTRIBUTE_USAGE:
344             case RULE_MATCH_USERID:
345                 return true;
346             default:
347                 return false;
348         }
349     }
350 
isRecorderRule(int rule)351     private static boolean isRecorderRule(int rule) {
352         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
353         switch (match_rule) {
354             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
355                 return true;
356             default:
357                 return false;
358         }
359     }
360 
isAudioAttributeRule(int match_rule)361     private static boolean isAudioAttributeRule(int match_rule) {
362         switch(match_rule) {
363             case RULE_MATCH_ATTRIBUTE_USAGE:
364             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
365                 return true;
366             default:
367                 return false;
368         }
369     }
370 
371     /**
372      * Builder class for {@link AudioMixingRule} objects
373      */
374     public static class Builder {
375         private ArrayList<AudioMixMatchCriterion> mCriteria;
376         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
377         private boolean mAllowPrivilegedMediaPlaybackCapture = false;
378         // This value should be set internally according to a permission check
379         private boolean mVoiceCommunicationCaptureAllowed = false;
380 
381         /**
382          * Constructs a new Builder with no rules.
383          */
Builder()384         public Builder() {
385             mCriteria = new ArrayList<AudioMixMatchCriterion>();
386         }
387 
388         /**
389          * Add a rule for the selection of which streams are mixed together.
390          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
391          *     rule hasn't been set yet.
392          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
393          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
394          * @return the same Builder instance.
395          * @throws IllegalArgumentException
396          * @see #excludeRule(AudioAttributes, int)
397          */
addRule(AudioAttributes attrToMatch, int rule)398         public Builder addRule(AudioAttributes attrToMatch, int rule)
399                 throws IllegalArgumentException {
400             if (!isValidAttributesSystemApiRule(rule)) {
401                 throw new IllegalArgumentException("Illegal rule value " + rule);
402             }
403             return checkAddRuleObjInternal(rule, attrToMatch);
404         }
405 
406         /**
407          * Add a rule by exclusion for the selection of which streams are mixed together.
408          * <br>For instance the following code
409          * <br><pre>
410          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
411          *         .setUsage(AudioAttributes.USAGE_MEDIA)
412          *         .build();
413          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
414          *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
415          *         .build();
416          * </pre>
417          * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
418          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
419          *     rule hasn't been set yet.
420          * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
421          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
422          * @return the same Builder instance.
423          * @throws IllegalArgumentException
424          * @see #addRule(AudioAttributes, int)
425          */
excludeRule(AudioAttributes attrToMatch, int rule)426         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
427                 throws IllegalArgumentException {
428             if (!isValidAttributesSystemApiRule(rule)) {
429                 throw new IllegalArgumentException("Illegal rule value " + rule);
430             }
431             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch);
432         }
433 
434         /**
435          * Add a rule for the selection of which streams are mixed together.
436          * The rule defines what the matching will be made on. It also determines the type of the
437          * property to match against.
438          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
439          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
440          *     {@link AudioMixingRule#RULE_MATCH_UID} or
441          *     {@link AudioMixingRule#RULE_MATCH_USERID}.
442          * @param property see the definition of each rule for the type to use (either an
443          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
444          * @return the same Builder instance.
445          * @throws IllegalArgumentException
446          * @see #excludeMixRule(int, Object)
447          */
addMixRule(int rule, Object property)448         public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
449             if (!isValidSystemApiRule(rule)) {
450                 throw new IllegalArgumentException("Illegal rule value " + rule);
451             }
452             return checkAddRuleObjInternal(rule, property);
453         }
454 
455         /**
456          * Add a rule by exclusion for the selection of which streams are mixed together.
457          * <br>For instance the following code
458          * <br><pre>
459          * AudioAttributes mediaAttr = new AudioAttributes.Builder()
460          *         .setUsage(AudioAttributes.USAGE_MEDIA)
461          *         .build();
462          * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
463          *         .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr)
464          *         .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude)
465          *         .build();
466          * </pre>
467          * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream
468          * coming from the specified UID.
469          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
470          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
471          *     {@link AudioMixingRule#RULE_MATCH_UID} or
472          *     {@link AudioMixingRule#RULE_MATCH_USERID}.
473          * @param property see the definition of each rule for the type to use (either an
474          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
475          * @return the same Builder instance.
476          * @throws IllegalArgumentException
477          */
excludeMixRule(int rule, Object property)478         public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException {
479             if (!isValidSystemApiRule(rule)) {
480                 throw new IllegalArgumentException("Illegal rule value " + rule);
481             }
482             return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property);
483         }
484 
485         /**
486          * Set if the audio of app that opted out of audio playback capture should be captured.
487          *
488          * Caller of this method with <code>true</code>, MUST abide to the restriction listed in
489          * {@link ALLOW_CAPTURE_BY_SYSTEM}, including but not limited to the captured audio
490          * can not leave the capturing app, and the quality is limited to 16k mono.
491          *
492          * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed
493          * to ignore the opt-out.
494          *
495          * Only affects LOOPBACK|RENDER mix.
496          *
497          * @return the same Builder instance.
498          */
allowPrivilegedPlaybackCapture(boolean allow)499         public @NonNull Builder allowPrivilegedPlaybackCapture(boolean allow) {
500             mAllowPrivilegedMediaPlaybackCapture = allow;
501             return this;
502         }
503 
504         /**
505          * Set if the caller of the rule is able to capture voice communication output.
506          * A system app can capture voice communication output only if it is granted with the.
507          * CAPTURE_VOICE_COMMUNICATION_OUTPUT permission.
508          *
509          * Note that this method is for internal use only and should not be called by the app that
510          * creates the rule.
511          *
512          * @return the same Builder instance.
513          *
514          * @hide
515          */
voiceCommunicationCaptureAllowed(boolean allowed)516         public @NonNull Builder voiceCommunicationCaptureAllowed(boolean allowed) {
517             mVoiceCommunicationCaptureAllowed = allowed;
518             return this;
519         }
520 
521         /**
522          * Sets target mix role of the mixing rule.
523          *
524          * As each mixing rule is intended to be associated with an {@link AudioMix},
525          * explicitly setting the role of a mixing rule allows this {@link Builder} to
526          * verify validity of the mixing rules to be validated.<br>
527          * The mix role allows distinguishing between:
528          * <ul>
529          * <li>audio framework mixers that will mix / sample-rate convert / reformat the audio
530          *     signal of any audio player (e.g. a {@link android.media.MediaPlayer}) that matches
531          *     the selection rules defined in the object being built. Use
532          *     {@link AudioMixingRule#MIX_ROLE_PLAYERS} for such an {@code AudioMixingRule}</li>
533          * <li>audio framework mixers that will be used as the injection point (after sample-rate
534          *     conversion and reformatting of the audio signal) into any audio recorder (e.g. a
535          *     {@link android.media.AudioRecord}) that matches the selection rule defined in the
536          *     object being built. Use {@link AudioMixingRule#MIX_ROLE_INJECTOR} for such an
537          *     {@code AudioMixingRule}.</li>
538          * </ul>
539          * <p>If not specified, the mix role will be decided automatically when
540          * {@link #addRule(AudioAttributes, int)} or {@link #addMixRule(int, Object)} be called.
541          *
542          * @param mixRole integer value of {@link #MIX_ROLE_PLAYERS} or {@link #MIX_ROLE_INJECTOR}
543          * @return the same Builder instance.
544          */
setTargetMixRole(@ixRole int mixRole)545         public @NonNull Builder setTargetMixRole(@MixRole int mixRole) {
546             if (mixRole != MIX_ROLE_PLAYERS && mixRole != MIX_ROLE_INJECTOR) {
547                 throw new IllegalArgumentException("Illegal argument for mix role");
548             }
549 
550             Log.i("AudioMixingRule", "Builder setTargetMixRole " + mixRole);
551             mTargetMixType = mixRole == MIX_ROLE_INJECTOR
552                     ? AudioMix.MIX_TYPE_RECORDERS : AudioMix.MIX_TYPE_PLAYERS;
553             return this;
554         }
555 
556         /**
557          * Add or exclude a rule for the selection of which streams are mixed together.
558          * Does error checking on the parameters.
559          * @param rule
560          * @param property
561          * @return the same Builder instance.
562          * @throws IllegalArgumentException
563          */
checkAddRuleObjInternal(int rule, Object property)564         private Builder checkAddRuleObjInternal(int rule, Object property)
565                 throws IllegalArgumentException {
566             if (property == null) {
567                 throw new IllegalArgumentException("Illegal null argument for mixing rule");
568             }
569             if (!isValidRule(rule)) {
570                 throw new IllegalArgumentException("Illegal rule value " + rule);
571             }
572             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
573             if (isAudioAttributeRule(match_rule)) {
574                 if (!(property instanceof AudioAttributes)) {
575                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
576                 }
577                 return addRuleInternal((AudioAttributes) property, null, rule);
578             } else {
579                 // implies integer match rule
580                 if (!(property instanceof Integer)) {
581                     throw new IllegalArgumentException("Invalid Integer argument");
582                 }
583                 return addRuleInternal(null, (Integer) property, rule);
584             }
585         }
586 
587         /**
588          * Add or exclude a rule on AudioAttributes or integer property for the selection of which
589          * streams are mixed together.
590          * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}.
591          * Exceptions are thrown only when incompatible rules are added.
592          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
593          *     rule hasn't been set yet, null if not used.
594          * @param intProp an integer property to match or exclude, null if not used.
595          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
596          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
597          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
598          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
599          *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
600          *     {@link AudioMixingRule#RULE_MATCH_USERID},
601          *     {@link AudioMixingRule#RULE_EXCLUDE_USERID}.
602          * @return the same Builder instance.
603          * @throws IllegalArgumentException
604          */
addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)605         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
606                 throws IllegalArgumentException {
607             // as rules are added to the Builder, we verify they are consistent with the type
608             // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
609             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
610                 if (isPlayerRule(rule)) {
611                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
612                 } else if (isRecorderRule(rule)) {
613                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
614                 } else {
615                     // For rules which are not player or recorder specific (e.g. RULE_MATCH_UID),
616                     // the default mix type is MIX_TYPE_PLAYERS.
617                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
618                 }
619             } else if ((isPlayerRule(rule) && (mTargetMixType != AudioMix.MIX_TYPE_PLAYERS))
620                     || (isRecorderRule(rule)) && (mTargetMixType != AudioMix.MIX_TYPE_RECORDERS))
621             {
622                 throw new IllegalArgumentException("Incompatible rule for mix");
623             }
624             synchronized (mCriteria) {
625                 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
626                 final int match_rule = rule & ~RULE_EXCLUSION_MASK;
627                 while (crIterator.hasNext()) {
628                     final AudioMixMatchCriterion criterion = crIterator.next();
629 
630                     if ((criterion.mRule & ~RULE_EXCLUSION_MASK) != match_rule) {
631                         continue; // The two rules are not of the same type
632                     }
633                     switch (match_rule) {
634                         case RULE_MATCH_ATTRIBUTE_USAGE:
635                             // "usage"-based rule
636                             if (criterion.mAttr.getSystemUsage() == attrToMatch.getSystemUsage()) {
637                                 if (criterion.mRule == rule) {
638                                     // rule already exists, we're done
639                                     return this;
640                                 } else {
641                                     // criterion already exists with a another rule,
642                                     // it is incompatible
643                                     throw new IllegalArgumentException("Contradictory rule exists"
644                                             + " for " + attrToMatch);
645                                 }
646                             }
647                             break;
648                         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
649                             // "capture preset"-base rule
650                             if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
651                                 if (criterion.mRule == rule) {
652                                     // rule already exists, we're done
653                                     return this;
654                                 } else {
655                                     // criterion already exists with a another rule,
656                                     // it is incompatible
657                                     throw new IllegalArgumentException("Contradictory rule exists"
658                                             + " for " + attrToMatch);
659                                 }
660                             }
661                             break;
662                         case RULE_MATCH_UID:
663                             // "usage"-based rule
664                             if (criterion.mIntProp == intProp.intValue()) {
665                                 if (criterion.mRule == rule) {
666                                     // rule already exists, we're done
667                                     return this;
668                                 } else {
669                                     // criterion already exists with a another rule,
670                                     // it is incompatible
671                                     throw new IllegalArgumentException("Contradictory rule exists"
672                                             + " for UID " + intProp);
673                                 }
674                             }
675                             break;
676                         case RULE_MATCH_USERID:
677                             // "userid"-based rule
678                             if (criterion.mIntProp == intProp.intValue()) {
679                                 if (criterion.mRule == rule) {
680                                     // rule already exists, we're done
681                                     return this;
682                                 } else {
683                                     // criterion already exists with a another rule,
684                                     // it is incompatible
685                                     throw new IllegalArgumentException("Contradictory rule exists"
686                                             + " for userId " + intProp);
687                                 }
688                             }
689                             break;
690                     }
691                 }
692                 // rule didn't exist, add it
693                 switch (match_rule) {
694                     case RULE_MATCH_ATTRIBUTE_USAGE:
695                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
696                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
697                         break;
698                     case RULE_MATCH_UID:
699                     case RULE_MATCH_USERID:
700                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
701                         break;
702                     default:
703                         throw new IllegalStateException("Unreachable code in addRuleInternal()");
704                 }
705             }
706             return this;
707         }
708 
addRuleFromParcel(Parcel in)709         Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
710             final int rule = in.readInt();
711             final int match_rule = rule & ~RULE_EXCLUSION_MASK;
712             AudioAttributes attr = null;
713             Integer intProp = null;
714             switch (match_rule) {
715                 case RULE_MATCH_ATTRIBUTE_USAGE:
716                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
717                     attr =  AudioAttributes.CREATOR.createFromParcel(in);
718                     break;
719                 case RULE_MATCH_UID:
720                 case RULE_MATCH_USERID:
721                     intProp = new Integer(in.readInt());
722                     break;
723                 default:
724                     // assume there was in int value to read as for now they come in pair
725                     in.readInt();
726                     throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
727             }
728             return addRuleInternal(attr, intProp, rule);
729         }
730 
731         /**
732          * Combines all of the matching and exclusion rules that have been set and return a new
733          * {@link AudioMixingRule} object.
734          * @return a new {@link AudioMixingRule} object
735          */
build()736         public AudioMixingRule build() {
737             return new AudioMixingRule(mTargetMixType, mCriteria,
738                 mAllowPrivilegedMediaPlaybackCapture, mVoiceCommunicationCaptureAllowed);
739         }
740     }
741 }
742