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 android.annotation.NonNull; 20 import android.annotation.SystemApi; 21 import android.annotation.TestApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.media.AudioAttributes; 24 import android.os.Parcel; 25 import android.util.Log; 26 27 import java.util.ArrayList; 28 import java.util.Iterator; 29 import java.util.Objects; 30 31 32 /** 33 * @hide 34 * 35 * Here's an example of creating a mixing rule for all media playback: 36 * <pre> 37 * AudioAttributes mediaAttr = new AudioAttributes.Builder() 38 * .setUsage(AudioAttributes.USAGE_MEDIA) 39 * .build(); 40 * AudioMixingRule mediaRule = new AudioMixingRule.Builder() 41 * .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) 42 * .build(); 43 * </pre> 44 */ 45 @TestApi 46 @SystemApi 47 public class AudioMixingRule { 48 AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria, boolean allowPrivilegedPlaybackCapture)49 private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria, 50 boolean allowPrivilegedPlaybackCapture) { 51 mCriteria = criteria; 52 mTargetMixType = mixType; 53 mAllowPrivilegedPlaybackCapture = allowPrivilegedPlaybackCapture; 54 } 55 56 /** 57 * A rule requiring the usage information of the {@link AudioAttributes} to match. 58 * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or 59 * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of 60 * {@link AudioAttributes}. 61 */ 62 public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1; 63 /** 64 * A rule requiring the capture preset information of the {@link AudioAttributes} to match. 65 * This mixing rule can be added with {@link Builder#addRule(AudioAttributes, int)} or 66 * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of 67 * {@link AudioAttributes}. 68 */ 69 public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1; 70 /** 71 * A rule requiring the UID of the audio stream to match that specified. 72 * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object 73 * parameter is an instance of {@link java.lang.Integer}. 74 */ 75 public static final int RULE_MATCH_UID = 0x1 << 2; 76 77 private final static int RULE_EXCLUSION_MASK = 0x8000; 78 /** 79 * @hide 80 * A rule requiring the usage information of the {@link AudioAttributes} to differ. 81 */ 82 public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 83 RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE; 84 /** 85 * @hide 86 * A rule requiring the capture preset information of the {@link AudioAttributes} to differ. 87 */ 88 public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET = 89 RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET; 90 /** 91 * @hide 92 * A rule requiring the UID information to differ. 93 */ 94 public static final int RULE_EXCLUDE_UID = 95 RULE_EXCLUSION_MASK | RULE_MATCH_UID; 96 97 /** @hide */ 98 public static final class AudioMixMatchCriterion { 99 @UnsupportedAppUsage 100 final AudioAttributes mAttr; 101 @UnsupportedAppUsage 102 final int mIntProp; 103 @UnsupportedAppUsage 104 final int mRule; 105 106 /** input parameters must be valid */ AudioMixMatchCriterion(AudioAttributes attributes, int rule)107 AudioMixMatchCriterion(AudioAttributes attributes, int rule) { 108 mAttr = attributes; 109 mIntProp = Integer.MIN_VALUE; 110 mRule = rule; 111 } 112 /** input parameters must be valid */ AudioMixMatchCriterion(Integer intProp, int rule)113 AudioMixMatchCriterion(Integer intProp, int rule) { 114 mAttr = null; 115 mIntProp = intProp.intValue(); 116 mRule = rule; 117 } 118 119 @Override hashCode()120 public int hashCode() { 121 return Objects.hash(mAttr, mIntProp, mRule); 122 } 123 writeToParcel(Parcel dest)124 void writeToParcel(Parcel dest) { 125 dest.writeInt(mRule); 126 final int match_rule = mRule & ~RULE_EXCLUSION_MASK; 127 switch (match_rule) { 128 case RULE_MATCH_ATTRIBUTE_USAGE: 129 dest.writeInt(mAttr.getUsage()); 130 break; 131 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 132 dest.writeInt(mAttr.getCapturePreset()); 133 break; 134 case RULE_MATCH_UID: 135 dest.writeInt(mIntProp); 136 break; 137 default: 138 Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule 139 + " when writing to Parcel"); 140 dest.writeInt(-1); 141 } 142 } 143 getAudioAttributes()144 public AudioAttributes getAudioAttributes() { return mAttr; } getIntProp()145 public int getIntProp() { return mIntProp; } getRule()146 public int getRule() { return mRule; } 147 } 148 isAffectingUsage(int usage)149 boolean isAffectingUsage(int usage) { 150 for (AudioMixMatchCriterion criterion : mCriteria) { 151 if ((criterion.mRule & RULE_MATCH_ATTRIBUTE_USAGE) != 0 152 && criterion.mAttr != null 153 && criterion.mAttr.getUsage() == usage) { 154 return true; 155 } 156 } 157 return false; 158 } 159 areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1, ArrayList<AudioMixMatchCriterion> cr2)160 private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1, 161 ArrayList<AudioMixMatchCriterion> cr2) { 162 if (cr1 == null || cr2 == null) return false; 163 if (cr1 == cr2) return true; 164 if (cr1.size() != cr2.size()) return false; 165 //TODO iterate over rules to check they contain the same criterion 166 return (cr1.hashCode() == cr2.hashCode()); 167 } 168 169 private final int mTargetMixType; getTargetMixType()170 int getTargetMixType() { return mTargetMixType; } 171 @UnsupportedAppUsage 172 private final ArrayList<AudioMixMatchCriterion> mCriteria; 173 /** @hide */ getCriteria()174 public ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; } 175 @UnsupportedAppUsage 176 private boolean mAllowPrivilegedPlaybackCapture = false; 177 178 /** @hide */ allowPrivilegedPlaybackCapture()179 public boolean allowPrivilegedPlaybackCapture() { 180 return mAllowPrivilegedPlaybackCapture; 181 } 182 183 /** @hide */ 184 @Override equals(Object o)185 public boolean equals(Object o) { 186 if (this == o) return true; 187 if (o == null || getClass() != o.getClass()) return false; 188 189 final AudioMixingRule that = (AudioMixingRule) o; 190 return (this.mTargetMixType == that.mTargetMixType) 191 && (areCriteriaEquivalent(this.mCriteria, that.mCriteria) 192 && this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture); 193 } 194 195 @Override hashCode()196 public int hashCode() { 197 return Objects.hash(mTargetMixType, mCriteria, mAllowPrivilegedPlaybackCapture); 198 } 199 isValidSystemApiRule(int rule)200 private static boolean isValidSystemApiRule(int rule) { 201 // API rules only expose the RULE_MATCH_* rules 202 switch (rule) { 203 case RULE_MATCH_ATTRIBUTE_USAGE: 204 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 205 case RULE_MATCH_UID: 206 return true; 207 default: 208 return false; 209 } 210 } isValidAttributesSystemApiRule(int rule)211 private static boolean isValidAttributesSystemApiRule(int rule) { 212 // API rules only expose the RULE_MATCH_* rules 213 switch (rule) { 214 case RULE_MATCH_ATTRIBUTE_USAGE: 215 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 216 return true; 217 default: 218 return false; 219 } 220 } 221 isValidRule(int rule)222 private static boolean isValidRule(int rule) { 223 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 224 switch (match_rule) { 225 case RULE_MATCH_ATTRIBUTE_USAGE: 226 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 227 case RULE_MATCH_UID: 228 return true; 229 default: 230 return false; 231 } 232 } 233 isPlayerRule(int rule)234 private static boolean isPlayerRule(int rule) { 235 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 236 switch (match_rule) { 237 case RULE_MATCH_ATTRIBUTE_USAGE: 238 case RULE_MATCH_UID: 239 return true; 240 default: 241 return false; 242 } 243 } 244 isAudioAttributeRule(int match_rule)245 private static boolean isAudioAttributeRule(int match_rule) { 246 switch(match_rule) { 247 case RULE_MATCH_ATTRIBUTE_USAGE: 248 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 249 return true; 250 default: 251 return false; 252 } 253 } 254 255 /** 256 * Builder class for {@link AudioMixingRule} objects 257 */ 258 public static class Builder { 259 private ArrayList<AudioMixMatchCriterion> mCriteria; 260 private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; 261 private boolean mAllowPrivilegedPlaybackCapture = false; 262 263 /** 264 * Constructs a new Builder with no rules. 265 */ Builder()266 public Builder() { 267 mCriteria = new ArrayList<AudioMixMatchCriterion>(); 268 } 269 270 /** 271 * Add a rule for the selection of which streams are mixed together. 272 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 273 * rule hasn't been set yet. 274 * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or 275 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. 276 * @return the same Builder instance. 277 * @throws IllegalArgumentException 278 * @see #excludeRule(AudioAttributes, int) 279 */ addRule(AudioAttributes attrToMatch, int rule)280 public Builder addRule(AudioAttributes attrToMatch, int rule) 281 throws IllegalArgumentException { 282 if (!isValidAttributesSystemApiRule(rule)) { 283 throw new IllegalArgumentException("Illegal rule value " + rule); 284 } 285 return checkAddRuleObjInternal(rule, attrToMatch); 286 } 287 288 /** 289 * Add a rule by exclusion for the selection of which streams are mixed together. 290 * <br>For instance the following code 291 * <br><pre> 292 * AudioAttributes mediaAttr = new AudioAttributes.Builder() 293 * .setUsage(AudioAttributes.USAGE_MEDIA) 294 * .build(); 295 * AudioMixingRule noMediaRule = new AudioMixingRule.Builder() 296 * .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) 297 * .build(); 298 * </pre> 299 * <br>will create a rule which maps to any usage value, except USAGE_MEDIA. 300 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 301 * rule hasn't been set yet. 302 * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or 303 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. 304 * @return the same Builder instance. 305 * @throws IllegalArgumentException 306 * @see #addRule(AudioAttributes, int) 307 */ excludeRule(AudioAttributes attrToMatch, int rule)308 public Builder excludeRule(AudioAttributes attrToMatch, int rule) 309 throws IllegalArgumentException { 310 if (!isValidAttributesSystemApiRule(rule)) { 311 throw new IllegalArgumentException("Illegal rule value " + rule); 312 } 313 return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch); 314 } 315 316 /** 317 * Add a rule for the selection of which streams are mixed together. 318 * The rule defines what the matching will be made on. It also determines the type of the 319 * property to match against. 320 * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, 321 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or 322 * {@link AudioMixingRule#RULE_MATCH_UID}. 323 * @param property see the definition of each rule for the type to use (either an 324 * {@link AudioAttributes} or an {@link java.lang.Integer}). 325 * @return the same Builder instance. 326 * @throws IllegalArgumentException 327 * @see #excludeMixRule(int, Object) 328 */ addMixRule(int rule, Object property)329 public Builder addMixRule(int rule, Object property) throws IllegalArgumentException { 330 if (!isValidSystemApiRule(rule)) { 331 throw new IllegalArgumentException("Illegal rule value " + rule); 332 } 333 return checkAddRuleObjInternal(rule, property); 334 } 335 336 /** 337 * Add a rule by exclusion for the selection of which streams are mixed together. 338 * <br>For instance the following code 339 * <br><pre> 340 * AudioAttributes mediaAttr = new AudioAttributes.Builder() 341 * .setUsage(AudioAttributes.USAGE_MEDIA) 342 * .build(); 343 * AudioMixingRule noMediaRule = new AudioMixingRule.Builder() 344 * .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr) 345 * .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude) 346 * .build(); 347 * </pre> 348 * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream 349 * coming from the specified UID. 350 * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, 351 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or 352 * {@link AudioMixingRule#RULE_MATCH_UID}. 353 * @param property see the definition of each rule for the type to use (either an 354 * {@link AudioAttributes} or an {@link java.lang.Integer}). 355 * @return the same Builder instance. 356 * @throws IllegalArgumentException 357 */ excludeMixRule(int rule, Object property)358 public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException { 359 if (!isValidSystemApiRule(rule)) { 360 throw new IllegalArgumentException("Illegal rule value " + rule); 361 } 362 return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property); 363 } 364 365 /** 366 * Set if the audio of app that opted out of audio playback capture should be captured. 367 * 368 * Caller of this method with <code>true</code>, MUST abide to the restriction listed in 369 * {@link ALLOW_CAPTURE_BY_SYSTEM}, including but not limited to the captured audio 370 * can not leave the capturing app, and the quality is limited to 16k mono. 371 * 372 * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed 373 * to ignore the opt-out. 374 * 375 * Only affects LOOPBACK|RENDER mix. 376 * 377 * @return the same Builder instance. 378 */ allowPrivilegedPlaybackCapture(boolean allow)379 public @NonNull Builder allowPrivilegedPlaybackCapture(boolean allow) { 380 mAllowPrivilegedPlaybackCapture = allow; 381 return this; 382 } 383 384 /** 385 * Add or exclude a rule for the selection of which streams are mixed together. 386 * Does error checking on the parameters. 387 * @param rule 388 * @param property 389 * @return the same Builder instance. 390 * @throws IllegalArgumentException 391 */ checkAddRuleObjInternal(int rule, Object property)392 private Builder checkAddRuleObjInternal(int rule, Object property) 393 throws IllegalArgumentException { 394 if (property == null) { 395 throw new IllegalArgumentException("Illegal null argument for mixing rule"); 396 } 397 if (!isValidRule(rule)) { 398 throw new IllegalArgumentException("Illegal rule value " + rule); 399 } 400 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 401 if (isAudioAttributeRule(match_rule)) { 402 if (!(property instanceof AudioAttributes)) { 403 throw new IllegalArgumentException("Invalid AudioAttributes argument"); 404 } 405 return addRuleInternal((AudioAttributes) property, null, rule); 406 } else { 407 // implies integer match rule 408 if (!(property instanceof Integer)) { 409 throw new IllegalArgumentException("Invalid Integer argument"); 410 } 411 return addRuleInternal(null, (Integer) property, rule); 412 } 413 } 414 415 /** 416 * Add or exclude a rule on AudioAttributes or integer property for the selection of which 417 * streams are mixed together. 418 * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}. 419 * Exceptions are thrown only when incompatible rules are added. 420 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 421 * rule hasn't been set yet, null if not used. 422 * @param intProp an integer property to match or exclude, null if not used. 423 * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, 424 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, 425 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or 426 * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}, 427 * {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}. 428 * @return the same Builder instance. 429 * @throws IllegalArgumentException 430 */ addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)431 private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule) 432 throws IllegalArgumentException { 433 // as rules are added to the Builder, we verify they are consistent with the type 434 // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID. 435 if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) { 436 if (isPlayerRule(rule)) { 437 mTargetMixType = AudioMix.MIX_TYPE_PLAYERS; 438 } else { 439 mTargetMixType = AudioMix.MIX_TYPE_RECORDERS; 440 } 441 } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule)) 442 || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule))) 443 { 444 throw new IllegalArgumentException("Incompatible rule for mix"); 445 } 446 synchronized (mCriteria) { 447 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator(); 448 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 449 while (crIterator.hasNext()) { 450 final AudioMixMatchCriterion criterion = crIterator.next(); 451 452 if ((criterion.mRule & ~RULE_EXCLUSION_MASK) != match_rule) { 453 continue; // The two rules are not of the same type 454 } 455 switch (match_rule) { 456 case RULE_MATCH_ATTRIBUTE_USAGE: 457 // "usage"-based rule 458 if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) { 459 if (criterion.mRule == rule) { 460 // rule already exists, we're done 461 return this; 462 } else { 463 // criterion already exists with a another rule, 464 // it is incompatible 465 throw new IllegalArgumentException("Contradictory rule exists" 466 + " for " + attrToMatch); 467 } 468 } 469 break; 470 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 471 // "capture preset"-base rule 472 if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) { 473 if (criterion.mRule == rule) { 474 // rule already exists, we're done 475 return this; 476 } else { 477 // criterion already exists with a another rule, 478 // it is incompatible 479 throw new IllegalArgumentException("Contradictory rule exists" 480 + " for " + attrToMatch); 481 } 482 } 483 break; 484 case RULE_MATCH_UID: 485 // "usage"-based rule 486 if (criterion.mIntProp == intProp.intValue()) { 487 if (criterion.mRule == rule) { 488 // rule already exists, we're done 489 return this; 490 } else { 491 // criterion already exists with a another rule, 492 // it is incompatible 493 throw new IllegalArgumentException("Contradictory rule exists" 494 + " for UID " + intProp); 495 } 496 } 497 break; 498 } 499 } 500 // rule didn't exist, add it 501 switch (match_rule) { 502 case RULE_MATCH_ATTRIBUTE_USAGE: 503 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 504 mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule)); 505 break; 506 case RULE_MATCH_UID: 507 mCriteria.add(new AudioMixMatchCriterion(intProp, rule)); 508 break; 509 default: 510 throw new IllegalStateException("Unreachable code in addRuleInternal()"); 511 } 512 } 513 return this; 514 } 515 addRuleFromParcel(Parcel in)516 Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException { 517 final int rule = in.readInt(); 518 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 519 AudioAttributes attr = null; 520 Integer intProp = null; 521 switch (match_rule) { 522 case RULE_MATCH_ATTRIBUTE_USAGE: 523 int usage = in.readInt(); 524 attr = new AudioAttributes.Builder() 525 .setUsage(usage).build(); 526 break; 527 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 528 int preset = in.readInt(); 529 attr = new AudioAttributes.Builder() 530 .setInternalCapturePreset(preset).build(); 531 break; 532 case RULE_MATCH_UID: 533 intProp = new Integer(in.readInt()); 534 break; 535 default: 536 // assume there was in int value to read as for now they come in pair 537 in.readInt(); 538 throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel"); 539 } 540 return addRuleInternal(attr, intProp, rule); 541 } 542 543 /** 544 * Combines all of the matching and exclusion rules that have been set and return a new 545 * {@link AudioMixingRule} object. 546 * @return a new {@link AudioMixingRule} object 547 */ build()548 public AudioMixingRule build() { 549 return new AudioMixingRule(mTargetMixType, mCriteria, mAllowPrivilegedPlaybackCapture); 550 } 551 } 552 } 553