Merge changes from topic "audio stat"

* changes:
  Add startup time from idle statistics
  Track alsa buffer underruns
diff --git a/hal/audio_extn/a2dp.c b/hal/audio_extn/a2dp.c
index 7c2e4db..43b3da9 100644
--- a/hal/audio_extn/a2dp.c
+++ b/hal/audio_extn/a2dp.c
@@ -34,6 +34,7 @@
 
 #ifdef A2DP_OFFLOAD_ENABLED
 #define BT_IPC_LIB_NAME  "libbthost_if.so"
+#define BTAUDIO_OFFLOAD_LIB_NAME  "btaudio_offload_if.so"
 
 // Media format definitions
 #define ENC_MEDIA_FMT_AAC                                  0x00010DA6
@@ -78,6 +79,7 @@
 // System properties used for A2DP Offload
 #define SYSPROP_A2DP_OFFLOAD_SUPPORTED "ro.bluetooth.a2dp_offload.supported"
 #define SYSPROP_A2DP_OFFLOAD_DISABLED  "persist.bluetooth.a2dp_offload.disabled"
+#define SYSPROP_BLUETOOTH_AUDIO_HAL_ENABLED  "persist.vendor.bluetooth.bluetooth_audio_hal.enabled"
 #define SYSPROP_A2DP_CODEC_LATENCIES   "vendor.audio.a2dp.codec.latency"
 
 // Default encoder bit width
