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