• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
20 
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
22 
23 import android.media.AudioDeviceAttributes;
24 import android.util.ArraySet;
25 import android.util.SparseArray;
26 
27 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.List;
31 import java.util.Set;
32 
33 /*
34  * Class to help validate audio zones are constructed correctly.
35  */
36 final class CarAudioZonesValidator {
37 
38     @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
CarAudioZonesValidator()39     private CarAudioZonesValidator() {
40         throw new UnsupportedOperationException(
41                 "CarAudioZonesValidator class is non-instantiable, contains static members only");
42     }
43 
44     /**
45      * Returns {@code true} if validation succeeds, throws an a run time exception otherwise.
46      *
47      * <p>The current rules that apply are:
48      * <ul>
49      * <li>There must be a zone defined
50      * <li>Has valid zone configuration, see
51      *  {@link CarAudioZoneConfig#validateVolumeGroups(CarAudioContext, boolean)}) for further
52      *  information.
53      *  <li>Configurations can be routed by dynamic audio policy if core routing is not used, see
54      *  {@link CarAudioZoneConfig#validateCanUseDynamicMixRouting(boolean)} for further information.
55      *  <li>Device addresses are not shared across zones
56      *  <li>Device addresses are not shared across volume groups in same config
57      *  <li>Device addresses can be shared across configs in the same zone
58      * </ul>
59      *
60      * @param carAudioZones Audio zones to validate
61      * @param useCoreAudioRouting If the service is using core audio routing
62      * @throws RuntimeException when ever there is a failure when validating the audio zones
63      */
validate(SparseArray<CarAudioZone> carAudioZones, boolean useCoreAudioRouting)64     static void validate(SparseArray<CarAudioZone> carAudioZones, boolean useCoreAudioRouting)
65             throws RuntimeException {
66         validateAtLeastOneZoneDefined(carAudioZones);
67         validateZoneConfigsForEachZone(carAudioZones, useCoreAudioRouting);
68         if (!useCoreAudioRouting) {
69             validateEachAddressAppearsAtMostOnceInOneConfig(carAudioZones);
70         }
71         validatePrimaryZoneHasInputDevice(carAudioZones);
72     }
73 
validatePrimaryZoneHasInputDevice(SparseArray<CarAudioZone> carAudioZones)74     private static void validatePrimaryZoneHasInputDevice(SparseArray<CarAudioZone> carAudioZones) {
75         CarAudioZone primaryZone = carAudioZones.get(PRIMARY_AUDIO_ZONE);
76         List<AudioDeviceAttributes> devices = primaryZone.getInputAudioDevices();
77         Preconditions.checkCollectionNotEmpty(devices, "Primary Zone Input Devices");
78         for (int index = 0; index < devices.size(); index++) {
79             AudioDeviceAttributes device = devices.get(index);
80             if (device.getType() == TYPE_BUILTIN_MIC) {
81                 return;
82             }
83         }
84         throw new RuntimeException("Primary Zone must have at least one microphone input device");
85     }
86 
validateAtLeastOneZoneDefined(SparseArray<CarAudioZone> carAudioZones)87     private static void validateAtLeastOneZoneDefined(SparseArray<CarAudioZone> carAudioZones) {
88         if (carAudioZones.size() == 0) {
89             throw new RuntimeException("At least one zone should be defined");
90         }
91     }
92 
validateZoneConfigsForEachZone(SparseArray<CarAudioZone> carAudioZones, boolean useCoreAudioRouting)93     private static void validateZoneConfigsForEachZone(SparseArray<CarAudioZone> carAudioZones,
94             boolean useCoreAudioRouting) {
95         for (int i = 0; i < carAudioZones.size(); i++) {
96             CarAudioZone zone = carAudioZones.valueAt(i);
97             if (!zone.validateZoneConfigs(useCoreAudioRouting)) {
98                 throw new RuntimeException(
99                         "Invalid zone configurations for zone " + zone.getId());
100             }
101             // TODO(b/301391301) use fore routing for all zones.
102             // Currently force "useCoreAudioRouting" to be false for non primary zones as only
103             // primary zone supports core routing
104             if (!zone.validateCanUseDynamicMixRouting(
105                     zone.isPrimaryZone() && useCoreAudioRouting)) {
106                 throw new RuntimeException(
107                         "Invalid Configuration to use Dynamic Mix for zone " + zone.getId());
108             }
109         }
110     }
111 
validateEachAddressAppearsAtMostOnceInOneConfig( SparseArray<CarAudioZone> carAudioZones)112     private static void validateEachAddressAppearsAtMostOnceInOneConfig(
113             SparseArray<CarAudioZone> carAudioZones) {
114         Set<String> addresses = new ArraySet<>();
115         for (int i = 0; i < carAudioZones.size(); i++) {
116             List<CarAudioZoneConfig> zoneConfigs =
117                     carAudioZones.valueAt(i).getAllCarAudioZoneConfigs();
118             ArraySet<String> addressesPerZone = new ArraySet<>();
119             for (int configIndex = 0; configIndex < zoneConfigs.size(); configIndex++) {
120                 Set<String> addressesPerConfig = new ArraySet<>();
121                 CarAudioZoneConfig config = zoneConfigs.get(configIndex);
122                 CarVolumeGroup[] groups = config.getVolumeGroups();
123                 for (CarVolumeGroup carVolumeGroup : groups) {
124                     validateVolumeGroupAddresses(addressesPerConfig, carVolumeGroup.getAddresses());
125                 }
126                 // No need to check for addresses shared among configurations
127                 // as that is allowed
128                 addressesPerZone.addAll(addressesPerConfig);
129             }
130 
131             for (int c = 0; c < addressesPerZone.size(); c++) {
132                 String address = addressesPerZone.valueAt(c);
133                 if (addresses.add(address)) {
134                     continue;
135                 }
136                 throw  new IllegalStateException("Address " + address + " repeats among multiple"
137                         + " zones in car_audio_configuration.xml");
138             }
139         }
140     }
141 
validateVolumeGroupAddresses(Set<String> addressesPerConfig, List<String> groupAddresses)142     private static void validateVolumeGroupAddresses(Set<String> addressesPerConfig,
143                                                      List<String> groupAddresses) {
144         for (int c = 0; c < groupAddresses.size(); c++) {
145             String address = groupAddresses.get(c);
146             // Ignore dynamic devices as they may not have addresses until the device is connected
147             if (address == null || address.isEmpty()) {
148                 continue;
149             }
150             if (!addressesPerConfig.add(address)) {
151                 throw new RuntimeException("Device with address " + address
152                         + " appears in multiple volume groups in the same configuration");
153             }
154         }
155     }
156 }
157