audio: sndmonitor

Monitor sound card status and report online/offline
events back to the streams registered with it and
to audio_dev.

Bug: 30075678
Change-Id: Iea74d5c83f7fe92ba4a24f4f4650ce255bdb48e8
diff --git a/hal/Android.mk b/hal/Android.mk
index 6125573..fc2be7d 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -128,6 +128,11 @@
   LOCAL_SRC_FILES += audio_extn/hwdep_cal.c
 endif
 
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_SND_MONITOR)), true)
+    LOCAL_CFLAGS += -DSND_MONITOR_ENABLED
+    LOCAL_SRC_FILES += audio_extn/sndmonitor.c
+endif
+
 LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM)
 
 LOCAL_MODULE_RELATIVE_PATH := hw
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 715284a..5ce3093 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -132,4 +132,18 @@
 void hw_info_append_hw_type(void *hw_info, snd_device_t snd_device,
                              char *device_name);
 #endif /* HW_VARIANTS_ENABLED */
+
+typedef void (* snd_mon_cb)(void * stream, struct str_parms * parms);
+#ifndef SND_MONITOR_ENABLED
+#define audio_extn_snd_mon_init()           (0)
+#define audio_extn_snd_mon_deinit()         (0)
+#define audio_extn_snd_mon_register_listener(stream, cb) (0)
+#define audio_extn_snd_mon_unregister_listener(stream) (0)
+#else
+int audio_extn_snd_mon_init();
+int audio_extn_snd_mon_deinit();
+int audio_extn_snd_mon_register_listener(void *stream, snd_mon_cb cb);
+int audio_extn_snd_mon_unregister_listener(void *stream);
+#endif
+
 #endif /* AUDIO_EXTN_H */
