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