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.cts.verifier.audio.audiolib; 18 19 import android.content.Context; 20 import android.hardware.usb.UsbDevice; 21 import android.hardware.usb.UsbManager; 22 import android.media.AudioDeviceInfo; 23 import android.media.AudioManager; 24 import android.util.Log; 25 26 import java.util.Collection; 27 import java.util.HashMap; 28 import java.util.Set; 29 30 /** 31 * Utility methods for AudioDevices 32 */ 33 public class AudioDeviceUtils { 34 private static final String TAG = "AudioDeviceUtils"; 35 private static final boolean LOG = false; 36 37 /* 38 * Channel Mask Utilities 39 */ 40 private static final HashMap<Integer, String> sDeviceTypeStrings = 41 new HashMap<Integer, String>(); 42 initDeviceTypeStrings()43 private static void initDeviceTypeStrings() { 44 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_UNKNOWN, "UNKNOWN"); 45 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, "BUILTIN_EARPIECE"); 46 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "BUILTIN_SPEAKER"); 47 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, "WIRED_HEADSET"); 48 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, "WIRED_HEADPHONES"); 49 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_LINE_ANALOG, "LINE_ANALOG"); 50 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_LINE_DIGITAL, "LINE_DIGITAL"); 51 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, "BLUETOOTH_SCO"); 52 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, "BLUETOOTH_A2DP"); 53 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_HDMI, "HDMI"); 54 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_HDMI_ARC, "HDMI_ARC"); 55 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_USB_DEVICE, "USB_DEVICE"); 56 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, "USB_ACCESSORY"); 57 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_DOCK, "DOCK"); 58 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_FM, "FM"); 59 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BUILTIN_MIC, "BUILTIN_MIC"); 60 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_FM_TUNER, "FM_TUNER"); 61 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_TV_TUNER, "TV_TUNER"); 62 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_TELEPHONY, "TELEPHONY"); 63 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_AUX_LINE, "AUX_LINE"); 64 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_IP, "IP"); 65 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BUS, "BUS"); 66 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_USB_HEADSET, "USB_HEADSET"); 67 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_HEARING_AID, "HEARING_AID"); 68 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE, 69 "BUILTIN_SPEAKER_SAFE"); 70 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_REMOTE_SUBMIX, "REMOTE_SUBMIX"); 71 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BLE_HEADSET, "BLE_HEADSET"); 72 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BLE_SPEAKER, "BLE_SPEAKER"); 73 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_ECHO_REFERENCE, "ECHO_REFERENCE"); 74 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_HDMI_EARC, "HDMI_EARC"); 75 sDeviceTypeStrings.put(AudioDeviceInfo.TYPE_BLE_BROADCAST, "BLE_BROADCAST"); 76 } 77 78 static { initDeviceTypeStrings()79 initDeviceTypeStrings(); 80 } 81 82 // return codes for various supports device methods() 83 // Does not 84 public static final int SUPPORTSDEVICE_NO = 0; 85 // Does 86 public static final int SUPPORTSDEVICE_YES = 1; 87 // AudioManager.getSupportedDeviceTypes() is not implemented 88 public static final int SUPPORTSDEVICE_UNDETERMINED = 2; 89 90 /** 91 * @param deviceType The AudioDeviceInfo type ID of the desired device. 92 * @return a human-readable full device type name. 93 */ getDeviceTypeName( @udioDeviceInfo.AudioDeviceType int deviceType)94 public static String getDeviceTypeName( 95 @AudioDeviceInfo.AudioDeviceType int deviceType) { 96 String typeName = sDeviceTypeStrings.get(deviceType); 97 return typeName != null ? "TYPE_" + typeName : "invalid type"; 98 } 99 100 /** 101 * @param deviceType The AudioDeviceInfo type ID of the desired device. 102 * @return a human-readable abreviated device type name. 103 */ getShortDeviceTypeName( @udioDeviceInfo.AudioDeviceType int deviceType)104 public static String getShortDeviceTypeName( 105 @AudioDeviceInfo.AudioDeviceType int deviceType) { 106 String typeName = sDeviceTypeStrings.get(deviceType); 107 return typeName != null ? typeName : "invalid type"; 108 } 109 110 /** 111 * @param deviceInfo 112 * @return A human-readable description of the specified DeviceInfo 113 */ formatDeviceName(AudioDeviceInfo deviceInfo)114 public static String formatDeviceName(AudioDeviceInfo deviceInfo) { 115 StringBuilder sb = new StringBuilder(); 116 if (deviceInfo != null) { 117 sb.append(deviceInfo.getProductName()); 118 sb.append(" - " + getDeviceTypeName(deviceInfo.getType())); 119 } else { 120 sb.append("null"); 121 } 122 123 return sb.toString(); 124 } 125 126 /** 127 * @param deviceInfo Specifies the audio device to characterize. 128 * @return true if the device is (probably) a Mic 129 */ isMicDevice(AudioDeviceInfo deviceInfo)130 public static boolean isMicDevice(AudioDeviceInfo deviceInfo) { 131 if (deviceInfo == null || !deviceInfo.isSource()) { 132 return false; 133 } 134 135 switch (deviceInfo.getType()) { 136 case AudioDeviceInfo.TYPE_BUILTIN_MIC: 137 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 138 case AudioDeviceInfo.TYPE_USB_HEADSET: 139 return true; 140 141 default: 142 return false; 143 } 144 } 145 146 /** 147 * Determine device support for an analog headset. 148 * 149 * @param context The application context. 150 * @return the SUPPORTSDEVICE_ constant indicating support. 151 */ supportsAnalogHeadset(Context context)152 public static int supportsAnalogHeadset(Context context) { 153 if (LOG) { 154 Log.d(TAG, "supportsAnalogHeadset()"); 155 } 156 157 // TYPE_LINE_ANALOG? 158 AudioManager audioManager = context.getSystemService(AudioManager.class); 159 160 Set<Integer> deviceTypeIds = 161 audioManager.getSupportedDeviceTypes(AudioManager.GET_DEVICES_OUTPUTS); 162 if (LOG) { 163 for (Integer type : deviceTypeIds) { 164 Log.d(TAG, " " + getDeviceTypeName(type)); 165 } 166 } 167 return deviceTypeIds.contains(AudioDeviceInfo.TYPE_WIRED_HEADSET) 168 ? SUPPORTSDEVICE_YES : SUPPORTSDEVICE_NO; 169 } 170 171 /** 172 * Determine device support for a USB audio interface. 173 * 174 * @param context The application context. 175 * @return the SUPPORTSDEVICE_ constant indicating support. 176 */ supportsUsbAudioInterface(Context context)177 public static int supportsUsbAudioInterface(Context context) { 178 if (LOG) { 179 Log.d(TAG, "supportsUsbAudioInterface()"); 180 } 181 182 AudioManager audioManager = context.getSystemService(AudioManager.class); 183 Set<Integer> deviceTypeIds = 184 audioManager.getSupportedDeviceTypes(AudioManager.GET_DEVICES_OUTPUTS); 185 if (LOG) { 186 for (Integer type : deviceTypeIds) { 187 Log.d(TAG, " " + getDeviceTypeName(type)); 188 } 189 } 190 return deviceTypeIds.contains(AudioDeviceInfo.TYPE_USB_DEVICE) 191 ? SUPPORTSDEVICE_YES : SUPPORTSDEVICE_NO; 192 } 193 194 /** 195 * Determine device support for a USB headset peripheral. 196 * 197 * @param context The application context. 198 * @return the SUPPORTSDEVICE_ constant indicating support. 199 */ supportsUsbHeadset(Context context)200 public static int supportsUsbHeadset(Context context) { 201 if (LOG) { 202 Log.d(TAG, "supportsUsbHeadset()"); 203 } 204 205 AudioManager audioManager = context.getSystemService(AudioManager.class); 206 Set<Integer> outputDeviceTypeIds = 207 audioManager.getSupportedDeviceTypes(AudioManager.GET_DEVICES_OUTPUTS); 208 if (LOG) { 209 Log.d(TAG, "Output Device Types:"); 210 for (Integer type : outputDeviceTypeIds) { 211 Log.d(TAG, " " + getDeviceTypeName(type)); 212 } 213 } 214 215 Set<Integer> inputDeviceTypeIds = 216 audioManager.getSupportedDeviceTypes(AudioManager.GET_DEVICES_INPUTS); 217 if (LOG) { 218 Log.d(TAG, "Input Device Types:"); 219 for (Integer type : inputDeviceTypeIds) { 220 Log.d(TAG, " " + getDeviceTypeName(type)); 221 } 222 } 223 224 if (outputDeviceTypeIds.contains(AudioDeviceInfo.TYPE_USB_HEADSET) 225 && inputDeviceTypeIds.contains(AudioDeviceInfo.TYPE_USB_HEADSET)) { 226 return SUPPORTSDEVICE_YES; 227 } else { 228 return SUPPORTSDEVICE_NO; 229 } 230 } 231 232 /** 233 * Determine device support for a USB interface or headset peripheral. 234 * 235 * @param context The application context. 236 * @return the SUPPORTSDEVICE_ constant indicating support. 237 */ supportsUsbAudio(Context context)238 public static int supportsUsbAudio(Context context) { 239 if (LOG) { 240 Log.d(TAG, "supportsUsbAudio()"); 241 } 242 int hasInterface = supportsUsbAudioInterface(context); 243 int hasHeadset = supportsUsbHeadset(context); 244 if (LOG) { 245 Log.d(TAG, " hasInterface:" + hasInterface + " hasHeadset:" + hasHeadset); 246 } 247 248 // At least one is YES, so YES. 249 if (hasInterface == SUPPORTSDEVICE_YES || hasHeadset == SUPPORTSDEVICE_YES) { 250 return SUPPORTSDEVICE_YES; 251 } 252 253 // Both are NO, so NO 254 if (hasInterface == SUPPORTSDEVICE_NO && hasHeadset == SUPPORTSDEVICE_NO) { 255 return SUPPORTSDEVICE_NO; 256 } 257 258 // Some mixture of NO and UNDETERMINED, so UNDETERMINED 259 return SUPPORTSDEVICE_UNDETERMINED; 260 } 261 262 // 263 // USB Device Support 264 // 265 private static final int USB_VENDORID_GOOGLE = 0x18D1; 266 private static final int USB_PRODUCTID_GOOGLE_ADAPTER_A = 0x5025; 267 private static final int USB_PRODUCTID_GOOGLE_ADAPTER_B = 0x5034; 268 private static final int USB_VENDORID_XUMEE = 0x0BDA; 269 private static final int USB_PRODUCTID_XUMEE_ADAPTER = 0x4BE2; 270 private static final int USB_VENDORID_MOSHI = 0x282B; 271 private static final int USB_PRODUCTID_MOSHI_ADAPTER = 0x0033; 272 private static final int USB_VENDORID_ANKER = 0x0572; 273 private static final int USB_PRODUCTID_ANKER_ADAPTER = 0x1B08; 274 // This is the difference in round-trip latency over USB compared to the Google adapter. 275 // Measured using OboeTester. 276 private static final double USB_LATENCY_OFFSET_ANKER_MILLIS = 3.23; // higher than Google 277 private static final int USB_VENDORID_REALTEK_ALC5686 = 0x0BDA; 278 private static final int USB_PRODUCTID_REALTEK_ALC5686_ADAPTER = 0x4BD1; 279 280 /** 281 * Returns the UsbDevice corresponding to any connected USB peripheral. 282 * @param context The Application Context. 283 * @return the UsbDevice corresponding to any connected USB peripheral. 284 */ getConnectedUsbDevice(Context context)285 public static UsbDevice getConnectedUsbDevice(Context context) { 286 UsbManager usbManager = context.getSystemService(UsbManager.class); 287 288 if (usbManager == null) { 289 Log.e(TAG, "Can't get UsbManager!"); 290 } else { 291 HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList(); 292 Collection<UsbDevice> devices = deviceList.values(); 293 UsbDevice[] deviceArray = new UsbDevice[1]; 294 deviceArray = (UsbDevice[]) devices.toArray(deviceArray); 295 return deviceArray[0]; 296 } 297 298 return null; 299 } 300 301 /** 302 * Determines if the specified UsbDevice is a validated USB Audio headset adapter. 303 * Valid adapters have low latency and no echo cancellation. 304 * @param usbDevice the device to test. 305 * @return true if the specified UsbDevice is a valid USB Audio headset adapter. 306 */ isUsbHeadsetValidForTest(UsbDevice usbDevice)307 public static boolean isUsbHeadsetValidForTest(UsbDevice usbDevice) { 308 if (usbDevice != null) { 309 final int vId = usbDevice.getVendorId(); 310 final int pId = usbDevice.getProductId(); 311 if (vId == USB_VENDORID_GOOGLE && (pId == USB_PRODUCTID_GOOGLE_ADAPTER_A 312 || pId == USB_PRODUCTID_GOOGLE_ADAPTER_B)) { 313 return true; 314 } 315 if (vId == USB_VENDORID_XUMEE && pId == USB_PRODUCTID_XUMEE_ADAPTER) return true; 316 if (vId == USB_VENDORID_MOSHI && pId == USB_PRODUCTID_MOSHI_ADAPTER) return true; 317 if (vId == USB_VENDORID_ANKER && pId == USB_PRODUCTID_ANKER_ADAPTER) return true; 318 if (vId == USB_VENDORID_REALTEK_ALC5686 319 && pId == USB_PRODUCTID_REALTEK_ALC5686_ADAPTER) return true; 320 } 321 return false; 322 } 323 324 /** 325 * Get latency added by the USB adapter relative to the Google Adapter in msec. 326 * @param usbDevice the USB device that may have a latency offset 327 * @return latency dfiference in msec 328 */ getUsbLatencyOffsetMillis(UsbDevice usbDevice)329 public static double getUsbLatencyOffsetMillis(UsbDevice usbDevice) { 330 if (usbDevice.getVendorId() == USB_VENDORID_ANKER 331 && usbDevice.getProductId() == USB_PRODUCTID_ANKER_ADAPTER) { 332 return USB_LATENCY_OFFSET_ANKER_MILLIS; 333 } else { 334 return 0.0; 335 } 336 } 337 338 public static class UsbDeviceReport { 339 public boolean isValid; 340 public double latencyOffset; // round-trip latency relative to Google 341 } 342 343 /** 344 * Checks for any connected USB peripheral that is a valid USB Audio headset adapter. 345 * Displays a warning dialog if validity can not be determined. 346 * @param context The application context. 347 * @return a report with information about validity and latency 348 */ validateUsbDevice(Context context)349 public static UsbDeviceReport validateUsbDevice(Context context) { 350 AudioManager audioManager = context.getSystemService(AudioManager.class); 351 352 // Determine if the connected device is a USB Headset 353 AudioDeviceInfo inputUsbHeadset = null; 354 for (AudioDeviceInfo devInfo : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) { 355 if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) { 356 inputUsbHeadset = devInfo; 357 break; 358 } 359 } 360 361 AudioDeviceInfo outputUsbHeadset = null; 362 for (AudioDeviceInfo devInfo : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) { 363 if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) { 364 outputUsbHeadset = devInfo; 365 break; 366 } 367 } 368 369 UsbDeviceReport report = new UsbDeviceReport(); 370 if (inputUsbHeadset != null && outputUsbHeadset != null) { 371 // Now see if it is a compatible USB adapter 372 UsbDevice usbDevice = AudioDeviceUtils.getConnectedUsbDevice(context); 373 if (usbDevice != null) { 374 if (AudioDeviceUtils.isUsbHeadsetValidForTest(usbDevice)) { 375 report.isValid = true; 376 report.latencyOffset = getUsbLatencyOffsetMillis(usbDevice); 377 } else { 378 UsbDeviceWarningDialog warningDialog = new UsbDeviceWarningDialog(context); 379 warningDialog.show(); 380 } 381 } 382 } 383 return report; 384 } 385 } 386