diff --git a/hal/audio_extn/sndmonitor.c b/hal/audio_extn/sndmonitor.c
new file mode 100644
index 0000000..4b6332c
--- /dev/null
+++ b/hal/audio_extn/sndmonitor.c
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "audio_hw_sndmonitor"
+/*#define LOG_NDEBUG 0*/
+#define LOG_NDDEBUG 0
+
+/* monitor sound card, cpe state
+
+   audio_dev registers for a callback from this module in adev_open
+   Each stream in audio_hal registers for a callback in
+   adev_open_*_stream.
+
+   A thread is spawned to poll() on sound card state files in /proc.
+   On observing a sound card state change, this thread invokes the
+   callbacks registered.
+
+   Callbacks are deregistered in adev_close_*_stream and adev_close
+*/
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <cutils/list.h>
+#include <cutils/hashmap.h>
+#include <cutils/log.h>
+#include <cutils/str_parms.h>
+#include <ctype.h>
+
+#include "audio_hw.h"
+#include "audio_extn.h"
+
+//#define MONITOR_DEVICE_EVENTS
+#define CPE_MAGIC_NUM 0x2000
+#define MAX_CPE_SLEEP_RETRY 2
+#define CPE_SLEEP_WAIT 100
+
+#define MAX_SLEEP_RETRY 100
+#define AUDIO_INIT_SLEEP_WAIT 100 /* 100 ms */
+
+#define AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE "ext_audio_device"
+
+typedef enum {
+    audio_event_on,
+    audio_event_off
+} audio_event_status;
+
+typedef struct {
+    int card;
+    int fd;
+    struct listnode node; // membership in sndcards list
+    card_status_t status;
+} sndcard_t;
+
+typedef struct {
+    char * dev;
+    int fd;
+    int status;
+    struct listnode node; // membership in deviceevents list;
+} dev_event_t;
+
+typedef void (* notifyfn)(const void * target, const char * msg);
+
+typedef struct {
+    const void * target;
+    notifyfn notify;
+    struct listnode cards;
+    unsigned int num_cards;
+    struct listnode dev_events;
+    unsigned int num_dev_events;
+    pthread_t monitor_thread;
+    int intpipe[2];
+    Hashmap * listeners; // from stream * -> callback func
+    bool initcheck;
+} sndmonitor_state_t;
+
+static sndmonitor_state_t sndmonitor;
+
+static char * read_state(int fd)
+{
+    struct stat buf;
+    if (fstat(fd, &buf) < 0)
+        return NULL;
+
+    off_t pos = lseek(fd, 0, SEEK_CUR);
+    off_t avail = buf.st_size - pos;
+    if (avail <= 0) {
+        ALOGD("avail %ld", avail);
+        return NULL;
+    }
+
+    char * state = (char *)calloc(avail+1, sizeof(char));
+    if (!state)
+        return NULL;
+
+    ssize_t bytes=read(fd, state, avail);
+    if (bytes <= 0)
+        return NULL;
+
+    // trim trailing whitespace
+    while (bytes && isspace(*(state+bytes-1))) {
+        *(state + bytes - 1) = '\0';
+        --bytes;
+    }
+    lseek(fd, 0, SEEK_SET);
+    return state;
+}
+
+static int add_new_sndcard(int card, int fd)
+{
+    sndcard_t * s = (sndcard_t *)calloc(sizeof(sndcard_t), 1);
+
+    if (!s)
+        return -1;
+
+    s->card = card;
+    s->fd = fd; // dup?
+
+    char * state = read_state(fd);
+    bool online = state && !strcmp(state, "ONLINE");
+
+    ALOGV("card %d initial state %s %d", card, state, online);
+
+    if (state)
+        free(state);
+
+    s->status = online ? CARD_STATUS_ONLINE : CARD_STATUS_OFFLINE;
+    list_add_tail(&sndmonitor.cards, &s->node);
+    return 0;
+}
+
+static int enum_sndcards()
+{
+    const char* cards = "/proc/asound/cards";
+    int tries = 10;
+    char *line = NULL;
+    size_t len = 0;
+    ssize_t bytes_read;
+    char path[128] = {0};
+    char *ptr, *saveptr;
+    int line_no=0;
+    unsigned int num_cards=0, num_cpe=0;
+    FILE *fp;
+    int fd, ret;
+
+    while (--tries) {
+        if ((fp = fopen(cards, "r")) == NULL) {
+            ALOGE("Cannot open %s file to get list of sound cards", cards);
+            usleep(100000);
+            continue;
+        }
+        break;
+    }
+
+    if (!tries)
+        return -ENODEV;
+
+    while ((bytes_read = getline(&line, &len, fp) != -1)) {
+        // skip every other line to to match
+        // the output format of /proc/asound/cards
+        if (line_no++ % 2)
+            continue;
+
+        ptr = strtok_r(line, " [", &saveptr);
+        if (!ptr)
+            continue;
+
+        snprintf(path, sizeof(path), "/proc/asound/card%s/state", ptr);
+        ALOGV("Opening sound card state : %s", path);
+
+        fd = open(path, O_RDONLY);
+        if (fd == -1) {
+            ALOGE("Open %s failed : %s", path, strerror(errno));
+            continue;
+        }
+
+        ret = add_new_sndcard(atoi(ptr), fd);
+        if (ret != 0)
+            continue;
+
+        num_cards++;
+
+        // query cpe state for this card as well
+        tries=MAX_CPE_SLEEP_RETRY;
+        snprintf(path, sizeof(path), "/proc/asound/card%s/cpe0_state", ptr);
+
+        if (access(path, R_OK) < 0) {
+            ALOGW("access %s failed w/ err %s", path, strerror(errno));
+            continue;
+        }
+
+        ALOGV("Open cpe state card state %s", path);
+        while (--tries) {
+            if ((fd = open(path, O_RDONLY)) < 0) {
+                ALOGW("Open cpe state card state failed, retry : %s", path);
+                usleep(CPE_SLEEP_WAIT*1000);
+                continue;
+            }
+            break;
+        }
+
+        if (!tries)
+            continue;
+
+        ret = add_new_sndcard(CPE_MAGIC_NUM+num_cpe, fd);
+        if (ret != 0)
+            continue;
+
+        num_cpe++;
+        num_cards++;
+    }
+    if (line)
+        free(line);
+    fclose(fp);
+    ALOGV("sndmonitor registerer num_cards %d", num_cards);
+    sndmonitor.num_cards = num_cards;
+    return num_cards ? 0 : -1;
+}
+
+static void free_sndcards()
+{
+    while (!list_empty(&sndmonitor.cards)) {
+        struct listnode * n = list_head(&sndmonitor.cards);
+        sndcard_t * s = node_to_item(n, sndcard_t, node);
+        list_remove(n);
+        close(s->fd);
+        free(s);
+    }
+}
+
+static int add_new_dev_event(char * d_name, int fd)
+{
+    dev_event_t * d = (dev_event_t *)calloc(sizeof(dev_event_t), 1);
+
+    if (!d)
+        return -1;
+
+    d->dev = strdup(d_name);
+    d->fd = fd;
+    list_add_tail(&sndmonitor.dev_events, &d->node);
+    return 0;
+}
+
+static int enum_dev_events()
+{
+    const char* events_dir = "/sys/class/switch/";
+    DIR *dp;
+    struct dirent* in_file;
+    int fd;
+    char path[128] = {0};
+    unsigned int num_dev_events = 0;
+
+    if ((dp = opendir(events_dir)) == NULL) {
+        ALOGE("Cannot open switch directory %s err %s",
+              events_dir, strerror(errno));
+        return -1;
+    }
+
+    while ((in_file = readdir(dp)) != NULL) {
+        if (!strstr(in_file->d_name, "qc_"))
+            continue;
+
+        snprintf(path, sizeof(path), "%s/%s/state",
+                 events_dir, in_file->d_name);
+
+        ALOGV("Opening audio dev event state : %s ", path);
+        fd = open(path, O_RDONLY);
+        if (fd == -1) {
+            ALOGE("Open %s failed : %s", path, strerror(errno));
+        } else {
+            if (!add_new_dev_event(in_file->d_name, fd))
+                num_dev_events++;
+        }
+    }
+    closedir(dp);
+    sndmonitor.num_dev_events = num_dev_events;
+    return num_dev_events ? 0 : -1;
+}
+
+static void free_dev_events()
+{
+    while (!list_empty(&sndmonitor.dev_events)) {
+        struct listnode * n = list_head(&sndmonitor.dev_events);
+        dev_event_t * d = node_to_item(n, dev_event_t, node);
+        list_remove(n);
+        close(d->fd);
+        free(d->dev);
+        free(d);
+    }
+}
+
+static int notify(const struct str_parms * params)
+{
+    if (!params)
+        return -1;
+
+    char * str = str_parms_to_str((struct str_parms *)params);
+
+    if (!str)
+        return -1;
+
+    if (sndmonitor.notify)
+        sndmonitor.notify(sndmonitor.target, str);
+
+    ALOGV("%s", str);
+    free(str);
+    return 0;
+}
+
+int on_dev_event(dev_event_t * dev_event)
+{
+    char state_buf[2];
+    if (read(dev_event->fd, state_buf, 1) <= 0)
+        return -1;
+
+    lseek(dev_event->fd, 0, SEEK_SET);
+    state_buf[1]='\0';
+    if (atoi(state_buf) == dev_event->status)
+        return 0;
+
+    dev_event->status = atoi(state_buf);
+
+    struct str_parms * params = str_parms_create();
+
+    if (!params)
+        return -1;
+
+    char val[32] = {0};
+    snprintf(val, sizeof(val), "%s,%s", dev_event->dev,
+             dev_event->status ? "ON" : "OFF");
+
+    if (str_parms_add_str(params, AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE, val) < 0)
+        return -1;
+
+    int ret = notify(params);
+    str_parms_destroy(params);
+    return ret;
+}
+
+bool on_sndcard_state_update(sndcard_t * s)
+{
+    char rd_buf[9]={0};
+    card_status_t status;
+
+    if (read(s->fd, rd_buf, 8) <= 0)
+        return -1;
+
+    rd_buf[8] = '\0';
+    lseek(s->fd, 0, SEEK_SET);
+
+    ALOGV("card num %d, new state %s", s->card, rd_buf);
+
+    bool is_cpe = (s->card >= CPE_MAGIC_NUM);
+    if (strstr(rd_buf, "OFFLINE"))
+        status = CARD_STATUS_OFFLINE;
+    else if (strstr(rd_buf, "ONLINE"))
+        status = CARD_STATUS_ONLINE;
+    else {
+        ALOGE("unknown state");
+        return 0;
+    }
+
+    if (status == s->status) // no change
+        return 0;
+
+    s->status = status;
+
+    struct str_parms * params = str_parms_create();
+
+    if (!params)
+        return -1;
+
+    char val[32] = {0};
+    // cpe actual card num is (card - MAGIC_NUM). so subtract accordingly
+    snprintf(val, sizeof(val), "%d,%s", s->card - (is_cpe ? CPE_MAGIC_NUM : 0),
+                 status == CARD_STATUS_ONLINE ? "ONLINE" : "OFFLINE");
+
+    if (str_parms_add_str(params, is_cpe ? "CPE_STATUS" : "SND_CARD_STATUS",
+                          val) < 0)
+        return -1;
+
+    int ret = notify(params);
+    str_parms_destroy(params);
+    return ret;
+}
+
+void * monitor_thread_loop(void * args __unused)
+{
+    ALOGV("Start threadLoop()");
+    unsigned int num_poll_fds = sndmonitor.num_cards +
+                                sndmonitor.num_dev_events + 1/*pipe*/;
+    struct pollfd * pfd = (struct pollfd *)calloc(sizeof(struct pollfd),
+                                                  num_poll_fds);
+    if (!pfd)
+        return NULL;
+
+    pfd[0].fd = sndmonitor.intpipe[0];
+    pfd[0].events = POLLPRI|POLLIN;
+
+    int i=1;
+    struct listnode *node;
+    list_for_each(node, &sndmonitor.cards) {
+        sndcard_t * s = node_to_item(node, sndcard_t, node);
+        pfd[i].fd = s->fd;
+        pfd[i].events = POLLPRI;
+        ++i;
+    }
+
+    list_for_each(node, &sndmonitor.dev_events) {
+        dev_event_t * d = node_to_item(node, dev_event_t, node);
+        pfd[i].fd = d->fd;
+        pfd[i].events = POLLPRI;
+        ++i;
+    }
+
+    while (1) {
+        if (poll(pfd, num_poll_fds, -1) < 0) {
+            ALOGE("poll() failed w/ err %s", strerror(errno));
+            break;
+        }
+        ALOGV("out of poll()");
+
+#define READY_TO_READ(p) ((p)->revents & (POLLIN|POLLPRI))
+#define ERROR_IN_FD(p) ((p)->revents & (POLLERR|POLLHUP|POLLNVAL))
+
+        // check if requested to exit
+        if (READY_TO_READ(&pfd[0])) {
+            char buf[2]={0};
+            read(pfd[0].fd, buf, 1);
+            if (!strcmp(buf, "Q"))
+                break;
+        } else if (ERROR_IN_FD(&pfd[0])) {
+            // do not consider for poll again
+            pfd[0].fd *= -1;
+        }
+
+        i=1;
+        list_for_each(node, &sndmonitor.cards) {
+            sndcard_t * s = node_to_item(node, sndcard_t, node);
+            if (READY_TO_READ(&pfd[i]))
+                on_sndcard_state_update(s);
+            else if (ERROR_IN_FD(&pfd[i])) {
+                // do not consider for poll again
+                pfd[i].fd *= -1;
+            }
+            ++i;
+        }
+
+        list_for_each(node, &sndmonitor.dev_events) {
+            dev_event_t * d = node_to_item(node, dev_event_t, node);
+            if (READY_TO_READ(&pfd[i]))
+                on_dev_event(d);
+            else if (ERROR_IN_FD(&pfd[i])) {
+                // do not consider for poll again
+                pfd[i].fd *= -1;
+            }
+            ++i;
+        }
+    }
+    return NULL;
+}
+
+// ---- listener static APIs ---- //
+static int hashfn(void * key)
+{
+    return (int)key;
+}
+
+static bool hasheq(void * key1, void *key2)
+{
+    return key1 == key2;
+}
+
+static bool snd_cb(void* key, void* value, void* context)
+{
+    snd_mon_cb cb = (snd_mon_cb)value;
+    cb(key, context);
+    return true;
+}
+
+static void snd_mon_update(const void * target __unused, const char * msg)
+{
+    // target can be used to check if this message is intended for the
+    // recipient or not. (using some statically saved state)
+
+    struct str_parms *parms = str_parms_create_str(msg);
+
+    if (!parms)
+        return;
+
+    hashmapLock(sndmonitor.listeners);
+    hashmapForEach(sndmonitor.listeners, snd_cb, parms);
+    hashmapUnlock(sndmonitor.listeners);
+
+    str_parms_destroy(parms);
+}
+
+static int listeners_init()
+{
+    sndmonitor.listeners = hashmapCreate(5, hashfn, hasheq);
+    if (!sndmonitor.listeners)
+        return -1;
+    return 0;
+}
+
+static int listeners_deinit()
+{
+    // XXX TBD
+    return -1;
+}
+
+static int add_listener(void *stream, snd_mon_cb cb)
+{
+    Hashmap * map = sndmonitor.listeners;
+    hashmapLock(map);
+    hashmapPut(map, stream, cb);
+    hashmapUnlock(map);
+    return 0;
+}
+
+static int del_listener(void * stream)
+{
+    Hashmap * map = sndmonitor.listeners;
+    hashmapLock(map);
+    hashmapRemove(map, stream);
+    hashmapUnlock(map);
+    return 0;
+}
+
+// --- public APIs --- //
+
+int audio_extn_snd_mon_deinit()
+{
+    if (!sndmonitor.initcheck)
+        return -1;
+
+    write(sndmonitor.intpipe[1], "Q", 1);
+    pthread_join(sndmonitor.monitor_thread, (void **) NULL);
+    free_sndcards();
+    free_dev_events();
+    sndmonitor.initcheck = 0;
+    return 0;
+}
+
+int audio_extn_snd_mon_init()
+{
+    sndmonitor.notify = snd_mon_update;
+    sndmonitor.target = NULL; // unused for now
+    list_init(&sndmonitor.cards);
+    list_init(&sndmonitor.dev_events);
+    sndmonitor.initcheck = false;
+
+    if (pipe(sndmonitor.intpipe) < 0)
+        return -ENODEV;
+
+    if (enum_sndcards() < 0)
+        return -ENODEV;
+
+    if (listeners_init() < 0)
+        return -ENODEV;
+
+#ifdef MONITOR_DEVICE_EVENTS
+    enum_dev_events(); // failure here isn't fatal
+#endif
+
+    int ret = pthread_create(&sndmonitor.monitor_thread,
+                             (const pthread_attr_t *) NULL,
+                             monitor_thread_loop, NULL);
+
+    if (ret) {
+        free_sndcards();
+        free_dev_events();
+        close(sndmonitor.intpipe[0]);
+        close(sndmonitor.intpipe[1]);
+        return -ENODEV;
+    }
+    sndmonitor.initcheck = true;
+    return 0;
+}
+
+int audio_extn_snd_mon_register_listener(void *stream, snd_mon_cb cb)
+{
+    if (!sndmonitor.initcheck) {
+        ALOGW("sndmonitor initcheck failed, cannot register");
+        return -1;
+    }
+
+    return add_listener(stream, cb);
+}
+
+int audio_extn_snd_mon_unregister_listener(void * stream)
+{
+    if (!sndmonitor.initcheck) {
+        ALOGW("sndmonitor initcheck failed, cannot deregister");
+        return -1;
+    }
+
+    ALOGV("deregister listener for stream %p ", stream);
+    return del_listener(stream);
+}
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 527e9c5..1e185e5 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -348,6 +348,27 @@
         adev->adm_abandon_focus(adev->adm_data, in->capture_handle);
 }
 
