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.NonNull; 20 import android.media.AudioFormat; 21 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.util.Log; 25 26 import com.android.internal.annotations.GuardedBy; 27 28 import java.util.ArrayList; 29 import java.util.Objects; 30 31 /** 32 * @hide 33 * Internal storage class for AudioPolicy configuration. 34 */ 35 public class AudioPolicyConfig implements Parcelable { 36 37 private static final String TAG = "AudioPolicyConfig"; 38 39 protected final ArrayList<AudioMix> mMixes; 40 protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP; 41 42 private String mRegistrationId = null; 43 44 /** counter for the mixes that are / have been in the list of AudioMix 45 * e.g. register 4 mixes (counter is 3), remove 1 (counter is 3), add 1 (counter is 4) 46 */ 47 private int mMixCounter = 0; 48 AudioPolicyConfig(AudioPolicyConfig conf)49 protected AudioPolicyConfig(AudioPolicyConfig conf) { 50 mMixes = conf.mMixes; 51 } 52 AudioPolicyConfig(ArrayList<AudioMix> mixes)53 AudioPolicyConfig(ArrayList<AudioMix> mixes) { 54 mMixes = mixes; 55 } 56 57 /** 58 * Add an {@link AudioMix} to be part of the audio policy being built. 59 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 60 * @return the same Builder instance. 61 * @throws IllegalArgumentException 62 */ addMix(AudioMix mix)63 public void addMix(AudioMix mix) throws IllegalArgumentException { 64 if (mix == null) { 65 throw new IllegalArgumentException("Illegal null AudioMix argument"); 66 } 67 mMixes.add(mix); 68 } 69 getMixes()70 public ArrayList<AudioMix> getMixes() { 71 return mMixes; 72 } 73 74 @Override hashCode()75 public int hashCode() { 76 return Objects.hash(mMixes); 77 } 78 79 @Override describeContents()80 public int describeContents() { 81 return 0; 82 } 83 84 @Override writeToParcel(Parcel dest, int flags)85 public void writeToParcel(Parcel dest, int flags) { 86 dest.writeInt(mMixes.size()); 87 for (AudioMix mix : mMixes) { 88 // write mix route flags 89 dest.writeInt(mix.getRouteFlags()); 90 // write callback flags 91 dest.writeInt(mix.mCallbackFlags); 92 // write device information 93 dest.writeInt(mix.mDeviceSystemType); 94 dest.writeString(mix.mDeviceAddress); 95 // write mix format 96 dest.writeInt(mix.getFormat().getSampleRate()); 97 dest.writeInt(mix.getFormat().getEncoding()); 98 dest.writeInt(mix.getFormat().getChannelMask()); 99 // write opt-out respect 100 dest.writeBoolean(mix.getRule().allowPrivilegedPlaybackCapture()); 101 // write mix rules 102 final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); 103 dest.writeInt(criteria.size()); 104 for (AudioMixMatchCriterion criterion : criteria) { 105 criterion.writeToParcel(dest); 106 } 107 } 108 } 109 AudioPolicyConfig(Parcel in)110 private AudioPolicyConfig(Parcel in) { 111 mMixes = new ArrayList<AudioMix>(); 112 int nbMixes = in.readInt(); 113 for (int i = 0 ; i < nbMixes ; i++) { 114 final AudioMix.Builder mixBuilder = new AudioMix.Builder(); 115 // read mix route flags 116 int routeFlags = in.readInt(); 117 mixBuilder.setRouteFlags(routeFlags); 118 // read callback flags 119 mixBuilder.setCallbackFlags(in.readInt()); 120 // read device information 121 mixBuilder.setDevice(in.readInt(), in.readString()); 122 // read mix format 123 int sampleRate = in.readInt(); 124 int encoding = in.readInt(); 125 int channelMask = in.readInt(); 126 final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate) 127 .setChannelMask(channelMask).setEncoding(encoding).build(); 128 mixBuilder.setFormat(format); 129 130 AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder(); 131 // write opt-out respect 132 ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean()); 133 // read mix rules 134 int nbRules = in.readInt(); 135 for (int j = 0 ; j < nbRules ; j++) { 136 // read the matching rules 137 ruleBuilder.addRuleFromParcel(in); 138 } 139 mixBuilder.setMixingRule(ruleBuilder.build()); 140 mMixes.add(mixBuilder.build()); 141 } 142 } 143 144 public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR 145 = new Parcelable.Creator<AudioPolicyConfig>() { 146 /** 147 * Rebuilds an AudioPolicyConfig previously stored with writeToParcel(). 148 * @param p Parcel object to read the AudioPolicyConfig from 149 * @return a new AudioPolicyConfig created from the data in the parcel 150 */ 151 public AudioPolicyConfig createFromParcel(Parcel p) { 152 return new AudioPolicyConfig(p); 153 } 154 public AudioPolicyConfig[] newArray(int size) { 155 return new AudioPolicyConfig[size]; 156 } 157 }; 158 toLogFriendlyString()159 public String toLogFriendlyString () { 160 String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n"); 161 textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n"; 162 for(AudioMix mix : mMixes) { 163 // write mix route flags 164 textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n"; 165 // write mix format 166 textDump += " rate=" + mix.getFormat().getSampleRate() + "Hz\n"; 167 textDump += " encoding=" + mix.getFormat().getEncoding() + "\n"; 168 textDump += " channels=0x"; 169 textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n"; 170 textDump += " ignore playback capture opt out=" 171 + mix.getRule().allowPrivilegedPlaybackCapture() + "\n"; 172 // write mix rules 173 final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); 174 for (AudioMixMatchCriterion criterion : criteria) { 175 switch(criterion.mRule) { 176 case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE: 177 textDump += " exclude usage "; 178 textDump += criterion.mAttr.usageToString(); 179 break; 180 case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE: 181 textDump += " match usage "; 182 textDump += criterion.mAttr.usageToString(); 183 break; 184 case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET: 185 textDump += " exclude capture preset "; 186 textDump += criterion.mAttr.getCapturePreset(); 187 break; 188 case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 189 textDump += " match capture preset "; 190 textDump += criterion.mAttr.getCapturePreset(); 191 break; 192 case AudioMixingRule.RULE_MATCH_UID: 193 textDump += " match UID "; 194 textDump += criterion.mIntProp; 195 break; 196 case AudioMixingRule.RULE_EXCLUDE_UID: 197 textDump += " exclude UID "; 198 textDump += criterion.mIntProp; 199 break; 200 default: 201 textDump += "invalid rule!"; 202 } 203 textDump += "\n"; 204 } 205 } 206 return textDump; 207 } 208 setRegistration(String regId)209 protected void setRegistration(String regId) { 210 final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty(); 211 final boolean newRegNull = (regId == null) || regId.isEmpty(); 212 if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) { 213 Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId); 214 return; 215 } 216 mRegistrationId = regId == null ? "" : regId; 217 for (AudioMix mix : mMixes) { 218 setMixRegistration(mix); 219 } 220 } 221 setMixRegistration(@onNull final AudioMix mix)222 private void setMixRegistration(@NonNull final AudioMix mix) { 223 if (!mRegistrationId.isEmpty()) { 224 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) == 225 AudioMix.ROUTE_FLAG_LOOP_BACK) { 226 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":" 227 + mMixCounter); 228 } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) == 229 AudioMix.ROUTE_FLAG_RENDER) { 230 mix.setRegistration(mix.mDeviceAddress); 231 } 232 } else { 233 mix.setRegistration(""); 234 } 235 mMixCounter++; 236 } 237 238 @GuardedBy("mMixes") add(@onNull ArrayList<AudioMix> mixes)239 protected void add(@NonNull ArrayList<AudioMix> mixes) { 240 for (AudioMix mix : mixes) { 241 setMixRegistration(mix); 242 mMixes.add(mix); 243 } 244 } 245 246 @GuardedBy("mMixes") remove(@onNull ArrayList<AudioMix> mixes)247 protected void remove(@NonNull ArrayList<AudioMix> mixes) { 248 for (AudioMix mix : mixes) { 249 mMixes.remove(mix); 250 } 251 } 252 mixTypeId(int type)253 private static String mixTypeId(int type) { 254 if (type == AudioMix.MIX_TYPE_PLAYERS) return "p"; 255 else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r"; 256 else return "i"; 257 } 258 getRegistration()259 protected String getRegistration() { 260 return mRegistrationId; 261 } 262 } 263