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 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.util.ArrayList; 30 import java.util.Objects; 31 32 /** 33 * @hide 34 * Internal storage class for AudioPolicy configuration. 35 */ 36 public class AudioPolicyConfig implements Parcelable { 37 38 private static final String TAG = "AudioPolicyConfig"; 39 40 protected final ArrayList<AudioMix> mMixes; 41 protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP; 42 43 private String mRegistrationId = null; 44 45 // Corresponds to id of next mix to be registered. 46 private int mMixCounter = 0; 47 AudioPolicyConfig(AudioPolicyConfig conf)48 protected AudioPolicyConfig(AudioPolicyConfig conf) { 49 mMixes = conf.mMixes; 50 } 51 52 @VisibleForTesting AudioPolicyConfig(ArrayList<AudioMix> mixes)53 public 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().allowPrivilegedMediaPlaybackCapture()); 101 // write voice communication capture allowed flag 102 dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed()); 103 // write specified mix type 104 dest.writeInt(mix.getRule().getTargetMixRole()); 105 // write mix rules 106 final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); 107 dest.writeInt(criteria.size()); 108 for (AudioMixMatchCriterion criterion : criteria) { 109 criterion.writeToParcel(dest); 110 } 111 } 112 } 113 AudioPolicyConfig(Parcel in)114 private AudioPolicyConfig(Parcel in) { 115 mMixes = new ArrayList<AudioMix>(); 116 int nbMixes = in.readInt(); 117 for (int i = 0 ; i < nbMixes ; i++) { 118 final AudioMix.Builder mixBuilder = new AudioMix.Builder(); 119 // read mix route flags 120 int routeFlags = in.readInt(); 121 mixBuilder.setRouteFlags(routeFlags); 122 // read callback flags 123 mixBuilder.setCallbackFlags(in.readInt()); 124 // read device information 125 mixBuilder.setDevice(in.readInt(), in.readString()); 126 // read mix format 127 int sampleRate = in.readInt(); 128 int encoding = in.readInt(); 129 int channelMask = in.readInt(); 130 final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate) 131 .setChannelMask(channelMask).setEncoding(encoding).build(); 132 mixBuilder.setFormat(format); 133 134 AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder(); 135 // read opt-out respect 136 ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean()); 137 // read voice capture allowed flag 138 ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean()); 139 // read specified mix type 140 ruleBuilder.setTargetMixRole(in.readInt()); 141 // read mix rules 142 int nbRules = in.readInt(); 143 for (int j = 0 ; j < nbRules ; j++) { 144 // read the matching rules 145 ruleBuilder.addRuleFromParcel(in); 146 } 147 mixBuilder.setMixingRule(ruleBuilder.build()); 148 mMixes.add(mixBuilder.build()); 149 } 150 } 151 152 public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR 153 = new Parcelable.Creator<AudioPolicyConfig>() { 154 /** 155 * Rebuilds an AudioPolicyConfig previously stored with writeToParcel(). 156 * @param p Parcel object to read the AudioPolicyConfig from 157 * @return a new AudioPolicyConfig created from the data in the parcel 158 */ 159 public AudioPolicyConfig createFromParcel(Parcel p) { 160 return new AudioPolicyConfig(p); 161 } 162 public AudioPolicyConfig[] newArray(int size) { 163 return new AudioPolicyConfig[size]; 164 } 165 }; 166 toLogFriendlyString()167 public String toLogFriendlyString () { 168 String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n"); 169 textDump += mMixes.size() + " AudioMix, reg:" + mRegistrationId + "\n"; 170 for(AudioMix mix : mMixes) { 171 // write mix route flags 172 textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n"; 173 // write mix format 174 textDump += " rate=" + mix.getFormat().getSampleRate() + "Hz\n"; 175 textDump += " encoding=" + mix.getFormat().getEncoding() + "\n"; 176 textDump += " channels=0x"; 177 textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n"; 178 textDump += " ignore playback capture opt out=" 179 + mix.getRule().allowPrivilegedMediaPlaybackCapture() + "\n"; 180 textDump += " allow voice communication capture=" 181 + mix.getRule().voiceCommunicationCaptureAllowed() + "\n"; 182 // write mix rules 183 textDump += " specified mix type=" 184 + mix.getRule().getTargetMixRole() + "\n"; 185 final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); 186 for (AudioMixMatchCriterion criterion : criteria) { 187 switch(criterion.mRule) { 188 case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE: 189 textDump += " exclude usage "; 190 textDump += criterion.mAttr.usageToString(); 191 break; 192 case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE: 193 textDump += " match usage "; 194 textDump += criterion.mAttr.usageToString(); 195 break; 196 case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET: 197 textDump += " exclude capture preset "; 198 textDump += criterion.mAttr.getCapturePreset(); 199 break; 200 case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 201 textDump += " match capture preset "; 202 textDump += criterion.mAttr.getCapturePreset(); 203 break; 204 case AudioMixingRule.RULE_MATCH_UID: 205 textDump += " match UID "; 206 textDump += criterion.mIntProp; 207 break; 208 case AudioMixingRule.RULE_EXCLUDE_UID: 209 textDump += " exclude UID "; 210 textDump += criterion.mIntProp; 211 break; 212 case AudioMixingRule.RULE_MATCH_USERID: 213 textDump += " match userId "; 214 textDump += criterion.mIntProp; 215 break; 216 case AudioMixingRule.RULE_EXCLUDE_USERID: 217 textDump += " exclude userId "; 218 textDump += criterion.mIntProp; 219 break; 220 case AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID: 221 textDump += " match audio session id"; 222 textDump += criterion.mIntProp; 223 break; 224 case AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID: 225 textDump += " exclude audio session id "; 226 textDump += criterion.mIntProp; 227 break; 228 default: 229 textDump += "invalid rule!"; 230 } 231 textDump += "\n"; 232 } 233 } 234 return textDump; 235 } 236 237 /** 238 * Very short dump of configuration 239 * @return a condensed dump of configuration, uniquely identifies a policy in a log 240 */ toCompactLogString()241 public String toCompactLogString() { 242 String compactDump = "reg:" + mRegistrationId; 243 int mixNum = 0; 244 for (AudioMix mix : mMixes) { 245 compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType()) 246 + "-Rul:" + mix.getRule().getCriteria().size(); 247 mixNum++; 248 } 249 return compactDump; 250 } 251 mixTypePrefix(int mixType)252 private static String mixTypePrefix(int mixType) { 253 switch (mixType) { 254 case AudioMix.MIX_TYPE_PLAYERS: 255 return "p"; 256 case AudioMix.MIX_TYPE_RECORDERS: 257 return "r"; 258 case AudioMix.MIX_TYPE_INVALID: 259 default: 260 return "#"; 261 262 } 263 } 264 reset()265 protected void reset() { 266 mMixCounter = 0; 267 } 268 setRegistration(String regId)269 protected void setRegistration(String regId) { 270 final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty(); 271 final boolean newRegNull = (regId == null) || regId.isEmpty(); 272 if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) { 273 Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId); 274 return; 275 } 276 mRegistrationId = regId == null ? "" : regId; 277 for (AudioMix mix : mMixes) { 278 setMixRegistration(mix); 279 } 280 } 281 setMixRegistration(@onNull final AudioMix mix)282 private void setMixRegistration(@NonNull final AudioMix mix) { 283 if (!mRegistrationId.isEmpty()) { 284 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) == 285 AudioMix.ROUTE_FLAG_LOOP_BACK) { 286 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":" 287 + mMixCounter++); 288 } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) == 289 AudioMix.ROUTE_FLAG_RENDER) { 290 mix.setRegistration(mix.mDeviceAddress); 291 } 292 } else { 293 mix.setRegistration(""); 294 } 295 } 296 297 @GuardedBy("mMixes") add(@onNull ArrayList<AudioMix> mixes)298 protected void add(@NonNull ArrayList<AudioMix> mixes) { 299 for (AudioMix mix : mixes) { 300 setMixRegistration(mix); 301 mMixes.add(mix); 302 } 303 } 304 305 @GuardedBy("mMixes") remove(@onNull ArrayList<AudioMix> mixes)306 protected void remove(@NonNull ArrayList<AudioMix> mixes) { 307 for (AudioMix mix : mixes) { 308 mMixes.remove(mix); 309 } 310 } 311 mixTypeId(int type)312 private static String mixTypeId(int type) { 313 if (type == AudioMix.MIX_TYPE_PLAYERS) return "p"; 314 else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r"; 315 else return "i"; 316 } 317 getRegistration()318 protected String getRegistration() { 319 return mRegistrationId; 320 } 321 } 322