+static int parse_snd_card_status(struct str_parms * parms, int * card,
+                                 card_status_t * status)
+{
+    char value[32]={0};
+    char state[32]={0};
+
+    int ret = str_parms_get_str(parms, "SND_CARD_STATUS", value, sizeof(value));
+
+    if (ret < 0)
+        return -1;
+
+    // sscanf should be okay as value is of max length 32.
+    // same as sizeof state.
+    if (sscanf(value, "%d,%s", card, state) < 2)
+        return -1;
+
+    *status = !strcmp(state, "ONLINE") ? CARD_STATUS_ONLINE :
+                                         CARD_STATUS_OFFLINE;
+    return 0;
+}
+
 __attribute__ ((visibility ("default")))
 bool audio_hw_send_gain_dep_calibration(int level) {
     bool ret_val = false;
@@ -1006,6 +1027,14 @@
     struct audio_device *adev = in->dev;
 
     ALOGV("%s: enter: usecase(%d)", __func__, in->usecase);
+
+    if (in->card_status == CARD_STATUS_OFFLINE ||
+        adev->card_status == CARD_STATUS_OFFLINE) {
+        ALOGW("in->card_status or adev->card_status offline, try again");
+        ret = -EAGAIN;
+        goto error_config;
+    }
+
     in->pcm_device_id = platform_get_pcm_device_id(in->usecase, PCM_CAPTURE);
     if (in->pcm_device_id < 0) {
         ALOGE("%s: Could not find PCM device id for the usecase(%d)",
@@ -1074,7 +1103,6 @@
         ret = pcm_start(in->pcm);
     }
     audio_extn_perf_lock_release();
-
     ALOGV("%s: exit", __func__);
 
     return ret;
@@ -1373,6 +1401,14 @@
 
     ALOGV("%s: enter: usecase(%d: %s) devices(%#x)",
           __func__, out->usecase, use_case_table[out->usecase], out->devices);
+
+    if (out->card_status == CARD_STATUS_OFFLINE ||
+        adev->card_status == CARD_STATUS_OFFLINE) {
+        ALOGW("out->card_status or adev->card_status offline, try again");
+        ret = -EAGAIN;
+        goto error_config;
+    }
+
     out->pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
     if (out->pcm_device_id < 0) {
         ALOGE("%s: Invalid PCM device id(%d) for the usecase(%d)",
@@ -1848,6 +1884,46 @@
     return -ENOSYS;
 }
 
+// note: this call is safe only if the stream_cb is
+// removed first in close_output_stream (as is done now).
+static void out_snd_mon_cb(void * stream, struct str_parms * parms)
+{
+    if (!stream || !parms)
+        return;
+
+    struct stream_out *out = (struct stream_out *)stream;
+    struct audio_device *adev = out->dev;
+
+    card_status_t status;
+    int card;
+    if (parse_snd_card_status(parms, &card, &status) < 0)
+        return;
+
+    pthread_mutex_lock(&adev->lock);
+    bool valid_cb = (card == adev->snd_card);
+    pthread_mutex_unlock(&adev->lock);
+
+    if (!valid_cb)
+        return;
+
+    ALOGV("out_snd_mon_cb for card %d usecase %s", card,
+          use_case_table[out->usecase]);
+
+    lock_output_stream(out);
+    if (out->card_status != status)
+        out->card_status = status;
+    pthread_mutex_unlock(&out->lock);
+
+    // a better solution would be to report error back to AF and let
+    // it put the stream to standby
+    if (status == CARD_STATUS_OFFLINE) {
+        ALOGW("new state == offline, move stream to standby");
+        out_standby(&out->stream.common);
+    }
+
+    return;
+}
+
 #ifdef NO_AUDIO_OUT
 static ssize_t out_write_for_no_output(struct audio_stream_out *stream,
                                        const void *buffer, size_t bytes)
@@ -2256,6 +2332,43 @@
     return 0;
 }
 
+static void in_snd_mon_cb(void * stream, struct str_parms * parms)
+{
+    if (!stream || !parms)
+        return;
+
+    struct stream_in *in = (struct stream_in *)stream;
+    struct audio_device *adev = in->dev;
+
+    card_status_t status;
+    int card;
+    if (parse_snd_card_status(parms, &card, &status) < 0)
+        return;
+
+    pthread_mutex_lock(&adev->lock);
+    bool valid_cb = (card == adev->snd_card);
+    pthread_mutex_unlock(&adev->lock);
+
+    if (!valid_cb)
+        return;
+
+    lock_input_stream(in);
+    if (in->card_status != status)
+        in->card_status = status;
+    pthread_mutex_unlock(&in->lock);
+
+    ALOGW("in_snd_mon_cb for card %d usecase %s, status %s", card,
+          use_case_table[in->usecase],
+          status == CARD_STATUS_OFFLINE ? "offline" : "online");
+
+    // a better solution would be to report error back to AF and let
+    // it put the stream to standby
+    if (status == CARD_STATUS_OFFLINE)
+        in_standby(&in->stream.common);
+
+    return;
+}
+
 static ssize_t in_read(struct audio_stream_in *stream, void *buffer,
                        size_t bytes)
 {
@@ -2651,6 +2764,19 @@
     config->channel_mask = out->stream.common.get_channels(&out->stream.common);
     config->sample_rate = out->stream.common.get_sample_rate(&out->stream.common);
 
+
+    /*
+       By locking output stream before registering, we allow the callback
+       to update stream's state only after stream's initial state is set to
+       adev state.
+    */
+    lock_output_stream(out);
+    audio_extn_snd_mon_register_listener(out, out_snd_mon_cb);
+    pthread_mutex_lock(&adev->lock);
+    out->card_status = adev->card_status;
+    pthread_mutex_unlock(&adev->lock);
+    pthread_mutex_unlock(&out->lock);
+
     *stream_out = &out->stream;
     ALOGV("%s: exit", __func__);
     return 0;
@@ -2669,6 +2795,10 @@
     struct audio_device *adev = out->dev;
 
     ALOGV("%s: enter", __func__);
+
+    // must deregister from sndmonitor first to prevent races
+    // between the callback and close_stream
+    audio_extn_snd_mon_unregister_listener(out);
     out_standby(&stream->common);
     if (out->usecase == USECASE_AUDIO_PLAYBACK_OFFLOAD) {
         destroy_offload_callback_thread(out);
@@ -3010,6 +3140,13 @@
        get sound trigger pcm if present */
     audio_extn_sound_trigger_check_and_get_session(in);
 
+    lock_input_stream(in);
+    audio_extn_snd_mon_register_listener(in, in_snd_mon_cb);
+    pthread_mutex_lock(&adev->lock);
+    in->card_status = adev->card_status;
+    pthread_mutex_unlock(&adev->lock);
+    pthread_mutex_unlock(&in->lock);
+
     *stream_in = &in->stream;
     ALOGV("%s: exit", __func__);
     return 0;
@@ -3025,6 +3162,9 @@
 {
     ALOGV("%s", __func__);
 
+    // must deregister from sndmonitor first to prevent races
+    // between the callback and close_stream
+    audio_extn_snd_mon_unregister_listener(stream);
     in_standby(&stream->common);
     free(stream);
 
@@ -3177,6 +3317,7 @@
     if (!adev)
         return 0;
 
+    audio_extn_snd_mon_unregister_listener(adev);
     pthread_mutex_lock(&adev_init_lock);
 
     if ((--audio_device_ref_count) == 0) {
@@ -3219,6 +3360,29 @@
     }
 }
 
+static void adev_snd_mon_cb(void * stream __unused, struct str_parms * parms)
+{
+    int card;
+    card_status_t status;
+
+    if (!parms)
+        return;
+
+    if (parse_snd_card_status(parms, &card, &status) < 0)
+        return;
+
+    pthread_mutex_lock(&adev->lock);
+    bool valid_cb = (card == adev->snd_card);
+    if (valid_cb) {
+        if (adev->card_status != status) {
+            adev->card_status = status;
+            platform_snd_card_update(adev->platform, status);
+        }
+    }
+    pthread_mutex_unlock(&adev->lock);
+    return;
+}
+
 static int adev_open(const hw_module_t *module, const char *name,
                      hw_device_t **device)
 {
@@ -3285,7 +3449,6 @@
         pthread_mutex_unlock(&adev_init_lock);
         return -EINVAL;
     }
-
     adev->extspk = audio_extn_extspk_init(adev);
     audio_extn_sound_trigger_init(adev);
 
@@ -3390,6 +3553,11 @@
         adev->adm_data = adev->adm_init();
 
     audio_extn_perf_lock_init();
+    audio_extn_snd_mon_init();
+    pthread_mutex_lock(&adev->lock);
+    audio_extn_snd_mon_register_listener(NULL, adev_snd_mon_cb);
+    adev->card_status = CARD_STATUS_ONLINE;
+    pthread_mutex_unlock(&adev->lock);
 
     ALOGD("%s: exit", __func__);
     return 0;
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index b586c57..55c3be4 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -51,6 +51,11 @@
 #define MAX_SUPPORTED_CHANNEL_MASKS 2
 #define DEFAULT_HDMI_OUT_CHANNELS   2
 
+typedef enum card_status_t {
+    CARD_STATUS_OFFLINE,
+    CARD_STATUS_ONLINE
+} card_status_t;
+
 /* These are the supported use cases by the hardware.
  * Each usecase is mapped to a specific PCM device.
  * Refer to pcm_device_table[].
@@ -181,6 +186,7 @@
     int af_period_multiplier;
     bool routing_change;
     struct audio_device *dev;
+    card_status_t card_status;
 };
 
 struct stream_in {
@@ -208,6 +214,7 @@
     bool routing_change;
     struct audio_device *dev;
     audio_format_t format;
+    card_status_t card_status;
 };
 
 typedef enum {
@@ -272,6 +279,8 @@
     void *platform;
     void *extspk;
 
+    card_status_t card_status;
+
     void *visualizer_lib;
     int (*visualizer_start_output)(audio_io_handle_t, int);
     int (*visualizer_stop_output)(audio_io_handle_t, int);
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index a6d9789..6179d01 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -2208,3 +2208,9 @@
     }
     return 0;
 }
+
+int platform_snd_card_update(void *platform __unsed,
+                             card_status_t status __unused)
+{
+    return -1;
+}
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index bd12e93..b8efd28 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1112,3 +1112,9 @@
 {
     return 0;
 }
+
+int platform_snd_card_update(void *platform __unsed,
+                             card_status_t status __unused)
+{
+    return -1;
+}
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index e4bdbdc..441e2a1 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -105,6 +105,7 @@
 typedef void (*acdb_send_voice_cal_t)(int, int);
 typedef int (*acdb_reload_vocvoltable_t)(int);
 typedef int (*acdb_send_gain_dep_cal_t)(int, int, int, int, int);
+typedef int (*acdb_send_custom_top_t) (void);
 
 /* Audio calibration related functions */
 struct platform_data {
@@ -119,11 +120,21 @@
     bool speaker_lr_swap;
 
     void *acdb_handle;
+#if defined (PLATFORM_MSM8994) || (PLATFORM_MSM8996)
+    acdb_init_v2_cvd_t acdb_init;
+#elif defined (PLATFORM_MSM8084)
+    acdb_init_v2_t acdb_init;
+#else
+    acdb_init_t acdb_init;
+#endif
     acdb_deallocate_t          acdb_deallocate;
     acdb_send_audio_cal_t      acdb_send_audio_cal;
     acdb_send_voice_cal_t      acdb_send_voice_cal;
     acdb_reload_vocvoltable_t  acdb_reload_vocvoltable;
     acdb_send_gain_dep_cal_t   acdb_send_gain_dep_cal;
+    acdb_send_custom_top_t     acdb_send_custom_top;
+    bool acdb_initialized;
+
     struct csd_data *csd;
     char ec_ref_mixer_path[64];
 
@@ -946,6 +957,39 @@
     return;
 }
 
+static int platform_acdb_init(void *platform)
+{
+    struct platform_data *my_data = (struct platform_data *)platform;
+    struct audio_device *adev = my_data->adev;
+
+    if (!my_data->acdb_init) {
+        ALOGE("%s: no acdb_init fn provided", __func__);
+        return -1;
+    }
+
+    if (my_data->acdb_initialized) {
+        ALOGW("acdb is already initialized");
+        return 0;
+    }
+
+#if defined (PLATFORM_MSM8994) || (PLATFORM_MSM8996)
+    char *cvd_version = calloc(1, MAX_CVD_VERSION_STRING_SIZE);
+    if (!cvd_version)
+        ALOGE("failed to allocate cvd_version");
+    else {
+        get_cvd_version(cvd_version, adev);
+        my_data->acdb_init((char *)my_data->snd_card_name, cvd_version, 0);
+        free(cvd_version);
+    }
+#elif defined (PLATFORM_MSM8084)
+    my_data->acdb_init((char *)my_data->snd_card_name);
+#else
+    my_data->acdb_init();
+#endif
+    my_data->acdb_initialized = true;
+    return 0;
+}
+
 void *platform_init(struct audio_device *adev)
 {
     char value[PROPERTY_VALUE_MAX];
@@ -1198,39 +1242,38 @@
         acdb_init_v2_cvd_t acdb_init;
         acdb_init = (acdb_init_v2_cvd_t)dlsym(my_data->acdb_handle,
                                               "acdb_loader_init_v2");
-        if (acdb_init == NULL) {
-            ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__, dlerror());
-            goto acdb_init_fail;
-        }
+        if (acdb_init == NULL)
+            ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__,
+                  dlerror());
 
-        cvd_version = calloc(1, MAX_CVD_VERSION_STRING_SIZE);
-        get_cvd_version(cvd_version, adev);
-        if (!cvd_version)
-            ALOGE("failed to allocate cvd_version");
-        else
-            acdb_init((char *)snd_card_name, cvd_version, 0);
-        free(cvd_version);
 #elif defined (PLATFORM_MSM8084)
         acdb_init_v2_t acdb_init;
         acdb_init = (acdb_init_v2_t)dlsym(my_data->acdb_handle,
                                           "acdb_loader_init_v2");
-        if (acdb_init == NULL) {
-            ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__, dlerror());
-            goto acdb_init_fail;
-        }
-        acdb_init((char *)snd_card_name);
+        if (acdb_init == NULL)
+            ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__,
+                  dlerror());
+
 #else
         acdb_init_t acdb_init;
         acdb_init = (acdb_init_t)dlsym(my_data->acdb_handle,
                                                     "acdb_loader_init_ACDB");
         if (acdb_init == NULL)
-            ALOGE("%s: dlsym error %s for acdb_loader_init_ACDB", __func__, dlerror());
-        else
-            acdb_init();
+            ALOGE("%s: dlsym error %s for acdb_loader_init_ACDB", __func__,
+                  dlerror());
 #endif
-    }
+        my_data->acdb_init = acdb_init;
 
