1 /*
2  * Copyright (C) 2021 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.media.AudioDeviceInfo.TYPE_AUX_LINE;
20 import static android.media.AudioDeviceInfo.TYPE_BLE_BROADCAST;
21 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
22 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
23 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
24 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
25 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
26 import static android.media.AudioDeviceInfo.TYPE_BUS;
27 import static android.media.AudioDeviceInfo.TYPE_HDMI;
28 import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
29 import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
30 import static android.media.AudioDeviceInfo.TYPE_USB_HEADSET;
31 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
32 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
33 import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
34 
35 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
36 
37 import static java.util.Collections.EMPTY_LIST;
38 
39 import android.annotation.Nullable;
40 import android.car.feature.Flags;
41 import android.car.media.CarAudioZoneConfigInfo;
42 import android.car.media.CarVolumeGroupEvent;
43 import android.car.media.CarVolumeGroupInfo;
44 import android.media.AudioAttributes;
45 import android.media.AudioDeviceAttributes;
46 import android.media.AudioDeviceInfo;
47 import android.media.AudioManager;
48 import android.text.TextUtils;
49 import android.util.ArrayMap;
50 
51 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Objects;
56 
57 final class CarAudioUtils {
58 
59     static final int ACTIVATION_VOLUME_PERCENTAGE_MIN = 0;
60     static final int ACTIVATION_VOLUME_PERCENTAGE_MAX = 100;
61     static final int ACTIVATION_VOLUME_INVOCATION_TYPE =
62             CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT
63                     | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED
64                     | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
65 
66     static final CarActivationVolumeConfig DEFAULT_ACTIVATION_VOLUME =
67             new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE,
68                     ACTIVATION_VOLUME_PERCENTAGE_MIN, ACTIVATION_VOLUME_PERCENTAGE_MAX);
69 
70     @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
CarAudioUtils()71     private CarAudioUtils() {
72         throw new UnsupportedOperationException();
73     }
74 
hasExpired(long startTimeMs, long currentTimeMs, int timeoutMs)75     static boolean hasExpired(long startTimeMs, long currentTimeMs, int timeoutMs) {
76         return (currentTimeMs - startTimeMs) > timeoutMs;
77     }
78 
isMicrophoneInputDevice(AudioDeviceInfo device)79     static boolean isMicrophoneInputDevice(AudioDeviceInfo device) {
80         return device.getType() == TYPE_BUILTIN_MIC;
81     }
82 
convertVolumeChangeToEvent(CarVolumeGroupInfo info, int flags, int eventTypes)83     static CarVolumeGroupEvent convertVolumeChangeToEvent(CarVolumeGroupInfo info, int flags,
84             int eventTypes) {
85         List<Integer> extraInfos = CarVolumeGroupEvent.convertFlagsToExtraInfo(flags,
86                 eventTypes);
87         return convertVolumeChangesToEvents(List.of(info), eventTypes, extraInfos);
88     }
89 
convertVolumeChangesToEvents(List<CarVolumeGroupInfo> infoList, int eventTypes, List<Integer> extraInfos)90     static CarVolumeGroupEvent convertVolumeChangesToEvents(List<CarVolumeGroupInfo> infoList,
91                                                             int eventTypes,
92                                                             List<Integer> extraInfos) {
93         return new CarVolumeGroupEvent.Builder(infoList, eventTypes, extraInfos).build();
94     }
95 
96     @Nullable
getAudioDeviceInfo(AudioDeviceAttributes audioDeviceAttributes, AudioManagerWrapper audioManager)97     static AudioDeviceInfo getAudioDeviceInfo(AudioDeviceAttributes audioDeviceAttributes,
98             AudioManagerWrapper audioManager) {
99         AudioDeviceInfo[] infos = audioManager.getDevices(GET_DEVICES_OUTPUTS);
100         for (int c = 0; c < infos.length; c++) {
101             if (!infos[c].getAddress().equals(audioDeviceAttributes.getAddress())) {
102                 continue;
103             }
104             return infos[c];
105         }
106         return null;
107     }
108 
isDynamicDeviceType(int type)109     static boolean isDynamicDeviceType(int type) {
110         switch (type) {
111             case TYPE_WIRED_HEADSET: // fallthrough
112             case TYPE_WIRED_HEADPHONES: // fallthrough
113             case TYPE_BLUETOOTH_A2DP: // fallthrough
114             case TYPE_HDMI: // fallthrough
115             case TYPE_USB_ACCESSORY: // fallthrough
116             case TYPE_USB_DEVICE: // fallthrough
117             case TYPE_USB_HEADSET: // fallthrough
118             case TYPE_AUX_LINE: // fallthrough
119             case TYPE_BLE_HEADSET: // fallthrough
120             case TYPE_BLE_SPEAKER: // fallthrough
121             case TYPE_BLE_BROADCAST:
122                 return true;
123             case TYPE_BUILTIN_SPEAKER: // fallthrough
124             case TYPE_BUS:  // fallthrough
125             default:
126                 return false;
127         }
128     }
129 
getDynamicDevicesInConfig(CarAudioZoneConfigInfo zoneConfig, AudioManagerWrapper manager)130     static List<AudioDeviceInfo> getDynamicDevicesInConfig(CarAudioZoneConfigInfo zoneConfig,
131             AudioManagerWrapper manager) {
132         return Flags.carAudioDynamicDevices()
133                 ? getDynamicAudioDevices(zoneConfig.getConfigVolumeGroups(), manager) : EMPTY_LIST;
134     }
135 
excludesDynamicDevices(CarAudioZoneConfigInfo zoneConfig)136     static boolean excludesDynamicDevices(CarAudioZoneConfigInfo zoneConfig) {
137         if (!Flags.carAudioDynamicDevices()) {
138             return true;
139         }
140         List<CarVolumeGroupInfo> carVolumeInfos = zoneConfig.getConfigVolumeGroups();
141         for (int c = 0; c < carVolumeInfos.size(); c++) {
142             if (excludesDynamicDevices(carVolumeInfos.get(c).getAudioDeviceAttributes())) {
143                 continue;
144             }
145             return false;
146         }
147         return true;
148     }
149 
isInvalidActivationPercentage(int activationValue)150     static boolean isInvalidActivationPercentage(int activationValue) {
151         return activationValue < ACTIVATION_VOLUME_PERCENTAGE_MIN
152                 || activationValue > ACTIVATION_VOLUME_PERCENTAGE_MAX;
153     }
154 
excludesDynamicDevices(List<AudioDeviceAttributes> devices)155     private static boolean excludesDynamicDevices(List<AudioDeviceAttributes> devices) {
156         for (int c = 0; c < devices.size(); c++) {
157             if (!isDynamicDeviceType(devices.get(c).getType())) {
158                 continue;
159             }
160             return false;
161         }
162         return true;
163     }
164 
getAudioAttributesForDynamicDevices(CarAudioZoneConfigInfo info)165     static List<AudioAttributes> getAudioAttributesForDynamicDevices(CarAudioZoneConfigInfo info) {
166         List<AudioAttributes> audioAttributes = new ArrayList<>();
167         if (!Flags.carAudioDynamicDevices()) {
168             return audioAttributes;
169         }
170         List<CarVolumeGroupInfo> groups = info.getConfigVolumeGroups();
171         for (int c = 0; c < groups.size(); c++) {
172             CarVolumeGroupInfo groupInfo = groups.get(c);
173             if (excludesDynamicDevices(groupInfo.getAudioDeviceAttributes())) {
174                 continue;
175             }
176             audioAttributes.addAll(groupInfo.getAudioAttributes());
177         }
178         return audioAttributes;
179     }
180 
generateAddressToCarAudioDeviceInfoMap( List<CarAudioDeviceInfo> carAudioDeviceInfos)181     static ArrayMap<String, CarAudioDeviceInfo> generateAddressToCarAudioDeviceInfoMap(
182             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
183         Objects.requireNonNull(carAudioDeviceInfos, "Car audio device infos can not be null");
184         var addressToCarInfo = new ArrayMap<String, CarAudioDeviceInfo>();
185         for (int i = 0; i < carAudioDeviceInfos.size(); i++) {
186             CarAudioDeviceInfo info = carAudioDeviceInfos.get(i);
187             if (!TextUtils.isEmpty(info.getAddress())) {
188                 addressToCarInfo.put(info.getAddress(), info);
189             }
190         }
191         return addressToCarInfo;
192     }
193 
generateAddressToInputAudioDeviceInfoMap( AudioDeviceInfo[] deviceInfos)194     static ArrayMap<String, AudioDeviceInfo> generateAddressToInputAudioDeviceInfoMap(
195             AudioDeviceInfo[] deviceInfos) {
196         Objects.requireNonNull(deviceInfos, "Input audio device infos can not be null");
197         var addressToInputDevice = new ArrayMap<String, AudioDeviceInfo>();
198         for (AudioDeviceInfo info : deviceInfos) {
199             if (info.isSource() && !TextUtils.isEmpty(info.getAddress())) {
200                 addressToInputDevice.put(info.getAddress(), info);
201             }
202         }
203         return addressToInputDevice;
204     }
205 
generateCarAudioDeviceInfos(AudioManagerWrapper audioManager)206     static List<CarAudioDeviceInfo> generateCarAudioDeviceInfos(AudioManagerWrapper audioManager) {
207         Objects.requireNonNull(audioManager, "Audio manager can not be null");
208         AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
209         List<CarAudioDeviceInfo> carInfos = new ArrayList<>();
210         for (int index = 0; index < deviceInfos.length; index++) {
211             if (!isValidDeviceType(deviceInfos[index].getType())) {
212                 continue;
213             }
214             AudioDeviceInfo info = deviceInfos[index];
215             AudioDeviceAttributes attributes = new AudioDeviceAttributes(info);
216             CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(audioManager, attributes);
217             carInfo.setAudioDeviceInfo(info);
218             carInfos.add(carInfo);
219         }
220         return carInfos;
221     }
222 
223     /*
224      * Currently only BUS and BUILT_SPEAKER devices are valid static devices.
225      */
isValidDeviceType(int type)226     private static boolean isValidDeviceType(int type) {
227         return type == AudioDeviceInfo.TYPE_BUS || type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
228     }
229 
getDynamicAudioDevices( List<CarVolumeGroupInfo> volumeGroups, AudioManagerWrapper manager)230     private static List<AudioDeviceInfo> getDynamicAudioDevices(
231             List<CarVolumeGroupInfo> volumeGroups, AudioManagerWrapper manager) {
232         List<AudioDeviceInfo> dynamicDevices = new ArrayList<>();
233         for (int c = 0; c < volumeGroups.size(); c++) {
234             dynamicDevices.addAll(getDynamicDevices(volumeGroups.get(c), manager));
235         }
236         return dynamicDevices;
237     }
238 
getDynamicDevices(CarVolumeGroupInfo carVolumeGroupInfo, AudioManagerWrapper manager)239     private static List<AudioDeviceInfo> getDynamicDevices(CarVolumeGroupInfo carVolumeGroupInfo,
240             AudioManagerWrapper manager) {
241         List<AudioDeviceInfo> dynamicDevices = new ArrayList<>();
242         List<AudioDeviceAttributes> devices = carVolumeGroupInfo.getAudioDeviceAttributes();
243         for (int c = 0; c < devices.size(); c++) {
244             if (!isDynamicDeviceType(devices.get(c).getType())) {
245                 continue;
246             }
247             AudioDeviceInfo info = getAudioDeviceInfo(devices.get(c), manager);
248             if (info == null) {
249                 continue;
250             }
251             dynamicDevices.add(info);
252         }
253         return dynamicDevices;
254     }
255 }
256