@@ -676,8 +678,9 @@
 static int open_a2dp_output()
 {
     int ret = 0;
-
     ALOGD("%s: Open A2DP output start", __func__);
+    bool hal_v2_enabled =
+              property_get_bool(SYSPROP_BLUETOOTH_AUDIO_HAL_ENABLED, false);
 
     if (a2dp.bt_state != A2DP_STATE_DISCONNECTED) {
         ALOGD("%s: Called A2DP open with improper state, Ignoring request state %d",
@@ -686,9 +689,13 @@
     }
 
     if (a2dp.bt_lib_handle == NULL) {
-        ALOGD("%s: Requesting for Bluetooth IPC lib handle", __func__);
-        a2dp.bt_lib_handle = dlopen(BT_IPC_LIB_NAME, RTLD_NOW);
-
+        ALOGD("%s: Requesting for Bluetooth IPC lib handle [%s]", __func__,
+              hal_v2_enabled ? BTAUDIO_OFFLOAD_LIB_NAME : BT_IPC_LIB_NAME);
+        if (hal_v2_enabled) {
+           a2dp.bt_lib_handle = dlopen(BTAUDIO_OFFLOAD_LIB_NAME, RTLD_NOW);
+        } else {
+           a2dp.bt_lib_handle = dlopen(BT_IPC_LIB_NAME, RTLD_NOW);
+        }
         if (a2dp.bt_lib_handle == NULL) {
             ret = -errno;
             ALOGE("%s: DLOPEN failed for %s errno %d strerror %s", __func__,
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 1c28b6b..54ea017 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -86,6 +86,7 @@
 #define audio_extn_usb_alive(adev)                                     (false)
 #define audio_extn_usb_find_service_interval(m, p)      ((m), (p), 0) /* fix unused warn */
 #define audio_extn_usb_altset_for_service_interval(p, si, bw, sr, ch) (-1)
+#define audio_extn_usb_usbid()                                         (NULL)
 #else
 void audio_extn_usb_init(void *adev);
 void audio_extn_usb_deinit();
@@ -109,6 +110,7 @@
                                                uint32_t *bit_width,
                                                uint32_t *sample_rate,
                                                uint32_t *channel_count);
+char *audio_extn_usb_usbid(void);
 #endif
 
 
diff --git a/hal/audio_extn/usb.c b/hal/audio_extn/usb.c
index 71e1aed..b0b5049 100644
--- a/hal/audio_extn/usb.c
+++ b/hal/audio_extn/usb.c
@@ -43,6 +43,7 @@
 #define SAMPLE_RATE_8000          8000
 #define SAMPLE_RATE_11025         11025
 #define DEFAULT_SERVICE_INTERVAL_US    1000
+#define USBID_SIZE                16
 
 /* TODO: dynamically populate supported sample rates */
 static uint32_t supported_sample_rates[] =
@@ -83,6 +84,7 @@
     int usb_sidetone_index[USB_SIDETONE_MAX_INDEX];
     int usb_sidetone_vol_min;
     int usb_sidetone_vol_max;
+    char usbid[USBID_SIZE];
 };
 
 struct usb_module {
@@ -510,6 +512,48 @@
     return ret;
 }
 
+static int usb_get_usbid(struct usb_card_config *usb_card_info,
+                              int card)
+{
+    int32_t fd=-1;
+    char path[128];
+    int ret = 0;
+
+    memset(usb_card_info->usbid, 0, sizeof(usb_card_info->usbid));
+
+    ret = snprintf(path, sizeof(path), "/proc/asound/card%u/usbid",
+             card);
+
+    if (ret < 0) {
+        ALOGE("%s: failed on snprintf (%d) to path %s\n",
+          __func__, ret, path);
+        goto done;
+    }
+
+    fd = open(path, O_RDONLY);
+    if (fd < 0) {
+        ALOGE("%s: error failed to open file %s error: %d\n",
+              __func__, path, errno);
+        ret = -EINVAL;
+        goto done;
+    }
+
+    if (read(fd, usb_card_info->usbid, USBID_SIZE - 1) < 0) {
+        ALOGE("file read error\n");
+        ret = -EINVAL;
+        usb_card_info->usbid[0] = '\0';
+        goto done;
+    }
+
+    strtok(usb_card_info->usbid, "\n");
+
+done:
+    if (fd >= 0)
+        close(fd);
+
+    return ret;
+}
+
 static int usb_get_device_playback_config(struct usb_card_config *usb_card_info,
                                     int card)
 {
@@ -1082,6 +1126,10 @@
     }
     list_init(&usb_card_info->usb_device_conf_list);
     if (usb_output_device(device)) {
+        if (usb_get_usbid(usb_card_info, card) < 0) {
+            ALOGE("parse card %d usbid fail", card);
+        }
+
         if (!usb_get_device_playback_config(usb_card_info, card)){
             usb_card_info->usb_card = card;
             usb_card_info->usb_device_type = device;
@@ -1090,6 +1138,10 @@
             goto exit;
         }
     } else if (usb_input_device(device)) {
+        if (usb_get_usbid(usb_card_info, card) < 0) {
+            ALOGE("parse card %d usbid fail", card);
+        }
+
         if (!usb_get_device_capture_config(usb_card_info, card)) {
             usb_card_info->usb_card = card;
             usb_card_info->usb_device_type = device;
@@ -1251,6 +1303,22 @@
     return 0;
 }
 
+char *audio_extn_usb_usbid()
+{
+    struct usb_card_config *card_info;
+
+    if (usbmod == NULL)
+        return NULL;
+
+    if (list_empty(&usbmod->usb_card_conf_list))
+        return NULL;
+
+    card_info = node_to_item(list_head(&usbmod->usb_card_conf_list),\
+                             struct usb_card_config, list);
+
+    return strdup(card_info->usbid);
+}
+
 void audio_extn_usb_init(void *adev)
 {
     if (usbmod == NULL) {
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 4f2f3dc..4d69362 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -132,6 +132,26 @@
     .avail_min = LOW_LATENCY_OUTPUT_PERIOD_SIZE / 4,
 };
 
+struct pcm_config pcm_config_haptics_audio = {
+    .channels = 1,
+    .rate = DEFAULT_OUTPUT_SAMPLING_RATE,
+    .period_size = LOW_LATENCY_OUTPUT_PERIOD_SIZE,
+    .period_count = LOW_LATENCY_OUTPUT_PERIOD_COUNT,
+    .format = PCM_FORMAT_S16_LE,
+    .start_threshold = LOW_LATENCY_OUTPUT_PERIOD_SIZE / 4,
+    .stop_threshold = INT_MAX,
+    .avail_min = LOW_LATENCY_OUTPUT_PERIOD_SIZE / 4,
+};
+
+struct pcm_config pcm_config_haptics = {
+    .channels = 1,
+    .rate = DEFAULT_OUTPUT_SAMPLING_RATE,
+    .period_count = 2,
+    .format = PCM_FORMAT_S16_LE,
+    .stop_threshold = INT_MAX,
+    .avail_min = 0,
+};
+
 static int af_period_multiplier = 4;
 struct pcm_config pcm_config_rt = {
     .channels = DEFAULT_CHANNEL_COUNT,
@@ -257,6 +277,7 @@
 const char * const use_case_table[AUDIO_USECASE_MAX] = {
     [USECASE_AUDIO_PLAYBACK_DEEP_BUFFER] = "deep-buffer-playback",
     [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = "low-latency-playback",
+    [USECASE_AUDIO_PLAYBACK_WITH_HAPTICS] = "audio-with-haptics-playback",
     [USECASE_AUDIO_PLAYBACK_HIFI] = "hifi-playback",
     [USECASE_AUDIO_PLAYBACK_OFFLOAD] = "compress-offload-playback",
     [USECASE_AUDIO_PLAYBACK_TTS] = "audio-tts-playback",
@@ -1511,7 +1532,8 @@
                                                get_voice_usecase_id_from_list(adev));
             if ((vc_usecase != NULL) &&
                 ((vc_usecase->devices & AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND) ||
-                (usecase->devices == AUDIO_DEVICE_IN_VOICE_CALL))) {
+                 (vc_usecase->devices == AUDIO_DEVICE_OUT_HEARING_AID) ||
+                 (usecase->devices == AUDIO_DEVICE_IN_VOICE_CALL))) {
                 in_snd_device = vc_usecase->in_snd_device;
                 out_snd_device = vc_usecase->out_snd_device;
             }
@@ -1557,8 +1579,13 @@
                         out_device = AUDIO_DEVICE_OUT_TELEPHONY_TX;
                     } else if (voip_usecase) {
                         out_device = voip_usecase->stream.out->devices;
-                    } else if (adev->primary_output) {
+                    } else if (adev->primary_output &&
+                                  !adev->primary_output->standby) {
                         out_device = adev->primary_output->devices;
+                    } else {
+                        /* forcing speaker o/p device to get matching i/p pair
+                           in case o/p is not routed from same primary HAL */
+                        out_device = AUDIO_DEVICE_OUT_SPEAKER;
                     }
                 }
                 in_snd_device = platform_get_input_snd_device(adev->platform,
@@ -2214,14 +2241,19 @@
         }
         ret = 0;
     }
-
+    /* 1) media + voip output routing to handset must route media back to
+          speaker when voip stops.
+       2) trigger voip input to reroute when voip output changes to
+          hearing aid. */
     if (has_voip_usecase ||
             out->devices & AUDIO_DEVICE_OUT_SPEAKER_SAFE) {
         struct listnode *node;
         struct audio_usecase *usecase;
         list_for_each(node, &adev->usecase_list) {
             usecase = node_to_item(node, struct audio_usecase, list);
-            if (usecase->type == PCM_CAPTURE || usecase == uc_info)
+            if ((usecase->type == PCM_CAPTURE &&
+                     usecase->id != USECASE_AUDIO_RECORD_VOIP)
+                || usecase == uc_info)
                 continue;
 
             ALOGD("%s: select_devices at usecase(%d: %s) after removing the usecase(%d: %s)",
@@ -2236,6 +2268,41 @@
     return ret;
 }
 
+struct pcm* pcm_open_prepare_helper(unsigned int snd_card, unsigned int pcm_device_id,
+                                   unsigned int flags, unsigned int pcm_open_retry_count,
+                                   struct pcm_config *config)
+{
+    struct pcm* pcm = NULL;
+
+    while (1) {
+        pcm = pcm_open(snd_card, pcm_device_id, flags, config);
+        if (pcm == NULL || !pcm_is_ready(pcm)) {
+            ALOGE("%s: %s", __func__, pcm_get_error(pcm));
+            if (pcm != NULL) {
+                pcm_close(pcm);
+                pcm = NULL;
+            }
+            if (pcm_open_retry_count-- == 0)
+                return NULL;
+
+            usleep(PROXY_OPEN_WAIT_TIME * 1000);
+            continue;
+        }
+        break;
+    }
+
+    if (pcm_is_ready(pcm)) {
+        int ret = pcm_prepare(pcm);
+        if (ret < 0) {
+            ALOGE("%s: pcm_prepare returned %d", __func__, ret);
+            pcm_close(pcm);
+            pcm = NULL;
+        }
+    }
+
+    return pcm;
+}
+
 int start_output_stream(struct stream_out *out)
 {
     int ret = 0;
@@ -2243,8 +2310,10 @@
     struct audio_device *adev = out->dev;
     bool a2dp_combo = false;
 
-    ALOGV("%s: enter: usecase(%d: %s) devices(%#x)",
-          __func__, out->usecase, use_case_table[out->usecase], out->devices);
+    ALOGV("%s: enter: usecase(%d: %s) %s devices(%#x)",
+          __func__, out->usecase, use_case_table[out->usecase],
+          out->usecase == USECASE_AUDIO_PLAYBACK_WITH_HAPTICS ? "(with haptics)" : "",
+          out->devices);
 
     if (out->card_status == CARD_STATUS_OFFLINE ||
         adev->card_status == CARD_STATUS_OFFLINE) {
@@ -2364,34 +2433,27 @@
             flags |= PCM_MMAP | PCM_NOIRQ;
         }
 
-        while (1) {
-            out->pcm = pcm_open(adev->snd_card, out->pcm_device_id,
-                               flags, &out->config);
-            if (out->pcm == NULL || !pcm_is_ready(out->pcm)) {
-                ALOGE("%s: %s", __func__, pcm_get_error(out->pcm));
-                if (out->pcm != NULL) {
-                    pcm_close(out->pcm);
-                    out->pcm = NULL;
-                }
-                if (pcm_open_retry_count-- == 0) {
-                    ret = -EIO;
-                    goto error_open;
-                }
-                usleep(PROXY_OPEN_WAIT_TIME * 1000);
-                continue;
-            }
-            break;
+        out->pcm = pcm_open_prepare_helper(adev->snd_card, out->pcm_device_id,
+                                       flags, pcm_open_retry_count,
+                                       &(out->config));
+        if (out->pcm == NULL) {
+           ret = -EIO;
+           goto error_open;
         }
-        ALOGV("%s: pcm_prepare", __func__);
-        if (pcm_is_ready(out->pcm)) {
-            ret = pcm_prepare(out->pcm);
-            if (ret < 0) {
-                ALOGE("%s: pcm_prepare returned %d", __func__, ret);
-                pcm_close(out->pcm);
-                out->pcm = NULL;
-                goto error_open;
+
+        if (out->usecase == USECASE_AUDIO_PLAYBACK_WITH_HAPTICS) {
+            if (adev->haptic_pcm != NULL) {
+                pcm_close(adev->haptic_pcm);
+                adev->haptic_pcm = NULL;
             }
+            adev->haptic_pcm = pcm_open_prepare_helper(adev->snd_card,
+                                   adev->haptic_pcm_device_id,
+                                   flags, pcm_open_retry_count,
+                                   &(adev->haptics_config));
+            // failure to open haptics pcm shouldnt stop audio,
+            // so do not close audio pcm in case of error
         }
+
         if (out->realtime) {
             ret = pcm_start(out->pcm);
             if (ret < 0) {
@@ -2402,6 +2464,7 @@
             }
         }
     }
+
     register_out_stream(out);
     audio_streaming_hint_end();
     audio_extn_perf_lock_release();
@@ -2415,13 +2478,17 @@
     // consider a scenario where on pause lower layers are tear down.
     // so on resume, swap mixer control need to be sent only when
     // backend is active, hence rather than sending from enable device
-    // sending it from start of streamtream
+    // sending it from start of stream
 
     platform_set_swap_channels(adev, true);
 
     ALOGV("%s: exit", __func__);
     return 0;
 error_open:
+    if (adev->haptic_pcm) {
+        pcm_close(adev->haptic_pcm);
+        adev->haptic_pcm = NULL;
+    }
     audio_streaming_hint_end();
     audio_extn_perf_lock_release();
     stop_output_stream(out);
@@ -2595,6 +2662,19 @@
             if (out->pcm) {
                 pcm_close(out->pcm);
                 out->pcm = NULL;
+
+                if (out->usecase == USECASE_AUDIO_PLAYBACK_WITH_HAPTICS) {
+                    if (adev->haptic_pcm) {
+                        pcm_close(adev->haptic_pcm);
+                        adev->haptic_pcm = NULL;
+                    }
+
+                    if (adev->haptic_buffer != NULL) {
+                        free(adev->haptic_buffer);
+                        adev->haptic_buffer = NULL;
+                        adev->haptic_buffer_size = 0;
+                    }
+                }
             }
             if (out->usecase == USECASE_AUDIO_PLAYBACK_MMAP) {
                 do_stop = out->playback_started;
@@ -3341,11 +3421,77 @@
             request_out_focus(out, ns);
 
             bool use_mmap = is_mmap_usecase(out->usecase) || out->realtime;
-            if (use_mmap)
+            if (use_mmap) {
                 ret = pcm_mmap_write(out->pcm, (void *)buffer, bytes_to_write);
-            else
-                ret = pcm_write(out->pcm, (void *)buffer, bytes_to_write);
+            } else {
+                if (out->usecase == USECASE_AUDIO_PLAYBACK_WITH_HAPTICS) {
+                    size_t channel_count = audio_channel_count_from_out_mask(out->channel_mask);
+                    size_t bytes_per_sample = audio_bytes_per_sample(out->format);
+                    size_t frame_size = channel_count * bytes_per_sample;
+                    size_t frame_count = bytes_to_write / frame_size;
 
+                    bool force_haptic_path =
+                         property_get_bool("vendor.audio.test_haptic", false);
+
+                    // extract Haptics data from Audio buffer
+                    bool   alloc_haptic_buffer = false;
+                    int    haptic_channel_count = adev->haptics_config.channels;
+                    size_t haptic_frame_size = bytes_per_sample * haptic_channel_count;
+                    size_t audio_frame_size = frame_size - haptic_frame_size;
+                    size_t total_haptic_buffer_size = frame_count * haptic_frame_size;
+
+                    if (adev->haptic_buffer == NULL) {
+                        alloc_haptic_buffer = true;
+                    } else if (adev->haptic_buffer_size < total_haptic_buffer_size) {
+                        free(adev->haptic_buffer);
+                        adev->haptic_buffer_size = 0;
+                        alloc_haptic_buffer = true;
+                    }
+
+                    if (alloc_haptic_buffer) {
+                        adev->haptic_buffer = (uint8_t *)calloc(1, total_haptic_buffer_size);
+                        adev->haptic_buffer_size = total_haptic_buffer_size;
+                    }
+
+                    size_t src_index = 0, aud_index = 0, hap_index = 0;
+                    uint8_t *audio_buffer = (uint8_t *)buffer;
+                    uint8_t *haptic_buffer  = adev->haptic_buffer;
+
+                    // This is required for testing only. This works for stereo data only.
+                    // One channel is fed to audio stream and other to haptic stream for testing.
+                    if (force_haptic_path) {
+                       audio_frame_size = haptic_frame_size = bytes_per_sample;
+                    }
+
+                    for (size_t i = 0; i < frame_count; i++) {
+                        for (size_t j = 0; j < audio_frame_size; j++)
+                            audio_buffer[aud_index++] = audio_buffer[src_index++];
+
+                        for (size_t j = 0; j < haptic_frame_size; j++)
+                            haptic_buffer[hap_index++] = audio_buffer[src_index++];
+                        }
+
+                        // This is required for testing only.
+                        // Discard haptic channel data.
+                        if (force_haptic_path) {
+                            src_index += haptic_frame_size;
+                    }
+
+                    // write to audio pipeline
+                    ret = pcm_write(out->pcm,
+                                    (void *)audio_buffer,
+                                    frame_count * audio_frame_size);
+
+                    // write to haptics pipeline
+                    if (adev->haptic_pcm)
+                        ret = pcm_write(adev->haptic_pcm,
+                                        (void *)adev->haptic_buffer,
+                                        frame_count * haptic_frame_size);
+
+                } else {
+                    ret = pcm_write(out->pcm, (void *)buffer, bytes_to_write);
+                }
+            }
             release_out_focus(out, ns);
         } else {
             LOG_ALWAYS_FATAL("out->pcm is NULL after starting output stream");
@@ -4455,6 +4601,52 @@
     return -ENOSYS;
 }
 
+static void in_update_sink_metadata(struct audio_stream_in *stream,
+                                    const struct sink_metadata *sink_metadata) {
+
+    if (stream == NULL
+            || sink_metadata == NULL
+            || sink_metadata->tracks == NULL) {
+        return;
+    }
+
+    int error = 0;
+    struct stream_in *in = (struct stream_in *)stream;
+    struct audio_device *adev = in->dev;
+    audio_devices_t device = AUDIO_DEVICE_NONE;
+
+    if (sink_metadata->track_count != 0)
+        device = sink_metadata->tracks->dest_device;
+
+    lock_input_stream(in);
+    pthread_mutex_lock(&adev->lock);
+    ALOGV("%s: in->usecase: %d, device: %x", __func__, in->usecase, device);
+
+    if (in->usecase == USECASE_AUDIO_RECORD_AFE_PROXY
+            && device != AUDIO_DEVICE_NONE
+            && adev->voice_tx_output != NULL) {
+        /* Use the rx device from afe-proxy record to route voice call because
+           there is no routing if tx device is on primary hal and rx device
+           is on other hal during voice call. */
+        adev->voice_tx_output->devices = device;
+
+        if (!voice_is_call_state_active(adev)) {
+            if (adev->mode == AUDIO_MODE_IN_CALL) {
+                adev->current_call_output = adev->voice_tx_output;
+                error = voice_start_call(adev);
+                if (error != 0)
+                    ALOGE("%s: start voice call failed %d", __func__, error);
+            }
+        } else {
+            adev->current_call_output = adev->voice_tx_output;
+            voice_update_devices_for_all_voice_usecases(adev);
+        }
+    }
+
+    pthread_mutex_unlock(&adev->lock);
+    pthread_mutex_unlock(&in->lock);
+}
+
 static int adev_open_output_stream(struct audio_hw_device *dev,
                                    audio_io_handle_t handle,
                                    audio_devices_t devices,
@@ -4469,6 +4661,8 @@
     bool is_hdmi = devices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
     bool is_usb_dev = audio_is_usb_out_device(devices) &&
                       (devices != AUDIO_DEVICE_OUT_USB_ACCESSORY);
+    bool force_haptic_path =
+            property_get_bool("vendor.audio.test_haptic", false);
 
     if (is_usb_dev && !is_usb_ready(adev, true /* is_playback */)) {
         return -ENOSYS;
@@ -4476,6 +4670,7 @@
 
     ALOGV("%s: enter: format(%#x) sample_rate(%d) channel_mask(%#x) devices(%#x) flags(%#x)",
           __func__, config->format, config->sample_rate, config->channel_mask, devices, flags);
+
     *stream_out = NULL;
     out = (struct stream_out *)calloc(1, sizeof(struct stream_out));
 
@@ -4806,8 +5001,24 @@
             out->stream.create_mmap_buffer = out_create_mmap_buffer;
             out->stream.get_mmap_position = out_get_mmap_position;
         } else {
-            out->usecase = USECASE_AUDIO_PLAYBACK_LOW_LATENCY;
-            out->config = pcm_config_low_latency;
+            if (config->channel_mask & AUDIO_CHANNEL_HAPTIC_ALL) {
+                out->usecase = USECASE_AUDIO_PLAYBACK_WITH_HAPTICS;
+                adev->haptic_pcm_device_id = platform_get_haptics_pcm_device_id();
+                if (adev->haptic_pcm_device_id < 0) {
+                    ALOGE("%s: Invalid Haptics pcm device id(%d) for the usecase(%d)",
+                          __func__, adev->haptic_pcm_device_id, out->usecase);
+                    ret = -ENOSYS;
+                    goto error_open;
+                }
+                out->config = pcm_config_haptics_audio;
+                if (force_haptic_path)
+                    adev->haptics_config = pcm_config_haptics_audio;
+                else
+                    adev->haptics_config = pcm_config_haptics;
+            } else {
+                out->usecase = USECASE_AUDIO_PLAYBACK_LOW_LATENCY;
+                out->config = pcm_config_low_latency;
+            }
         }
 
         if (config->sample_rate == 0) {
@@ -4815,11 +5026,13 @@
         } else {
             out->sample_rate = config->sample_rate;
         }
+
         if (config->channel_mask == AUDIO_CHANNEL_NONE) {
             out->channel_mask = audio_channel_out_mask_from_count(out->config.channels);
         } else {
             out->channel_mask = config->channel_mask;
         }
+
         if (config->format == AUDIO_FORMAT_DEFAULT)
             out->format = audio_format_from_pcm_format(out->config.format);
         else if (!audio_is_linear_pcm(config->format)) {
@@ -4831,8 +5044,25 @@
         }
 
         out->config.rate = out->sample_rate;
-        out->config.channels =
-                audio_channel_count_from_out_mask(out->channel_mask);
+
+        if (config->channel_mask & AUDIO_CHANNEL_HAPTIC_ALL) {
+             out->config.channels =
+                audio_channel_count_from_out_mask(out->channel_mask &
+                                                  ~AUDIO_CHANNEL_HAPTIC_ALL);
+
+             if (force_haptic_path) {
+                 out->config.channels = 1;
+                 adev->haptics_config.channels = 1;
+             } else {
+                 adev->haptics_config.channels =
+                     audio_channel_count_from_out_mask(out->channel_mask &
+                                                      AUDIO_CHANNEL_HAPTIC_ALL);
+             }
+        } else {
+             out->config.channels =
+                    audio_channel_count_from_out_mask(out->channel_mask);
+        }
+
         if (out->format != audio_format_from_pcm_format(out->config.format)) {
             out->config.format = pcm_format_from_audio_format(out->format);
         }
@@ -5386,6 +5616,7 @@
     in->stream.get_active_microphones = in_get_active_microphones;
     in->stream.set_microphone_direction = in_set_microphone_direction;
     in->stream.set_microphone_field_dimension = in_set_microphone_field_dimension;
+    in->stream.update_sink_metadata = in_update_sink_metadata;
 
     in->device = devices;
     in->source = source;
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index 3fc1577..0026373 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -83,6 +83,7 @@
     USECASE_AUDIO_PLAYBACK_TTS,
     USECASE_AUDIO_PLAYBACK_ULL,
     USECASE_AUDIO_PLAYBACK_MMAP,
+    USECASE_AUDIO_PLAYBACK_WITH_HAPTICS,
 
     /* HFP Use case*/
     USECASE_AUDIO_HFP_SCO,
@@ -394,6 +395,13 @@
 
     void *adm_data;
     void *adm_lib;
+
+    struct pcm_config haptics_config;
+    struct pcm *haptic_pcm;
+    int    haptic_pcm_device_id;
+    uint8_t *haptic_buffer;
+    size_t haptic_buffer_size;
+
     adm_init_t adm_init;
     adm_deinit_t adm_deinit;
     adm_register_input_stream_t adm_register_input_stream;
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 7d35ff5..3602b97 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -1401,6 +1401,11 @@
     return device_id;
 }
 
+int platform_get_haptics_pcm_device_id()
+{
+    return -1;
+}
+
 static int find_index(struct name_to_index * table, int32_t len, const char * name)
 {
     int ret = 0;
@@ -1475,6 +1480,13 @@
     return ret;
 }
 
+void platform_add_external_specific_device(snd_device_t snd_device __unused,
+                                           const char *name __unused,
+                                           unsigned int acdb_id __unused)
+{
+    return;
+}
+
 void platform_add_operator_specific_device(snd_device_t snd_device,
                                            const char *operator,
                                            const char *mixer_path,
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index be2b95e..ae65ef2 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -424,6 +424,11 @@
     return device_id;
 }
 
+int platform_get_haptics_pcm_device_id()
+{
+    return -1;
+}
+
 int platform_get_snd_device_index(char *snd_device_index_name __unused)
 {
     return -ENODEV;
@@ -448,6 +453,13 @@
     return -ENOSYS;
 }
 
+void platform_add_external_specific_device(snd_device_t snd_device __unused,
+                                           const char *name __unused,
+                                           unsigned int acdb_id __unused)
+{
+    return;
+}
+
 void platform_add_operator_specific_device(snd_device_t snd_device __unused,
                                            const char *operator __unused,
                                            const char *mixer_path __unused,
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index f03356b..eb4bb4a 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -101,6 +101,12 @@
     int acdb_id;
 };
 
+struct external_specific_device {
+    struct listnode list;
+    char *usbid;
+    int acdb_id;
+};
+
 #define BE_DAI_NAME_MAX_LENGTH 24
 struct be_dai_name_struct {
     unsigned int be_id;
@@ -114,6 +120,7 @@
 
 static struct listnode operator_info_list;
 static struct listnode *operator_specific_device_table[SND_DEVICE_MAX];
+static struct listnode *external_specific_device_table[SND_DEVICE_MAX];
 
 #define AUDIO_PARAMETER_KEY_AUD_CALDATA "cal_data"
 
@@ -184,6 +191,8 @@
 static int pcm_device_table[AUDIO_USECASE_MAX][2] = {
     [USECASE_AUDIO_PLAYBACK_DEEP_BUFFER] = {DEEP_BUFFER_PCM_DEVICE,
                                             DEEP_BUFFER_PCM_DEVICE},
+    [USECASE_AUDIO_PLAYBACK_WITH_HAPTICS] = {AUDIO_HAPTICS_PCM_DEVICE,
+                                             AUDIO_HAPTICS_PCM_DEVICE},
     [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = {LOWLATENCY_PCM_DEVICE,
                                             LOWLATENCY_PCM_DEVICE},
     [USECASE_AUDIO_PLAYBACK_HIFI] = {MULTIMEDIA2_PCM_DEVICE,
@@ -293,6 +302,7 @@
     [SND_DEVICE_OUT_SPEAKER_SAFE_AND_BT_SCO] = "speaker-safe-and-bt-sco",
     [SND_DEVICE_OUT_SPEAKER_AND_BT_SCO_WB] = "speaker-and-bt-sco-wb",
     [SND_DEVICE_OUT_SPEAKER_SAFE_AND_BT_SCO_WB] = "speaker-safe-and-bt-sco-wb",
+    [SND_DEVICE_OUT_VOICE_HEARING_AID] = "hearing-aid",
 
     /* Capture sound devices */
     [SND_DEVICE_IN_HANDSET_MIC] = "handset-mic",
@@ -372,6 +382,7 @@
     [SND_DEVICE_IN_CAMCORDER_SELFIE_PORTRAIT] = "camcorder-mic",
     [SND_DEVICE_IN_SPEAKER_QMIC_NS] = "quad-mic",
     [SND_DEVICE_IN_SPEAKER_QMIC_AEC_NS] = "quad-mic",
+    [SND_DEVICE_IN_VOICE_HEARING_AID] = "hearing-aid-mic",
 };
 
 static struct audio_effect_config \
@@ -444,6 +455,7 @@
     [SND_DEVICE_OUT_SPEAKER_PROTECTED] = 124,
     [SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED] = 101,
     [SND_DEVICE_OUT_VOICE_SPEAKER_HFP] = ACDB_ID_VOICE_SPEAKER,
+    [SND_DEVICE_OUT_VOICE_HEARING_AID] = 45,
 
     [SND_DEVICE_IN_HANDSET_MIC] = 4,
     [SND_DEVICE_IN_HANDSET_MIC_AEC] = 106,
@@ -521,6 +533,7 @@
     [SND_DEVICE_IN_CAMCORDER_SELFIE_PORTRAIT] = 61,
     [SND_DEVICE_IN_SPEAKER_QMIC_NS] = 129,
     [SND_DEVICE_IN_SPEAKER_QMIC_AEC_NS] = 129,
+    [SND_DEVICE_IN_VOICE_HEARING_AID] = 44,
 };
 
 // Platform specific backend bit width table
@@ -579,6 +592,7 @@
     {TO_NAME_INDEX(SND_DEVICE_OUT_SPEAKER_PROTECTED)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_USB_HEADSET_SPEC)},
+    {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_HEARING_AID)},
 
     /* in */
     {TO_NAME_INDEX(SND_DEVICE_IN_HANDSET_MIC)},
@@ -655,6 +669,8 @@
     {TO_NAME_INDEX(SND_DEVICE_IN_CAMCORDER_SELFIE_LANDSCAPE)},
     {TO_NAME_INDEX(SND_DEVICE_IN_CAMCORDER_SELFIE_INVERT_LANDSCAPE)},
     {TO_NAME_INDEX(SND_DEVICE_IN_CAMCORDER_SELFIE_PORTRAIT)},
+    {TO_NAME_INDEX(SND_DEVICE_IN_VOICE_HEARING_AID)},
+
     /* For legacy xml file parsing */
     {TO_NAME_INDEX(SND_DEVICE_IN_CAMCORDER_MIC)},
     {TO_NAME_INDEX(SND_DEVICE_IN_SPEAKER_QMIC_NS)},
@@ -667,6 +683,7 @@
 static const struct name_to_index usecase_name_index[AUDIO_USECASE_MAX] = {
     {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_DEEP_BUFFER)},
     {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_LOW_LATENCY)},
+    {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_WITH_HAPTICS)},
     {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_HIFI)},
     {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_OFFLOAD)},
     {TO_NAME_INDEX(USECASE_AUDIO_PLAYBACK_TTS)},
@@ -726,6 +743,27 @@
 
 static int init_be_dai_name_table(struct audio_device *adev);
 
+static bool is_usb_snd_dev(snd_device_t snd_device)
+{
+    if (snd_device < SND_DEVICE_IN_BEGIN) {
+        if (snd_device == SND_DEVICE_OUT_USB_HEADSET ||\
+            snd_device == SND_DEVICE_OUT_USB_HEADPHONES ||\
+            snd_device == SND_DEVICE_OUT_VOICE_USB_HEADPHONES ||\
+            snd_device == SND_DEVICE_OUT_VOICE_USB_HEADSET ||\
+            snd_device == SND_DEVICE_OUT_VOICE_TTY_FULL_USB ||\
+            snd_device == SND_DEVICE_OUT_VOICE_TTY_VCO_USB)
+            return true;
+    } else {
+        if (snd_device == SND_DEVICE_IN_USB_HEADSET_MIC ||\
+            snd_device == SND_DEVICE_IN_USB_HEADSET_MIC_AEC ||\
+            snd_device == SND_DEVICE_IN_VOICE_USB_HEADSET_MIC ||\
+            snd_device == SND_DEVICE_IN_UNPROCESSED_USB_HEADSET_MIC ||\
+            snd_device == SND_DEVICE_IN_VOICE_RECOG_USB_HEADSET_MIC)
+            return true;
+    }
+    return false;
+}
+
 static void check_operator()
 {
     char value[PROPERTY_VALUE_MAX];
@@ -815,6 +853,30 @@
     return ret;
 }
 
+static int get_external_specific_device_acdb_id(snd_device_t snd_device)
+{
+    struct external_specific_device *ext_dev;
+    int ret = acdb_device_table[snd_device];
+    char *usbid = NULL;
+    struct listnode *node;
+
+    if (is_usb_snd_dev(snd_device))
+        usbid = audio_extn_usb_usbid();
+
+    if (usbid) {
+        list_for_each(node, external_specific_device_table[snd_device]) {
+            ext_dev = node_to_item(node, struct external_specific_device, list);
+            if (ext_dev->usbid && !strcmp(usbid, ext_dev->usbid)) {
+                ret = ext_dev->acdb_id;
+                break;
+            }
+        }
+
+        free(usbid);
+    }
+    return ret;
+}
+
 static const char *get_operator_specific_device_mixer_path(snd_device_t snd_device)
 {
     struct operator_specific_device *device;
@@ -1246,6 +1308,7 @@
         backend_tag_table[dev] = NULL;
         hw_interface_table[dev] = NULL;
         operator_specific_device_table[dev] = NULL;
+        external_specific_device_table[dev] = NULL;
     }
 
     for (dev = 0; dev < SND_DEVICE_MAX; dev++) {
@@ -1285,6 +1348,7 @@
     backend_tag_table[SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = strdup("speaker-and-bt-a2dp");
     backend_tag_table[SND_DEVICE_OUT_SPEAKER_SAFE_AND_BT_A2DP] = strdup("speaker-safe-and-bt-a2dp");
     backend_tag_table[SND_DEVICE_OUT_USB_HEADSET_SPEC] = strdup("usb-headset");
+    backend_tag_table[SND_DEVICE_OUT_VOICE_HEARING_AID] = strdup("hearing-aid");
 
     hw_interface_table[SND_DEVICE_OUT_HANDSET] = strdup("SLIMBUS_0_RX");
     hw_interface_table[SND_DEVICE_OUT_SPEAKER] = strdup("SLIMBUS_0_RX");
@@ -1333,6 +1397,11 @@
     hw_interface_table[SND_DEVICE_OUT_SPEAKER_AND_BT_SCO_WB] = strdup("SLIMBUS_0_RX-and-SEC_AUX_PCM_RX");
     hw_interface_table[SND_DEVICE_OUT_SPEAKER_SAFE_AND_BT_SCO] = strdup("QUAT_TDM_RX_0-and-SLIMBUS_7_RX"),
     hw_interface_table[SND_DEVICE_OUT_SPEAKER_SAFE_AND_BT_SCO_WB] = strdup("QUAT_TDM_RX_0-and-SLIMBUS_7_RX"),
+    /* So far, primary hal doesn't support hearing aid device.
+       Need snd_device to route voice call and use specific acdb tuning.
+       Also, BT_RX is a virtual port to indicate bluetooth hearing aid. */
+    hw_interface_table[SND_DEVICE_OUT_VOICE_HEARING_AID] = strdup("BT_RX"),
+
     hw_interface_table[SND_DEVICE_IN_USB_HEADSET_MIC] = strdup("USB_AUDIO_TX");
     hw_interface_table[SND_DEVICE_IN_VOICE_USB_HEADSET_MIC] = strdup("USB_AUDIO_TX");
     hw_interface_table[SND_DEVICE_IN_USB_HEADSET_MIC_AEC] =  strdup("USB_AUDIO_TX");
@@ -1400,6 +1469,8 @@
     hw_interface_table[SND_DEVICE_IN_CAMCORDER_SELFIE_LANDSCAPE] = strdup("SLIMBUS_0_TX");
     hw_interface_table[SND_DEVICE_IN_CAMCORDER_SELFIE_INVERT_LANDSCAPE] = strdup("SLIMBUS_0_TX");
     hw_interface_table[SND_DEVICE_IN_CAMCORDER_SELFIE_PORTRAIT] = strdup("SLIMBUS_0_TX");
+    hw_interface_table[SND_DEVICE_IN_VOICE_HEARING_AID] = strdup("SLIMBUS_0_TX");
+
     my_data->max_mic_count = PLATFORM_DEFAULT_MIC_COUNT;
 }
 
@@ -1905,6 +1976,7 @@
     int32_t dev;
     struct operator_info *info_item;
     struct operator_specific_device *device_item;
+    struct external_specific_device *ext_dev;
     struct app_type_entry *ap;
     struct listnode *node;
 
@@ -1931,6 +2003,17 @@
             }
             free(operator_specific_device_table[dev]);
         }
+
+        if (external_specific_device_table[dev]) {
+            while (!list_empty(external_specific_device_table[dev])) {
+                node = list_head(external_specific_device_table[dev]);
+                list_remove(node);
+                ext_dev = node_to_item(node, struct external_specific_device, list);
+                free(ext_dev->usbid);
+                free(ext_dev);
+            }
+            free(external_specific_device_table[dev]);
+        }
     }
 
     if (my_data->snd_card_name)
@@ -2060,6 +2143,11 @@
     return device_id;
 }
 
+int platform_get_haptics_pcm_device_id()
+{
+    return HAPTICS_PCM_DEVICE;
+}
+
 static int find_index(const struct name_to_index * table, int32_t len,
                       const char * name)
 {
@@ -2178,6 +2266,30 @@
 
 }
 
+void platform_add_external_specific_device(snd_device_t snd_device,
+                                           const char *usbid,
+                                           unsigned int acdb_id)
+{
+    struct external_specific_device *device;
+
+    if (external_specific_device_table[snd_device] == NULL) {
+        external_specific_device_table[snd_device] =
+            (struct listnode *)calloc(1, sizeof(struct listnode));
+        list_init(external_specific_device_table[snd_device]);
+    }
+
+    device = (struct external_specific_device *)calloc(1, sizeof(struct external_specific_device));
+
+    device->usbid = strdup(usbid);
+    device->acdb_id = acdb_id;
+
+    list_add_tail(external_specific_device_table[snd_device], &device->list);
+
+    ALOGD("%s: device[%s] usbid[%s] -> acdb_id[%d]", __func__,
+            platform_get_snd_device_name(snd_device), usbid, acdb_id);
+}
+
+
 int platform_set_snd_device_acdb_id(snd_device_t snd_device, unsigned int acdb_id)
 {
     int ret = 0;
@@ -2211,6 +2323,8 @@
 
     if (operator_specific_device_table[snd_device] != NULL)
         return get_operator_specific_device_acdb_id(snd_device);
+    else if (external_specific_device_table[snd_device] != NULL)
+        return get_external_specific_device_acdb_id(snd_device);
     else
         return acdb_device_table[snd_device];
 }
@@ -2893,8 +3007,11 @@
                 snd_device = SND_DEVICE_OUT_VOICE_HANDSET_TMUS;
             else
                 snd_device = SND_DEVICE_OUT_VOICE_HANDSET;
-        } else if (devices & AUDIO_DEVICE_OUT_TELEPHONY_TX)
+        } else if (devices & AUDIO_DEVICE_OUT_TELEPHONY_TX) {
             snd_device = SND_DEVICE_OUT_VOICE_TX;
+        } else if (devices & AUDIO_DEVICE_OUT_HEARING_AID) {
+            snd_device = SND_DEVICE_OUT_VOICE_HEARING_AID;
+        }
 
         if (snd_device != SND_DEVICE_NONE) {
             goto exit;
@@ -3186,6 +3303,8 @@
                     snd_device = SND_DEVICE_IN_VOICE_SPEAKER_MIC;
                 }
             }
