| /* |
| * 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.NonNull; |
| import android.media.AudioFormat; |
| 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; |
| |
| /** |
| * @hide |
| * Internal storage class for AudioPolicy configuration. |
| */ |
| 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; |
| |
| /** counter for the mixes that are / have been in the list of AudioMix |
| * e.g. register 4 mixes (counter is 3), remove 1 (counter is 3), add 1 (counter is 4) |
| */ |
| private int mMixCounter = 0; |
| |
| protected AudioPolicyConfig(AudioPolicyConfig conf) { |
| mMixes = conf.mMixes; |
| } |
| |
| AudioPolicyConfig(ArrayList<AudioMix> mixes) { |
| mMixes = mixes; |
| } |
| |
| /** |
| * Add an {@link AudioMix} to be part of the audio policy being built. |
| * @param mix a non-null {@link AudioMix} to be part of the audio policy. |
| * @return the same Builder instance. |
| * @throws IllegalArgumentException |
| */ |
| 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) { |
| // write mix route flags |
| dest.writeInt(mix.getRouteFlags()); |
| // write callback flags |
| dest.writeInt(mix.mCallbackFlags); |
| // write device information |
| dest.writeInt(mix.mDeviceSystemType); |
| dest.writeString(mix.mDeviceAddress); |
| // write mix format |
| dest.writeInt(mix.getFormat().getSampleRate()); |
| dest.writeInt(mix.getFormat().getEncoding()); |
| dest.writeInt(mix.getFormat().getChannelMask()); |
| // write opt-out respect |
| dest.writeBoolean(mix.getRule().allowPrivilegedMediaPlaybackCapture()); |
| // write voice communication capture allowed flag |
| dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed()); |
| // write specified mix type |
| dest.writeInt(mix.getRule().getTargetMixRole()); |
| // write mix rules |
| 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(); |
| // read mix route flags |
| int routeFlags = in.readInt(); |
| mixBuilder.setRouteFlags(routeFlags); |
| // read callback flags |
| mixBuilder.setCallbackFlags(in.readInt()); |
| // read device information |
| mixBuilder.setDevice(in.readInt(), in.readString()); |
| // read mix format |
| 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); |
| |
| AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder(); |
| // read opt-out respect |
| ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean()); |
| // read voice capture allowed flag |
| ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean()); |
| // read specified mix type |
| ruleBuilder.setTargetMixRole(in.readInt()); |
| // read mix rules |
| int nbRules = in.readInt(); |
| for (int j = 0 ; j < nbRules ; j++) { |
| // read the matching rules |
| ruleBuilder.addRuleFromParcel(in); |
| } |
| mixBuilder.setMixingRule(ruleBuilder.build()); |
| mMixes.add(mixBuilder.build()); |
| } |
| } |
| |
| public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR |
| = new Parcelable.Creator<AudioPolicyConfig>() { |
| /** |
| * Rebuilds an AudioPolicyConfig previously stored with writeToParcel(). |
| * @param p Parcel object to read the AudioPolicyConfig from |
| * @return a new AudioPolicyConfig created from the data in the parcel |
| */ |
| 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, reg:" + mRegistrationId + "\n"; |
| for(AudioMix mix : mMixes) { |
| // write mix route flags |
| textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n"; |
| // write mix format |
| textDump += " rate=" + mix.getFormat().getSampleRate() + "Hz\n"; |
| textDump += " encoding=" + mix.getFormat().getEncoding() + "\n"; |
| textDump += " channels=0x"; |
| textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n"; |
| textDump += " ignore playback capture opt out=" |
| + mix.getRule().allowPrivilegedMediaPlaybackCapture() + "\n"; |
| textDump += " allow voice communication capture=" |
| + mix.getRule().voiceCommunicationCaptureAllowed() + "\n"; |
| // write mix rules |
| textDump += " specified mix type=" |
| + mix.getRule().getTargetMixRole() + "\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; |
| case AudioMixingRule.RULE_MATCH_USERID: |
| textDump += " match userId "; |
| textDump += criterion.mIntProp; |
| break; |
| case AudioMixingRule.RULE_EXCLUDE_USERID: |
| textDump += " exclude userId "; |
| textDump += criterion.mIntProp; |
| break; |
| default: |
| textDump += "invalid rule!"; |
| } |
| textDump += "\n"; |
| } |
| } |
| return textDump; |
| } |
| |
| /** |
| * Very short dump of configuration |
| * @return a condensed dump of configuration, uniquely identifies a policy in a log |
| */ |
| public String toCompactLogString() { |
| String compactDump = "reg:" + mRegistrationId; |
| int mixNum = 0; |
| for (AudioMix mix : mMixes) { |
| compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType()) |
| + "-Rul:" + mix.getRule().getCriteria().size(); |
| mixNum++; |
| } |
| return compactDump; |
| } |
| |
| private static String mixTypePrefix(int mixType) { |
| switch (mixType) { |
| case AudioMix.MIX_TYPE_PLAYERS: |
| return "p"; |
| case AudioMix.MIX_TYPE_RECORDERS: |
| return "r"; |
| case AudioMix.MIX_TYPE_INVALID: |
| default: |
| return "#"; |
| |
| } |
| } |
| |
| protected void reset() { |
| mMixCounter = 0; |
| } |
| |
| 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; |
| } |
| } |