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