• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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