1 /*
2  * Copyright (C) 2024 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 com.android.car.audio;
18 
19 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
20 
21 import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice;
22 import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_CONFIGURATION;
23 
24 import android.hardware.automotive.audiocontrol.AudioDeviceConfiguration;
25 import android.hardware.automotive.audiocontrol.AudioZone;
26 import android.hardware.automotive.audiocontrol.RoutingDeviceConfiguration;
27 import android.media.AudioDeviceAttributes;
28 import android.media.AudioDeviceInfo;
29 import android.media.AudioManager;
30 import android.util.ArraySet;
31 import android.util.Slog;
32 import android.util.SparseArray;
33 import android.util.SparseIntArray;
34 
35 import com.android.car.CarLog;
36 import com.android.car.audio.hal.AudioControlWrapper;
37 import com.android.car.internal.util.LocalLog;
38 import com.android.internal.annotations.GuardedBy;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Objects;
43 
44 /**
45  * Class to get car audio service configuration from audio control HAL
46  */
47 final class CarAudioZonesHelperAudioControlHAL implements CarAudioZonesHelper {
48 
49     private static final String TAG = CarAudioZonesHelperAudioControlHAL.class.getSimpleName();
50 
51     private final LocalLog mCarAudioLog;
52     private final AudioControlWrapper mAudioControl;
53     private final AudioControlZoneConverter mZoneConverter;
54     private final AudioManagerWrapper mAudioManager;
55 
56     private final Object mLock = new Object();
57     @GuardedBy("mLock") //Use to guard access
58     private final SparseIntArray mAudioZoneIdToOccupantZoneId = new SparseIntArray();
59     @GuardedBy("mLock")
60     private CarAudioContext mAudioContext;
61     @GuardedBy("mLock")
62     private AudioDeviceConfiguration mAudioDeviceConfiguration;
63 
CarAudioZonesHelperAudioControlHAL(AudioControlWrapper wrapper, AudioManagerWrapper audioManager, CarAudioSettings settings, LocalLog serviceLog, boolean useFadeManagerConfiguration)64     CarAudioZonesHelperAudioControlHAL(AudioControlWrapper wrapper,
65             AudioManagerWrapper audioManager, CarAudioSettings settings, LocalLog serviceLog,
66             boolean useFadeManagerConfiguration)  {
67         mAudioControl = Objects.requireNonNull(wrapper, "Audio control HAL can not be null");
68         mAudioManager = Objects.requireNonNull(audioManager, "Audio manager can not be null");
69         Objects.requireNonNull(settings, "Car audio settings can not be null");
70         mCarAudioLog = Objects.requireNonNull(serviceLog, "Car audio log can not be null");
71         mZoneConverter = new AudioControlZoneConverter(audioManager, settings, serviceLog,
72                 useFadeManagerConfiguration);
73         mAudioDeviceConfiguration = new AudioDeviceConfiguration();
74     }
75 
76     @Override
loadAudioZones()77     public SparseArray<CarAudioZone> loadAudioZones() {
78         var deviceConfigs = getAudioDeviceConfiguration();
79         if (deviceConfigs.routingConfig == RoutingDeviceConfiguration.DEFAULT_AUDIO_ROUTING) {
80             return new SparseArray<>();
81         }
82         return initCarAudioZones(deviceConfigs);
83     }
84 
getAudioDeviceConfiguration()85     private AudioDeviceConfiguration getAudioDeviceConfiguration() {
86         if (!mAudioControl.supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_CONFIGURATION)) {
87             return getDefaultAudioDeviceConfiguration();
88         }
89         var deviceConfigs = mAudioControl.getAudioDeviceConfiguration();
90         if (deviceConfigs == null) {
91             return getDefaultAudioDeviceConfiguration();
92         }
93         return deviceConfigs;
94     }
95 
getDefaultAudioDeviceConfiguration()96     private static AudioDeviceConfiguration getDefaultAudioDeviceConfiguration() {
97         AudioDeviceConfiguration deviceConfiguration = new AudioDeviceConfiguration();
98         deviceConfiguration.routingConfig = RoutingDeviceConfiguration.DEFAULT_AUDIO_ROUTING;
99         return deviceConfiguration;
100     }
101 
initCarAudioZones(AudioDeviceConfiguration deviceConfigs)102     private SparseArray<CarAudioZone> initCarAudioZones(AudioDeviceConfiguration deviceConfigs) {
103         var halAudioZones = mAudioControl.getCarAudioZones();
104         if (halAudioZones == null || halAudioZones.isEmpty()) {
105             logParsingError("Audio control HAL returned invalid zones");
106             return new SparseArray<>();
107         }
108         var zoneIdToZone = new SparseArray<CarAudioZone>(halAudioZones.size());
109         boolean foundErrors = false;
110         var zoneIdToOccupantZoneId = new SparseIntArray();
111         var usedInputAddresses = new ArraySet<String>();
112         for (int c = 0; c < halAudioZones.size(); c++) {
113             var halZone = halAudioZones.get(c);
114             if (halZone == null) {
115                 logParsingError("Audio control HAL zones helper found null audio zone");
116                 foundErrors = true;
117                 continue;
118             }
119             var carAudioZone = mZoneConverter.convertAudioZone(halZone, deviceConfigs);
120             if (carAudioZone == null) {
121                 logParsingError("Audio control HAL zones helper failed to parse audio zone "
122                         + halZone.id);
123                 foundErrors = true;
124                 continue;
125             }
126             usedInputAddresses.addAll(getInputDeviceAddresses(carAudioZone));
127             if (zoneIdToZone.indexOfKey(carAudioZone.getId()) >= 0) {
128                 logParsingError("Audio control HAL zones helper found a repeating audio zone,"
129                         + " zone id " + halZone.id);
130                 foundErrors = true;
131                 continue;
132             }
133             // If found errors continue to parse other zones to get errors
134             if (foundErrors) {
135                 continue;
136             }
137             zoneIdToZone.put(carAudioZone.getId(), carAudioZone);
138             if (halZone.occupantZoneId != AudioZone.UNASSIGNED_OCCUPANT) {
139                 zoneIdToOccupantZoneId.put(carAudioZone.getId(), halZone.occupantZoneId);
140             }
141         }
142         if (foundErrors) {
143             zoneIdToZone.clear();
144             zoneIdToOccupantZoneId.clear();
145         }
146         CarAudioZone primaryZone = zoneIdToZone.get(PRIMARY_AUDIO_ZONE);
147         if (primaryZone != null) {
148             synchronized (mLock) {
149                 mAudioContext = primaryZone.getCarAudioContext();
150                 mAudioDeviceConfiguration = deviceConfigs;
151                 mAudioZoneIdToOccupantZoneId.clear();
152                 for (int c = 0; c < zoneIdToOccupantZoneId.size(); c++) {
153                     int zoneId = zoneIdToOccupantZoneId.keyAt(c);
154                     int occupantId = zoneIdToOccupantZoneId.valueAt(c);
155                     mAudioZoneIdToOccupantZoneId.put(zoneId, occupantId);
156                 }
157             }
158             addRemainingInputDevicesToZone(primaryZone, usedInputAddresses);
159         } else {
160             logParsingError("Audio control HAL zones helper could not find primary zone");
161             zoneIdToZone.clear();
162             zoneIdToOccupantZoneId.clear();
163         }
164 
165         return zoneIdToZone;
166     }
167 
addRemainingInputDevicesToZone(CarAudioZone zone, ArraySet<String> usedAddresses)168     private void addRemainingInputDevicesToZone(CarAudioZone zone,
169             ArraySet<String> usedAddresses) {
170         AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
171         for (var device : inputDevices) {
172             if (usedAddresses.contains(device.getAddress()) || !isMicrophoneInputDevice(device)) {
173                 continue;
174             }
175             zone.addInputAudioDevice(new AudioDeviceAttributes(device));
176         }
177     }
178 
getInputDeviceAddresses(CarAudioZone carAudioZone)179     private static List<String> getInputDeviceAddresses(CarAudioZone carAudioZone) {
180         var inputDevices = carAudioZone.getInputAudioDevices();
181         var addresses = new ArrayList<String>(inputDevices.size());
182         for (int c = 0; c < inputDevices.size(); c++) {
183             addresses.add(inputDevices.get(c).getAddress());
184         }
185         return addresses;
186     }
187 
logParsingError(String message)188     private void logParsingError(String message) {
189         Slog.e(CarLog.TAG_AUDIO, message);
190         mCarAudioLog.log(message);
191     }
192 
193     @Override
getCarAudioContext()194     public CarAudioContext getCarAudioContext() {
195         synchronized (mLock) {
196             return mAudioContext;
197         }
198     }
199 
200     @Override
getCarAudioZoneIdToOccupantZoneIdMapping()201     public SparseIntArray getCarAudioZoneIdToOccupantZoneIdMapping() {
202         synchronized (mLock) {
203             return mAudioZoneIdToOccupantZoneId;
204         }
205     }
206 
207     @Override
getMirrorDeviceInfos()208     public List<CarAudioDeviceInfo> getMirrorDeviceInfos() {
209         return mZoneConverter.convertZonesMirroringAudioPorts(
210                 mAudioControl.getOutputMirroringDevices());
211     }
212 
213     @Override
useCoreAudioRouting()214     public boolean useCoreAudioRouting() {
215         synchronized (mLock) {
216             return mAudioDeviceConfiguration.routingConfig
217                     == RoutingDeviceConfiguration.CONFIGURABLE_AUDIO_ENGINE_ROUTING;
218         }
219     }
220 
221     @Override
useCoreAudioVolume()222     public boolean useCoreAudioVolume() {
223         synchronized (mLock) {
224             return mAudioDeviceConfiguration.useCoreAudioVolume;
225         }
226     }
227 
228     @Override
useHalDuckingSignalOrDefault(boolean unusedDefaultUseHalDuckingSignal)229     public boolean useHalDuckingSignalOrDefault(boolean unusedDefaultUseHalDuckingSignal) {
230         // Prefer information from HAL over RRO since vendor freeze requires it and this API
231         // enables information directly from vendor
232         synchronized (mLock) {
233             return mAudioDeviceConfiguration.useHalDuckingSignals;
234         }
235     }
236 
237     @Override
useVolumeGroupMuting()238     public boolean useVolumeGroupMuting() {
239         synchronized (mLock) {
240             return mAudioDeviceConfiguration.useCarVolumeGroupMuting;
241         }
242     }
243 }
244