• 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.car.builtin.util.Slogf;
21 import android.car.media.CarAudioManager;
22 import android.car.media.CarVolumeGroupInfo;
23 import android.media.AudioAttributes;
24 import android.media.AudioDeviceAttributes;
25 import android.media.AudioDeviceInfo;
26 import android.media.AudioPlaybackConfiguration;
27 import android.util.ArraySet;
28 
29 import com.android.car.CarLog;
30 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
31 import com.android.car.internal.util.IndentingPrintWriter;
32 import com.android.internal.util.Preconditions;
33 
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Objects;
38 import java.util.Set;
39 
40 /**
41  * A class encapsulates an audio zone in car.
42  *
43  * An audio zone can contain multiple {@link CarVolumeGroup}s, and each zone has its own
44  * {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys
45  * attached to each zone.
46  *
47  * See also the unified car_audio_configuration.xml
48  */
49 public class CarAudioZone {
50 
51     private final int mId;
52     private final String mName;
53     private final List<CarVolumeGroup> mVolumeGroups;
54     private final Set<String> mDeviceAddresses;
55     private final CarAudioContext mCarAudioContext;
56     private List<AudioDeviceAttributes> mInputAudioDevice;
57 
CarAudioZone(CarAudioContext carAudioContext, String name, int id)58     CarAudioZone(CarAudioContext carAudioContext, String name, int id) {
59         mCarAudioContext = Objects.requireNonNull(carAudioContext,
60                 "Car audio context can not be null");
61         mName = name;
62         mId = id;
63         mVolumeGroups = new ArrayList<>();
64         mInputAudioDevice = new ArrayList<>();
65         mDeviceAddresses = new HashSet<>();
66     }
67 
getId()68     int getId() {
69         return mId;
70     }
71 
getName()72     String getName() {
73         return mName;
74     }
75 
isPrimaryZone()76     boolean isPrimaryZone() {
77         return mId == CarAudioManager.PRIMARY_AUDIO_ZONE;
78     }
79 
addVolumeGroup(CarVolumeGroup volumeGroup)80     void addVolumeGroup(CarVolumeGroup volumeGroup) {
81         mVolumeGroups.add(volumeGroup);
82         mDeviceAddresses.addAll(volumeGroup.getAddresses());
83     }
84 
getVolumeGroup(int groupId)85     CarVolumeGroup getVolumeGroup(int groupId) {
86         Preconditions.checkArgumentInRange(groupId, 0, mVolumeGroups.size() - 1,
87                 "groupId(" + groupId + ") is out of range");
88         return mVolumeGroups.get(groupId);
89     }
90 
91     /**
92      * @return Snapshot of available {@link AudioDeviceInfo}s in List.
93      */
getAudioDeviceInfos()94     List<AudioDeviceInfo> getAudioDeviceInfos() {
95         final List<AudioDeviceInfo> devices = new ArrayList<>();
96         for (CarVolumeGroup group : mVolumeGroups) {
97             for (String address : group.getAddresses()) {
98                 devices.add(group.getCarAudioDeviceInfoForAddress(address).getAudioDeviceInfo());
99             }
100         }
101         return devices;
102     }
103 
getVolumeGroupCount()104     int getVolumeGroupCount() {
105         return mVolumeGroups.size();
106     }
107 
108     /**
109      * @return Snapshot of available {@link CarVolumeGroup}s in array.
110      */
getVolumeGroups()111     CarVolumeGroup[] getVolumeGroups() {
112         return mVolumeGroups.toArray(new CarVolumeGroup[0]);
113     }
114 
115     /**
116      * Constraints applied here:
117      *
118      * - One context should not appear in two groups
119      * - All contexts are assigned
120      * - One device should not appear in two groups
121      * - All gain controllers in the same group have same step value
122      *
123      * Note that it is fine that there are devices which do not appear in any group. Those devices
124      * may be reserved for other purposes.
125      * Step value validation is done in
126      * {@link CarVolumeGroup.Builder#setDeviceInfoForContext(int, CarAudioDeviceInfo)}
127      */
validateVolumeGroups()128     boolean validateVolumeGroups() {
129         ArraySet<Integer> contexts = new ArraySet<>();
130         ArraySet<String> addresses = new ArraySet<>();
131         for (int index = 0; index <  mVolumeGroups.size(); index++) {
132             CarVolumeGroup group = mVolumeGroups.get(index);
133             // One context should not appear in two groups
134             int[] groupContexts = group.getContexts();
135             for (int groupIndex = 0; groupIndex < groupContexts.length; groupIndex++) {
136                 int contextId = groupContexts[groupIndex];
137                 if (!contexts.add(contextId)) {
138                     Slogf.e(CarLog.TAG_AUDIO, "Context appears in two groups %d", contextId);
139                     return false;
140                 }
141             }
142 
143             // One address should not appear in two groups
144             List<String> groupAddresses = group.getAddresses();
145             for (int addressIndex = 0; addressIndex < groupAddresses.size(); addressIndex++) {
146                 String address = groupAddresses.get(addressIndex);
147                 if (!addresses.add(address)) {
148                     Slogf.e(CarLog.TAG_AUDIO, "Address appears in two groups: " + address);
149                     return false;
150                 }
151             }
152         }
153 
154         boolean allContextValidated = true;
155         List<Integer> allContexts = mCarAudioContext.getAllContextsIds();
156         for (int index = 0; index < allContexts.size(); index++) {
157             if (!contexts.contains(allContexts.get(index))) {
158                 Slogf.e(CarLog.TAG_AUDIO, "Audio context %s is not assigned to a group",
159                         mCarAudioContext.toString(allContexts.get(index)));
160                 allContextValidated = false;
161             }
162         }
163 
164         if (!allContextValidated) {
165             return false;
166         }
167 
168         List<Integer> contextList = new ArrayList<>(contexts);
169         // All contexts are assigned
170         if (!mCarAudioContext.validateAllAudioAttributesSupported(contextList)) {
171             Slogf.e(CarLog.TAG_AUDIO, "Some audio attributes are not assigned to a group");
172             return false;
173         }
174         return true;
175     }
176 
synchronizeCurrentGainIndex()177     void synchronizeCurrentGainIndex() {
178         for (CarVolumeGroup group : mVolumeGroups) {
179             group.setCurrentGainIndex(group.getCurrentGainIndex());
180         }
181     }
182 
183     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)184     void dump(IndentingPrintWriter writer) {
185         writer.printf("CarAudioZone(%s:%d) isPrimary? %b\n", mName, mId, isPrimaryZone());
186         writer.increaseIndent();
187         for (CarVolumeGroup group : mVolumeGroups) {
188             group.dump(writer);
189         }
190 
191         writer.printf("Input Audio Device Addresses\n");
192         writer.increaseIndent();
193         for (AudioDeviceAttributes audioDevice : mInputAudioDevice) {
194             writer.printf("Device Address(%s)\n", audioDevice.getAddress());
195         }
196         writer.decreaseIndent();
197         writer.println();
198         writer.decreaseIndent();
199     }
200 
201     /**
202      * Return the audio device address mapping to a car audio context
203      */
getAddressForContext(int audioContext)204     public String getAddressForContext(int audioContext) {
205         mCarAudioContext.preconditionCheckAudioContext(audioContext);
206         String deviceAddress = null;
207         for (CarVolumeGroup volumeGroup : getVolumeGroups()) {
208             deviceAddress = volumeGroup.getAddressForContext(audioContext);
209             if (deviceAddress != null) {
210                 return deviceAddress;
211             }
212         }
213         // This should not happen unless something went wrong.
214         // Device address are unique per zone and all contexts are assigned in a zone.
215         throw new IllegalStateException("Could not find output device in zone " + mId
216                 + " for audio context " + audioContext);
217     }
218 
getAudioDeviceForContext(int audioContext)219     public AudioDeviceInfo getAudioDeviceForContext(int audioContext) {
220         mCarAudioContext.preconditionCheckAudioContext(audioContext);
221         for (CarVolumeGroup volumeGroup : getVolumeGroups()) {
222             AudioDeviceInfo deviceInfo = volumeGroup.getAudioDeviceForContext(audioContext);
223             if (deviceInfo != null) {
224                 return deviceInfo;
225             }
226         }
227         // This should not happen unless something went wrong.
228         // Device address are unique per zone and all contexts are assigned in a zone.
229         throw new IllegalStateException("Could not find output device in zone " + mId
230                 + " for audio context " + audioContext);
231     }
232 
233     /**
234      * Update the volume groups for the new user
235      * @param userId user id to update to
236      */
updateVolumeGroupsSettingsForUser(int userId)237     public void updateVolumeGroupsSettingsForUser(int userId) {
238         for (CarVolumeGroup group : mVolumeGroups) {
239             group.loadVolumesSettingsForUser(userId);
240         }
241     }
242 
addInputAudioDevice(AudioDeviceAttributes device)243     void addInputAudioDevice(AudioDeviceAttributes device) {
244         mInputAudioDevice.add(device);
245     }
246 
getInputAudioDevices()247     List<AudioDeviceAttributes> getInputAudioDevices() {
248         return mInputAudioDevice;
249     }
250 
findActiveAudioAttributesFromPlaybackConfigurations( List<AudioPlaybackConfiguration> configurations)251     public List<AudioAttributes> findActiveAudioAttributesFromPlaybackConfigurations(
252             List<AudioPlaybackConfiguration> configurations) {
253         Objects.requireNonNull(configurations, "Audio playback configurations can not be null");
254         List<AudioAttributes> audioAttributes = new ArrayList<>();
255         for (int index = 0; index < configurations.size(); index++) {
256             AudioPlaybackConfiguration configuration = configurations.get(index);
257             if (configuration.isActive()) {
258                 if (isAudioDeviceInfoValidForZone(configuration.getAudioDeviceInfo())) {
259                     // Note that address's context and the context actually supplied could be
260                     // different
261                     audioAttributes.add(configuration.getAudioAttributes());
262                 }
263             }
264         }
265         return audioAttributes;
266     }
267 
isAudioDeviceInfoValidForZone(AudioDeviceInfo info)268     boolean isAudioDeviceInfoValidForZone(AudioDeviceInfo info) {
269         return info != null
270                 && info.getAddress() != null
271                 && !info.getAddress().isEmpty()
272                 && containsDeviceAddress(info.getAddress());
273     }
274 
containsDeviceAddress(String deviceAddress)275     private boolean containsDeviceAddress(String deviceAddress) {
276         return mDeviceAddresses.contains(deviceAddress);
277     }
278 
onAudioGainChanged(List<Integer> halReasons, List<CarAudioGainConfigInfo> gains)279     void onAudioGainChanged(List<Integer> halReasons, List<CarAudioGainConfigInfo> gains) {
280         for (int index = 0; index < gains.size(); index++) {
281             CarAudioGainConfigInfo gainInfo = gains.get(index);
282             for (int groupIndex = 0; groupIndex < mVolumeGroups.size(); groupIndex++) {
283                 CarVolumeGroup group = mVolumeGroups.get(groupIndex);
284                 if (group.getAddresses().contains(gainInfo.getDeviceAddress())) {
285                     group.onAudioGainChanged(halReasons, gainInfo);
286                     break; // loop of CarVolumeGroup.
287                 }
288             }
289         }
290     }
291 
292     /**
293      * Returns the car audio context set for the car audio zone
294      */
getCarAudioContext()295     public CarAudioContext getCarAudioContext() {
296         return mCarAudioContext;
297     }
298 
299     /**
300      * Returns the car volume infos for all the volume groups in the audio zone
301      */
getVolumeGroupInfos()302     List<CarVolumeGroupInfo> getVolumeGroupInfos() {
303         List<CarVolumeGroupInfo> groupInfos = new ArrayList<>(mVolumeGroups.size());
304         for (int index = 0; index < mVolumeGroups.size(); index++) {
305             groupInfos.add(mVolumeGroups.get(index).getCarVolumeGroupInfo());
306         }
307 
308         return groupInfos;
309     }
310 }
311