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 138 private final int mTargetMixType; getTargetMixType()139 int getTargetMixType() { return mTargetMixType; } 140 private final ArrayList<AudioMixMatchCriterion> mCriteria; getCriteria()141 ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; } 142 143 @Override hashCode()144 public int hashCode() { 145 return Objects.hash(mTargetMixType, mCriteria); 146 } 147 isValidSystemApiRule(int rule)148 private static boolean isValidSystemApiRule(int rule) { 149 // API rules only expose the RULE_MATCH_* rules 150 switch (rule) { 151 case RULE_MATCH_ATTRIBUTE_USAGE: 152 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 153 case RULE_MATCH_UID: 154 return true; 155 default: 156 return false; 157 } 158 } isValidAttributesSystemApiRule(int rule)159 private static boolean isValidAttributesSystemApiRule(int rule) { 160 // API rules only expose the RULE_MATCH_* rules 161 switch (rule) { 162 case RULE_MATCH_ATTRIBUTE_USAGE: 163 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 164 return true; 165 default: 166 return false; 167 } 168 } 169 isValidRule(int rule)170 private static boolean isValidRule(int rule) { 171 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 172 switch (match_rule) { 173 case RULE_MATCH_ATTRIBUTE_USAGE: 174 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 175 case RULE_MATCH_UID: 176 return true; 177 default: 178 return false; 179 } 180 } 181 isPlayerRule(int rule)182 private static boolean isPlayerRule(int rule) { 183 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 184 switch (match_rule) { 185 case RULE_MATCH_ATTRIBUTE_USAGE: 186 case RULE_MATCH_UID: 187 return true; 188 default: 189 return false; 190 } 191 } 192 isAudioAttributeRule(int match_rule)193 private static boolean isAudioAttributeRule(int match_rule) { 194 switch(match_rule) { 195 case RULE_MATCH_ATTRIBUTE_USAGE: 196 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 197 return true; 198 default: 199 return false; 200 } 201 } 202 203 /** 204 * Builder class for {@link AudioMixingRule} objects 205 */ 206 @SystemApi 207 public static class Builder { 208 private ArrayList<AudioMixMatchCriterion> mCriteria; 209 private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; 210 211 /** 212 * Constructs a new Builder with no rules. 213 */ 214 @SystemApi Builder()215 public Builder() { 216 mCriteria = new ArrayList<AudioMixMatchCriterion>(); 217 } 218 219 /** 220 * Add a rule for the selection of which streams are mixed together. 221 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 222 * rule hasn't been set yet. 223 * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or 224 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. 225 * @return the same Builder instance. 226 * @throws IllegalArgumentException 227 * @see {@link #excludeRule(AudioAttributes, int)} 228 */ 229 @SystemApi addRule(AudioAttributes attrToMatch, int rule)230 public Builder addRule(AudioAttributes attrToMatch, int rule) 231 throws IllegalArgumentException { 232 if (!isValidAttributesSystemApiRule(rule)) { 233 throw new IllegalArgumentException("Illegal rule value " + rule); 234 } 235 return checkAddRuleObjInternal(rule, attrToMatch); 236 } 237 238 /** 239 * Add a rule by exclusion for the selection of which streams are mixed together. 240 * <br>For instance the following code 241 * <br><pre> 242 * AudioAttributes mediaAttr = new AudioAttributes.Builder() 243 * .setUsage(AudioAttributes.USAGE_MEDIA) 244 * .build(); 245 * AudioMixingRule noMediaRule = new AudioMixingRule.Builder() 246 * .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) 247 * .build(); 248 * </pre> 249 * <br>will create a rule which maps to any usage value, except USAGE_MEDIA. 250 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 251 * rule hasn't been set yet. 252 * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or 253 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. 254 * @return the same Builder instance. 255 * @throws IllegalArgumentException 256 * @see {@link #addRule(AudioAttributes, int)} 257 */ 258 @SystemApi excludeRule(AudioAttributes attrToMatch, int rule)259 public Builder excludeRule(AudioAttributes attrToMatch, int rule) 260 throws IllegalArgumentException { 261 if (!isValidAttributesSystemApiRule(rule)) { 262 throw new IllegalArgumentException("Illegal rule value " + rule); 263 } 264 return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, attrToMatch); 265 } 266 267 /** 268 * Add a rule for the selection of which streams are mixed together. 269 * The rule defines what the matching will be made on. It also determines the type of the 270 * property to match against. 271 * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, 272 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or 273 * {@link AudioMixingRule#RULE_MATCH_UID}. 274 * @param property see the definition of each rule for the type to use (either an 275 * {@link AudioAttributes} or an {@link java.lang.Integer}). 276 * @return the same Builder instance. 277 * @throws IllegalArgumentException 278 * @see {@link #excludeMixRule(int, Object)} 279 */ 280 @SystemApi addMixRule(int rule, Object property)281 public Builder addMixRule(int rule, Object property) throws IllegalArgumentException { 282 if (!isValidSystemApiRule(rule)) { 283 throw new IllegalArgumentException("Illegal rule value " + rule); 284 } 285 return checkAddRuleObjInternal(rule, property); 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 * .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, mediaAttr) 297 * .excludeMixRule(AudioMixingRule.RULE_MATCH_UID, new Integer(uidToExclude) 298 * .build(); 299 * </pre> 300 * <br>will create a rule which maps to usage USAGE_MEDIA, but excludes any stream 301 * coming from the specified UID. 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 */ 310 @SystemApi excludeMixRule(int rule, Object property)311 public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException { 312 if (!isValidSystemApiRule(rule)) { 313 throw new IllegalArgumentException("Illegal rule value " + rule); 314 } 315 return checkAddRuleObjInternal(rule | RULE_EXCLUSION_MASK, property); 316 } 317 318 /** 319 * Add or exclude a rule for the selection of which streams are mixed together. 320 * Does error checking on the parameters. 321 * @param rule 322 * @param property 323 * @return the same Builder instance. 324 * @throws IllegalArgumentException 325 */ checkAddRuleObjInternal(int rule, Object property)326 private Builder checkAddRuleObjInternal(int rule, Object property) 327 throws IllegalArgumentException { 328 if (property == null) { 329 throw new IllegalArgumentException("Illegal null argument for mixing rule"); 330 } 331 if (!isValidRule(rule)) { 332 throw new IllegalArgumentException("Illegal rule value " + rule); 333 } 334 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 335 if (isAudioAttributeRule(match_rule)) { 336 if (!(property instanceof AudioAttributes)) { 337 throw new IllegalArgumentException("Invalid AudioAttributes argument"); 338 } 339 return addRuleInternal((AudioAttributes) property, null, rule); 340 } else { 341 // implies integer match rule 342 if (!(property instanceof Integer)) { 343 throw new IllegalArgumentException("Invalid Integer argument"); 344 } 345 return addRuleInternal(null, (Integer) property, rule); 346 } 347 } 348 349 /** 350 * Add or exclude a rule on AudioAttributes or integer property for the selection of which 351 * streams are mixed together. 352 * No rule-to-parameter type check, all done in {@link #checkAddRuleObjInternal(int, Object)}. 353 * Exceptions are thrown only when incompatible rules are added. 354 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 355 * rule hasn't been set yet, null if not used. 356 * @param intProp an integer property to match or exclude, null if not used. 357 * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, 358 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, 359 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or 360 * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}, 361 * {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}. 362 * @return the same Builder instance. 363 * @throws IllegalArgumentException 364 */ addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)365 private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule) 366 throws IllegalArgumentException { 367 // as rules are added to the Builder, we verify they are consistent with the type 368 // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID. 369 if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) { 370 if (isPlayerRule(rule)) { 371 mTargetMixType = AudioMix.MIX_TYPE_PLAYERS; 372 } else { 373 mTargetMixType = AudioMix.MIX_TYPE_RECORDERS; 374 } 375 } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule)) 376 || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule))) 377 { 378 throw new IllegalArgumentException("Incompatible rule for mix"); 379 } 380 synchronized (mCriteria) { 381 Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator(); 382 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 383 while (crIterator.hasNext()) { 384 final AudioMixMatchCriterion criterion = crIterator.next(); 385 switch (match_rule) { 386 case RULE_MATCH_ATTRIBUTE_USAGE: 387 // "usage"-based rule 388 if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) { 389 if (criterion.mRule == rule) { 390 // rule already exists, we're done 391 return this; 392 } else { 393 // criterion already exists with a another rule, 394 // it is incompatible 395 throw new IllegalArgumentException("Contradictory rule exists" 396 + " for " + attrToMatch); 397 } 398 } 399 break; 400 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 401 // "capture preset"-base rule 402 if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) { 403 if (criterion.mRule == rule) { 404 // rule already exists, we're done 405 return this; 406 } else { 407 // criterion already exists with a another rule, 408 // it is incompatible 409 throw new IllegalArgumentException("Contradictory rule exists" 410 + " for " + attrToMatch); 411 } 412 } 413 break; 414 case RULE_MATCH_UID: 415 // "usage"-based rule 416 if (criterion.mIntProp == intProp.intValue()) { 417 if (criterion.mRule == rule) { 418 // rule already exists, we're done 419 return this; 420 } else { 421 // criterion already exists with a another rule, 422 // it is incompatible 423 throw new IllegalArgumentException("Contradictory rule exists" 424 + " for UID " + intProp); 425 } 426 } 427 break; 428 } 429 } 430 // rule didn't exist, add it 431 switch (match_rule) { 432 case RULE_MATCH_ATTRIBUTE_USAGE: 433 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 434 mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule)); 435 break; 436 case RULE_MATCH_UID: 437 mCriteria.add(new AudioMixMatchCriterion(intProp, rule)); 438 break; 439 default: 440 throw new IllegalStateException("Unreachable code in addRuleInternal()"); 441 } 442 } 443 return this; 444 } 445 addRuleFromParcel(Parcel in)446 Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException { 447 final int rule = in.readInt(); 448 final int match_rule = rule & ~RULE_EXCLUSION_MASK; 449 AudioAttributes attr = null; 450 Integer intProp = null; 451 switch (match_rule) { 452 case RULE_MATCH_ATTRIBUTE_USAGE: 453 int usage = in.readInt(); 454 attr = new AudioAttributes.Builder() 455 .setUsage(usage).build(); 456 break; 457 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 458 int preset = in.readInt(); 459 attr = new AudioAttributes.Builder() 460 .setInternalCapturePreset(preset).build(); 461 break; 462 case RULE_MATCH_UID: 463 intProp = new Integer(in.readInt()); 464 break; 465 default: 466 // assume there was in int value to read as for now they come in pair 467 in.readInt(); 468 throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel"); 469 } 470 return addRuleInternal(attr, intProp, rule); 471 } 472 473 /** 474 * Combines all of the matching and exclusion rules that have been set and return a new 475 * {@link AudioMixingRule} object. 476 * @return a new {@link AudioMixingRule} object 477 */ build()478 public AudioMixingRule build() { 479 return new AudioMixingRule(mTargetMixType, mCriteria); 480 } 481 } 482 } 483