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