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