package android.media.audiopolicy;
import android.annotation.NonNull;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioPatch;
import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Objects;
public class AudioPolicyConfig implements Parcelable {
private static final String TAG = "AudioPolicyConfig";
protected final ArrayList<AudioMix> mMixes;
protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
private String mRegistrationId = null;
private int mMixCounter = 0;
protected AudioPolicyConfig(AudioPolicyConfig conf) {
mMixes = conf.mMixes;
}
AudioPolicyConfig(ArrayList<AudioMix> mixes) {
mMixes = mixes;
}
public void addMix(AudioMix mix) throws IllegalArgumentException {
if (mix == null) {
throw new IllegalArgumentException("Illegal null AudioMix argument");
}
mMixes.add(mix);
}
public ArrayList<AudioMix> getMixes() {
return mMixes;
}
@Override
public int hashCode() {
return Objects.hash(mMixes);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mMixes.size());
for (AudioMix mix : mMixes) {
dest.writeInt(mix.getRouteFlags());
dest.writeInt(mix.mCallbackFlags);
dest.writeInt(mix.mDeviceSystemType);
dest.writeString(mix.mDeviceAddress);
dest.writeInt(mix.getFormat().getSampleRate());
dest.writeInt(mix.getFormat().getEncoding());
dest.writeInt(mix.getFormat().getChannelMask());
final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
dest.writeInt(criteria.size());
for (AudioMixMatchCriterion criterion : criteria) {
criterion.writeToParcel(dest);
}
}
}
private AudioPolicyConfig(Parcel in) {
mMixes = new ArrayList<AudioMix>();
int nbMixes = in.readInt();
for (int i = 0 ; i < nbMixes ; i++) {
final AudioMix.Builder mixBuilder = new AudioMix.Builder();
int routeFlags = in.readInt();
mixBuilder.setRouteFlags(routeFlags);
mixBuilder.setCallbackFlags(in.readInt());
mixBuilder.setDevice(in.readInt(), in.readString());
int sampleRate = in.readInt();
int encoding = in.readInt();
int channelMask = in.readInt();
final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
.setChannelMask(channelMask).setEncoding(encoding).build();
mixBuilder.setFormat(format);
int nbRules = in.readInt();
AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
for (int j = 0 ; j < nbRules ; j++) {
ruleBuilder.addRuleFromParcel(in);
}
mixBuilder.setMixingRule(ruleBuilder.build());
mMixes.add(mixBuilder.build());
}
}
public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
= new Parcelable.Creator<AudioPolicyConfig>() {
public AudioPolicyConfig createFromParcel(Parcel p) {
return new AudioPolicyConfig(p);
}
public AudioPolicyConfig[] newArray(int size) {
return new AudioPolicyConfig[size];
}
};
public String toLogFriendlyString () {
String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
for(AudioMix mix : mMixes) {
textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
textDump += " rate=" + mix.getFormat().getSampleRate() + "Hz\n";
textDump += " encoding=" + mix.getFormat().getEncoding() + "\n";
textDump += " channels=0x";
textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() +"\n";
final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
for (AudioMixMatchCriterion criterion : criteria) {
switch(criterion.mRule) {
case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
textDump += " exclude usage ";
textDump += criterion.mAttr.usageToString();
break;
case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
textDump += " match usage ";
textDump += criterion.mAttr.usageToString();
break;
case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
textDump += " exclude capture preset ";
textDump += criterion.mAttr.getCapturePreset();
break;
case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
textDump += " match capture preset ";
textDump += criterion.mAttr.getCapturePreset();
break;
case AudioMixingRule.RULE_MATCH_UID:
textDump += " match UID ";
textDump += criterion.mIntProp;
break;
case AudioMixingRule.RULE_EXCLUDE_UID:
textDump += " exclude UID ";
textDump += criterion.mIntProp;
break;
default:
textDump += "invalid rule!";
}
textDump += "\n";
}
}
return textDump;
}
protected void setRegistration(String regId) {
final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
final boolean newRegNull = (regId == null) || regId.isEmpty();
if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
return;
}
mRegistrationId = regId == null ? "" : regId;
for (AudioMix mix : mMixes) {
setMixRegistration(mix);
}
}
private void setMixRegistration(@NonNull final AudioMix mix) {
if (!mRegistrationId.isEmpty()) {
if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
AudioMix.ROUTE_FLAG_LOOP_BACK) {
mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+ mMixCounter);
} else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
AudioMix.ROUTE_FLAG_RENDER) {
mix.setRegistration(mix.mDeviceAddress);
}
} else {
mix.setRegistration("");
}
mMixCounter++;
}
@GuardedBy("mMixes")
protected void add(@NonNull ArrayList<AudioMix> mixes) {
for (AudioMix mix : mixes) {
setMixRegistration(mix);
mMixes.add(mix);
}
}
@GuardedBy("mMixes")
protected void remove(@NonNull ArrayList<AudioMix> mixes) {
for (AudioMix mix : mixes) {
mMixes.remove(mix);
}
}
private static String mixTypeId(int type) {
if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
else return "i";
}
protected String getRegistration() {
return mRegistrationId;
}
}