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.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.Log; 24 import android.util.Pair; 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.List; 31 import java.util.Objects; 32 33 /** 34 * @hide 35 * Internal storage class for AudioPolicy configuration. 36 */ 37 public class AudioPolicyConfig implements Parcelable { 38 39 private static final String TAG = "AudioPolicyConfig"; 40 41 protected final ArrayList<AudioMix> mMixes; 42 protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP; 43 44 private String mRegistrationId = null; 45 46 // Corresponds to id of next mix to be registered. 47 private int mMixCounter = 0; 48 AudioPolicyConfig(AudioPolicyConfig conf)49 protected AudioPolicyConfig(AudioPolicyConfig conf) { 50 mMixes = conf.mMixes; 51 } 52 53 @VisibleForTesting AudioPolicyConfig(ArrayList<AudioMix> mixes)54 public AudioPolicyConfig(ArrayList<AudioMix> mixes) { 55 mMixes = mixes; 56 } 57 58 /** 59 * Add an {@link AudioMix} to be part of the audio policy being built. 60 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 61 * @return the same Builder instance. 62 * @throws IllegalArgumentException 63 */ addMix(AudioMix mix)64 public void addMix(AudioMix mix) throws IllegalArgumentException { 65 if (mix == null) { 66 throw new IllegalArgumentException("Illegal null AudioMix argument"); 67 } 68 mMixes.add(mix); 69 } 70 getMixes()71 public ArrayList<AudioMix> getMixes() { 72 return mMixes; 73 } 74 75 @Override hashCode()76 public int hashCode() { 77 return Objects.hash(mMixes); 78 } 79 80 @Override describeContents()81 public int describeContents() { 82 return 0; 83 } 84 85 @Override writeToParcel(Parcel dest, int flags)86 public void writeToParcel(Parcel dest, int flags) { 87 dest.writeInt(mMixes.size()); 88 for (AudioMix mix : mMixes) { 89 mix.writeToParcel(dest, flags); 90 } 91 } 92 AudioPolicyConfig(Parcel in)93 private AudioPolicyConfig(Parcel in) { 94 int nbMixes = in.readInt(); 95 mMixes = new ArrayList<>(nbMixes); 96 for (int i = 0 ; i < nbMixes ; i++) { 97 mMixes.add(AudioMix.CREATOR.createFromParcel(in)); 98 } 99 } 100 101 public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR = 102 new Parcelable.Creator<>() { 103 /** 104 * Rebuilds an AudioPolicyConfig previously stored with writeToParcel(). 105 * @param p Parcel object to read the AudioPolicyConfig from 106 * @return a new AudioPolicyConfig created from the data in the parcel 107 */ 108 public AudioPolicyConfig createFromParcel(Parcel p) { 109 return new AudioPolicyConfig(p); 110 } 111 public AudioPolicyConfig[] newArray(int size) { 112 return new AudioPolicyConfig[size]; 113 } 114 }; 115 toLogFriendlyString()116 public String toLogFriendlyString () { 117 String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n"); 118 textDump += mMixes.size() + " AudioMix, reg:" + mRegistrationId + "\n"; 119 for(AudioMix mix : mMixes) { 120 // write mix route flags 121 textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n"; 122 // write mix format 123 textDump += " rate=" + mix.getFormat().getSampleRate() + "Hz\n"; 124 textDump += " encoding=" + mix.getFormat().getEncoding() + "\n"; 125 textDump += " channels=0x"; 126 textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n"; 127 textDump += " ignore playback capture opt out=" 128 + mix.getRule().allowPrivilegedMediaPlaybackCapture() + "\n"; 129 textDump += " allow voice communication capture=" 130 + mix.getRule().voiceCommunicationCaptureAllowed() + "\n"; 131 // write mix rules 132 textDump += " specified mix type=" 133 + mix.getRule().getTargetMixRole() + "\n"; 134 final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); 135 for (AudioMixMatchCriterion criterion : criteria) { 136 switch(criterion.mRule) { 137 case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE: 138 textDump += " exclude usage "; 139 textDump += criterion.mAttr.usageToString(); 140 break; 141 case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE: 142 textDump += " match usage "; 143 textDump += criterion.mAttr.usageToString(); 144 break; 145 case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET: 146 textDump += " exclude capture preset "; 147 textDump += criterion.mAttr.getCapturePreset(); 148 break; 149 case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: 150 textDump += " match capture preset "; 151 textDump += criterion.mAttr.getCapturePreset(); 152 break; 153 case AudioMixingRule.RULE_MATCH_UID: 154 textDump += " match UID "; 155 textDump += criterion.mIntProp; 156 break; 157 case AudioMixingRule.RULE_EXCLUDE_UID: 158 textDump += " exclude UID "; 159 textDump += criterion.mIntProp; 160 break; 161 case AudioMixingRule.RULE_MATCH_USERID: 162 textDump += " match userId "; 163 textDump += criterion.mIntProp; 164 break; 165 case AudioMixingRule.RULE_EXCLUDE_USERID: 166 textDump += " exclude userId "; 167 textDump += criterion.mIntProp; 168 break; 169 case AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID: 170 textDump += " match audio session id"; 171 textDump += criterion.mIntProp; 172 break; 173 case AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID: 174 textDump += " exclude audio session id "; 175 textDump += criterion.mIntProp; 176 break; 177 default: 178 textDump += "invalid rule!"; 179 } 180 textDump += "\n"; 181 } 182 } 183 return textDump; 184 } 185 186 /** 187 * Very short dump of configuration 188 * @return a condensed dump of configuration, uniquely identifies a policy in a log 189 */ toCompactLogString()190 public String toCompactLogString() { 191 String compactDump = "reg:" + mRegistrationId; 192 int mixNum = 0; 193 for (AudioMix mix : mMixes) { 194 compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType()) 195 + "-Rul:" + mix.getRule().getCriteria().size(); 196 mixNum++; 197 } 198 return compactDump; 199 } 200 mixTypePrefix(int mixType)201 private static String mixTypePrefix(int mixType) { 202 switch (mixType) { 203 case AudioMix.MIX_TYPE_PLAYERS: 204 return "p"; 205 case AudioMix.MIX_TYPE_RECORDERS: 206 return "r"; 207 case AudioMix.MIX_TYPE_INVALID: 208 default: 209 return "#"; 210 211 } 212 } 213 reset()214 protected void reset() { 215 mMixCounter = 0; 216 } 217 setRegistration(String regId)218 protected void setRegistration(String regId) { 219 final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty(); 220 final boolean newRegNull = (regId == null) || regId.isEmpty(); 221 if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) { 222 Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId); 223 return; 224 } 225 mRegistrationId = regId == null ? "" : regId; 226 for (AudioMix mix : mMixes) { 227 setMixRegistration(mix); 228 } 229 } 230 setMixRegistration(@onNull final AudioMix mix)231 protected void setMixRegistration(@NonNull final AudioMix mix) { 232 if (!mRegistrationId.isEmpty()) { 233 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) == 234 AudioMix.ROUTE_FLAG_LOOP_BACK) { 235 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":" 236 + mMixCounter++); 237 } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) == 238 AudioMix.ROUTE_FLAG_RENDER) { 239 mix.setRegistration(mix.mDeviceAddress); 240 } 241 } else { 242 mix.setRegistration(""); 243 } 244 } 245 246 @GuardedBy("mMixes") add(@onNull ArrayList<AudioMix> mixes)247 protected void add(@NonNull ArrayList<AudioMix> mixes) { 248 for (AudioMix mix : mixes) { 249 if (mix.getRegistration() == null || mix.getRegistration().isEmpty()) { 250 setMixRegistration(mix); 251 } 252 mMixes.add(mix); 253 } 254 } 255 256 @GuardedBy("mMixes") remove(@onNull ArrayList<AudioMix> mixes)257 protected void remove(@NonNull ArrayList<AudioMix> mixes) { 258 for (AudioMix mix : mixes) { 259 mMixes.remove(mix); 260 } 261 } 262 263 /** 264 * Update audio mixing rules for already registered {@link AudioMix}-es. 265 * 266 * @param audioMixingRuleUpdates {@link List} of {@link Pair}-s containing {@link AudioMix} to 267 * be updated and the new {@link AudioMixingRule}. 268 */ updateMixingRules( @onNull List<Pair<AudioMix, AudioMixingRule>> audioMixingRuleUpdates)269 public void updateMixingRules( 270 @NonNull List<Pair<AudioMix, AudioMixingRule>> audioMixingRuleUpdates) { 271 Objects.requireNonNull(audioMixingRuleUpdates).forEach( 272 update -> updateMixingRule(update.first, update.second)); 273 } 274 updateMixingRule(AudioMix audioMixToUpdate, AudioMixingRule audioMixingRule)275 private void updateMixingRule(AudioMix audioMixToUpdate, AudioMixingRule audioMixingRule) { 276 mMixes.stream().filter(audioMixToUpdate::equals).findAny().ifPresent( 277 mix -> mix.setAudioMixingRule(audioMixingRule)); 278 } 279 mixTypeId(int type)280 private static String mixTypeId(int type) { 281 if (type == AudioMix.MIX_TYPE_PLAYERS) return "p"; 282 else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r"; 283 else return "i"; 284 } 285 getRegistration()286 protected String getRegistration() { 287 return mRegistrationId; 288 } 289 } 290