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