/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.audiopolicy;
import android.annotation.SystemApi;
import android.media.AudioAttributes;
import android.os.Parcel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
/**
* @hide
*
* Here's an example of creating a mixing rule for all media playback:
*
* AudioAttributes mediaAttr = new AudioAttributes.Builder()
* .setUsage(AudioAttributes.USAGE_MEDIA)
* .build();
* AudioMixingRule mediaRule = new AudioMixingRule.Builder()
* .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
* .build();
*
*/
@SystemApi
public class AudioMixingRule {
private AudioMixingRule(int mixType, ArrayList criteria) {
mCriteria = criteria;
mTargetMixType = mixType;
}
/**
* A rule requiring the usage information of the {@link AudioAttributes} to match.
*/
@SystemApi
public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
/**
* A rule requiring the capture preset information of the {@link AudioAttributes} to match.
*/
@SystemApi
public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
private final static int RULE_EXCLUSION_MASK = 0x8000;
/**
* @hide
* A rule requiring the usage information of the {@link AudioAttributes} to differ.
*/
public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
/**
* @hide
* A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
*/
public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
static final class AttributeMatchCriterion {
AudioAttributes mAttr;
int mRule;
/** input parameters must be valid */
AttributeMatchCriterion(AudioAttributes attributes, int rule) {
mAttr = attributes;
mRule = rule;
}
@Override
public int hashCode() {
return Objects.hash(mAttr, mRule);
}
void writeToParcel(Parcel dest) {
dest.writeInt(mRule);
if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
dest.writeInt(mAttr.getUsage());
} else {
// capture preset rule
dest.writeInt(mAttr.getCapturePreset());
}
}
}
private final int mTargetMixType;
int getTargetMixType() { return mTargetMixType; }
private final ArrayList mCriteria;
ArrayList getCriteria() { return mCriteria; }
@Override
public int hashCode() {
return Objects.hash(mTargetMixType, mCriteria);
}
private static boolean isValidSystemApiRule(int rule) {
switch(rule) {
case RULE_MATCH_ATTRIBUTE_USAGE:
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
return true;
default:
return false;
}
}
private static boolean isValidIntRule(int rule) {
switch(rule) {
case RULE_MATCH_ATTRIBUTE_USAGE:
case RULE_EXCLUDE_ATTRIBUTE_USAGE:
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
return true;
default:
return false;
}
}
private static boolean isPlayerRule(int rule) {
return ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
|| (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE));
}
/**
* Builder class for {@link AudioMixingRule} objects
*/
@SystemApi
public static class Builder {
private ArrayList mCriteria;
private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
/**
* Constructs a new Builder with no rules.
*/
@SystemApi
public Builder() {
mCriteria = new ArrayList();
}
/**
* Add a rule for the selection of which streams are mixed together.
* @param attrToMatch a non-null AudioAttributes instance for which a contradictory
* rule hasn't been set yet.
* @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
@SystemApi
public Builder addRule(AudioAttributes attrToMatch, int rule)
throws IllegalArgumentException {
if (!isValidSystemApiRule(rule)) {
throw new IllegalArgumentException("Illegal rule value " + rule);
}
return addRuleInt(attrToMatch, rule);
}
/**
* Add a rule by exclusion for the selection of which streams are mixed together.
*
For instance the following code
*
* AudioAttributes mediaAttr = new AudioAttributes.Builder()
* .setUsage(AudioAttributes.USAGE_MEDIA)
* .build();
* AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
* .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
* .build();
*
*
will create a rule which maps to any usage value, except USAGE_MEDIA.
* @param attrToMatch a non-null AudioAttributes instance for which a contradictory
* rule hasn't been set yet.
* @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
@SystemApi
public Builder excludeRule(AudioAttributes attrToMatch, int rule)
throws IllegalArgumentException {
if (!isValidSystemApiRule(rule)) {
throw new IllegalArgumentException("Illegal rule value " + rule);
}
return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK);
}
/**
* Add or exclude a rule for the selection of which streams are mixed together.
* @param attrToMatch a non-null AudioAttributes instance for which a contradictory
* rule hasn't been set yet.
* @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
* {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
Builder addRuleInt(AudioAttributes attrToMatch, int rule)
throws IllegalArgumentException {
if (attrToMatch == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes argument");
}
if (!isValidIntRule(rule)) {
throw new IllegalArgumentException("Illegal rule value " + rule);
} else {
// as rules are added to the Builder, we verify they are consistent with the type
// of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
if (isPlayerRule(rule)) {
mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
} else {
mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
}
} else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
|| ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
{
throw new IllegalArgumentException("Incompatible rule for mix");
}
}
synchronized (mCriteria) {
Iterator crIterator = mCriteria.iterator();
while (crIterator.hasNext()) {
final AttributeMatchCriterion criterion = crIterator.next();
if ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
|| (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
// "usage"-based rule
if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
if (criterion.mRule == rule) {
// rule already exists, we're done
return this;
} else {
// criterion already exists with a another rule, it is incompatible
throw new IllegalArgumentException("Contradictory rule exists for "
+ attrToMatch);
}
}
} else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
|| (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
// "capture preset"-base rule
if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
if (criterion.mRule == rule) {
// rule already exists, we're done
return this;
} else {
// criterion already exists with a another rule, it is incompatible
throw new IllegalArgumentException("Contradictory rule exists for "
+ attrToMatch);
}
}
}
}
// rule didn't exist, add it
mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule));
}
return this;
}
Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
int rule = in.readInt();
AudioAttributes attr;
if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
int usage = in.readInt();
attr = new AudioAttributes.Builder()
.setUsage(usage).build();
} else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
|| (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
int preset = in.readInt();
attr = new AudioAttributes.Builder()
.setInternalCapturePreset(preset).build();
} else {
in.readInt(); // assume there was in int value to read as for now they come in pair
throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
}
return addRuleInt(attr, rule);
}
/**
* Combines all of the matching and exclusion rules that have been set and return a new
* {@link AudioMixingRule} object.
* @return a new {@link AudioMixingRule} object
*/
public AudioMixingRule build() {
return new AudioMixingRule(mTargetMixType, mCriteria);
}
}
}