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