+        } else if (out_device & AUDIO_DEVICE_OUT_HEARING_AID) {
+            snd_device = SND_DEVICE_IN_VOICE_HEARING_AID;
         }
     } else if (source == AUDIO_SOURCE_CAMCORDER) {
         if (in_device & AUDIO_DEVICE_IN_BUILTIN_MIC ||
@@ -3702,6 +3821,7 @@
         case USECASE_AUDIO_PLAYBACK_DEEP_BUFFER:
             return DEEP_BUFFER_PLATFORM_DELAY;
         case USECASE_AUDIO_PLAYBACK_LOW_LATENCY:
+        case USECASE_AUDIO_PLAYBACK_WITH_HAPTICS:
             return LOW_LATENCY_PLATFORM_DELAY;
         case USECASE_AUDIO_PLAYBACK_ULL:
             return ULL_PLATFORM_DELAY;
diff --git a/hal/msm8974/platform.h b/hal/msm8974/platform.h
index a885f27..c2beec9 100644
--- a/hal/msm8974/platform.h
+++ b/hal/msm8974/platform.h
@@ -106,6 +106,7 @@
     SND_DEVICE_OUT_VOICE_USB_HEADSET,
     /* Specific snd_devices */
     SND_DEVICE_OUT_USB_HEADSET_SPEC,
+    SND_DEVICE_OUT_VOICE_HEARING_AID,
     SND_DEVICE_OUT_END,
 
     /*
@@ -192,6 +193,7 @@
     SND_DEVICE_IN_CAMCORDER_SELFIE_PORTRAIT,
     SND_DEVICE_IN_SPEAKER_QMIC_NS,
     SND_DEVICE_IN_SPEAKER_QMIC_AEC_NS,
+    SND_DEVICE_IN_VOICE_HEARING_AID,
     SND_DEVICE_IN_END,
 
     SND_DEVICE_MAX = SND_DEVICE_IN_END,
@@ -289,6 +291,9 @@
 #define LOWLATENCY_PCM_DEVICE 15
 #define VOICE_VSID  0x10C01000
 
+#define AUDIO_HAPTICS_PCM_DEVICE 43
+#define HAPTICS_PCM_DEVICE 44
+
 //needs verification
 #define AUDIO_PLAYBACK_VOIP_PCM_DEVICE 5
 #define AUDIO_RECORD_VOIP_PCM_DEVICE 6
diff --git a/hal/platform_api.h b/hal/platform_api.h
index 5e5b662..1d46c31 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -111,6 +111,9 @@
                                            const char *operator,
                                            const char *mixer_path,
                                            unsigned int acdb_id);
+void platform_add_external_specific_device(snd_device_t snd_device,
+                                           const char *name,
+                                           unsigned int acdb_id);
 /* return true if adding entry success
    return false if adding entry fails */
 
@@ -204,6 +207,7 @@
 int platform_get_usb_service_interval(void *platform,
                                       bool playback,
                                       unsigned long *service_interval);
+int platform_get_haptics_pcm_device_id();
 
 /* callback functions from platform to common audio HAL */
 struct stream_in *adev_get_active_input(const struct audio_device *adev);
diff --git a/hal/platform_info.c b/hal/platform_info.c
index 233e120..5159d6d 100644
--- a/hal/platform_info.c
+++ b/hal/platform_info.c
@@ -54,6 +54,7 @@
     SND_DEV,
     MIC_INFO,
     ACDB_METAINFO_KEY,
+    EXTERNAL_DEVICE_SPECIFIC,
 } section_t;
 
 typedef void (* section_process_fn)(const XML_Char **attr);
@@ -73,6 +74,7 @@
 static void process_snd_dev(const XML_Char **attr);
 static void process_mic_info(const XML_Char **attr);
 static void process_acdb_metainfo_key(const XML_Char **attr);
+static void process_external_dev(const XML_Char **attr);
 
 static section_process_fn section_table[] = {
     [ROOT] = process_root,
@@ -89,6 +91,7 @@
     [SND_DEV] = process_snd_dev,
     [MIC_INFO] = process_mic_info,
     [ACDB_METAINFO_KEY] = process_acdb_metainfo_key,
+    [EXTERNAL_DEVICE_SPECIFIC] = process_external_dev,
 };
 
 static section_t section;
@@ -488,6 +491,38 @@
     return;
 }
 
