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 23 import java.util.ArrayList; 24 import java.util.Iterator; 25 import java.util.Objects; 26 27 28 /** 29 * @hide 30 * 31 * Here's an example of creating a mixing rule for all media playback: 32 * <pre> 33 * AudioAttributes mediaAttr = new AudioAttributes.Builder() 34 * .setUsage(AudioAttributes.USAGE_MEDIA) 35 * .build(); 36 * AudioMixingRule mediaRule = new AudioMixingRule.Builder() 37 * .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) 38 * .build(); 39 * </pre> 40 */ 41 @SystemApi 42 public class AudioMixingRule { 43 AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria)44 private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) { 45 mCriteria = criteria; 46 mTargetMixType = mixType; 47 } 48 49 /** 50 * A rule requiring the usage information of the {@link AudioAttributes} to match. 51 */ 52 @SystemApi 53 public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1; 54 /** 55 * A rule requiring the capture preset information of the {@link AudioAttributes} to match. 56 */ 57 @SystemApi 58 public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1; 59 60 private final static int RULE_EXCLUSION_MASK = 0x8000; 61 /** 62 * @hide 63 * A rule requiring the usage information of the {@link AudioAttributes} to differ. 64 */ 65 public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 66 RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE; 67 /** 68 * @hide 69 * A rule requiring the capture preset information of the {@link AudioAttributes} to differ. 70 */ 71 public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET = 72 RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET; 73 74 static final class AttributeMatchCriterion { 75 AudioAttributes mAttr; 76 int mRule; 77 78 /** input parameters must be valid */ AttributeMatchCriterion(AudioAttributes attributes, int rule)79 AttributeMatchCriterion(AudioAttributes attributes, int rule) { 80 mAttr = attributes; 81 mRule = rule; 82 } 83 84 @Override hashCode()85 public int hashCode() { 86 return Objects.hash(mAttr, mRule); 87 } 88 writeToParcel(Parcel dest)89 void writeToParcel(Parcel dest) { 90 dest.writeInt(mRule); 91 if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { 92 dest.writeInt(mAttr.getUsage()); 93 } else { 94 // capture preset rule 95 dest.writeInt(mAttr.getCapturePreset()); 96 } 97 } 98 } 99 100 private final int mTargetMixType; getTargetMixType()101 int getTargetMixType() { return mTargetMixType; } 102 private final ArrayList<AttributeMatchCriterion> mCriteria; getCriteria()103 ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; } 104 105 @Override hashCode()106 public int hashCode() { 107 return Objects.hash(mTargetMixType, mCriteria); 108 } 109 isValidSystemApiRule(int rule)110 private static boolean isValidSystemApiRule(int rule) { 111 switch(rule) { 112 case RULE_MATCH_ATTRIBUTE_USAGE: 113 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 114 return true; 115 default: 116 return false; 117 } 118 } 119 isValidIntRule(int rule)120 private static boolean isValidIntRule(int rule) { 121 switch(rule) { 122 case RULE_MATCH_ATTRIBUTE_USAGE: 123 case RULE_EXCLUDE_ATTRIBUTE_USAGE: 124 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 125 case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET: 126 return true; 127 default: 128 return false; 129 } 130 } 131 isPlayerRule(int rule)132 private static boolean isPlayerRule(int rule) { 133 return ((rule == RULE_MATCH_ATTRIBUTE_USAGE) 134 || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)); 135 } 136 137 /** 138 * Builder class for {@link AudioMixingRule} objects 139 */ 140 @SystemApi 141 public static class Builder { 142 private ArrayList<AttributeMatchCriterion> mCriteria; 143 private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; 144 145 /** 146 * Constructs a new Builder with no rules. 147 */ 148 @SystemApi Builder()149 public Builder() { 150 mCriteria = new ArrayList<AttributeMatchCriterion>(); 151 } 152 153 /** 154 * Add a rule for the selection of which streams are mixed together. 155 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 156 * rule hasn't been set yet. 157 * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or 158 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. 159 * @return the same Builder instance. 160 * @throws IllegalArgumentException 161 */ 162 @SystemApi addRule(AudioAttributes attrToMatch, int rule)163 public Builder addRule(AudioAttributes attrToMatch, int rule) 164 throws IllegalArgumentException { 165 if (!isValidSystemApiRule(rule)) { 166 throw new IllegalArgumentException("Illegal rule value " + rule); 167 } 168 return addRuleInt(attrToMatch, rule); 169 } 170 171 /** 172 * Add a rule by exclusion for the selection of which streams are mixed together. 173 * <br>For instance the following code 174 * <br><pre> 175 * AudioAttributes mediaAttr = new AudioAttributes.Builder() 176 * .setUsage(AudioAttributes.USAGE_MEDIA) 177 * .build(); 178 * AudioMixingRule noMediaRule = new AudioMixingRule.Builder() 179 * .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) 180 * .build(); 181 * </pre> 182 * <br>will create a rule which maps to any usage value, except USAGE_MEDIA. 183 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 184 * rule hasn't been set yet. 185 * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or 186 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. 187 * @return the same Builder instance. 188 * @throws IllegalArgumentException 189 */ 190 @SystemApi excludeRule(AudioAttributes attrToMatch, int rule)191 public Builder excludeRule(AudioAttributes attrToMatch, int rule) 192 throws IllegalArgumentException { 193 if (!isValidSystemApiRule(rule)) { 194 throw new IllegalArgumentException("Illegal rule value " + rule); 195 } 196 return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK); 197 } 198 199 /** 200 * Add or exclude a rule for the selection of which streams are mixed together. 201 * @param attrToMatch a non-null AudioAttributes instance for which a contradictory 202 * rule hasn't been set yet. 203 * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, 204 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, 205 * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or 206 * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}. 207 * @return the same Builder instance. 208 * @throws IllegalArgumentException 209 */ addRuleInt(AudioAttributes attrToMatch, int rule)210 Builder addRuleInt(AudioAttributes attrToMatch, int rule) 211 throws IllegalArgumentException { 212 if (attrToMatch == null) { 213 throw new IllegalArgumentException("Illegal null AudioAttributes argument"); 214 } 215 if (!isValidIntRule(rule)) { 216 throw new IllegalArgumentException("Illegal rule value " + rule); 217 } else { 218 // as rules are added to the Builder, we verify they are consistent with the type 219 // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID. 220 if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) { 221 if (isPlayerRule(rule)) { 222 mTargetMixType = AudioMix.MIX_TYPE_PLAYERS; 223 } else { 224 mTargetMixType = AudioMix.MIX_TYPE_RECORDERS; 225 } 226 } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule)) 227 || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule))) 228 { 229 throw new IllegalArgumentException("Incompatible rule for mix"); 230 } 231 } 232 synchronized (mCriteria) { 233 Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator(); 234 while (crIterator.hasNext()) { 235 final AttributeMatchCriterion criterion = crIterator.next(); 236 if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) 237 || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { 238 // "usage"-based rule 239 if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) { 240 if (criterion.mRule == rule) { 241 // rule already exists, we're done 242 return this; 243 } else { 244 // criterion already exists with a another rule, it is incompatible 245 throw new IllegalArgumentException("Contradictory rule exists for " 246 + attrToMatch); 247 } 248 } 249 } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET) 250 || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) { 251 // "capture preset"-base rule 252 if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) { 253 if (criterion.mRule == rule) { 254 // rule already exists, we're done 255 return this; 256 } else { 257 // criterion already exists with a another rule, it is incompatible 258 throw new IllegalArgumentException("Contradictory rule exists for " 259 + attrToMatch); 260 } 261 } 262 } 263 } 264 // rule didn't exist, add it 265 mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule)); 266 } 267 return this; 268 } 269 addRuleFromParcel(Parcel in)270 Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException { 271 int rule = in.readInt(); 272 AudioAttributes attr; 273 if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { 274 int usage = in.readInt(); 275 attr = new AudioAttributes.Builder() 276 .setUsage(usage).build(); 277 } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET) 278 || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) { 279 int preset = in.readInt(); 280 attr = new AudioAttributes.Builder() 281 .setInternalCapturePreset(preset).build(); 282 } else { 283 in.readInt(); // assume there was in int value to read as for now they come in pair 284 throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel"); 285 } 286 return addRuleInt(attr, rule); 287 } 288 289 /** 290 * Combines all of the matching and exclusion rules that have been set and return a new 291 * {@link AudioMixingRule} object. 292 * @return a new {@link AudioMixingRule} object 293 */ build()294 public AudioMixingRule build() { 295 return new AudioMixingRule(mTargetMixType, mCriteria); 296 } 297 } 298 } 299