1 /* 2 * Copyright (C) 2018 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 package com.android.car.audio; 17 18 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE; 19 20 import static com.android.car.audio.CarAudioService.CAR_DEFAULT_AUDIO_ATTRIBUTE; 21 import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice; 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE; 23 24 import android.annotation.XmlRes; 25 import android.car.builtin.util.Slogf; 26 import android.content.Context; 27 import android.content.res.TypedArray; 28 import android.content.res.XmlResourceParser; 29 import android.media.AudioDeviceAttributes; 30 import android.media.AudioDeviceInfo; 31 import android.util.AttributeSet; 32 import android.util.SparseArray; 33 import android.util.SparseIntArray; 34 import android.util.Xml; 35 36 import com.android.car.CarLog; 37 import com.android.car.R; 38 import com.android.car.audio.hal.AudioControlWrapperV1; 39 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 40 41 import org.xmlpull.v1.XmlPullParserException; 42 43 import java.io.IOException; 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.Locale; 47 import java.util.Objects; 48 49 /** 50 * A helper class loads volume groups from car_volume_groups.xml configuration into one zone. 51 * 52 * @deprecated This is replaced by {@link CarAudioZonesHelper}. 53 */ 54 @Deprecated 55 @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE) 56 class CarAudioZonesHelperLegacy { 57 private static final String TAG_VOLUME_GROUPS = "volumeGroups"; 58 private static final String TAG_GROUP = "group"; 59 private static final String TAG_CONTEXT = "context"; 60 61 private static final int ZONE_CONFIG_ID = 0; 62 63 private static final int NO_BUS_FOR_CONTEXT = -1; 64 65 private static final int MIN_ACTIVATION_VOLUME_PERCENTAGE = 0; 66 private static final int MAX_ACTIVATION_VOLUME_PERCENTAGE = 100; 67 private static final int ACTIVATION_VOLUME_INVOCATION_TYPE = 68 CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT; 69 70 private final Context mContext; 71 private final @XmlRes int mXmlConfiguration; 72 private final SparseIntArray mLegacyAudioContextToBus; 73 private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo; 74 private final CarAudioSettings mCarAudioSettings; 75 private final CarAudioContext mCarAudioContext; 76 private final AudioDeviceInfo[] mInputDevices; 77 CarAudioZonesHelperLegacy(Context context, CarAudioContext carAudioContext, @XmlRes int xmlConfiguration, List<CarAudioDeviceInfo> carAudioDeviceInfos, AudioControlWrapperV1 audioControlWrapper, CarAudioSettings carAudioSettings, AudioDeviceInfo[] inputDevices)78 CarAudioZonesHelperLegacy(Context context, CarAudioContext carAudioContext, 79 @XmlRes int xmlConfiguration, List<CarAudioDeviceInfo> carAudioDeviceInfos, 80 AudioControlWrapperV1 audioControlWrapper, CarAudioSettings carAudioSettings, 81 AudioDeviceInfo[] inputDevices) { 82 Objects.requireNonNull(context, "Context must not be null."); 83 mCarAudioContext = Objects.requireNonNull(carAudioContext, 84 "Car audio context must not be null"); 85 Objects.requireNonNull(carAudioDeviceInfos, 86 "Car Audio Device Info must not be null."); 87 Objects.requireNonNull(audioControlWrapper, 88 "Car Audio Control must not be null."); 89 Objects.requireNonNull(inputDevices, "Input Devices must not be null."); 90 mCarAudioSettings = Objects.requireNonNull(carAudioSettings, 91 "Car Audio Settings can not be null."); 92 mContext = context; 93 mXmlConfiguration = xmlConfiguration; 94 mBusToCarAudioDeviceInfo = 95 generateBusToCarAudioDeviceInfo(carAudioDeviceInfos); 96 97 mLegacyAudioContextToBus = 98 loadBusesForLegacyContexts(audioControlWrapper, carAudioContext); 99 mInputDevices = inputDevices; 100 } 101 102 /* Loads mapping from {@link CarAudioContext} values to bus numbers 103 * <p>Queries {@code IAudioControl#getBusForContext} for all 104 * {@code CArAudioZoneHelper.LEGACY_CONTEXTS}. Legacy 105 * contexts are those defined as part of 106 * {@code android.hardware.automotive.audiocontrol.V1_0.ContextNumber} 107 * 108 * @param audioControl wrapper for IAudioControl HAL interface. 109 * @param carAudioContext car audio context the defines the logical group of audio usages 110 * @return SparseIntArray mapping from {@link CarAudioContext} to bus number. 111 */ loadBusesForLegacyContexts( AudioControlWrapperV1 audioControlWrapper, CarAudioContext carAudioContext)112 private static SparseIntArray loadBusesForLegacyContexts( 113 AudioControlWrapperV1 audioControlWrapper, 114 CarAudioContext carAudioContext) { 115 SparseIntArray contextToBus = new SparseIntArray(); 116 List<Integer> nonSystemContexts = CarAudioContext.getNonCarSystemContextIds(); 117 118 for (int index = 0; index < nonSystemContexts.size(); index++) { 119 int legacyContext = nonSystemContexts.get(index); 120 int bus = audioControlWrapper.getBusForContext(legacyContext); 121 validateBusNumber(carAudioContext, legacyContext, bus); 122 contextToBus.put(legacyContext, bus); 123 } 124 return contextToBus; 125 } 126 validateBusNumber(CarAudioContext carAudioContext, int legacyContext, int bus)127 private static void validateBusNumber(CarAudioContext carAudioContext, 128 int legacyContext, int bus) { 129 if (bus == NO_BUS_FOR_CONTEXT) { 130 throw new IllegalArgumentException( 131 String.format("Invalid bus %d was associated with context %s", bus, 132 carAudioContext.toString(legacyContext))); 133 } 134 } 135 generateBusToCarAudioDeviceInfo( List<CarAudioDeviceInfo> carAudioDeviceInfos)136 private static SparseArray<CarAudioDeviceInfo> generateBusToCarAudioDeviceInfo( 137 List<CarAudioDeviceInfo> carAudioDeviceInfos) { 138 SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>(); 139 140 for (CarAudioDeviceInfo carAudioDeviceInfo : carAudioDeviceInfos) { 141 int busNumber = parseDeviceAddress(carAudioDeviceInfo.getAddress()); 142 if (busNumber >= 0) { 143 if (busToCarAudioDeviceInfo.get(busNumber) != null) { 144 throw new IllegalArgumentException("Two addresses map to same bus number: " 145 + carAudioDeviceInfo.getAddress() 146 + " and " 147 + busToCarAudioDeviceInfo.get(busNumber).getAddress()); 148 } 149 busToCarAudioDeviceInfo.put(busNumber, carAudioDeviceInfo); 150 } 151 } 152 153 return busToCarAudioDeviceInfo; 154 } 155 loadAudioZones()156 SparseArray<CarAudioZone> loadAudioZones() { 157 String zoneName = "Primary zone"; 158 CarAudioZoneConfig.Builder zoneConfigBuilder = new CarAudioZoneConfig.Builder(zoneName, 159 PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID, /* isDefault= */ true); 160 List<CarVolumeGroup> volumeGroups = loadVolumeGroups(); 161 for (int index = 0; index < volumeGroups.size(); index++) { 162 zoneConfigBuilder.addVolumeGroup(volumeGroups.get(index)); 163 } 164 CarAudioZone zone = new CarAudioZone(mCarAudioContext, zoneName, PRIMARY_AUDIO_ZONE); 165 zone.addZoneConfig(zoneConfigBuilder.build()); 166 167 SparseArray<CarAudioZone> carAudioZones = new SparseArray<>(); 168 addMicrophonesToPrimaryZone(zone); 169 carAudioZones.put(PRIMARY_AUDIO_ZONE, zone); 170 171 return carAudioZones; 172 } 173 174 /** 175 * @return all {@link CarVolumeGroup} read from configuration. 176 */ loadVolumeGroups()177 private List<CarVolumeGroup> loadVolumeGroups() { 178 List<CarVolumeGroup> carVolumeGroups = new ArrayList<>(); 179 try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) { 180 AttributeSet attrs = Xml.asAttributeSet(parser); 181 int type; 182 // Traverse to the first start tag 183 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 184 && type != XmlResourceParser.START_TAG) { 185 // ignored 186 } 187 188 if (!TAG_VOLUME_GROUPS.equals(parser.getName())) { 189 throw new IllegalArgumentException( 190 "Meta-data does not start with volumeGroups tag"); 191 } 192 int outerDepth = parser.getDepth(); 193 int id = 0; 194 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 195 && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) { 196 if (type == XmlResourceParser.END_TAG) { 197 continue; 198 } 199 if (TAG_GROUP.equals(parser.getName())) { 200 carVolumeGroups.add(parseVolumeGroup(id, attrs, parser)); 201 id++; 202 } 203 } 204 } catch (Exception e) { 205 Slogf.e(CarLog.TAG_AUDIO, "Error parsing volume groups configuration", e); 206 } 207 return carVolumeGroups; 208 } 209 parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)210 private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, 211 XmlResourceParser parser) throws XmlPullParserException, IOException { 212 CarVolumeGroupFactory groupFactory = 213 new CarVolumeGroupFactory(/* audioManager= */ null, mCarAudioSettings, 214 mCarAudioContext, PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID, id, 215 String.valueOf(id), /* useCarVolumeGroupMute= */ false, 216 new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE, 217 MIN_ACTIVATION_VOLUME_PERCENTAGE, MAX_ACTIVATION_VOLUME_PERCENTAGE) 218 ); 219 220 List<Integer> audioContexts = parseAudioContexts(parser, attrs); 221 222 for (int i = 0; i < audioContexts.size(); i++) { 223 bindContextToBuilder(groupFactory, audioContexts.get(i)); 224 } 225 226 return groupFactory.getCarVolumeGroup(/* useCoreAudioVolume= */ false); 227 } 228 bindContextToBuilder(CarVolumeGroupFactory groupFactory, int legacyAudioContext)229 private void bindContextToBuilder(CarVolumeGroupFactory groupFactory, int legacyAudioContext) { 230 int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext); 231 CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber); 232 groupFactory.setDeviceInfoForContext(legacyAudioContext, info); 233 234 if (legacyAudioContext == mCarAudioContext 235 .getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE)) { 236 groupFactory.setNonLegacyContexts(info); 237 } 238 } 239 parseAudioContexts(XmlResourceParser parser, AttributeSet attrs)240 private List<Integer> parseAudioContexts(XmlResourceParser parser, AttributeSet attrs) 241 throws IOException, XmlPullParserException { 242 List<Integer> contexts = new ArrayList<>(); 243 int type; 244 int innerDepth = parser.getDepth(); 245 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 246 && (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) { 247 if (type == XmlResourceParser.END_TAG) { 248 continue; 249 } 250 if (TAG_CONTEXT.equals(parser.getName())) { 251 TypedArray c = mContext.getResources().obtainAttributes( 252 attrs, R.styleable.volumeGroups_context); 253 contexts.add(c.getInt(R.styleable.volumeGroups_context_context, -1)); 254 c.recycle(); 255 } 256 } 257 258 return contexts; 259 } 260 addMicrophonesToPrimaryZone(CarAudioZone primaryAudioZone)261 private void addMicrophonesToPrimaryZone(CarAudioZone primaryAudioZone) { 262 for (int index = 0; index < mInputDevices.length; index++) { 263 AudioDeviceInfo info = mInputDevices[index]; 264 if (isMicrophoneInputDevice(info)) { 265 primaryAudioZone.addInputAudioDevice(new AudioDeviceAttributes(info)); 266 } 267 } 268 } 269 270 /** 271 * Parse device address. Expected format is BUS%d_%s, address, usage hint 272 * 273 * @return valid address (from 0 to positive) or -1 for invalid address. 274 */ parseDeviceAddress(String address)275 private static int parseDeviceAddress(String address) { 276 String[] words = address.split("_"); 277 int addressParsed = -1; 278 if (words[0].toLowerCase(Locale.US).startsWith("bus")) { 279 try { 280 addressParsed = Integer.parseInt(words[0].substring(3)); 281 } catch (NumberFormatException e) { 282 //ignore 283 } 284 } 285 if (addressParsed < 0) { 286 return -1; 287 } 288 return addressParsed; 289 } 290 } 291