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