xref: /aosp_15_r20/cts/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioDeviceUtils.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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