| /* |
| * Copyright (c) 2020, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Changes from Qualcomm Innovation Center are provided under the following license: |
| * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. |
| * SPDX-License-Identifier: BSD-3-Clause-Clear |
| * |
| */ |
| |
| #define LOG_TAG "device_utils" |
| /*#define LOG_NDEBUG 0*/ |
| #define LOG_NDDEBUG 0 |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <hardware/audio.h> |
| #include <cutils/list.h> |
| #include <log/log.h> |
| |
| #include "device_utils.h" |
| |
| /* |
| * Below are the devices for which is back end is same, SLIMBUS_0_RX. |
| * All these devices are handled by the internal HW codec. We can |
| * enable any one of these devices at any time. An exception here is |
| * 44.1k headphone which uses different backend. This is filtered |
| * as different hal internal device in the code but remains same |
| * as standard android device AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
| * for other layers. |
| */ |
| static const uint32_t AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND_ARRAY[] = { |
| AUDIO_DEVICE_OUT_EARPIECE, |
| AUDIO_DEVICE_OUT_SPEAKER, |
| AUDIO_DEVICE_OUT_WIRED_HEADSET, |
| AUDIO_DEVICE_OUT_WIRED_HEADPHONE, |
| AUDIO_DEVICE_OUT_LINE, |
| AUDIO_DEVICE_OUT_SPEAKER_SAFE, |
| }; |
| |
| /* |
| * Below are the input devices for which back end is same, SLIMBUS_0_TX. |
| * All these devices are handled by the internal HW codec. We can |
| * enable any one of these devices at any time |
| */ |
| static const uint32_t AUDIO_DEVICE_IN_ALL_CODEC_BACKEND_ARRAY[] = { |
| AUDIO_DEVICE_IN_BUILTIN_MIC, |
| AUDIO_DEVICE_IN_WIRED_HEADSET, |
| AUDIO_DEVICE_IN_VOICE_CALL, |
| AUDIO_DEVICE_IN_BACK_MIC, |
| }; |
| |
| static const uint32_t AUDIO_DEVICE_OUT_CODEC_BACKEND_CNT = |
| AUDIO_ARRAY_SIZE(AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND_ARRAY); |
| static const uint32_t AUDIO_DEVICE_IN_CODEC_BACKEND_CNT = |
| AUDIO_ARRAY_SIZE(AUDIO_DEVICE_IN_ALL_CODEC_BACKEND_ARRAY); |
| |
| |
| int list_length(struct listnode *list) |
| { |
| struct listnode *node; |
| int length = 0; |
| |
| if (list == NULL) |
| goto done; |
| |
| for (node = list->next; node != list; node = node->next) |
| ++length; |
| done: |
| return length; |
| } |
| |
| /* |
| * Returns true if devices list contains input device type. |
| */ |
| bool is_audio_in_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_input_device(item->type)) { |
| ALOGV("%s: in device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if devices list contains output device type. |
| */ |
| bool is_audio_out_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_output_device(item->type)) { |
| ALOGV("%s: out device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if devices list contains codec backend |
| * input device type. |
| */ |
| bool is_codec_backend_in_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item == NULL) |
| return false; |
| for (int i = 0; i < AUDIO_DEVICE_IN_CODEC_BACKEND_CNT; i++) { |
| if (item->type == AUDIO_DEVICE_IN_ALL_CODEC_BACKEND_ARRAY[i]) { |
| ALOGV("%s: codec backend in device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if devices list contains codec backend |
| * output device type. |
| */ |
| bool is_codec_backend_out_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item == NULL) |
| return false; |
| for (int i = 0; i < AUDIO_DEVICE_OUT_CODEC_BACKEND_CNT; i++) { |
| if (item->type == AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND_ARRAY[i]) { |
| ALOGV("%s: codec backend out device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /* Returns true if USB input device is found in passed devices list */ |
| bool is_usb_in_device_type(struct listnode *devices) |
| { |
| struct listnode *node, *temp; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each_safe (node, temp, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_usb_in_device(item->type)) { |
| ALOGV("%s: USB in device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if USB output device is found in passed devices list |
| */ |
| bool is_usb_out_device_type(struct listnode *devices) |
| { |
| struct listnode *node, *temp; |
| struct audio_device_info *item = NULL; |
| |
| list_for_each_safe (node, temp, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_usb_out_device(item->type)) { |
| ALOGV("%s: USB out device %#x address %s", __func__, |
| item->type, item->address); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns USB device address information (card, device) |
| */ |
| const char *get_usb_device_address(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return ""; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && (audio_is_usb_out_device(item->type) || |
| audio_is_usb_in_device(item->type))) { |
| ALOGV("%s: USB device %#x address %s", __func__, |
| item->type, item->address); |
| return (const char *)&item->address[0]; |
| } |
| } |
| return ""; |
| } |
| |
| /* |
| * Returns true if SCO output device is found in passed devices list |
| */ |
| bool is_sco_in_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && |
| audio_is_bluetooth_in_sco_device(item->type)) { |
| ALOGV("%s: SCO in device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if SCO output device is found in passed devices list |
| */ |
| bool is_sco_out_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && |
| audio_is_bluetooth_out_sco_device(item->type)) { |
| ALOGV("%s: SCO out device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if A2DP input device is found in passed devices list |
| */ |
| bool is_a2dp_in_device_type(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_a2dp_in_device(item->type)) { |
| ALOGV("%s: A2DP in device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if A2DP output device is found in passed devices list |
| */ |
| bool is_a2dp_out_device_type(struct listnode *devices) |
| { |
| struct listnode *node, *temp; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each_safe (node, temp, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_a2dp_out_device(item->type)) { |
| ALOGV("%s: A2DP out device %#x", __func__, item->type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Clear device list |
| * Operation: devices = {}; |
| */ |
| |
| int clear_devices(struct listnode *devices) |
| { |
| struct listnode *node, *temp; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return 0; |
| |
| list_for_each_safe (node, temp, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL) { |
| list_remove(&item->list); |
| free(item); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Check if a device with given type is present in devices list |
| */ |
| bool compare_device_type(struct listnode *devices, audio_devices_t device_type) |
| { |
| struct listnode *node, *temp; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each_safe (node, temp, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && (item->type == device_type)) { |
| ALOGV("%s: device types %d match", __func__, device_type); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Check if a device with given type and address is present in devices list |
| */ |
| bool compare_device_type_and_address(struct listnode *devices, |
| audio_devices_t type, const char* address) |
| { |
| struct listnode *node = devices; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && (item->type == type) && |
| (strcmp((const char *)&item->address[0], address) == 0)) { |
| ALOGV("%s: device type %x and address %s match", __func__, |
| item->type, (const char *)&item->address[0]); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if intersection of d1 and d2 is not NULL |
| */ |
| bool compare_devices_for_any_match(struct listnode *d1, struct listnode *d2) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (d1 == NULL || d2 == NULL) |
| return false; |
| |
| list_for_each (node, d1) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && compare_device_type(d2, item->type)) |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * Returns all device types from list in bitfield |
| * ToDo: Use of this function is not recommended. |
| * It has been introduced for compatibility with legacy functions. |
| * This can be removed once audio HAL switches to device |
| * list usage for all audio extensions. |
| */ |
| audio_devices_t get_device_types(struct listnode *devices) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| audio_devices_t device_type = AUDIO_DEVICE_NONE; |
| |
| if (devices == NULL) |
| return AUDIO_DEVICE_NONE; |
| |
| list_for_each (node, devices) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL) |
| device_type |= item->type; |
| } |
| return device_type; |
| } |
| |
| /* |
| * If single device in devices list is equal to passed type |
| * type should represent a single device. |
| */ |
| bool is_single_device_type_equal(struct listnode *devices, |
| audio_devices_t type) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (devices == NULL) |
| return false; |
| |
| if (list_length(devices) == 1) { |
| node = devices->next; |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && (item->type == type)) |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * Returns true if lists are equal in terms of device type |
| * ToDO: Check if device addresses are also equal in the future |
| */ |
| bool compare_devices(struct listnode *d1, struct listnode *d2) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| |
| if (d1 == NULL && d2 == NULL) |
| return true; |
| |
| if (d1 == NULL || d2 == NULL || |
| (list_length(d1) != list_length(d2))) |
| return false; |
| |
| list_for_each (node, d1) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && !compare_device_type(d2, item->type)) |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * Add or remove device from list denoted by head |
| */ |
| int update_device_list(struct listnode *head, audio_devices_t type, |
| const char* address, bool add_device) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| struct audio_device_info *device = NULL; |
| int ret = 0; |
| |
| if (head == NULL) |
| goto done; |
| |
| if (type == AUDIO_DEVICE_NONE) { |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| list_for_each (node, head) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && (item->type == type)) { |
| device = item; |
| break; |
| } |
| } |
| |
| if (add_device) { |
| if (device == NULL) { |
| device = (struct audio_device_info *) |
| calloc (1, sizeof(struct audio_device_info)); |
| if (!device) { |
| ALOGE("%s: Cannot allocate memory for device_info", __func__); |
| ret = -ENOMEM; |
| goto done; |
| } |
| device->type = type; |
| list_add_tail(head, &device->list); |
| } |
| strlcpy(device->address, address, AUDIO_DEVICE_MAX_ADDRESS_LEN); |
| ALOGV("%s: Added device type %#x, address %s", __func__, type, address); |
| } else { |
| if (device != NULL) { |
| list_remove(&device->list); |
| free(device); |
| ALOGV("%s: Removed device type %#x, address %s", __func__, type, address); |
| } |
| } |
| done: |
| return ret; |
| } |
| |
| /* |
| * Assign source device list to destination device list |
| * Operation: dest list = source list |
| */ |
| int assign_devices(struct listnode *dest, const struct listnode *source) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| int ret = 0; |
| |
| if (source == NULL || dest == NULL) |
| return ret; |
| |
| if (!list_empty(dest)) |
| clear_devices(dest); |
| |
| list_for_each (node, source) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL) |
| ret = update_device_list(dest, item->type, item->address, true); |
| } |
| return ret; |
| } |
| |
| /* |
| * Assign output devices from source device list to destination device list |
| * Operation: dest devices = source output devices |
| */ |
| int assign_output_devices(struct listnode *dest, const struct listnode *source) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| int ret = 0; |
| |
| if (source == NULL || dest == NULL) |
| return ret; |
| |
| if (!list_empty(dest)) |
| clear_devices(dest); |
| |
| list_for_each (node, source) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL && audio_is_output_device(item->type)) |
| ret = update_device_list(dest, item->type, item->address, true); |
| } |
| return ret; |
| } |
| |
| /* |
| * Clear device list and replace it with single device |
| */ |
| int reassign_device_list(struct listnode *device_list, |
| audio_devices_t type, char *address) |
| { |
| if (device_list == NULL) |
| return 0; |
| |
| if (!list_empty(device_list)) |
| clear_devices(device_list); |
| |
| return update_device_list(device_list, type, address, true); |
| } |
| |
| /* |
| * Append source devices to destination devices |
| * Operation: dest devices |= source devices |
| */ |
| int append_devices(struct listnode *dest, const struct listnode *source) |
| { |
| struct listnode *node; |
| struct audio_device_info *item = NULL; |
| int ret = 0; |
| |
| if (source == NULL || dest == NULL) |
| return ret; |
| |
| list_for_each (node, source) { |
| item = node_to_item(node, struct audio_device_info, list); |
| if (item != NULL) |
| ret = update_device_list(dest, item->type, item->address, true); |
| } |
| return ret; |
| } |