+static void process_external_dev(const XML_Char **attr)
+{
+    snd_device_t snd_device = SND_DEVICE_NONE;
+
+    if (strcmp(attr[0], "name") != 0) {
+        ALOGE("%s: 'name' not found", __func__);
+        goto done;
+    }
+
+    snd_device = platform_get_snd_device_index((char *)attr[1]);
+    if (snd_device < 0) {
+        ALOGE("%s: Device %s in %s not found, no ACDB ID set!",
+              __func__, (char *)attr[3], PLATFORM_INFO_XML_PATH);
+        goto done;
+    }
+
+    if (strcmp(attr[2], "usbid") != 0) {
+        ALOGE("%s: 'usbid' not found", __func__);
+        goto done;
+    }
+
+    if (strcmp(attr[4], "acdb_id") != 0) {
+        ALOGE("%s: 'acdb_id' not found", __func__);
+        goto done;
+    }
+
+    platform_add_external_specific_device(snd_device, (char *)attr[3], atoi((char *)attr[5]));
+
+done:
+    return;
+}
+
 /* platform specific configuration key-value pairs */
 static void process_config_params(const XML_Char **attr)
 {
@@ -900,6 +935,11 @@
             }
             section_process_fn fn = section_table[MIC_INFO];
             fn(attr);
+        } else if (strcmp(tag_name, "external_specific_dev") == 0) {
+            section = EXTERNAL_DEVICE_SPECIFIC;
+        } else if (strcmp(tag_name, "ext_device") == 0) {
+            section_process_fn fn = section_table[section];
+            fn(attr);
         }
         else if (strncmp(tag_name, "aec", strlen("aec")) == 0) {
             if (section != MODULE) {
@@ -958,6 +998,8 @@
         section = ROOT;
     } else if (strcmp(tag_name, "snd_devices") == 0) {
         section = ROOT;
+    } else if (strcmp(tag_name, "external_specific_dev") == 0) {
+        section = ROOT;
     } else if (strcmp(tag_name, "input_snd_device") == 0) {
         section = SND_DEVICES;
     } else if (strcmp(tag_name, "input_snd_device_mic_mapping") == 0) {