-acdb_init_fail:
+        my_data->acdb_send_custom_top = (acdb_send_custom_top_t)
+                                        dlsym(my_data->acdb_handle,
+                                              "acdb_loader_send_common_custom_topology");
+
+        if (!my_data->acdb_send_custom_top)
+            ALOGE("%s: Could not find the symbol acdb_get_default_app_type from %s",
+                  __func__, LIB_ACDB_LOADER);
+
+        platform_acdb_init(my_data);
+    }
 
     audio_extn_spkr_prot_init(adev);
 
@@ -2783,3 +2826,15 @@
 
     return num_gain_tbl_entry;
 }
+
+int platform_snd_card_update(void *platform, card_status_t status)
+{
+    struct platform_data *my_data = (struct platform_data *)platform;
+    struct audio_device *adev = my_data->adev;
+
+    if (status == CARD_STATUS_ONLINE) {
+        if (my_data->acdb_send_custom_top)
+            my_data->acdb_send_custom_top();
+    }
+    return 0;
+}
diff --git a/hal/platform_api.h b/hal/platform_api.h
index c813793..41e600e 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -26,6 +26,8 @@
     uint32_t level;
 };
 
+enum card_status_t;
+
 void *platform_init(struct audio_device *adev);
 void platform_deinit(void *platform);
 const char *platform_get_snd_device_name(snd_device_t snd_device);
@@ -109,4 +111,5 @@
 bool platform_check_and_set_capture_backend_cfg(struct audio_device* adev,
                    struct audio_usecase *usecase, snd_device_t snd_device);
 
+int platform_snd_card_update(void *platform, enum card_status_t status);
 #endif // AUDIO_PLATFORM_API_H