• 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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
19 
20 import android.annotation.Nullable;
21 import android.car.builtin.util.Slogf;
22 import android.car.media.CarAudioManager;
23 import android.car.media.CarAudioZoneConfigInfo;
24 import android.car.media.CarVolumeGroupEvent;
25 import android.car.media.CarVolumeGroupInfo;
26 import android.media.AudioAttributes;
27 import android.media.AudioDeviceAttributes;
28 import android.media.AudioDeviceInfo;
29 import android.media.AudioPlaybackConfiguration;
30 import android.util.SparseArray;
31 
32 import com.android.car.CarLog;
33 import com.android.car.audio.hal.HalAudioDeviceInfo;
34 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
35 import com.android.car.internal.util.IndentingPrintWriter;
36 import com.android.internal.annotations.GuardedBy;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Objects;
41 
42 /**
43  * A class encapsulates an audio zone in car.
44  *
45  * An audio zone can contain multiple {@link CarAudioZoneConfig}s, and each zone has its own
46  * {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys
47  * attached to each zone.
48  *
49  * See also the unified car_audio_configuration.xml
50  */
51 public class CarAudioZone {
52 
53     private final int mId;
54     private final String mName;
55     private final CarAudioContext mCarAudioContext;
56     private final List<AudioDeviceAttributes> mInputAudioDevice;
57     // zone configuration id to zone configuration mapping
58     // We don't protect mCarAudioZoneConfigs by a lock because it's only written at XML parsing.
59     private final SparseArray<CarAudioZoneConfig> mCarAudioZoneConfigs;
60     private final Object mLock = new Object();
61 
62     @GuardedBy("mLock")
63     private int mCurrentConfigId;
64 
CarAudioZone(CarAudioContext carAudioContext, String name, int id)65     CarAudioZone(CarAudioContext carAudioContext, String name, int id) {
66         mCarAudioContext = Objects.requireNonNull(carAudioContext,
67                 "Car audio context can not be null");
68         mName = name;
69         mId = id;
70         mCurrentConfigId = 0;
71         mInputAudioDevice = new ArrayList<>();
72         mCarAudioZoneConfigs = new SparseArray<>();
73     }
74 
getCurrentConfigId()75     private int getCurrentConfigId() {
76         synchronized (mLock) {
77             return mCurrentConfigId;
78         }
79     }
80 
getId()81     int getId() {
82         return mId;
83     }
84 
getName()85     String getName() {
86         return mName;
87     }
88 
isPrimaryZone()89     boolean isPrimaryZone() {
90         return mId == CarAudioManager.PRIMARY_AUDIO_ZONE;
91     }
92 
getCurrentCarAudioZoneConfig()93     CarAudioZoneConfig getCurrentCarAudioZoneConfig() {
94         synchronized (mLock) {
95             return mCarAudioZoneConfigs.get(mCurrentConfigId);
96         }
97     }
98 
getAllCarAudioZoneConfigs()99     List<CarAudioZoneConfig> getAllCarAudioZoneConfigs() {
100         List<CarAudioZoneConfig> zoneConfigList = new ArrayList<>(mCarAudioZoneConfigs.size());
101         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
102             zoneConfigList.add(mCarAudioZoneConfigs.valueAt(index));
103         }
104         return zoneConfigList;
105     }
106 
107     @Nullable
getCurrentVolumeGroup(String groupName)108     CarVolumeGroup getCurrentVolumeGroup(String groupName) {
109         return getCurrentCarAudioZoneConfig().getVolumeGroup(groupName);
110     }
111 
getCurrentVolumeGroup(int groupId)112     CarVolumeGroup getCurrentVolumeGroup(int groupId) {
113         return getCurrentCarAudioZoneConfig().getVolumeGroup(groupId);
114     }
115 
116     /**
117      * @return Snapshot of available {@link AudioDeviceInfo}s in List.
118      */
getCurrentAudioDeviceInfos()119     List<AudioDeviceInfo> getCurrentAudioDeviceInfos() {
120         return getCurrentCarAudioZoneConfig().getAudioDeviceInfos();
121     }
122 
getCurrentAudioDeviceInfosSupportingDynamicMix()123     List<AudioDeviceInfo> getCurrentAudioDeviceInfosSupportingDynamicMix() {
124         return getCurrentCarAudioZoneConfig().getAudioDeviceInfosSupportingDynamicMix();
125     }
126 
getCurrentVolumeGroupCount()127     int getCurrentVolumeGroupCount() {
128         return getCurrentCarAudioZoneConfig().getVolumeGroupCount();
129     }
130 
131     /**
132      * @return Snapshot of available {@link CarVolumeGroup}s in array.
133      */
getCurrentVolumeGroups()134     CarVolumeGroup[] getCurrentVolumeGroups() {
135         return getCurrentCarAudioZoneConfig().getVolumeGroups();
136     }
137 
validateCanUseDynamicMixRouting(boolean useCoreAudioRouting)138     boolean validateCanUseDynamicMixRouting(boolean useCoreAudioRouting) {
139         return getCurrentCarAudioZoneConfig().validateCanUseDynamicMixRouting(useCoreAudioRouting);
140     }
141 
142     /**
143      * Constraints applied here:
144      *
145      * <ul>
146      * <li>At least one zone configuration exists.
147      * <li>Current zone configuration exists.
148      * <li>The zone id of all zone configurations matches zone id of the zone.
149      * <li>Exactly one zone configuration is default.
150      * <li>Volume groups for each zone configuration is valid (see
151      * {@link CarAudioZoneConfig#validateVolumeGroups(CarAudioContext, boolean)}).
152      * </ul>
153      */
validateZoneConfigs(boolean useCoreAudioRouting)154     boolean validateZoneConfigs(boolean useCoreAudioRouting) {
155         if (mCarAudioZoneConfigs.size() == 0) {
156             Slogf.w(CarLog.TAG_AUDIO, "No zone configurations for zone %d", mId);
157             return false;
158         }
159         int currentConfigId = getCurrentConfigId();
160         if (!mCarAudioZoneConfigs.contains(currentConfigId)) {
161             Slogf.w(CarLog.TAG_AUDIO, "Current zone configuration %d for zone %d does not exist",
162                     currentConfigId, mId);
163             return false;
164         }
165         boolean isDefaultConfigFound = false;
166         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
167             CarAudioZoneConfig zoneConfig = mCarAudioZoneConfigs.valueAt(index);
168             if (zoneConfig.getZoneId() != mId) {
169                 Slogf.w(CarLog.TAG_AUDIO,
170                         "Zone id %d of zone configuration %d does not match zone id %d",
171                         zoneConfig.getZoneId(),
172                         mCarAudioZoneConfigs.keyAt(index), mId);
173                 return false;
174             }
175             if (zoneConfig.isDefault()) {
176                 if (isDefaultConfigFound) {
177                     Slogf.w(CarLog.TAG_AUDIO,
178                             "Multiple default zone configurations exist in zone %d", mId);
179                     return false;
180                 }
181                 isDefaultConfigFound = true;
182             }
183             if (!zoneConfig.validateVolumeGroups(mCarAudioContext,
184                     useCoreAudioRouting)) {
185                 return false;
186             }
187         }
188         if (!isDefaultConfigFound) {
189             Slogf.w(CarLog.TAG_AUDIO, "No default zone configuration exists in zone %d", mId);
190             return false;
191         }
192         return true;
193     }
194 
isCurrentZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo)195     boolean isCurrentZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo) {
196         synchronized (mLock) {
197             return configInfoSwitchedTo.equals(mCarAudioZoneConfigs.get(mCurrentConfigId)
198                     .getCarAudioZoneConfigInfo());
199         }
200     }
201 
setCurrentCarZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo)202     void setCurrentCarZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo) {
203         synchronized (mLock) {
204             mCurrentConfigId = configInfoSwitchedTo.getConfigId();
205         }
206     }
207 
init()208     void init() {
209         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
210             mCarAudioZoneConfigs.valueAt(index).synchronizeCurrentGainIndex();
211         }
212     }
213 
214     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)215     void dump(IndentingPrintWriter writer) {
216         writer.printf("CarAudioZone(%s:%d) isPrimary? %b\n", mName, mId,
217                 isPrimaryZone());
218         writer.increaseIndent();
219         writer.printf("Current Config Id: %d\n", getCurrentConfigId());
220         writer.printf("Input Audio Device Addresses\n");
221         writer.increaseIndent();
222         for (int index = 0; index < mInputAudioDevice.size(); index++) {
223             writer.printf("Device Address(%s)\n", mInputAudioDevice.get(index).getAddress());
224         }
225         writer.decreaseIndent();
226         writer.println();
227         writer.printf("Audio Zone Configurations\n");
228         writer.increaseIndent();
229         for (int i = 0; i < mCarAudioZoneConfigs.size(); i++) {
230             mCarAudioZoneConfigs.valueAt(i).dump(writer);
231         }
232         writer.decreaseIndent();
233         writer.println();
234         writer.decreaseIndent();
235     }
236 
237     /**
238      * Return the audio device address mapping to a car audio context
239      */
getAddressForContext(int audioContext)240     public String getAddressForContext(int audioContext) {
241         mCarAudioContext.preconditionCheckAudioContext(audioContext);
242         String deviceAddress = null;
243         for (CarVolumeGroup volumeGroup : getCurrentVolumeGroups()) {
244             deviceAddress = volumeGroup.getAddressForContext(audioContext);
245             if (deviceAddress != null) {
246                 return deviceAddress;
247             }
248         }
249         // This should not happen unless something went wrong.
250         // Device address are unique per zone and all contexts are assigned in a zone.
251         throw new IllegalStateException("Could not find output device in zone " + mId
252                 + " for audio context " + audioContext);
253     }
254 
getAudioDeviceForContext(int audioContext)255     public AudioDeviceInfo getAudioDeviceForContext(int audioContext) {
256         mCarAudioContext.preconditionCheckAudioContext(audioContext);
257         for (CarVolumeGroup volumeGroup : getCurrentVolumeGroups()) {
258             AudioDeviceInfo deviceInfo = volumeGroup.getAudioDeviceForContext(audioContext);
259             if (deviceInfo != null) {
260                 return deviceInfo;
261             }
262         }
263         // This should not happen unless something went wrong.
264         // Device address are unique per zone and all contexts are assigned in a zone.
265         throw new IllegalStateException("Could not find output device in zone " + mId
266                 + " for audio context " + audioContext);
267     }
268 
269     /**
270      * Update the volume groups for the new user
271      * @param userId user id to update to
272      */
updateVolumeGroupsSettingsForUser(int userId)273     public void updateVolumeGroupsSettingsForUser(int userId) {
274         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
275             mCarAudioZoneConfigs.valueAt(index).updateVolumeGroupsSettingsForUser(userId);
276         }
277     }
278 
addInputAudioDevice(AudioDeviceAttributes device)279     void addInputAudioDevice(AudioDeviceAttributes device) {
280         mInputAudioDevice.add(device);
281     }
282 
getInputAudioDevices()283     List<AudioDeviceAttributes> getInputAudioDevices() {
284         return mInputAudioDevice;
285     }
286 
addZoneConfig(CarAudioZoneConfig zoneConfig)287     void addZoneConfig(CarAudioZoneConfig zoneConfig) {
288         mCarAudioZoneConfigs.put(zoneConfig.getZoneConfigId(), zoneConfig);
289         if (zoneConfig.isDefault()) {
290             synchronized (mLock) {
291                 mCurrentConfigId = zoneConfig.getZoneConfigId();
292             }
293         }
294     }
295 
findActiveAudioAttributesFromPlaybackConfigurations( List<AudioPlaybackConfiguration> configurations)296     public List<AudioAttributes> findActiveAudioAttributesFromPlaybackConfigurations(
297             List<AudioPlaybackConfiguration> configurations) {
298         Objects.requireNonNull(configurations, "Audio playback configurations can not be null");
299         List<AudioAttributes> audioAttributes = new ArrayList<>();
300         for (int index = 0; index < configurations.size(); index++) {
301             AudioPlaybackConfiguration configuration = configurations.get(index);
302             if (configuration.isActive()) {
303                 if (isAudioDeviceInfoValidForZone(configuration.getAudioDeviceInfo())) {
304                     // Note that address's context and the context actually supplied could be
305                     // different
306                     audioAttributes.add(configuration.getAudioAttributes());
307                 }
308             }
309         }
310         return audioAttributes;
311     }
312 
isAudioDeviceInfoValidForZone(AudioDeviceInfo info)313     boolean isAudioDeviceInfoValidForZone(AudioDeviceInfo info) {
314         return getCurrentCarAudioZoneConfig().isAudioDeviceInfoValidForZone(info);
315     }
316 
onAudioGainChanged(List<Integer> halReasons, List<CarAudioGainConfigInfo> gainInfos)317     List<CarVolumeGroupEvent> onAudioGainChanged(List<Integer> halReasons,
318             List<CarAudioGainConfigInfo> gainInfos) {
319         List<CarVolumeGroupEvent> events = new ArrayList<>();
320         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
321             List<CarVolumeGroupEvent> eventsForZoneConfig = mCarAudioZoneConfigs.valueAt(index)
322                     .onAudioGainChanged(halReasons, gainInfos);
323             // use events for callback only if current zone configuration
324             if (mCarAudioZoneConfigs.keyAt(index) == getCurrentConfigId()) {
325                 events.addAll(eventsForZoneConfig);
326             }
327         }
328         return events;
329     }
330 
onAudioPortsChanged(List<HalAudioDeviceInfo> deviceInfos)331     List<CarVolumeGroupEvent> onAudioPortsChanged(List<HalAudioDeviceInfo> deviceInfos) {
332         List<CarVolumeGroupEvent> events = new ArrayList<>();
333         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
334             List<CarVolumeGroupEvent> eventsForZoneConfig = mCarAudioZoneConfigs.valueAt(index)
335                     .onAudioPortsChanged(deviceInfos);
336             // Use events for callback only if current zone configuration
337             if (mCarAudioZoneConfigs.keyAt(index) == getCurrentConfigId()) {
338                 events.addAll(eventsForZoneConfig);
339             }
340         }
341         return events;
342     }
343 
344     /**
345      * Returns the car audio context set for the car audio zone
346      */
getCarAudioContext()347     public CarAudioContext getCarAudioContext() {
348         return mCarAudioContext;
349     }
350 
351     /**
352      * Returns the car volume infos for all the volume groups in the audio zone
353      */
getCurrentVolumeGroupInfos()354     List<CarVolumeGroupInfo> getCurrentVolumeGroupInfos() {
355         return getCurrentCarAudioZoneConfig().getVolumeGroupInfos();
356     }
357 
358     /**
359      * Returns all audio zone config info in the audio zone
360      */
getCarAudioZoneConfigInfos()361     List<CarAudioZoneConfigInfo> getCarAudioZoneConfigInfos() {
362         List<CarAudioZoneConfigInfo> zoneConfigInfos = new ArrayList<>(mCarAudioZoneConfigs.size());
363         for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
364             zoneConfigInfos.add(mCarAudioZoneConfigs.valueAt(index).getCarAudioZoneConfigInfo());
365         }
366 
367         return zoneConfigInfos;
368     }
369 }
370