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