Merge "Add the AVRCP Device class for the AVRCP Refactor"
diff --git a/audio_a2dp_hw/test/audio_a2dp_hw_test.cc b/audio_a2dp_hw/test/audio_a2dp_hw_test.cc
index 53faa6a..8fcbae5 100644
--- a/audio_a2dp_hw/test/audio_a2dp_hw_test.cc
+++ b/audio_a2dp_hw/test/audio_a2dp_hw_test.cc
@@ -36,6 +36,10 @@
       return 176400;
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
       return 192000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+      return 16000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
+      return 24000;
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       break;
   }
diff --git a/audio_hearing_aid_hw/Android.bp b/audio_hearing_aid_hw/Android.bp
new file mode 100644
index 0000000..229fc25
--- /dev/null
+++ b/audio_hearing_aid_hw/Android.bp
@@ -0,0 +1,51 @@
+cc_defaults {
+    name: "audio_hearing_aid_hw_defaults",
+    defaults: ["fluoride_defaults"],
+    include_dirs: [
+        "system/bt",
+        "system/bt/include",
+        "system/bt/audio_hearing_aid_hw/include",
+    ]
+}
+
+// Audio A2DP shared library for target
+// ========================================================
+cc_library {
+    name: "audio.hearing_aid.default",
+    defaults: ["audio_hearing_aid_hw_defaults"],
+    relative_install_path: "hw",
+    srcs: [
+        "src/audio_hearing_aid_hw.cc",
+        "src/audio_hearing_aid_hw_utils.cc",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: ["libosi"],
+}
+
+cc_library_static {
+    name: "libaudio-hearing-aid-hw-utils",
+    defaults: ["audio_hearing_aid_hw_defaults"],
+    srcs: [
+        "src/audio_hearing_aid_hw_utils.cc",
+    ],
+}
+
+// Audio A2DP library unit tests for target and host
+// ========================================================
+cc_test {
+    name: "net_test_audio_hearing_aid_hw",
+    test_suites: ["device-tests"],
+    defaults: ["audio_hearing_aid_hw_defaults"],
+    srcs: [
+        "test/audio_hearing_aid_hw_test.cc",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: [
+        "audio.hearing_aid.default",
+        "libosi",
+    ],
+}
diff --git a/audio_hearing_aid_hw/include/audio_hearing_aid_hw.h b/audio_hearing_aid_hw/include/audio_hearing_aid_hw.h
new file mode 100644
index 0000000..56d8576
--- /dev/null
+++ b/audio_hearing_aid_hw/include/audio_hearing_aid_hw.h
@@ -0,0 +1,150 @@
+/******************************************************************************
+ *
+ *  Copyright 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.
+ *
+ ******************************************************************************/
+
+/*****************************************************************************
+ *
+ *  Filename:      audio_hearing_aid_hw.h
+ *
+ *  Description:
+ *
+ *****************************************************************************/
+
+#ifndef AUDIO_HEARING_AID_HW_H
+#define AUDIO_HEARING_AID_HW_H
+
+#include <stdint.h>
+
+#include <hardware/bt_av.h>
+
+/*****************************************************************************
+ *  Constants & Macros
+ *****************************************************************************/
+
+#define HEARING_AID_AUDIO_HARDWARE_INTERFACE "audio.hearing_aid"
+#define HEARING_AID_CTRL_PATH "/data/misc/bluedroid/.hearing_aid_ctrl"
+#define HEARING_AID_DATA_PATH "/data/misc/bluedroid/.hearing_aid_data"
+
+// AUDIO_STREAM_OUTPUT_BUFFER_SZ controls the size of the audio socket buffer.
+// If one assumes the write buffer is always full during normal BT playback,
+// then increasing this value increases our playback latency.
+//
+// FIXME: The BT HAL should consume data at a constant rate.
+// AudioFlinger assumes that the HAL draws data at a constant rate, which is
+// true for most audio devices; however, the BT engine reads data at a variable
+// rate (over the short term), which confuses both AudioFlinger as well as
+// applications which deliver data at a (generally) fixed rate.
+//
+// 20 * 512 is not sufficient to smooth the variability for some BT devices,
+// resulting in mixer sleep and throttling. We increase this to 28 * 512 to help
+// reduce the effect of variable data consumption.
+#define AUDIO_STREAM_OUTPUT_BUFFER_SZ (28 * 512)
+#define AUDIO_STREAM_CONTROL_OUTPUT_BUFFER_SZ 256
+
+// AUDIO_STREAM_OUTPUT_BUFFER_PERIODS controls how the socket buffer is divided
+// for AudioFlinger data delivery. The AudioFlinger mixer delivers data in
+// chunks of AUDIO_STREAM_OUTPUT_BUFFER_SZ / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS.
+// If the number of periods is 2, the socket buffer represents "double
+// buffering" of the AudioFlinger mixer buffer.
+//
+// In general, AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * 16 * 4 should be a divisor
+// of AUDIO_STREAM_OUTPUT_BUFFER_SZ.
+//
+// These values should be chosen such that
+//
+// AUDIO_STREAM_BUFFER_SIZE * 1000 / (AUDIO_STREAM_OUTPUT_BUFFER_PERIODS
+//         * AUDIO_STREAM_DEFAULT_RATE * 4) > 20 (ms)
+//
+// to avoid introducing the FastMixer in AudioFlinger. Using the FastMixer
+// results in unnecessary latency and CPU overhead for Bluetooth.
+#define AUDIO_STREAM_OUTPUT_BUFFER_PERIODS 2
+
+#define AUDIO_SKT_DISCONNECTED (-1)
+
+typedef enum {
+  HEARING_AID_CTRL_CMD_NONE,
+  HEARING_AID_CTRL_CMD_CHECK_READY,
+  HEARING_AID_CTRL_CMD_START,
+  HEARING_AID_CTRL_CMD_STOP,
+  HEARING_AID_CTRL_CMD_SUSPEND,
+  HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG,
+  HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG,
+  HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG,
+  HEARING_AID_CTRL_CMD_OFFLOAD_START,
+} tHEARING_AID_CTRL_CMD;
+
+typedef enum {
+  HEARING_AID_CTRL_ACK_SUCCESS,
+  HEARING_AID_CTRL_ACK_FAILURE,
+  HEARING_AID_CTRL_ACK_INCALL_FAILURE, /* Failure when in Call*/
+  HEARING_AID_CTRL_ACK_UNSUPPORTED
+} tHEARING_AID_CTRL_ACK;
+
+typedef uint32_t tHA_SAMPLE_RATE;
+typedef uint8_t tHA_CHANNEL_COUNT;
+
+/*****************************************************************************
+ *  Type definitions for callback functions
+ *****************************************************************************/
+
+/*****************************************************************************
+ *  Type definitions and return values
+ *****************************************************************************/
+
+/*****************************************************************************
+ *  Extern variables and functions
+ *****************************************************************************/
+
+/*****************************************************************************
+ *  Functions
+ *****************************************************************************/
+
+// Computes the Audio Hearing Aid HAL output buffer size.
+// |codec_sample_rate| is the sample rate of the output stream.
+// |codec_bits_per_sample| is the number of bits per sample of the output
+// stream.
+// |codec_channel_mode| is the channel mode of the output stream.
+//
+// The buffer size is computed by using the following formula:
+//
+// AUDIO_STREAM_OUTPUT_BUFFER_SIZE =
+//    (TIME_PERIOD_MS * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS *
+//     SAMPLE_RATE_HZ * NUMBER_OF_CHANNELS * (BITS_PER_SAMPLE / 8)) / 1000
+//
+// AUDIO_STREAM_OUTPUT_BUFFER_PERIODS controls how the socket buffer is
+// divided for AudioFlinger data delivery. The AudioFlinger mixer delivers
+// data in chunks of
+// (AUDIO_STREAM_OUTPUT_BUFFER_SIZE / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS) .
+// If the number of periods is 2, the socket buffer represents "double
+// buffering" of the AudioFlinger mixer buffer.
+//
+// Furthermore, the AudioFlinger expects the buffer size to be a multiple
+// of 16 frames.
+//
+// NOTE: Currently, the computation uses the conservative 20ms time period.
+//
+// Returns the computed buffer size. If any of the input parameters is
+// invalid, the return value is the default |AUDIO_STREAM_OUTPUT_BUFFER_SZ|.
+extern size_t audio_ha_hw_stream_compute_buffer_size(
+    btav_a2dp_codec_sample_rate_t codec_sample_rate,
+    btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample,
+    btav_a2dp_codec_channel_mode_t codec_channel_mode);
+
+// Returns a string representation of |event|.
+extern const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event);
+
+#endif /* AUDIO_HEARING_AID_HW_H */
diff --git a/audio_hearing_aid_hw/src/audio_hearing_aid_hw.cc b/audio_hearing_aid_hw/src/audio_hearing_aid_hw.cc
new file mode 100644
index 0000000..b3a8201
--- /dev/null
+++ b/audio_hearing_aid_hw/src/audio_hearing_aid_hw.cc
@@ -0,0 +1,1875 @@
+/******************************************************************************
+ *
+ *  Copyright 2018 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.
+ *
+ ******************************************************************************/
+
+/* Implements hal for bluedroid ha audio device */
+
+#define LOG_TAG "bt_hearing_aid_hw"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <mutex>
+
+#include <hardware/audio.h>
+#include <hardware/hardware.h>
+#include <system/audio.h>
+
+#include "osi/include/hash_map_utils.h"
+#include "osi/include/log.h"
+#include "osi/include/osi.h"
+#include "osi/include/socket_utils/sockets.h"
+
+#include "audio_hearing_aid_hw.h"
+
+/*****************************************************************************
+ *  Constants & Macros
+ *****************************************************************************/
+
+#define CTRL_CHAN_RETRY_COUNT 3
+#define USEC_PER_SEC 1000000L
+#define SOCK_SEND_TIMEOUT_MS 2000 /* Timeout for sending */
+#define SOCK_RECV_TIMEOUT_MS 5000 /* Timeout for receiving */
+
+// set WRITE_POLL_MS to 0 for blocking sockets, nonzero for polled non-blocking
+// sockets
+#define WRITE_POLL_MS 20
+
+#define FNLOG() LOG_VERBOSE(LOG_TAG, "%s", __func__);
+#define DEBUG(fmt, ...) \
+  LOG_VERBOSE(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+#define INFO(fmt, ...) LOG_INFO(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+#define WARN(fmt, ...) LOG_WARN(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+#define ERROR(fmt, ...) LOG_ERROR(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+
+#define ASSERTC(cond, msg, val)                                           \
+  if (!(cond)) {                                                          \
+    ERROR("### ASSERT : %s line %d %s (%d) ###", __FILE__, __LINE__, msg, \
+          val);                                                           \
+  }
+
+/*****************************************************************************
+ *  Local type definitions
+ *****************************************************************************/
+
+typedef enum {
+  AUDIO_HA_STATE_STARTING,
+  AUDIO_HA_STATE_STARTED,
+  AUDIO_HA_STATE_STOPPING,
+  AUDIO_HA_STATE_STOPPED,
+  /* need explicit set param call to resume (suspend=false) */
+  AUDIO_HA_STATE_SUSPENDED,
+  AUDIO_HA_STATE_STANDBY /* allows write to autoresume */
+} ha_state_t;
+
+struct ha_stream_in;
+struct ha_stream_out;
+
+struct ha_audio_device {
+  // Important: device must be first as an audio_hw_device* may be cast to
+  // ha_audio_device* when the type is implicitly known.
+  struct audio_hw_device device;
+  std::recursive_mutex* mutex;  // See note below on mutex acquisition order.
+  struct ha_stream_in* input;
+  struct ha_stream_out* output;
+};
+
+struct ha_config {
+  uint32_t rate;
+  uint32_t channel_mask;
+  bool is_stereo_to_mono;  // True if fetching Stereo and mixing into Mono
+  int format;
+};
+
+/* move ctrl_fd outside output stream and keep open until HAL unloaded ? */
+
+struct ha_stream_common {
+  std::recursive_mutex* mutex;  // See note below on mutex acquisition order.
+  int ctrl_fd;
+  int audio_fd;
+  size_t buffer_sz;
+  struct ha_config cfg;
+  ha_state_t state;
+};
+
+struct ha_stream_out {
+  struct audio_stream_out stream;
+  struct ha_stream_common common;
+  uint64_t frames_presented;  // frames written, never reset
+  uint64_t frames_rendered;   // frames written, reset on standby
+};
+
+struct ha_stream_in {
+  struct audio_stream_in stream;
+  struct ha_stream_common common;
+};
+
+/*
+ * Mutex acquisition order:
+ *
+ * The ha_audio_device (adev) mutex must be acquired before
+ * the ha_stream_common (out or in) mutex.
+ *
+ * This may differ from other audio HALs.
+ */
+
+/*****************************************************************************
+ *  Static variables
+ *****************************************************************************/
+
+/*****************************************************************************
+ *  Static functions
+ *****************************************************************************/
+
+static size_t out_get_buffer_size(const struct audio_stream* stream);
+
+/*****************************************************************************
+ *  Externs
+ *****************************************************************************/
+
+/*****************************************************************************
+ *  Functions
+ *****************************************************************************/
+static void ha_open_ctrl_path(struct ha_stream_common* common);
+
+/*****************************************************************************
+ *   Miscellaneous helper functions
+ *****************************************************************************/
+
+/* logs timestamp with microsec precision
+   pprev is optional in case a dedicated diff is required */
+static void ts_log(UNUSED_ATTR const char* tag, UNUSED_ATTR int val,
+                   struct timespec* pprev_opt) {
+  struct timespec now;
+  static struct timespec prev = {0, 0};
+  unsigned long long now_us;
+  unsigned long long diff_us;
+
+  clock_gettime(CLOCK_MONOTONIC, &now);
+
+  now_us = now.tv_sec * USEC_PER_SEC + now.tv_nsec / 1000;
+
+  if (pprev_opt) {
+    diff_us = (now.tv_sec - prev.tv_sec) * USEC_PER_SEC +
+              (now.tv_nsec - prev.tv_nsec) / 1000;
+    *pprev_opt = now;
+    DEBUG("[%s] ts %08lld, *diff %08lld, val %d", tag, now_us, diff_us, val);
+  } else {
+    diff_us = (now.tv_sec - prev.tv_sec) * USEC_PER_SEC +
+              (now.tv_nsec - prev.tv_nsec) / 1000;
+    prev = now;
+    DEBUG("[%s] ts %08lld, diff %08lld, val %d", tag, now_us, diff_us, val);
+  }
+}
+
+static int calc_audiotime_usec(struct ha_config cfg, int bytes) {
+  int chan_count = audio_channel_count_from_out_mask(cfg.channel_mask);
+  int bytes_per_sample;
+
+  switch (cfg.format) {
+    case AUDIO_FORMAT_PCM_8_BIT:
+      bytes_per_sample = 1;
+      break;
+    case AUDIO_FORMAT_PCM_16_BIT:
+      bytes_per_sample = 2;
+      break;
+    case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+      bytes_per_sample = 3;
+      break;
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+      bytes_per_sample = 4;
+      break;
+    case AUDIO_FORMAT_PCM_32_BIT:
+      bytes_per_sample = 4;
+      break;
+    default:
+      ASSERTC(false, "unsupported sample format", cfg.format);
+      bytes_per_sample = 2;
+      break;
+  }
+
+  return (
+      int)(((int64_t)bytes * (USEC_PER_SEC / (chan_count * bytes_per_sample))) /
+           cfg.rate);
+}
+
+/*****************************************************************************
+ *
+ *   bluedroid stack adaptation
+ *
+ ****************************************************************************/
+
+static int skt_connect(const char* path, size_t buffer_sz) {
+  int ret;
+  int skt_fd;
+  int len;
+
+  INFO("connect to %s (sz %zu)", path, buffer_sz);
+
+  skt_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+
+  if (osi_socket_local_client_connect(
+          skt_fd, path, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM) < 0) {
+    ERROR("failed to connect (%s)", strerror(errno));
+    close(skt_fd);
+    return -1;
+  }
+
+  len = buffer_sz;
+  ret =
+      setsockopt(skt_fd, SOL_SOCKET, SO_SNDBUF, (char*)&len, (int)sizeof(len));
+  if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+  ret =
+      setsockopt(skt_fd, SOL_SOCKET, SO_RCVBUF, (char*)&len, (int)sizeof(len));
+  if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+  /* Socket send/receive timeout value */
+  struct timeval tv;
+  tv.tv_sec = SOCK_SEND_TIMEOUT_MS / 1000;
+  tv.tv_usec = (SOCK_SEND_TIMEOUT_MS % 1000) * 1000;
+
+  ret = setsockopt(skt_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
+  if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+  tv.tv_sec = SOCK_RECV_TIMEOUT_MS / 1000;
+  tv.tv_usec = (SOCK_RECV_TIMEOUT_MS % 1000) * 1000;
+
+  ret = setsockopt(skt_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+  if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+  INFO("connected to stack fd = %d", skt_fd);
+
+  return skt_fd;
+}
+
+static int skt_read(int fd, void* p, size_t len) {
+  ssize_t read;
+
+  FNLOG();
+
+  ts_log("skt_read recv", len, NULL);
+
+  OSI_NO_INTR(read = recv(fd, p, len, MSG_NOSIGNAL));
+  if (read == -1) ERROR("read failed with errno=%d\n", errno);
+
+  return (int)read;
+}
+
+static int skt_write(int fd, const void* p, size_t len) {
+  ssize_t sent;
+  FNLOG();
+
+  ts_log("skt_write", len, NULL);
+
+  if (WRITE_POLL_MS == 0) {
+    // do not poll, use blocking send
+    OSI_NO_INTR(sent = send(fd, p, len, MSG_NOSIGNAL));
+    if (sent == -1) ERROR("write failed with error(%s)", strerror(errno));
+
+    return (int)sent;
+  }
+
+  // use non-blocking send, poll
+  int ms_timeout = SOCK_SEND_TIMEOUT_MS;
+  size_t count = 0;
+  while (count < len) {
+    OSI_NO_INTR(sent = send(fd, p, len - count, MSG_NOSIGNAL | MSG_DONTWAIT));
+    if (sent == -1) {
+      if (errno != EAGAIN && errno != EWOULDBLOCK) {
+        ERROR("write failed with error(%s)", strerror(errno));
+        return -1;
+      }
+      if (ms_timeout >= WRITE_POLL_MS) {
+        usleep(WRITE_POLL_MS * 1000);
+        ms_timeout -= WRITE_POLL_MS;
+        continue;
+      }
+      WARN("write timeout exceeded, sent %zu bytes", count);
+      return -1;
+    }
+    count += sent;
+    p = (const uint8_t*)p + sent;
+  }
+  return (int)count;
+}
+
+static int skt_disconnect(int fd) {
+  INFO("fd %d", fd);
+
+  if (fd != AUDIO_SKT_DISCONNECTED) {
+    shutdown(fd, SHUT_RDWR);
+    close(fd);
+  }
+  return 0;
+}
+
+/*****************************************************************************
+ *
+ *  AUDIO CONTROL PATH
+ *
+ ****************************************************************************/
+
+static int ha_ctrl_receive(struct ha_stream_common* common, void* buffer,
+                           size_t length) {
+  ssize_t ret;
+  int i;
+
+  for (i = 0;; i++) {
+    OSI_NO_INTR(ret = recv(common->ctrl_fd, buffer, length, MSG_NOSIGNAL));
+    if (ret > 0) {
+      break;
+    }
+    if (ret == 0) {
+      ERROR("receive control data failed: peer closed");
+      break;
+    }
+    if (errno != EWOULDBLOCK && errno != EAGAIN) {
+      ERROR("receive control data failed: error(%s)", strerror(errno));
+      break;
+    }
+    if (i == (CTRL_CHAN_RETRY_COUNT - 1)) {
+      ERROR("receive control data failed: max retry count");
+      break;
+    }
+    INFO("receive control data failed (%s), retrying", strerror(errno));
+  }
+  if (ret <= 0) {
+    skt_disconnect(common->ctrl_fd);
+    common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+  }
+  return ret;
+}
+
+// Sends control info for stream |common|. The data to send is stored in
+// |buffer| and has size |length|.
+// On success, returns the number of octets sent, otherwise -1.
+static int ha_ctrl_send(struct ha_stream_common* common, const void* buffer,
+                        size_t length) {
+  ssize_t sent;
+  size_t remaining = length;
+  int i;
+
+  if (length == 0) return 0;  // Nothing to do
+
+  for (i = 0;; i++) {
+    OSI_NO_INTR(sent = send(common->ctrl_fd, buffer, remaining, MSG_NOSIGNAL));
+    if (sent == static_cast<ssize_t>(remaining)) {
+      remaining = 0;
+      break;
+    }
+    if (sent > 0) {
+      buffer = (static_cast<const char*>(buffer) + sent);
+      remaining -= sent;
+      continue;
+    }
+    if (sent < 0) {
+      if (errno != EWOULDBLOCK && errno != EAGAIN) {
+        ERROR("send control data failed: error(%s)", strerror(errno));
+        break;
+      }
+      INFO("send control data failed (%s), retrying", strerror(errno));
+    }
+    if (i >= (CTRL_CHAN_RETRY_COUNT - 1)) {
+      ERROR("send control data failed: max retry count");
+      break;
+    }
+  }
+  if (remaining > 0) {
+    skt_disconnect(common->ctrl_fd);
+    common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+    return -1;
+  }
+  return length;
+}
+
+static int ha_command(struct ha_stream_common* common,
+                      tHEARING_AID_CTRL_CMD cmd) {
+  char ack;
+
+  DEBUG("HEARING_AID COMMAND %s", audio_ha_hw_dump_ctrl_event(cmd));
+
+  if (common->ctrl_fd == AUDIO_SKT_DISCONNECTED) {
+    INFO("starting up or recovering from previous error");
+    ha_open_ctrl_path(common);
+    if (common->ctrl_fd == AUDIO_SKT_DISCONNECTED) {
+      ERROR("failure to open ctrl path");
+      return -1;
+    }
+  }
+
+  /* send command */
+  ssize_t sent;
+  OSI_NO_INTR(sent = send(common->ctrl_fd, &cmd, 1, MSG_NOSIGNAL));
+  if (sent == -1) {
+    ERROR("cmd failed (%s)", strerror(errno));
+    skt_disconnect(common->ctrl_fd);
+    common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+    return -1;
+  }
+
+  /* wait for ack byte */
+  if (ha_ctrl_receive(common, &ack, 1) < 0) {
+    ERROR("HEARING_AID COMMAND %s: no ACK", audio_ha_hw_dump_ctrl_event(cmd));
+    return -1;
+  }
+
+  DEBUG("HEARING_AID COMMAND %s DONE STATUS %d",
+        audio_ha_hw_dump_ctrl_event(cmd), ack);
+
+  if (ack == HEARING_AID_CTRL_ACK_INCALL_FAILURE) return ack;
+  if (ack != HEARING_AID_CTRL_ACK_SUCCESS) {
+    ERROR("HEARING_AID COMMAND %s error %d", audio_ha_hw_dump_ctrl_event(cmd),
+          ack);
+    return -1;
+  }
+
+  return 0;
+}
+
+static int check_ha_ready(struct ha_stream_common* common) {
+  if (ha_command(common, HEARING_AID_CTRL_CMD_CHECK_READY) < 0) {
+    ERROR("check ha ready failed");
+    return -1;
+  }
+  return 0;
+}
+
+static int ha_read_input_audio_config(struct ha_stream_common* common) {
+  tHA_SAMPLE_RATE sample_rate;
+  tHA_CHANNEL_COUNT channel_count;
+
+  if (ha_command(common, HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG) < 0) {
+    ERROR("get ha input audio config failed");
+    return -1;
+  }
+
+  if (ha_ctrl_receive(common, &sample_rate, sizeof(tHA_SAMPLE_RATE)) < 0)
+    return -1;
+  if (ha_ctrl_receive(common, &channel_count, sizeof(tHA_CHANNEL_COUNT)) < 0) {
+    return -1;
+  }
+
+  switch (sample_rate) {
+    case 16000:
+    case 44100:
+    case 48000:
+      common->cfg.rate = sample_rate;
+      break;
+    default:
+      ERROR("Invalid sample rate: %" PRIu32, sample_rate);
+      return -1;
+  }
+
+  switch (channel_count) {
+    case 1:
+      common->cfg.channel_mask = AUDIO_CHANNEL_IN_MONO;
+      break;
+    case 2:
+      common->cfg.channel_mask = AUDIO_CHANNEL_IN_STEREO;
+      break;
+    default:
+      ERROR("Invalid channel count: %" PRIu32, channel_count);
+      return -1;
+  }
+
+  // TODO: For now input audio format is always hard-coded as PCM 16-bit
+  common->cfg.format = AUDIO_FORMAT_PCM_16_BIT;
+
+  INFO("got input audio config %d %d", common->cfg.format, common->cfg.rate);
+
+  return 0;
+}
+
+static int ha_read_output_audio_config(
+    struct ha_stream_common* common, btav_a2dp_codec_config_t* codec_config,
+    btav_a2dp_codec_config_t* codec_capability, bool update_stream_config) {
+  struct ha_config stream_config;
+
+  if (ha_command(common, HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG) < 0) {
+    ERROR("get ha output audio config failed");
+    return -1;
+  }
+
+  // Receive the current codec config
+  if (ha_ctrl_receive(common, &codec_config->sample_rate,
+                      sizeof(btav_a2dp_codec_sample_rate_t)) < 0) {
+    return -1;
+  }
+  if (ha_ctrl_receive(common, &codec_config->bits_per_sample,
+                      sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
+    return -1;
+  }
+  if (ha_ctrl_receive(common, &codec_config->channel_mode,
+                      sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
+    return -1;
+  }
+
+  // Receive the current codec capability
+  if (ha_ctrl_receive(common, &codec_capability->sample_rate,
+                      sizeof(btav_a2dp_codec_sample_rate_t)) < 0) {
+    return -1;
+  }
+  if (ha_ctrl_receive(common, &codec_capability->bits_per_sample,
+                      sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
+    return -1;
+  }
+  if (ha_ctrl_receive(common, &codec_capability->channel_mode,
+                      sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
+    return -1;
+  }
+
+  // Check the codec config sample rate
+  switch (codec_config->sample_rate) {
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+      stream_config.rate = 44100;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+      stream_config.rate = 48000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+      stream_config.rate = 88200;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+      stream_config.rate = 96000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+      stream_config.rate = 176400;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+      stream_config.rate = 192000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+      stream_config.rate = 16000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+    default:
+      ERROR("Invalid sample rate: 0x%x", codec_config->sample_rate);
+      return -1;
+  }
+
+  // Check the codec config bits per sample
+  switch (codec_config->bits_per_sample) {
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+      stream_config.format = AUDIO_FORMAT_PCM_16_BIT;
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+      stream_config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+      stream_config.format = AUDIO_FORMAT_PCM_32_BIT;
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+    default:
+      ERROR("Invalid bits per sample: 0x%x", codec_config->bits_per_sample);
+      return -1;
+  }
+
+  // Check the codec config channel mode
+  switch (codec_config->channel_mode) {
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+      stream_config.channel_mask = AUDIO_CHANNEL_OUT_MONO;
+      stream_config.is_stereo_to_mono = true;
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+      stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
+      stream_config.is_stereo_to_mono = false;
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+    default:
+      ERROR("Invalid channel mode: 0x%x", codec_config->channel_mode);
+      return -1;
+  }
+  if (stream_config.is_stereo_to_mono) {
+    stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
+  }
+
+  // Update the output stream configuration
+  if (update_stream_config) {
+    common->cfg.rate = stream_config.rate;
+    common->cfg.channel_mask = stream_config.channel_mask;
+    common->cfg.is_stereo_to_mono = stream_config.is_stereo_to_mono;
+    common->cfg.format = stream_config.format;
+    common->buffer_sz = audio_ha_hw_stream_compute_buffer_size(
+        codec_config->sample_rate, codec_config->bits_per_sample,
+        codec_config->channel_mode);
+    if (common->cfg.is_stereo_to_mono) {
+      // We need to fetch twice as much data from the Audio framework
+      common->buffer_sz *= 2;
+    }
+  }
+
+  INFO(
+      "got output codec config (update_stream_config=%s): "
+      "sample_rate=0x%x bits_per_sample=0x%x channel_mode=0x%x",
+      update_stream_config ? "true" : "false", codec_config->sample_rate,
+      codec_config->bits_per_sample, codec_config->channel_mode);
+
+  INFO(
+      "got output codec capability: sample_rate=0x%x bits_per_sample=0x%x "
+      "channel_mode=0x%x",
+      codec_capability->sample_rate, codec_capability->bits_per_sample,
+      codec_capability->channel_mode);
+
+  return 0;
+}
+
+static int ha_write_output_audio_config(struct ha_stream_common* common) {
+  btav_a2dp_codec_config_t codec_config;
+
+  if (ha_command(common, HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG) < 0) {
+    ERROR("set ha output audio config failed");
+    return -1;
+  }
+
+  codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+  codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+  codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+
+  switch (common->cfg.rate) {
+    case 44100:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_44100;
+      break;
+    case 48000:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_48000;
+      break;
+    case 88200:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_88200;
+      break;
+    case 96000:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_96000;
+      break;
+    case 176400:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_176400;
+      break;
+    case 192000:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_192000;
+      break;
+    case 16000:
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
+      break;
+    default:
+      ERROR("Invalid sample rate: %" PRIu32, common->cfg.rate);
+      return -1;
+  }
+
+  switch (common->cfg.format) {
+    case AUDIO_FORMAT_PCM_16_BIT:
+      codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+      break;
+    case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+      codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24;
+      break;
+    case AUDIO_FORMAT_PCM_32_BIT:
+      codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32;
+      break;
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+    // FALLTHROUGH
+    // All 24-bit audio is expected in AUDIO_FORMAT_PCM_24_BIT_PACKED format
+    default:
+      ERROR("Invalid audio format: 0x%x", common->cfg.format);
+      return -1;
+  }
+
+  switch (common->cfg.channel_mask) {
+    case AUDIO_CHANNEL_OUT_MONO:
+      codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+      break;
+    case AUDIO_CHANNEL_OUT_STEREO:
+      if (common->cfg.is_stereo_to_mono) {
+        codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+      } else {
+        codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+      }
+      break;
+    default:
+      ERROR("Invalid channel mask: 0x%x", common->cfg.channel_mask);
+      return -1;
+  }
+
+  // Send the current codec config that has been selected by us
+  if (ha_ctrl_send(common, &codec_config.sample_rate,
+                   sizeof(btav_a2dp_codec_sample_rate_t)) < 0)
+    return -1;
+  if (ha_ctrl_send(common, &codec_config.bits_per_sample,
+                   sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
+    return -1;
+  }
+  if (ha_ctrl_send(common, &codec_config.channel_mode,
+                   sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
+    return -1;
+  }
+
+  INFO(
+      "sent output codec config: sample_rate=0x%x bits_per_sample=0x%x "
+      "channel_mode=0x%x",
+      codec_config.sample_rate, codec_config.bits_per_sample,
+      codec_config.channel_mode);
+
+  return 0;
+}
+
+static void ha_open_ctrl_path(struct ha_stream_common* common) {
+  int i;
+
+  if (common->ctrl_fd != AUDIO_SKT_DISCONNECTED) return;  // already connected
+
+  /* retry logic to catch any timing variations on control channel */
+  for (i = 0; i < CTRL_CHAN_RETRY_COUNT; i++) {
+    /* connect control channel if not already connected */
+    if ((common->ctrl_fd = skt_connect(
+             HEARING_AID_CTRL_PATH, AUDIO_STREAM_CONTROL_OUTPUT_BUFFER_SZ)) >=
+        0) {
+      /* success, now check if stack is ready */
+      if (check_ha_ready(common) == 0) break;
+
+      ERROR("error : ha not ready, wait 250 ms and retry");
+      usleep(250000);
+      skt_disconnect(common->ctrl_fd);
+      common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+    }
+
+    /* ctrl channel not ready, wait a bit */
+    usleep(250000);
+  }
+}
+
+/*****************************************************************************
+ *
+ * AUDIO DATA PATH
+ *
+ ****************************************************************************/
+
+static void ha_stream_common_init(struct ha_stream_common* common) {
+  FNLOG();
+
+  common->mutex = new std::recursive_mutex;
+
+  common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+  common->audio_fd = AUDIO_SKT_DISCONNECTED;
+  common->state = AUDIO_HA_STATE_STOPPED;
+
+  /* manages max capacity of socket pipe */
+  common->buffer_sz = AUDIO_STREAM_OUTPUT_BUFFER_SZ;
+}
+
+static void ha_stream_common_destroy(struct ha_stream_common* common) {
+  FNLOG();
+
+  delete common->mutex;
+  common->mutex = NULL;
+}
+
+static int start_audio_datapath(struct ha_stream_common* common) {
+  INFO("state %d", common->state);
+
+  int oldstate = common->state;
+  common->state = AUDIO_HA_STATE_STARTING;
+
+  int ha_status = ha_command(common, HEARING_AID_CTRL_CMD_START);
+  if (ha_status < 0) {
+    ERROR("Audiopath start failed (status %d)", ha_status);
+    goto error;
+  } else if (ha_status == HEARING_AID_CTRL_ACK_INCALL_FAILURE) {
+    ERROR("Audiopath start failed - in call, move to suspended");
+    goto error;
+  }
+
+  /* connect socket if not yet connected */
+  if (common->audio_fd == AUDIO_SKT_DISCONNECTED) {
+    common->audio_fd = skt_connect(HEARING_AID_DATA_PATH, common->buffer_sz);
+    if (common->audio_fd < 0) {
+      ERROR("Audiopath start failed - error opening data socket");
+      goto error;
+    }
+  }
+  common->state = (ha_state_t)AUDIO_HA_STATE_STARTED;
+  return 0;
+
+error:
+  common->state = (ha_state_t)oldstate;
+  return -1;
+}
+
+static int stop_audio_datapath(struct ha_stream_common* common) {
+  int oldstate = common->state;
+
+  INFO("state %d", common->state);
+
+  /* prevent any stray output writes from autostarting the stream
+     while stopping audiopath */
+  common->state = AUDIO_HA_STATE_STOPPING;
+
+  if (ha_command(common, HEARING_AID_CTRL_CMD_STOP) < 0) {
+    ERROR("audiopath stop failed");
+    common->state = (ha_state_t)oldstate;
+    return -1;
+  }
+
+  common->state = (ha_state_t)AUDIO_HA_STATE_STOPPED;
+
+  /* disconnect audio path */
+  skt_disconnect(common->audio_fd);
+  common->audio_fd = AUDIO_SKT_DISCONNECTED;
+
+  return 0;
+}
+
+static int suspend_audio_datapath(struct ha_stream_common* common,
+                                  bool standby) {
+  INFO("state %d", common->state);
+
+  if (common->state == AUDIO_HA_STATE_STOPPING) return -1;
+
+  if (ha_command(common, HEARING_AID_CTRL_CMD_SUSPEND) < 0) return -1;
+
+  if (standby)
+    common->state = AUDIO_HA_STATE_STANDBY;
+  else
+    common->state = AUDIO_HA_STATE_SUSPENDED;
+
+  /* disconnect audio path */
+  skt_disconnect(common->audio_fd);
+
+  common->audio_fd = AUDIO_SKT_DISCONNECTED;
+
+  return 0;
+}
+
+/*****************************************************************************
+ *
+ *  audio output callbacks
+ *
+ ****************************************************************************/
+
+static ssize_t out_write(struct audio_stream_out* stream, const void* buffer,
+                         size_t bytes) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+  int sent = -1;
+  size_t write_bytes = bytes;
+
+  DEBUG("write %zu bytes (fd %d)", bytes, out->common.audio_fd);
+
+  std::unique_lock<std::recursive_mutex> lock(*out->common.mutex);
+  if (out->common.state == AUDIO_HA_STATE_SUSPENDED ||
+      out->common.state == AUDIO_HA_STATE_STOPPING) {
+    DEBUG("stream suspended or closing");
+    goto finish;
+  }
+
+  /* only allow autostarting if we are in stopped or standby */
+  if ((out->common.state == AUDIO_HA_STATE_STOPPED) ||
+      (out->common.state == AUDIO_HA_STATE_STANDBY)) {
+    if (start_audio_datapath(&out->common) < 0) {
+      goto finish;
+    }
+  } else if (out->common.state != AUDIO_HA_STATE_STARTED) {
+    ERROR("stream not in stopped or standby");
+    goto finish;
+  }
+
+  // Mix the stereo into mono if necessary
+  if (out->common.cfg.is_stereo_to_mono) {
+    const size_t frames = bytes / audio_stream_out_frame_size(stream);
+    int16_t* src = (int16_t*)buffer;
+    int16_t* dst = (int16_t*)buffer;
+    for (size_t i = 0; i < frames; i++, dst++, src += 2) {
+      *dst = (int16_t)(((int32_t)src[0] + (int32_t)src[1]) >> 1);
+    }
+    write_bytes /= 2;
+    DEBUG("stereo-to-mono mixing: write %zu bytes (fd %d)", write_bytes,
+          out->common.audio_fd);
+  }
+
+  lock.unlock();
+  sent = skt_write(out->common.audio_fd, buffer, write_bytes);
+  lock.lock();
+
+  if (sent == -1) {
+    skt_disconnect(out->common.audio_fd);
+    out->common.audio_fd = AUDIO_SKT_DISCONNECTED;
+    if ((out->common.state != AUDIO_HA_STATE_SUSPENDED) &&
+        (out->common.state != AUDIO_HA_STATE_STOPPING)) {
+      out->common.state = AUDIO_HA_STATE_STOPPED;
+    } else {
+      ERROR("write failed : stream suspended, avoid resetting state");
+    }
+    goto finish;
+  }
+
+finish:;
+  const size_t frames = bytes / audio_stream_out_frame_size(stream);
+  out->frames_rendered += frames;
+  out->frames_presented += frames;
+  lock.unlock();
+
+  // If send didn't work out, sleep to emulate write delay.
+  if (sent == -1) {
+    const int us_delay = calc_audiotime_usec(out->common.cfg, bytes);
+    DEBUG("emulate ha write delay (%d us)", us_delay);
+    usleep(us_delay);
+  }
+  return bytes;
+}
+
+static uint32_t out_get_sample_rate(const struct audio_stream* stream) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  DEBUG("rate %" PRIu32, out->common.cfg.rate);
+
+  return out->common.cfg.rate;
+}
+
+static int out_set_sample_rate(struct audio_stream* stream, uint32_t rate) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  DEBUG("out_set_sample_rate : %" PRIu32, rate);
+
+  out->common.cfg.rate = rate;
+
+  return 0;
+}
+
+static size_t out_get_buffer_size(const struct audio_stream* stream) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+  // period_size is the AudioFlinger mixer buffer size.
+  const size_t period_size =
+      out->common.buffer_sz / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS;
+
+  DEBUG("socket buffer size: %zu  period size: %zu", out->common.buffer_sz,
+        period_size);
+
+  return period_size;
+}
+
+size_t audio_ha_hw_stream_compute_buffer_size(
+    btav_a2dp_codec_sample_rate_t codec_sample_rate,
+    btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample,
+    btav_a2dp_codec_channel_mode_t codec_channel_mode) {
+  size_t buffer_sz = AUDIO_STREAM_OUTPUT_BUFFER_SZ;  // Default value
+  const uint64_t time_period_ms = 20;                // Conservative 20ms
+  uint32_t sample_rate;
+  uint32_t bits_per_sample;
+  uint32_t number_of_channels;
+
+  // Check the codec config sample rate
+  switch (codec_sample_rate) {
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+      sample_rate = 44100;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+      sample_rate = 48000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+      sample_rate = 88200;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+      sample_rate = 96000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+      sample_rate = 176400;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+      sample_rate = 192000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+      sample_rate = 16000;
+      break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+    default:
+      ERROR("Invalid sample rate: 0x%x", codec_sample_rate);
+      return buffer_sz;
+  }
+
+  // Check the codec config bits per sample
+  switch (codec_bits_per_sample) {
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+      bits_per_sample = 16;
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+      bits_per_sample = 24;
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+      bits_per_sample = 32;
+      break;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+    default:
+      ERROR("Invalid bits per sample: 0x%x", codec_bits_per_sample);
+      return buffer_sz;
+  }
+
+  // Check the codec config channel mode
+  switch (codec_channel_mode) {
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+      number_of_channels = 1;
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+      number_of_channels = 2;
+      break;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+    default:
+      ERROR("Invalid channel mode: 0x%x", codec_channel_mode);
+      return buffer_sz;
+  }
+
+  //
+  // The buffer size is computed by using the following formula:
+  //
+  // AUDIO_STREAM_OUTPUT_BUFFER_SIZE =
+  //    (TIME_PERIOD_MS * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS *
+  //     SAMPLE_RATE_HZ * NUMBER_OF_CHANNELS * (BITS_PER_SAMPLE / 8)) / 1000
+  //
+  // AUDIO_STREAM_OUTPUT_BUFFER_PERIODS controls how the socket buffer is
+  // divided for AudioFlinger data delivery. The AudioFlinger mixer delivers
+  // data in chunks of
+  // (AUDIO_STREAM_OUTPUT_BUFFER_SIZE / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS) .
+  // If the number of periods is 2, the socket buffer represents "double
+  // buffering" of the AudioFlinger mixer buffer.
+  //
+  // Furthermore, the AudioFlinger expects the buffer size to be a multiple
+  // of 16 frames.
+  const size_t divisor = (AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * 16 *
+                          number_of_channels * bits_per_sample) /
+                         8;
+
+  buffer_sz = (time_period_ms * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS *
+               sample_rate * number_of_channels * (bits_per_sample / 8)) /
+              1000;
+
+  // Adjust the buffer size so it can be divided by the divisor
+  const size_t remainder = buffer_sz % divisor;
+  if (remainder != 0) {
+    buffer_sz += divisor - remainder;
+  }
+
+  return buffer_sz;
+}
+
+static uint32_t out_get_channels(const struct audio_stream* stream) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  DEBUG("channels 0x%" PRIx32, out->common.cfg.channel_mask);
+
+  return out->common.cfg.channel_mask;
+}
+
+static audio_format_t out_get_format(const struct audio_stream* stream) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+  DEBUG("format 0x%x", out->common.cfg.format);
+  return (audio_format_t)out->common.cfg.format;
+}
+
+static int out_set_format(UNUSED_ATTR struct audio_stream* stream,
+                          UNUSED_ATTR audio_format_t format) {
+  DEBUG("setting format not yet supported (0x%x)", format);
+  return -ENOSYS;
+}
+
+static int out_standby(struct audio_stream* stream) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+  int retVal = 0;
+
+  FNLOG();
+
+  std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+  // Do nothing in SUSPENDED state.
+  if (out->common.state != AUDIO_HA_STATE_SUSPENDED)
+    retVal = suspend_audio_datapath(&out->common, true);
+  out->frames_rendered = 0;  // rendered is reset, presented is not
+
+  return retVal;
+}
+
+static int out_dump(UNUSED_ATTR const struct audio_stream* stream,
+                    UNUSED_ATTR int fd) {
+  FNLOG();
+  return 0;
+}
+
+static int out_set_parameters(struct audio_stream* stream,
+                              const char* kvpairs) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  INFO("state %d kvpairs %s", out->common.state, kvpairs);
+
+  std::unordered_map<std::string, std::string> params =
+      hash_map_utils_new_from_string_params(kvpairs);
+  int status = 0;
+
+  if (params.empty()) return status;
+
+  std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+
+  /* dump params */
+  hash_map_utils_dump_string_keys_string_values(params);
+
+  if (params["closing"].compare("true") == 0) {
+    DEBUG("stream closing, disallow any writes");
+    out->common.state = AUDIO_HA_STATE_STOPPING;
+  }
+
+  if (params["HearingAidSuspended"].compare("true") == 0) {
+    if (out->common.state == AUDIO_HA_STATE_STARTED)
+      status = suspend_audio_datapath(&out->common, false);
+  } else {
+    /* Do not start the streaming automatically. If the phone was streaming
+     * prior to being suspended, the next out_write shall trigger the
+     * AVDTP start procedure */
+    if (out->common.state == AUDIO_HA_STATE_SUSPENDED)
+      out->common.state = AUDIO_HA_STATE_STANDBY;
+    /* Irrespective of the state, return 0 */
+  }
+
+  return status;
+}
+
+static char* out_get_parameters(const struct audio_stream* stream,
+                                const char* keys) {
+  FNLOG();
+
+  btav_a2dp_codec_config_t codec_config;
+  btav_a2dp_codec_config_t codec_capability;
+
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  std::unordered_map<std::string, std::string> params =
+      hash_map_utils_new_from_string_params(keys);
+  std::unordered_map<std::string, std::string> return_params;
+
+  if (params.empty()) return strdup("");
+
+  std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+
+  if (ha_read_output_audio_config(&out->common, &codec_config,
+                                  &codec_capability,
+                                  false /* update_stream_config */) < 0) {
+    ERROR("ha_read_output_audio_config failed");
+    goto done;
+  }
+
+  // Add the format
+  if (params.find(AUDIO_PARAMETER_STREAM_SUP_FORMATS) != params.end()) {
+    std::string param;
+    if (codec_capability.bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) {
+      if (!param.empty()) param += "|";
+      param += "AUDIO_FORMAT_PCM_16_BIT";
+    }
+    if (codec_capability.bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) {
+      if (!param.empty()) param += "|";
+      param += "AUDIO_FORMAT_PCM_24_BIT_PACKED";
+    }
+    if (codec_capability.bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) {
+      if (!param.empty()) param += "|";
+      param += "AUDIO_FORMAT_PCM_32_BIT";
+    }
+    if (param.empty()) {
+      ERROR("Invalid codec capability bits_per_sample=0x%x",
+            codec_capability.bits_per_sample);
+      goto done;
+    } else {
+      return_params[AUDIO_PARAMETER_STREAM_SUP_FORMATS] = param;
+    }
+  }
+
+  // Add the sample rate
+  if (params.find(AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES) != params.end()) {
+    std::string param;
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_44100) {
+      if (!param.empty()) param += "|";
+      param += "44100";
+    }
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_48000) {
+      if (!param.empty()) param += "|";
+      param += "48000";
+    }
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_88200) {
+      if (!param.empty()) param += "|";
+      param += "88200";
+    }
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_96000) {
+      if (!param.empty()) param += "|";
+      param += "96000";
+    }
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_176400) {
+      if (!param.empty()) param += "|";
+      param += "176400";
+    }
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_192000) {
+      if (!param.empty()) param += "|";
+      param += "192000";
+    }
+    if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_16000) {
+      if (!param.empty()) param += "|";
+      param += "16000";
+    }
+    if (param.empty()) {
+      ERROR("Invalid codec capability sample_rate=0x%x",
+            codec_capability.sample_rate);
+      goto done;
+    } else {
+      return_params[AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES] = param;
+    }
+  }
+
+  // Add the channel mask
+  if (params.find(AUDIO_PARAMETER_STREAM_SUP_CHANNELS) != params.end()) {
+    std::string param;
+    if (codec_capability.channel_mode & BTAV_A2DP_CODEC_CHANNEL_MODE_MONO) {
+      if (!param.empty()) param += "|";
+      param += "AUDIO_CHANNEL_OUT_MONO";
+    }
+    if (codec_capability.channel_mode & BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO) {
+      if (!param.empty()) param += "|";
+      param += "AUDIO_CHANNEL_OUT_STEREO";
+    }
+    if (param.empty()) {
+      ERROR("Invalid codec capability channel_mode=0x%x",
+            codec_capability.channel_mode);
+      goto done;
+    } else {
+      return_params[AUDIO_PARAMETER_STREAM_SUP_CHANNELS] = param;
+    }
+  }
+
+done:
+  std::string result;
+  for (const auto& ptr : return_params) {
+    result += ptr.first + "=" + ptr.second + ";";
+  }
+
+  INFO("get parameters result = %s", result.c_str());
+
+  return strdup(result.c_str());
+}
+
+static uint32_t out_get_latency(const struct audio_stream_out* stream) {
+  int latency_us;
+
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  FNLOG();
+
+  latency_us =
+      ((out->common.buffer_sz * 1000) /
+       audio_stream_out_frame_size(&out->stream) / out->common.cfg.rate) *
+      1000;
+
+  return (latency_us / 1000) + 200;
+}
+
+static int out_set_volume(UNUSED_ATTR struct audio_stream_out* stream,
+                          UNUSED_ATTR float left, UNUSED_ATTR float right) {
+  FNLOG();
+
+  /* volume controlled in audioflinger mixer (digital) */
+
+  return -ENOSYS;
+}
+
+static int out_get_presentation_position(const struct audio_stream_out* stream,
+                                         uint64_t* frames,
+                                         struct timespec* timestamp) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  FNLOG();
+  if (stream == NULL || frames == NULL || timestamp == NULL) return -EINVAL;
+
+  int ret = -EWOULDBLOCK;
+  std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+  uint64_t latency_frames =
+      (uint64_t)out_get_latency(stream) * out->common.cfg.rate / 1000;
+  if (out->frames_presented >= latency_frames) {
+    *frames = out->frames_presented - latency_frames;
+    clock_gettime(CLOCK_MONOTONIC,
+                  timestamp);  // could also be associated with out_write().
+    ret = 0;
+  }
+  return ret;
+}
+
+static int out_get_render_position(const struct audio_stream_out* stream,
+                                   uint32_t* dsp_frames) {
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  FNLOG();
+  if (stream == NULL || dsp_frames == NULL) return -EINVAL;
+
+  std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+  uint64_t latency_frames =
+      (uint64_t)out_get_latency(stream) * out->common.cfg.rate / 1000;
+  if (out->frames_rendered >= latency_frames) {
+    *dsp_frames = (uint32_t)(out->frames_rendered - latency_frames);
+  } else {
+    *dsp_frames = 0;
+  }
+  return 0;
+}
+
+static int out_add_audio_effect(UNUSED_ATTR const struct audio_stream* stream,
+                                UNUSED_ATTR effect_handle_t effect) {
+  FNLOG();
+  return 0;
+}
+
+static int out_remove_audio_effect(
+    UNUSED_ATTR const struct audio_stream* stream,
+    UNUSED_ATTR effect_handle_t effect) {
+  FNLOG();
+  return 0;
+}
+
+/*
+ * AUDIO INPUT STREAM
+ */
+
+static uint32_t in_get_sample_rate(const struct audio_stream* stream) {
+  struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+  FNLOG();
+  return in->common.cfg.rate;
+}
+
+static int in_set_sample_rate(struct audio_stream* stream, uint32_t rate) {
+  struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+  FNLOG();
+
+  if (in->common.cfg.rate > 0 && in->common.cfg.rate == rate)
+    return 0;
+  else
+    return -1;
+}
+
+static size_t in_get_buffer_size(
+    UNUSED_ATTR const struct audio_stream* stream) {
+  FNLOG();
+  return 320;
+}
+
+static uint32_t in_get_channels(const struct audio_stream* stream) {
+  struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+  FNLOG();
+  return in->common.cfg.channel_mask;
+}
+
+static audio_format_t in_get_format(
+    UNUSED_ATTR const struct audio_stream* stream) {
+  FNLOG();
+  return AUDIO_FORMAT_PCM_16_BIT;
+}
+
+static int in_set_format(UNUSED_ATTR struct audio_stream* stream,
+                         UNUSED_ATTR audio_format_t format) {
+  FNLOG();
+  if (format == AUDIO_FORMAT_PCM_16_BIT)
+    return 0;
+  else
+    return -1;
+}
+
+static int in_standby(UNUSED_ATTR struct audio_stream* stream) {
+  FNLOG();
+  return 0;
+}
+
+static int in_dump(UNUSED_ATTR const struct audio_stream* stream,
+                   UNUSED_ATTR int fd) {
+  FNLOG();
+  return 0;
+}
+
+static int in_set_parameters(UNUSED_ATTR struct audio_stream* stream,
+                             UNUSED_ATTR const char* kvpairs) {
+  FNLOG();
+  return 0;
+}
+
+static char* in_get_parameters(UNUSED_ATTR const struct audio_stream* stream,
+                               UNUSED_ATTR const char* keys) {
+  FNLOG();
+  return strdup("");
+}
+
+static int in_set_gain(UNUSED_ATTR struct audio_stream_in* stream,
+                       UNUSED_ATTR float gain) {
+  FNLOG();
+  return 0;
+}
+
+static ssize_t in_read(struct audio_stream_in* stream, void* buffer,
+                       size_t bytes) {
+  struct ha_stream_in* in = (struct ha_stream_in*)stream;
+  int read;
+  int us_delay;
+
+  DEBUG("read %zu bytes, state: %d", bytes, in->common.state);
+
+  std::unique_lock<std::recursive_mutex> lock(*in->common.mutex);
+  if (in->common.state == AUDIO_HA_STATE_SUSPENDED ||
+      in->common.state == AUDIO_HA_STATE_STOPPING) {
+    DEBUG("stream suspended");
+    goto error;
+  }
+
+  /* only allow autostarting if we are in stopped or standby */
+  if ((in->common.state == AUDIO_HA_STATE_STOPPED) ||
+      (in->common.state == AUDIO_HA_STATE_STANDBY)) {
+    if (start_audio_datapath(&in->common) < 0) {
+      goto error;
+    }
+  } else if (in->common.state != AUDIO_HA_STATE_STARTED) {
+    ERROR("stream not in stopped or standby");
+    goto error;
+  }
+
+  lock.unlock();
+  read = skt_read(in->common.audio_fd, buffer, bytes);
+  lock.lock();
+  if (read == -1) {
+    skt_disconnect(in->common.audio_fd);
+    in->common.audio_fd = AUDIO_SKT_DISCONNECTED;
+    if ((in->common.state != AUDIO_HA_STATE_SUSPENDED) &&
+        (in->common.state != AUDIO_HA_STATE_STOPPING)) {
+      in->common.state = AUDIO_HA_STATE_STOPPED;
+    } else {
+      ERROR("read failed : stream suspended, avoid resetting state");
+    }
+    goto error;
+  } else if (read == 0) {
+    DEBUG("read time out - return zeros");
+    memset(buffer, 0, bytes);
+    read = bytes;
+  }
+  lock.unlock();
+
+  DEBUG("read %d bytes out of %zu bytes", read, bytes);
+  return read;
+
+error:
+  memset(buffer, 0, bytes);
+  us_delay = calc_audiotime_usec(in->common.cfg, bytes);
+  DEBUG("emulate ha read delay (%d us)", us_delay);
+
+  usleep(us_delay);
+  return bytes;
+}
+
+static uint32_t in_get_input_frames_lost(
+    UNUSED_ATTR struct audio_stream_in* stream) {
+  FNLOG();
+  return 0;
+}
+
+static int in_add_audio_effect(UNUSED_ATTR const struct audio_stream* stream,
+                               UNUSED_ATTR effect_handle_t effect) {
+  FNLOG();
+  return 0;
+}
+
+static int in_remove_audio_effect(UNUSED_ATTR const struct audio_stream* stream,
+                                  UNUSED_ATTR effect_handle_t effect) {
+  FNLOG();
+
+  return 0;
+}
+
+static int adev_open_output_stream(struct audio_hw_device* dev,
+                                   UNUSED_ATTR audio_io_handle_t handle,
+                                   UNUSED_ATTR audio_devices_t devices,
+                                   UNUSED_ATTR audio_output_flags_t flags,
+                                   struct audio_config* config,
+                                   struct audio_stream_out** stream_out,
+                                   UNUSED_ATTR const char* address)
+
+{
+  struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+  struct ha_stream_out* out;
+  int ret = 0;
+
+  INFO("opening output");
+  // protect against adev->output and stream_out from being inconsistent
+  std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+  out = (struct ha_stream_out*)calloc(1, sizeof(struct ha_stream_out));
+
+  if (!out) return -ENOMEM;
+
+  out->stream.common.get_sample_rate = out_get_sample_rate;
+  out->stream.common.set_sample_rate = out_set_sample_rate;
+  out->stream.common.get_buffer_size = out_get_buffer_size;
+  out->stream.common.get_channels = out_get_channels;
+  out->stream.common.get_format = out_get_format;
+  out->stream.common.set_format = out_set_format;
+  out->stream.common.standby = out_standby;
+  out->stream.common.dump = out_dump;
+  out->stream.common.set_parameters = out_set_parameters;
+  out->stream.common.get_parameters = out_get_parameters;
+  out->stream.common.add_audio_effect = out_add_audio_effect;
+  out->stream.common.remove_audio_effect = out_remove_audio_effect;
+  out->stream.get_latency = out_get_latency;
+  out->stream.set_volume = out_set_volume;
+  out->stream.write = out_write;
+  out->stream.get_render_position = out_get_render_position;
+  out->stream.get_presentation_position = out_get_presentation_position;
+
+  /* initialize ha specifics */
+  ha_stream_common_init(&out->common);
+
+  // Make sure we always have the feeding parameters configured
+  btav_a2dp_codec_config_t codec_config;
+  btav_a2dp_codec_config_t codec_capability;
+  if (ha_read_output_audio_config(&out->common, &codec_config,
+                                  &codec_capability,
+                                  true /* update_stream_config */) < 0) {
+    ERROR("ha_read_output_audio_config failed");
+    ret = -1;
+    goto err_open;
+  }
+  // ha_read_output_audio_config() opens the socket control path (or fails)
+
+  /* set output config values */
+  if (config != nullptr) {
+    // Try to use the config parameters and send it to the remote side
+    // TODO: Shall we use out_set_format() and similar?
+    if (config->format != 0) out->common.cfg.format = config->format;
+    if (config->sample_rate != 0) out->common.cfg.rate = config->sample_rate;
+    if (config->channel_mask != 0)
+      out->common.cfg.channel_mask = config->channel_mask;
+    if ((out->common.cfg.format != 0) || (out->common.cfg.rate != 0) ||
+        (out->common.cfg.channel_mask != 0)) {
+      if (ha_write_output_audio_config(&out->common) < 0) {
+        ERROR("ha_write_output_audio_config failed");
+        ret = -1;
+        goto err_open;
+      }
+      // Read again and make sure we use the same parameters as the remote side
+      if (ha_read_output_audio_config(&out->common, &codec_config,
+                                      &codec_capability,
+                                      true /* update_stream_config */) < 0) {
+        ERROR("ha_read_output_audio_config failed");
+        ret = -1;
+        goto err_open;
+      }
+    }
+    config->format = out_get_format((const struct audio_stream*)&out->stream);
+    config->sample_rate =
+        out_get_sample_rate((const struct audio_stream*)&out->stream);
+    config->channel_mask =
+        out_get_channels((const struct audio_stream*)&out->stream);
+
+    INFO(
+        "Output stream config: format=0x%x sample_rate=%d channel_mask=0x%x "
+        "buffer_sz=%zu",
+        config->format, config->sample_rate, config->channel_mask,
+        out->common.buffer_sz);
+  }
+  *stream_out = &out->stream;
+  ha_dev->output = out;
+
+  DEBUG("success");
+  /* Delay to ensure Headset is in proper state when START is initiated from
+   * DUT immediately after the connection due to ongoing music playback. */
+  usleep(250000);
+  return 0;
+
+err_open:
+  ha_stream_common_destroy(&out->common);
+  free(out);
+  *stream_out = NULL;
+  ha_dev->output = NULL;
+  ERROR("failed");
+  return ret;
+}
+
+static void adev_close_output_stream(struct audio_hw_device* dev,
+                                     struct audio_stream_out* stream) {
+  struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+  struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+  // prevent interference with adev_set_parameters.
+  std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+  {
+    std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+    const ha_state_t state = out->common.state;
+    INFO("closing output (state %d)", (int)state);
+    if ((state == AUDIO_HA_STATE_STARTED) ||
+        (state == AUDIO_HA_STATE_STOPPING)) {
+      stop_audio_datapath(&out->common);
+    }
+
+    skt_disconnect(out->common.ctrl_fd);
+    out->common.ctrl_fd = AUDIO_SKT_DISCONNECTED;
+  }
+
+  ha_stream_common_destroy(&out->common);
+  free(stream);
+  ha_dev->output = NULL;
+
+  DEBUG("done");
+}
+
+static int adev_set_parameters(struct audio_hw_device* dev,
+                               const char* kvpairs) {
+  struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+  int retval = 0;
+
+  // prevent interference with adev_close_output_stream
+  std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+  struct ha_stream_out* out = ha_dev->output;
+
+  if (out == NULL) return retval;
+
+  INFO("state %d", out->common.state);
+
+  retval =
+      out->stream.common.set_parameters((struct audio_stream*)out, kvpairs);
+
+  return retval;
+}
+
+static char* adev_get_parameters(UNUSED_ATTR const struct audio_hw_device* dev,
+                                 const char* keys) {
+  FNLOG();
+
+  std::unordered_map<std::string, std::string> params =
+      hash_map_utils_new_from_string_params(keys);
+  hash_map_utils_dump_string_keys_string_values(params);
+
+  return strdup("");
+}
+
+static int adev_init_check(UNUSED_ATTR const struct audio_hw_device* dev) {
+  FNLOG();
+
+  return 0;
+}
+
+static int adev_set_voice_volume(UNUSED_ATTR struct audio_hw_device* dev,
+                                 UNUSED_ATTR float volume) {
+  FNLOG();
+
+  return -ENOSYS;
+}
+
+static int adev_set_master_volume(UNUSED_ATTR struct audio_hw_device* dev,
+                                  UNUSED_ATTR float volume) {
+  FNLOG();
+
+  return -ENOSYS;
+}
+
+static int adev_set_mode(UNUSED_ATTR struct audio_hw_device* dev,
+                         UNUSED_ATTR audio_mode_t mode) {
+  FNLOG();
+
+  return 0;
+}
+
+static int adev_set_mic_mute(UNUSED_ATTR struct audio_hw_device* dev,
+                             UNUSED_ATTR bool state) {
+  FNLOG();
+
+  return -ENOSYS;
+}
+
+static int adev_get_mic_mute(UNUSED_ATTR const struct audio_hw_device* dev,
+                             UNUSED_ATTR bool* state) {
+  FNLOG();
+
+  return -ENOSYS;
+}
+
+static size_t adev_get_input_buffer_size(
+    UNUSED_ATTR const struct audio_hw_device* dev,
+    UNUSED_ATTR const struct audio_config* config) {
+  FNLOG();
+
+  return 320;
+}
+
+static int adev_open_input_stream(struct audio_hw_device* dev,
+                                  UNUSED_ATTR audio_io_handle_t handle,
+                                  UNUSED_ATTR audio_devices_t devices,
+                                  UNUSED_ATTR struct audio_config* config,
+                                  struct audio_stream_in** stream_in,
+                                  UNUSED_ATTR audio_input_flags_t flags,
+                                  UNUSED_ATTR const char* address,
+                                  UNUSED_ATTR audio_source_t source) {
+  struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+  struct ha_stream_in* in;
+  int ret;
+
+  FNLOG();
+
+  // protect against adev->input and stream_in from being inconsistent
+  std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+  in = (struct ha_stream_in*)calloc(1, sizeof(struct ha_stream_in));
+
+  if (!in) return -ENOMEM;
+
+  in->stream.common.get_sample_rate = in_get_sample_rate;
+  in->stream.common.set_sample_rate = in_set_sample_rate;
+  in->stream.common.get_buffer_size = in_get_buffer_size;
+  in->stream.common.get_channels = in_get_channels;
+  in->stream.common.get_format = in_get_format;
+  in->stream.common.set_format = in_set_format;
+  in->stream.common.standby = in_standby;
+  in->stream.common.dump = in_dump;
+  in->stream.common.set_parameters = in_set_parameters;
+  in->stream.common.get_parameters = in_get_parameters;
+  in->stream.common.add_audio_effect = in_add_audio_effect;
+  in->stream.common.remove_audio_effect = in_remove_audio_effect;
+  in->stream.set_gain = in_set_gain;
+  in->stream.read = in_read;
+  in->stream.get_input_frames_lost = in_get_input_frames_lost;
+
+  /* initialize ha specifics */
+  ha_stream_common_init(&in->common);
+
+  *stream_in = &in->stream;
+  ha_dev->input = in;
+
+  if (ha_read_input_audio_config(&in->common) < 0) {
+    ERROR("ha_read_input_audio_config failed (%s)", strerror(errno));
+    ret = -1;
+    goto err_open;
+  }
+  // ha_read_input_audio_config() opens socket control path (or fails)
+
+  DEBUG("success");
+  return 0;
+
+err_open:
+  ha_stream_common_destroy(&in->common);
+  free(in);
+  *stream_in = NULL;
+  ha_dev->input = NULL;
+  ERROR("failed");
+  return ret;
+}
+
+static void adev_close_input_stream(struct audio_hw_device* dev,
+                                    struct audio_stream_in* stream) {
+  struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+  struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+  std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+  {
+    std::lock_guard<std::recursive_mutex> lock(*in->common.mutex);
+    const ha_state_t state = in->common.state;
+    INFO("closing input (state %d)", (int)state);
+
+    if ((state == AUDIO_HA_STATE_STARTED) || (state == AUDIO_HA_STATE_STOPPING))
+      stop_audio_datapath(&in->common);
+
+    skt_disconnect(in->common.ctrl_fd);
+    in->common.ctrl_fd = AUDIO_SKT_DISCONNECTED;
+  }
+  ha_stream_common_destroy(&in->common);
+  free(stream);
+  ha_dev->input = NULL;
+
+  DEBUG("done");
+}
+
+static int adev_dump(UNUSED_ATTR const audio_hw_device_t* device,
+                     UNUSED_ATTR int fd) {
+  FNLOG();
+
+  return 0;
+}
+
+static int adev_close(hw_device_t* device) {
+  struct ha_audio_device* ha_dev = (struct ha_audio_device*)device;
+  FNLOG();
+
+  delete ha_dev->mutex;
+  ha_dev->mutex = nullptr;
+  free(device);
+  return 0;
+}
+
+static int adev_open(const hw_module_t* module, const char* name,
+                     hw_device_t** device) {
+  struct ha_audio_device* adev;
+
+  INFO(" adev_open in ha_hw module");
+  FNLOG();
+
+  if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) {
+    ERROR("interface %s not matching [%s]", name, AUDIO_HARDWARE_INTERFACE);
+    return -EINVAL;
+  }
+
+  adev = (struct ha_audio_device*)calloc(1, sizeof(struct ha_audio_device));
+
+  if (!adev) return -ENOMEM;
+
+  adev->mutex = new std::recursive_mutex;
+
+  adev->device.common.tag = HARDWARE_DEVICE_TAG;
+  adev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
+  adev->device.common.module = (struct hw_module_t*)module;
+  adev->device.common.close = adev_close;
+
+  adev->device.init_check = adev_init_check;
+  adev->device.set_voice_volume = adev_set_voice_volume;
+  adev->device.set_master_volume = adev_set_master_volume;
+  adev->device.set_mode = adev_set_mode;
+  adev->device.set_mic_mute = adev_set_mic_mute;
+  adev->device.get_mic_mute = adev_get_mic_mute;
+  adev->device.set_parameters = adev_set_parameters;
+  adev->device.get_parameters = adev_get_parameters;
+  adev->device.get_input_buffer_size = adev_get_input_buffer_size;
+  adev->device.open_output_stream = adev_open_output_stream;
+  adev->device.close_output_stream = adev_close_output_stream;
+  adev->device.open_input_stream = adev_open_input_stream;
+  adev->device.close_input_stream = adev_close_input_stream;
+  adev->device.dump = adev_dump;
+
+  adev->output = NULL;
+
+  *device = &adev->device.common;
+
+  return 0;
+}
+
+static struct hw_module_methods_t hal_module_methods = {
+    .open = adev_open,
+};
+
+__attribute__((
+    visibility("default"))) struct audio_module HAL_MODULE_INFO_SYM = {
+    .common =
+        {
+            .tag = HARDWARE_MODULE_TAG,
+            .version_major = 1,
+            .version_minor = 0,
+            .id = AUDIO_HARDWARE_MODULE_ID,
+            .name = "Hearing Aid Audio HW HAL",
+            .author = "The Android Open Source Project",
+            .methods = &hal_module_methods,
+        },
+};
diff --git a/audio_hearing_aid_hw/src/audio_hearing_aid_hw_utils.cc b/audio_hearing_aid_hw/src/audio_hearing_aid_hw_utils.cc
new file mode 100644
index 0000000..2354632
--- /dev/null
+++ b/audio_hearing_aid_hw/src/audio_hearing_aid_hw_utils.cc
@@ -0,0 +1,41 @@
+/******************************************************************************
+ *
+ *  Copyright 2018 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.
+ *
+ ******************************************************************************/
+
+#include "audio_hearing_aid_hw.h"
+
+#define CASE_RETURN_STR(const) \
+  case const:                  \
+    return #const;
+
+const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event) {
+  switch (event) {
+    CASE_RETURN_STR(HEARING_AID_CTRL_CMD_NONE)
+    CASE_RETURN_STR(HEARING_AID_CTRL_CMD_CHECK_READY)
+    CASE_RETURN_STR(HEARING_AID_CTRL_CMD_START)
+    CASE_RETURN_STR(HEARING_AID_CTRL_CMD_STOP)
+    CASE_RETURN_STR(HEARING_AID_CTRL_CMD_SUSPEND)
+    CASE_RETURN_STR(HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG)
+    CASE_RETURN_STR(HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG)
+    CASE_RETURN_STR(HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG)
+    CASE_RETURN_STR(HEARING_AID_CTRL_CMD_OFFLOAD_START)
+    default:
+      break;
+  }
+
+  return "UNKNOWN HEARING_AID_CTRL_CMD";
+}
diff --git a/audio_hearing_aid_hw/test/audio_hearing_aid_hw_test.cc b/audio_hearing_aid_hw/test/audio_hearing_aid_hw_test.cc
new file mode 100644
index 0000000..c5d0e2b
--- /dev/null
+++ b/audio_hearing_aid_hw/test/audio_hearing_aid_hw_test.cc
@@ -0,0 +1,146 @@
+/******************************************************************************
+ *
+ *  Copyright 2017 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.
+ *
+ ******************************************************************************/
+
+#include <gtest/gtest.h>
+
+#include "audio_hearing_aid_hw/include/audio_hearing_aid_hw.h"
+
+namespace {
+static uint32_t codec_sample_rate2value(
+    btav_a2dp_codec_sample_rate_t codec_sample_rate) {
+  switch (codec_sample_rate) {
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+      return 44100;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+      return 48000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+      return 88200;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+      return 96000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+      return 176400;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+      return 192000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+      return 16000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
+      return 24000;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+      break;
+  }
+  return 0;
+}
+
+static uint32_t codec_bits_per_sample2value(
+    btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample) {
+  switch (codec_bits_per_sample) {
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+      return 16;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+      return 24;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+      return 32;
+    case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+      break;
+  }
+  return 0;
+}
+
+static uint32_t codec_channel_mode2value(
+    btav_a2dp_codec_channel_mode_t codec_channel_mode) {
+  switch (codec_channel_mode) {
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+      return 1;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+      return 2;
+    case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+      break;
+  }
+  return 0;
+}
+
+}  // namespace
+
+class AudioA2dpHwTest : public ::testing::Test {
+ protected:
+  AudioA2dpHwTest() {}
+
+ private:
+};
+
+TEST_F(AudioA2dpHwTest, test_compute_buffer_size) {
+  const btav_a2dp_codec_sample_rate_t codec_sample_rate_array[] = {
+      BTAV_A2DP_CODEC_SAMPLE_RATE_NONE,  BTAV_A2DP_CODEC_SAMPLE_RATE_44100,
+      BTAV_A2DP_CODEC_SAMPLE_RATE_48000, BTAV_A2DP_CODEC_SAMPLE_RATE_88200,
+      BTAV_A2DP_CODEC_SAMPLE_RATE_96000, BTAV_A2DP_CODEC_SAMPLE_RATE_176400,
+      BTAV_A2DP_CODEC_SAMPLE_RATE_192000};
+
+  const btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample_array[] = {
+      BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE, BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16,
+      BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24, BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32};
+
+  const btav_a2dp_codec_channel_mode_t codec_channel_mode_array[] = {
+      BTAV_A2DP_CODEC_CHANNEL_MODE_NONE, BTAV_A2DP_CODEC_CHANNEL_MODE_MONO,
+      BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO};
+
+  for (const auto codec_sample_rate : codec_sample_rate_array) {
+    for (const auto codec_bits_per_sample : codec_bits_per_sample_array) {
+      for (const auto codec_channel_mode : codec_channel_mode_array) {
+        size_t buffer_size = audio_ha_hw_stream_compute_buffer_size(
+            codec_sample_rate, codec_bits_per_sample, codec_channel_mode);
+
+        // Check for invalid input
+        if ((codec_sample_rate == BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) ||
+            (codec_bits_per_sample == BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE) ||
+            (codec_channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_NONE)) {
+          EXPECT_EQ(buffer_size,
+                    static_cast<size_t>(AUDIO_STREAM_OUTPUT_BUFFER_SZ));
+          continue;
+        }
+
+        uint32_t sample_rate = codec_sample_rate2value(codec_sample_rate);
+        EXPECT_TRUE(sample_rate != 0);
+
+        uint32_t bits_per_sample =
+            codec_bits_per_sample2value(codec_bits_per_sample);
+        EXPECT_TRUE(bits_per_sample != 0);
+
+        uint32_t number_of_channels =
+            codec_channel_mode2value(codec_channel_mode);
+        EXPECT_TRUE(number_of_channels != 0);
+
+        const uint64_t time_period_ms = 20;  // TODO: Must be a parameter
+        size_t expected_buffer_size =
+            (time_period_ms * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * sample_rate *
+             number_of_channels * (bits_per_sample / 8)) /
+            1000;
+
+        // Compute the divisor and adjust the buffer size
+        const size_t divisor = (AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * 16 *
+                                number_of_channels * bits_per_sample) /
+                               8;
+        const size_t remainder = expected_buffer_size % divisor;
+        if (remainder != 0) {
+          expected_buffer_size += divisor - remainder;
+        }
+
+        EXPECT_EQ(buffer_size, expected_buffer_size);
+      }
+    }
+  }
+}
diff --git a/binder/android/bluetooth/IBluetoothA2dp.aidl b/binder/android/bluetooth/IBluetoothA2dp.aidl
index f852d30..6606a1b 100644
--- a/binder/android/bluetooth/IBluetoothA2dp.aidl
+++ b/binder/android/bluetooth/IBluetoothA2dp.aidl
@@ -37,7 +37,6 @@
     boolean setPriority(in BluetoothDevice device, int priority);
     int getPriority(in BluetoothDevice device);
     boolean isAvrcpAbsoluteVolumeSupported();
-    oneway void adjustAvrcpAbsoluteVolume(int direction);
     oneway void setAvrcpAbsoluteVolume(int volume);
     boolean isA2dpPlaying(in BluetoothDevice device);
     BluetoothCodecStatus getCodecStatus(in BluetoothDevice device);
diff --git a/bta/Android.bp b/bta/Android.bp
index e7f94be..5759183 100644
--- a/bta/Android.bp
+++ b/bta/Android.bp
@@ -66,6 +66,8 @@
         "gatt/bta_gatts_api.cc",
         "gatt/bta_gatts_main.cc",
         "gatt/bta_gatts_utils.cc",
+        "hearing_aid/hearing_aid.cc",
+        "hearing_aid/hearing_aid_audio_source.cc",
         "hf_client/bta_hf_client_act.cc",
         "hf_client/bta_hf_client_api.cc",
         "hf_client/bta_hf_client_at.cc",
@@ -107,6 +109,10 @@
         "sys/bta_sys_main.cc",
         "sys/utl.cc",
     ],
+
+    whole_static_libs: [
+        "libaudio-hearing-aid-hw-utils",
+    ],
 }
 
 // bta unit tests for target
diff --git a/bta/av/bta_av_aact.cc b/bta/av/bta_av_aact.cc
index 2429bff..c5ba927 100644
--- a/bta/av/bta_av_aact.cc
+++ b/bta/av/bta_av_aact.cc
@@ -873,7 +873,7 @@
   /* if de-registering shut everything down */
   msg.hdr.layer_specific = p_scb->hndl;
   p_scb->started = false;
-  p_scb->current_codec = nullptr;
+  p_scb->use_rtp_header_marker_bit = false;
   p_scb->cong = false;
   p_scb->role = role;
   p_scb->cur_psc_mask = 0;
@@ -1326,7 +1326,7 @@
 
   /* close stream */
   p_scb->started = false;
-  p_scb->current_codec = nullptr;
+  p_scb->use_rtp_header_marker_bit = false;
 
   /* drop the buffers queued in L2CAP */
   L2CA_FlushChannel(p_scb->l2c_cid, L2CAP_FLUSH_CHANS_ALL);
@@ -2028,7 +2028,7 @@
 
   if (p_scb->cong) return;
 
-  if (p_scb->current_codec->useRtpHeaderMarkerBit()) {
+  if (p_scb->use_rtp_header_marker_bit) {
     m_pt |= AVDT_MARKER_SET;
   }
 
@@ -2157,7 +2157,11 @@
                    p_scb->wait, p_scb->role);
 
   p_scb->started = true;
-  p_scb->current_codec = bta_av_get_a2dp_current_codec();
+  // The RTP Header marker bit
+  A2dpCodecConfig* codec_config =
+      bta_av_get_a2dp_peer_current_codec(p_scb->peer_addr);
+  CHECK(codec_config != nullptr);
+  p_scb->use_rtp_header_marker_bit = codec_config->useRtpHeaderMarkerBit();
 
   if (p_scb->sco_suspend) {
     p_scb->sco_suspend = false;
@@ -2182,8 +2186,9 @@
     p_data = (tBTA_AV_DATA*)&hdr;
     hdr.offset = BTA_AV_RS_FAIL;
   }
-  APPL_TRACE_DEBUG("%s: peer %s wait:0x%x", __func__,
-                   p_scb->peer_addr.ToString().c_str(), p_scb->wait);
+  APPL_TRACE_DEBUG("%s: peer %s wait:0x%x use_rtp_header_marker_bit:%s",
+                   __func__, p_scb->peer_addr.ToString().c_str(), p_scb->wait,
+                   (p_scb->use_rtp_header_marker_bit) ? "true" : "false");
 
   if (p_data && (p_data->hdr.offset != BTA_AV_RS_NONE)) {
     p_scb->wait &= ~BTA_AV_WAIT_ROLE_SW_BITS;
diff --git a/bta/av/bta_av_int.h b/bta/av/bta_av_int.h
index e97321d..c453ed5 100644
--- a/bta/av/bta_av_int.h
+++ b/bta/av/bta_av_int.h
@@ -492,7 +492,9 @@
   uint8_t rc_handle; /* connected AVRCP handle */
   bool use_rc;       /* true if AVRCP is allowed */
   bool started;      /* true if stream started */
-  A2dpCodecConfig* current_codec; /* The current A2DP codec */
+  bool use_rtp_header_marker_bit; /* true if the encoded data packets have RTP
+                                   * headers, and the Marker bit in the header
+                                   * is set according to RFC 6416 */
   uint8_t
       co_started;    /* non-zero, if stream started from call-out perspective */
   bool recfg_sup;    /* true if the first attempt to reconfigure the stream was
diff --git a/bta/hearing_aid/hearing_aid.cc b/bta/hearing_aid/hearing_aid.cc
new file mode 100644
index 0000000..65eb1a1
--- /dev/null
+++ b/bta/hearing_aid/hearing_aid.cc
@@ -0,0 +1,48 @@
+/******************************************************************************
+ *
+ *  Copyright 2018 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.
+ *
+ ******************************************************************************/
+
+#include "bta_hearing_aid_api.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+
+using base::Closure;
+
+void HearingAid::Initialize(
+    bluetooth::hearing_aid::HearingAidCallbacks* callbacks, Closure initCb) {
+  CHECK(false) << "unimplemented yet";
+}
+
+bool HearingAid::IsInitialized() {
+  CHECK(false) << "unimplemented yet";
+  return false;
+}
+
+HearingAid* HearingAid::Get() {
+  CHECK(false) << "unimplemented yet";
+  return nullptr;
+};
+
+void HearingAid::AddFromStorage(const RawAddress& address, uint16_t psm,
+                                uint8_t capabilities, uint8_t codecs,
+                                uint16_t audio_control_point_handle,
+                                uint16_t volume_handle, uint64_t hiSyncId) {
+  CHECK(false) << "unimplemented yet";
+};
+
+void HearingAid::CleanUp() { CHECK(false) << "unimplemented yet"; };
diff --git a/bta/hearing_aid/hearing_aid_audio_source.cc b/bta/hearing_aid/hearing_aid_audio_source.cc
new file mode 100644
index 0000000..276957b
--- /dev/null
+++ b/bta/hearing_aid/hearing_aid_audio_source.cc
@@ -0,0 +1,262 @@
+/******************************************************************************
+ *
+ *  Copyright 2018 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.
+ *
+ ******************************************************************************/
+
+#include "audio_hearing_aid_hw/include/audio_hearing_aid_hw.h"
+#include "bta_hearing_aid_api.h"
+#include "osi/include/alarm.h"
+#include "uipc.h"
+
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <include/hardware/bt_av.h>
+
+using base::FilePath;
+extern const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event);
+
+namespace {
+int bit_rate = 16;
+int sample_rate = 16000;
+int data_interval_ms = 20 /* msec */;
+int num_channels = 2;
+alarm_t* audio_timer = nullptr;
+
+HearingAidAudioReceiver* localAudioReceiver;
+std::unique_ptr<tUIPC_STATE> uipc_hearing_aid;
+
+void send_audio_data(void*) {
+  int bytes_per_tick =
+      (num_channels * sample_rate * data_interval_ms * (bit_rate / 8)) / 1000;
+
+  uint16_t event;
+  uint8_t p_buf[bytes_per_tick];
+
+  uint32_t bytes_read = UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO,
+                                  &event, p_buf, bytes_per_tick);
+
+  VLOG(2) << "bytes_read: " << bytes_read;
+
+  std::vector<uint8_t> data(p_buf, p_buf + bytes_read);
+
+  localAudioReceiver->OnAudioDataReady(data);
+}
+
+void hearing_aid_send_ack(tHEARING_AID_CTRL_ACK status) {
+  uint8_t ack = status;
+  DVLOG(2) << "Hearing Aid audio ctrl ack: " << status;
+  UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
+}
+
+void hearing_aid_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
+  DVLOG(2) << "Hearing Aid audio data event: " << event;
+  switch (event) {
+    case UIPC_OPEN_EVT:
+      /*
+       * Read directly from media task from here on (keep callback for
+       * connection events.
+       */
+      UIPC_Ioctl(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO,
+                 UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
+      UIPC_Ioctl(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
+                 reinterpret_cast<void*>(0));
+
+      audio_timer = alarm_new_periodic("hearing_aid_data_timer");
+      alarm_set_on_mloop(audio_timer, data_interval_ms, send_audio_data,
+                         nullptr);
+      break;
+    case UIPC_CLOSE_EVT:
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      if (audio_timer) {
+        alarm_cancel(audio_timer);
+      }
+      break;
+    default:
+      LOG(ERROR) << "Hearing Aid audio data event not recognized:" << event;
+  }
+}
+
+void hearing_aid_recv_ctrl_data() {
+  tHEARING_AID_CTRL_CMD cmd = HEARING_AID_CTRL_CMD_NONE;
+  int n;
+
+  uint8_t read_cmd = 0; /* The read command size is one octet */
+  n = UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
+  cmd = static_cast<tHEARING_AID_CTRL_CMD>(read_cmd);
+
+  /* detach on ctrl channel means audioflinger process was terminated */
+  if (n == 0) {
+    LOG(WARNING) << __func__ << "CTRL CH DETACHED";
+    UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL);
+    return;
+  }
+
+  VLOG(2) << __func__ << " " << audio_ha_hw_dump_ctrl_event(cmd);
+  //  a2dp_cmd_pending = cmd;
+
+  switch (cmd) {
+    case HEARING_AID_CTRL_CMD_CHECK_READY:
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      break;
+
+    case HEARING_AID_CTRL_CMD_START:
+      localAudioReceiver->OnAudioResume();
+      // timer is restarted in UIPC_Open
+      UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, hearing_aid_data_cb);
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      break;
+
+    case HEARING_AID_CTRL_CMD_STOP:
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      break;
+
+    case HEARING_AID_CTRL_CMD_SUSPEND:
+      if (audio_timer) alarm_cancel(audio_timer);
+      localAudioReceiver->OnAudioSuspend();
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      break;
+
+    case HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG: {
+      btav_a2dp_codec_config_t codec_config;
+      btav_a2dp_codec_config_t codec_capability;
+      if (sample_rate == 16000) {
+        codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
+        codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
+      } else if (sample_rate == 24000) {
+        codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
+        codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
+      } else {
+        LOG(FATAL) << "unsupported sample rate: " << sample_rate;
+      }
+
+      codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+      codec_capability.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+
+      codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+      codec_capability.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      // Send the current codec config
+      UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                reinterpret_cast<const uint8_t*>(&codec_config.sample_rate),
+                sizeof(btav_a2dp_codec_sample_rate_t));
+      UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                reinterpret_cast<const uint8_t*>(&codec_config.bits_per_sample),
+                sizeof(btav_a2dp_codec_bits_per_sample_t));
+      UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                reinterpret_cast<const uint8_t*>(&codec_config.channel_mode),
+                sizeof(btav_a2dp_codec_channel_mode_t));
+      // Send the current codec capability
+      UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                reinterpret_cast<const uint8_t*>(&codec_capability.sample_rate),
+                sizeof(btav_a2dp_codec_sample_rate_t));
+      UIPC_Send(
+          *uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+          reinterpret_cast<const uint8_t*>(&codec_capability.bits_per_sample),
+          sizeof(btav_a2dp_codec_bits_per_sample_t));
+      UIPC_Send(
+          *uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+          reinterpret_cast<const uint8_t*>(&codec_capability.channel_mode),
+          sizeof(btav_a2dp_codec_channel_mode_t));
+      break;
+    }
+
+    case HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: {
+      // TODO: we only support one config for now!
+      btav_a2dp_codec_config_t codec_config;
+      codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+      codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+      codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+      // Send the current codec config
+      if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                    reinterpret_cast<uint8_t*>(&codec_config.sample_rate),
+                    sizeof(btav_a2dp_codec_sample_rate_t)) !=
+          sizeof(btav_a2dp_codec_sample_rate_t)) {
+        LOG(ERROR) << __func__ << "Error reading sample rate from audio HAL";
+        break;
+      }
+      if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                    reinterpret_cast<uint8_t*>(&codec_config.bits_per_sample),
+                    sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
+          sizeof(btav_a2dp_codec_bits_per_sample_t)) {
+        LOG(ERROR) << __func__
+                   << "Error reading bits per sample from audio HAL";
+
+        break;
+      }
+      if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+                    reinterpret_cast<uint8_t*>(&codec_config.channel_mode),
+                    sizeof(btav_a2dp_codec_channel_mode_t)) !=
+          sizeof(btav_a2dp_codec_channel_mode_t)) {
+        LOG(ERROR) << __func__ << "Error reading channel mode from audio HAL";
+
+        break;
+      }
+      LOG(INFO) << __func__ << " HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: "
+                << "sample_rate=" << codec_config.sample_rate
+                << "bits_per_sample=" << codec_config.bits_per_sample
+                << "channel_mode=" << codec_config.channel_mode;
+      break;
+    }
+
+    default:
+      LOG(ERROR) << __func__ << "UNSUPPORTED CMD: " << cmd;
+      hearing_aid_send_ack(HEARING_AID_CTRL_ACK_FAILURE);
+      break;
+  }
+  VLOG(2) << __func__ << " a2dp-ctrl-cmd : " << audio_ha_hw_dump_ctrl_event(cmd)
+          << " DONE";
+}
+
+void hearing_aid_ctrl_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
+  VLOG(2) << "Hearing Aid audio ctrl event: " << event;
+  switch (event) {
+    case UIPC_OPEN_EVT:
+      break;
+    case UIPC_CLOSE_EVT:
+      UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb);
+      break;
+    case UIPC_RX_DATA_READY_EVT:
+      hearing_aid_recv_ctrl_data();
+      break;
+    default:
+      LOG(ERROR) << "Hearing Aid audio ctrl unrecognized event: " << event;
+  }
+}
+}  // namespace
+
+void HearingAidAudioSource::Start(const CodecConfiguration& codecConfiguration,
+                                  HearingAidAudioReceiver* audioReceiver) {
+  localAudioReceiver = audioReceiver;
+  VLOG(2) << "Hearing Aid UIPC Open";
+}
+
+void HearingAidAudioSource::Stop() {
+  if (audio_timer) {
+    alarm_cancel(audio_timer);
+  }
+}
+
+void HearingAidAudioSource::Initialize() {
+  uipc_hearing_aid = UIPC_Init();
+  UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb);
+}
+
+void HearingAidAudioSource::CleanUp() {
+  UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_ALL);
+}
diff --git a/bta/include/bta_hearing_aid_api.h b/bta/include/bta_hearing_aid_api.h
new file mode 100644
index 0000000..6b1f249
--- /dev/null
+++ b/bta/include/bta_hearing_aid_api.h
@@ -0,0 +1,85 @@
+/******************************************************************************
+ *
+ *  Copyright 2018 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.
+ *
+ ******************************************************************************/
+
+#pragma once
+
+#include <base/callback_forward.h>
+#include <hardware/bt_hearing_aid.h>
+
+using bluetooth::Uuid;
+
+/** Implementations of HearingAid will also implement this interface */
+class HearingAidAudioReceiver {
+ public:
+  virtual ~HearingAidAudioReceiver() = default;
+  virtual void OnAudioDataReady(const std::vector<uint8_t>& data) = 0;
+  virtual void OnAudioSuspend();
+  virtual void OnAudioResume();
+};
+
+class HearingAid {
+ public:
+  virtual ~HearingAid() = default;
+
+  static void Initialize(bluetooth::hearing_aid::HearingAidCallbacks* callbacks,
+                         base::Closure initCb);
+  static void CleanUp();
+  static bool IsInitialized();
+  static HearingAid* Get();
+
+  static void AddFromStorage(const RawAddress& address, uint16_t psm,
+                             uint8_t capabilities, uint8_t codec,
+                             uint16_t audioControlPointHandle,
+                             uint16_t volumeHandle, uint64_t hiSyncId);
+
+  virtual void Connect(const RawAddress& address) = 0;
+  virtual void Disconnect(const RawAddress& address) = 0;
+  virtual void SetVolume(int8_t volume) = 0;
+};
+
+/* Represents configuration of audio codec, as exchanged between hearing aid and
+ * phone.
+ * It can also be passed to the audio source to configure its parameters.
+ */
+struct CodecConfiguration {
+  /** sampling rate that the codec expects to receive from audio framework */
+  uint32_t sample_rate;
+
+  /** bitrate that codec expects to receive from audio framework in bits per
+   * channel */
+  uint32_t bit_rate;
+
+  /** Data interval determines how often we send samples to the remote. This
+   * should match how often we grab data from audio source, optionally we can
+   * grab data every 2 or 3 intervals, but this would increase latency.
+   *
+   * Value is provided in ms, must be divisable by 1.25 to make sure the
+   * connection interval is integer.
+   */
+  uint16_t data_interval_ms;
+};
+
+/** Represents source of audio for hearing aids */
+class HearingAidAudioSource {
+ public:
+  static void Start(const CodecConfiguration& codecConfiguration,
+                    HearingAidAudioReceiver* audioReceiver);
+  static void Stop();
+  static void Initialize();
+  static void CleanUp();
+};
diff --git a/btif/Android.bp b/btif/Android.bp
index 88b3cfa..c23ffa5 100644
--- a/btif/Android.bp
+++ b/btif/Android.bp
@@ -54,6 +54,7 @@
         "src/btif_gatt_server.cc",
         "src/btif_gatt_test.cc",
         "src/btif_gatt_util.cc",
+        "src/btif_hearing_aid.cc",
         "src/btif_hf.cc",
         "src/btif_hf_client.cc",
         "src/btif_hh.cc",
diff --git a/btif/co/bta_av_co.cc b/btif/co/bta_av_co.cc
index 21842eb..edaa7b6 100644
--- a/btif/co/bta_av_co.cc
+++ b/btif/co/bta_av_co.cc
@@ -185,6 +185,14 @@
   A2dpCodecConfig* GetActivePeerCurrentCodec();
 
   /**
+   * Get the current codec configuration for a peer.
+   *
+   * @param peer_address the peer address
+   * @return the current codec configuration if found, otherwise nullptr
+   */
+  A2dpCodecConfig* GetPeerCurrentCodec(const RawAddress& peer_address);
+
+  /**
    * Find the peer UUID for a given BTA AV handle.
    *
    * @param bta_av_handle the BTA AV handle to use
@@ -699,6 +707,16 @@
   return active_peer_->GetCodecs()->getCurrentCodecConfig();
 }
 
+A2dpCodecConfig* BtaAvCo::GetPeerCurrentCodec(const RawAddress& peer_address) {
+  std::lock_guard<std::recursive_mutex> lock(codec_lock_);
+
+  BtaAvCoPeer* peer = FindPeer(peer_address);
+  if (peer == nullptr || peer->GetCodecs() == nullptr) {
+    return nullptr;
+  }
+  return peer->GetCodecs()->getCurrentCodecConfig();
+}
+
 BtaAvCoPeer* BtaAvCo::FindPeer(const RawAddress& peer_address) {
   for (size_t i = 0; i < BTA_AV_CO_NUM_ELEMENTS(peers_); i++) {
     BtaAvCoPeer* p_peer = &peers_[i];
@@ -1785,6 +1803,11 @@
   return bta_av_co_cb.GetActivePeerCurrentCodec();
 }
 
+A2dpCodecConfig* bta_av_get_a2dp_peer_current_codec(
+    const RawAddress& peer_address) {
+  return bta_av_co_cb.GetPeerCurrentCodec(peer_address);
+}
+
 bool bta_av_co_audio_init(btav_a2dp_codec_index_t codec_index,
                           AvdtpSepConfig* p_cfg) {
   return A2DP_InitCodecConfig(codec_index, p_cfg);
diff --git a/btif/include/btif_av_co.h b/btif/include/btif_av_co.h
index 0576c27..df7454f 100644
--- a/btif/include/btif_av_co.h
+++ b/btif/include/btif_av_co.h
@@ -66,9 +66,16 @@
     const std::vector<btav_a2dp_codec_config_t>& codec_priorities);
 
 // Gets the current A2DP codec for the active peer.
-// Returns a pointer to the current |A2dpCodec| if valid, otherwise nullptr.
+// Returns a pointer to the current |A2dpCodecConfig| if valid, otherwise
+// nullptr.
 A2dpCodecConfig* bta_av_get_a2dp_current_codec(void);
 
+// Gets the current A2DP codec for a peer identified by |peer_address|.
+// Returns a pointer to the current |A2dpCodecConfig| if valid, otherwise
+// nullptr.
+A2dpCodecConfig* bta_av_get_a2dp_peer_current_codec(
+    const RawAddress& peer_address);
+
 // Dump A2DP codec debug-related information for the A2DP module.
 // |fd| is the file descriptor to use for writing the ASCII formatted
 // information.
diff --git a/btif/include/btif_storage.h b/btif/include/btif_storage.h
index 1ff7f22..7e90162 100644
--- a/btif/include/btif_storage.h
+++ b/btif/include/btif_storage.h
@@ -195,6 +195,12 @@
  ******************************************************************************/
 bt_status_t btif_storage_remove_hid_info(RawAddress* remote_bd_addr);
 
+/** Loads information about bonded hearing aid devices */
+void btif_storage_load_bonded_hearing_aids();
+
+/** Deletes the bonded hearing aid device info from NVRAM */
+void btif_storage_remove_hearing_aid(const RawAddress& address);
+
 /*******************************************************************************
  *
  * Function         btif_storage_is_retricted_device
diff --git a/btif/src/bluetooth.cc b/btif/src/bluetooth.cc
index d0c090b..edd6dab 100644
--- a/btif/src/bluetooth.cc
+++ b/btif/src/bluetooth.cc
@@ -37,6 +37,7 @@
 #include <hardware/bt_av.h>
 #include <hardware/bt_gatt.h>
 #include <hardware/bt_hd.h>
+#include <hardware/bt_hearing_aid.h>
 #include <hardware/bt_hf_client.h>
 #include <hardware/bt_hh.h>
 #include <hardware/bt_hl.h>
@@ -71,6 +72,8 @@
 /* Test interface includes */
 #include "mca_api.h"
 
+using bluetooth::hearing_aid::HearingAidInterface;
+
 /*******************************************************************************
  *  Static variables
  ******************************************************************************/
@@ -109,6 +112,8 @@
 extern const btrc_ctrl_interface_t* btif_rc_ctrl_get_interface();
 /*SDP search client*/
 extern const btsdp_interface_t* btif_sdp_get_interface();
+/*Hearing Aid client*/
+extern HearingAidInterface* btif_hearing_aid_get_interface();
 
 /* List all test interface here */
 extern const btmcap_test_interface_t* stack_mcap_get_interface();
@@ -375,6 +380,8 @@
   if (is_profile(profile_id, BT_TEST_INTERFACE_MCAP_ID))
     return stack_mcap_get_interface();
 
+  if (is_profile(profile_id, BT_PROFILE_HEARING_AID_ID))
+    return btif_hearing_aid_get_interface();
   return NULL;
 }
 
diff --git a/btif/src/btif_a2dp_control.cc b/btif/src/btif_a2dp_control.cc
index d2a804f..714cbe2 100644
--- a/btif/src/btif_a2dp_control.cc
+++ b/btif/src/btif_a2dp_control.cc
@@ -48,15 +48,16 @@
 
 /* We can have max one command pending */
 static tA2DP_CTRL_CMD a2dp_cmd_pending = A2DP_CTRL_CMD_NONE;
+std::unique_ptr<tUIPC_STATE> a2dp_uipc;
 
 void btif_a2dp_control_init(void) {
-  UIPC_Init(NULL, UIPC_USER_A2DP);
-  UIPC_Open(UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
+  a2dp_uipc = UIPC_Init();
+  UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
 }
 
 void btif_a2dp_control_cleanup(void) {
   /* This calls blocks until UIPC is fully closed */
-  UIPC_Close(UIPC_CH_ID_ALL, UIPC_USER_A2DP);
+  UIPC_Close(*a2dp_uipc, UIPC_CH_ID_ALL);
 }
 
 static void btif_a2dp_recv_ctrl_data(void) {
@@ -64,13 +65,13 @@
   int n;
 
   uint8_t read_cmd = 0; /* The read command size is one octet */
-  n = UIPC_Read(UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
+  n = UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
   cmd = static_cast<tA2DP_CTRL_CMD>(read_cmd);
 
   /* detach on ctrl channel means audioflinger process was terminated */
   if (n == 0) {
     APPL_TRACE_WARNING("%s: CTRL CH DETACHED", __func__);
-    UIPC_Close(UIPC_CH_ID_AV_CTRL, UIPC_USER_A2DP);
+    UIPC_Close(*a2dp_uipc, UIPC_CH_ID_AV_CTRL);
     return;
   }
 
@@ -117,7 +118,7 @@
 
       if (btif_av_stream_ready()) {
         /* Setup audio data channel listener */
-        UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
+        UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
 
         /*
          * Post start event and wait for audio path to open.
@@ -135,7 +136,7 @@
          * Already started, setup audio data channel listener and ACK
          * back immediately.
          */
-        UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
+        UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
         btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
         break;
       }
@@ -174,9 +175,10 @@
       tA2DP_CHANNEL_COUNT channel_count = btif_a2dp_sink_get_channel_count();
 
       btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, reinterpret_cast<uint8_t*>(&sample_rate),
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
+                reinterpret_cast<uint8_t*>(&sample_rate),
                 sizeof(tA2DP_SAMPLE_RATE));
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, &channel_count,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, &channel_count,
                 sizeof(tA2DP_CHANNEL_COUNT));
       break;
     }
@@ -199,25 +201,27 @@
 
       btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
       // Send the current codec config
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                 reinterpret_cast<const uint8_t*>(&codec_config.sample_rate),
                 sizeof(btav_a2dp_codec_sample_rate_t));
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                 reinterpret_cast<const uint8_t*>(&codec_config.bits_per_sample),
                 sizeof(btav_a2dp_codec_bits_per_sample_t));
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                 reinterpret_cast<const uint8_t*>(&codec_config.channel_mode),
                 sizeof(btav_a2dp_codec_channel_mode_t));
       // Send the current codec capability
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                 reinterpret_cast<const uint8_t*>(&codec_capability.sample_rate),
                 sizeof(btav_a2dp_codec_sample_rate_t));
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, reinterpret_cast<const uint8_t*>(
-                                           &codec_capability.bits_per_sample),
-                sizeof(btav_a2dp_codec_bits_per_sample_t));
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, reinterpret_cast<const uint8_t*>(
-                                           &codec_capability.channel_mode),
-                sizeof(btav_a2dp_codec_channel_mode_t));
+      UIPC_Send(
+          *a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
+          reinterpret_cast<const uint8_t*>(&codec_capability.bits_per_sample),
+          sizeof(btav_a2dp_codec_bits_per_sample_t));
+      UIPC_Send(
+          *a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
+          reinterpret_cast<const uint8_t*>(&codec_capability.channel_mode),
+          sizeof(btav_a2dp_codec_channel_mode_t));
       break;
     }
 
@@ -229,7 +233,7 @@
 
       btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
       // Send the current codec config
-      if (UIPC_Read(UIPC_CH_ID_AV_CTRL, 0,
+      if (UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                     reinterpret_cast<uint8_t*>(&codec_config.sample_rate),
                     sizeof(btav_a2dp_codec_sample_rate_t)) !=
           sizeof(btav_a2dp_codec_sample_rate_t)) {
@@ -237,7 +241,7 @@
                          __func__);
         break;
       }
-      if (UIPC_Read(UIPC_CH_ID_AV_CTRL, 0,
+      if (UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                     reinterpret_cast<uint8_t*>(&codec_config.bits_per_sample),
                     sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
           sizeof(btav_a2dp_codec_bits_per_sample_t)) {
@@ -245,7 +249,7 @@
                          __func__);
         break;
       }
-      if (UIPC_Read(UIPC_CH_ID_AV_CTRL, 0,
+      if (UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                     reinterpret_cast<uint8_t*>(&codec_config.channel_mode),
                     sizeof(btav_a2dp_codec_channel_mode_t)) !=
           sizeof(btav_a2dp_codec_channel_mode_t)) {
@@ -270,17 +274,19 @@
     case A2DP_CTRL_GET_PRESENTATION_POSITION: {
       btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
 
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                 (uint8_t*)&(delay_report_stats.total_bytes_read),
                 sizeof(uint64_t));
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
                 (uint8_t*)&(delay_report_stats.audio_delay), sizeof(uint16_t));
 
       uint32_t seconds = delay_report_stats.timestamp.tv_sec;
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&seconds, sizeof(seconds));
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&seconds,
+                sizeof(seconds));
 
       uint32_t nsec = delay_report_stats.timestamp.tv_nsec;
-      UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&nsec, sizeof(nsec));
+      UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&nsec,
+                sizeof(nsec));
       break;
     }
     default:
@@ -304,7 +310,7 @@
     case UIPC_CLOSE_EVT:
       /* restart ctrl server unless we are shutting down */
       if (btif_a2dp_source_media_task_is_running())
-        UIPC_Open(UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
+        UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
       break;
 
     case UIPC_RX_DATA_READY_EVT:
@@ -329,8 +335,9 @@
        * Read directly from media task from here on (keep callback for
        * connection events.
        */
-      UIPC_Ioctl(UIPC_CH_ID_AV_AUDIO, UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
-      UIPC_Ioctl(UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
+      UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO,
+                 UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
+      UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
                  reinterpret_cast<void*>(A2DP_DATA_READ_POLL_MS));
 
       if (btif_av_get_peer_sep() == AVDT_TSEP_SNK) {
@@ -378,7 +385,7 @@
   a2dp_cmd_pending = A2DP_CTRL_CMD_NONE;
 
   /* Acknowledge start request */
-  UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
+  UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
 }
 
 void btif_a2dp_control_log_bytes_read(uint32_t bytes_read) {
diff --git a/btif/src/btif_a2dp_source.cc b/btif/src/btif_a2dp_source.cc
index ba1184a..669789e 100644
--- a/btif/src/btif_a2dp_source.cc
+++ b/btif/src/btif_a2dp_source.cc
@@ -52,6 +52,8 @@
 using system_bt_osi::BluetoothMetricsLogger;
 using system_bt_osi::A2dpSessionMetrics;
 
+extern std::unique_ptr<tUIPC_STATE> a2dp_uipc;
+
 /**
  * The typical runlevel of the tx queue size is ~1 buffer
  * but due to link flow control or thread preemption in lower
@@ -694,13 +696,13 @@
 
   // Keep track of audio data still left in the pipe
   btif_a2dp_control_log_bytes_read(
-      UIPC_Read(UIPC_CH_ID_AV_AUDIO, &event, p_buf, sizeof(p_buf)));
+      UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, &event, p_buf, sizeof(p_buf)));
 
   /* Stop the timer first */
   alarm_free(btif_a2dp_source_cb.media_alarm);
   btif_a2dp_source_cb.media_alarm = nullptr;
 
-  UIPC_Close(UIPC_CH_ID_AV_AUDIO, UIPC_USER_A2DP);
+  UIPC_Close(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO);
 
   /*
    * Try to send acknowldegment once the media stream is
@@ -759,7 +761,8 @@
 
 static uint32_t btif_a2dp_source_read_callback(uint8_t* p_buf, uint32_t len) {
   uint16_t event;
-  uint32_t bytes_read = UIPC_Read(UIPC_CH_ID_AV_AUDIO, &event, p_buf, len);
+  uint32_t bytes_read =
+      UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, &event, p_buf, len);
 
   if (bytes_read < len) {
     LOG_WARN(LOG_TAG, "%s: UNDERFLOW: ONLY READ %d BYTES OUT OF %d", __func__,
@@ -869,7 +872,7 @@
       time_get_os_boottime_us();
   fixed_queue_flush(btif_a2dp_source_cb.tx_audio_queue, osi_free);
 
-  UIPC_Ioctl(UIPC_CH_ID_AV_AUDIO, UIPC_REQ_RX_FLUSH, nullptr);
+  UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_REQ_RX_FLUSH, nullptr);
 }
 
 static bool btif_a2dp_source_audio_tx_flush_req(void) {
diff --git a/btif/src/btif_av.cc b/btif/src/btif_av.cc
index ed48d9a..b4a7891 100644
--- a/btif/src/btif_av.cc
+++ b/btif/src/btif_av.cc
@@ -232,6 +232,20 @@
    */
   bool CanBeDeleted() const;
 
+  /**
+   * Check whether the peer is the active one.
+   *
+   * @return true if this peer is the active one
+   */
+  bool IsActivePeer() const { return (PeerAddress() == ActivePeerAddress()); }
+
+  /**
+   * Get the address of the active peer.
+   *
+   * @return the address of the active peer
+   */
+  const RawAddress& ActivePeerAddress() const;
+
   const RawAddress& PeerAddress() const { return peer_address_; }
   bool IsSource() const { return (peer_sep_ == AVDT_TSEP_SRC); }
   bool IsSink() const { return (peer_sep_ == AVDT_TSEP_SNK); }
@@ -800,6 +814,18 @@
       (state_machine_.PreviousStateId() != BtifAvStateMachine::kStateInvalid));
 }
 
+const RawAddress& BtifAvPeer::ActivePeerAddress() const {
+  if (IsSource()) {
+    return btif_av_sink.ActivePeer();
+  }
+  if (IsSink()) {
+    return btif_av_source.ActivePeer();
+  }
+  LOG(FATAL) << __PRETTY_FUNCTION__ << ": A2DP peer " << PeerAddress()
+             << " is neither Source nor Sink";
+  return RawAddress::kEmpty;
+}
+
 bool BtifAvPeer::IsConnected() const {
   int state = state_machine_.StateId();
   return ((state == BtifAvStateMachine::kStateOpened) ||
@@ -1175,20 +1201,13 @@
   peer_.ClearAllFlags();
 
   // Stop A2DP if this is the active peer
-  RawAddress active_peer = RawAddress::kEmpty;
-  if (peer_.IsSink()) {
-    active_peer = btif_av_source.ActivePeer();
-  } else if (peer_.IsSource()) {
-    active_peer = btif_av_sink.ActivePeer();
-  }
-  if (peer_.PeerAddress() == active_peer || active_peer.IsEmpty()) {
+  if (peer_.IsActivePeer() || peer_.ActivePeerAddress().IsEmpty()) {
     btif_a2dp_on_idle();
   }
 
   // Reset the active peer if this was the active peer and
   // the Idle state was reentered
-  if (peer_.PeerAddress() == active_peer && !active_peer.IsEmpty() &&
-      peer_.CanBeDeleted()) {
+  if (peer_.IsActivePeer() && peer_.CanBeDeleted()) {
     if (peer_.IsSink()) {
       btif_av_source.SetActivePeer(RawAddress::kEmpty);
     } else if (peer_.IsSource()) {
@@ -1710,7 +1729,7 @@
       // Remain in Open state if status failed
       if (p_av->start.status != BTA_AV_SUCCESS) return false;
 
-      if (peer_.IsSource()) {
+      if (peer_.IsSource() && peer_.IsActivePeer()) {
         // Remove flush state, ready for streaming
         btif_a2dp_sink_set_rx_flush(false);
       }
@@ -1740,7 +1759,9 @@
 
     case BTA_AV_CLOSE_EVT:
       // AVDTP link is closed
-      btif_a2dp_on_stopped(nullptr);
+      if (peer_.IsActivePeer()) {
+        btif_a2dp_on_stopped(nullptr);
+      }
 
       // Inform the application that we are disconnected
       btif_report_connection_state(peer_.PeerAddress(),
@@ -1857,7 +1878,9 @@
 
       if (peer_.IsSink()) {
         // Immediately stop transmission of frames while suspend is pending
-        btif_a2dp_source_set_tx_flush(true);
+        if (peer_.IsActivePeer()) {
+          btif_a2dp_source_set_tx_flush(true);
+        }
       } else if (peer_.IsSource()) {
         btif_a2dp_on_stopped(nullptr);
       }
@@ -1898,7 +1921,7 @@
       if (p_av->suspend.status != BTA_AV_SUCCESS) {
         peer_.ClearFlags(BtifAvPeer::kFlagLocalSuspendPending);
 
-        if (peer_.IsSink()) {
+        if (peer_.IsSink() && peer_.IsActivePeer()) {
           // Suspend failed, reset back tx flush state
           btif_a2dp_source_set_tx_flush(false);
         }
@@ -1952,7 +1975,9 @@
       peer_.SetFlags(BtifAvPeer::kFlagPendingStop);
 
       // AVDTP link is closed
-      btif_a2dp_on_stopped(nullptr);
+      if (peer_.IsActivePeer()) {
+        btif_a2dp_on_stopped(nullptr);
+      }
 
       // Inform the application that we are disconnected
       btif_report_connection_state(peer_.PeerAddress(),
@@ -1986,12 +2011,14 @@
   BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__,
                    peer_.PeerAddress().ToString().c_str());
 
-  if (peer_.IsSink()) {
-    // Immediately stop transmission of frames
-    btif_a2dp_source_set_tx_flush(true);
-    // Wait for Audio Flinger to stop A2DP
-  } else if (peer_.IsSource()) {
-    btif_a2dp_sink_set_rx_flush(true);
+  if (peer_.IsActivePeer()) {
+    if (peer_.IsSink()) {
+      // Immediately stop transmission of frames
+      btif_a2dp_source_set_tx_flush(true);
+      // Wait for Audio Flinger to stop A2DP
+    } else if (peer_.IsSource()) {
+      btif_a2dp_sink_set_rx_flush(true);
+    }
   }
 }
 
@@ -2014,7 +2041,9 @@
 
     case BTA_AV_STOP_EVT:
     case BTIF_AV_STOP_STREAM_REQ_EVT:
-      btif_a2dp_on_stopped(nullptr);
+      if (peer_.IsActivePeer()) {
+        btif_a2dp_on_stopped(nullptr);
+      }
       break;
 
     case BTA_AV_CLOSE_EVT:
diff --git a/btif/src/btif_core.cc b/btif/src/btif_core.cc
index e3508ad..975fb26 100644
--- a/btif/src/btif_core.cc
+++ b/btif/src/btif_core.cc
@@ -527,7 +527,7 @@
 bt_status_t btif_cleanup_bluetooth(void) {
   LOG_INFO(LOG_TAG, "%s entered", __func__);
 
-  BTA_VendorCleanup();
+  do_in_bta_thread(FROM_HERE, base::Bind(&BTA_VendorCleanup));
 
   btif_dm_cleanup();
   btif_jni_disassociate();
diff --git a/btif/src/btif_hearing_aid.cc b/btif/src/btif_hearing_aid.cc
new file mode 100644
index 0000000..961a542
--- /dev/null
+++ b/btif/src/btif_hearing_aid.cc
@@ -0,0 +1,120 @@
+/******************************************************************************
+ *
+ *  Copyright 2018 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.
+ *
+ ******************************************************************************/
+
+/* Hearing Aid Profile Interface */
+
+#include "bta_closure_api.h"
+#include "bta_hearing_aid_api.h"
+#include "btif_common.h"
+#include "btif_storage.h"
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_hearing_aid.h>
+
+using base::Bind;
+using base::Unretained;
+using bluetooth::hearing_aid::ConnectionState;
+using bluetooth::hearing_aid::HearingAidCallbacks;
+using bluetooth::hearing_aid::HearingAidInterface;
+
+// template specialization
+template <>
+base::Callback<void()> jni_thread_wrapper(
+    const tracked_objects::Location& from_here, base::Callback<void()> cb) {
+  return base::Bind(
+      [](const tracked_objects::Location& from_here,
+         base::Callback<void()> cb) { do_in_jni_thread(from_here, cb); },
+      from_here, std::move(cb));
+}
+
+namespace {
+class HearingAidInterfaceImpl;
+std::unique_ptr<HearingAidInterface> hearingAidInstance;
+
+class HearingAidInterfaceImpl
+    : public bluetooth::hearing_aid::HearingAidInterface,
+      public HearingAidCallbacks {
+  ~HearingAidInterfaceImpl() = default;
+
+  void Init(HearingAidCallbacks* callbacks) {
+    DVLOG(2) << __func__;
+    this->callbacks = callbacks;
+    do_in_bta_thread(
+        FROM_HERE,
+        Bind(&HearingAid::Initialize, this,
+             jni_thread_wrapper(FROM_HERE,
+                                Bind(&btif_storage_load_bonded_hearing_aids))));
+  }
+
+  void OnConnectionState(ConnectionState state,
+                         const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address;
+    do_in_jni_thread(FROM_HERE, Bind(&HearingAidCallbacks::OnConnectionState,
+                                     Unretained(callbacks), state, address));
+  }
+
+  void OnDeviceAvailable(uint8_t capabilities, uint64_t hiSyncId,
+                         const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address
+             << ", hiSyncId: " << loghex(hiSyncId)
+             << ", capabilities: " << loghex(capabilities);
+    do_in_jni_thread(FROM_HERE, Bind(&HearingAidCallbacks::OnDeviceAvailable,
+                                     Unretained(callbacks), capabilities,
+                                     hiSyncId, address));
+  }
+
+  void Connect(const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address;
+    do_in_bta_thread(FROM_HERE, Bind(&HearingAid::Connect,
+                                     Unretained(HearingAid::Get()), address));
+  }
+
+  void Disconnect(const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address;
+    do_in_bta_thread(FROM_HERE, Bind(&HearingAid::Disconnect,
+                                     Unretained(HearingAid::Get()), address));
+    do_in_jni_thread(FROM_HERE,
+                     Bind(&btif_storage_remove_hearing_aid, address));
+  }
+
+  void SetVolume(int8_t volume) override {
+    DVLOG(2) << __func__ << " volume: " << +volume;
+    do_in_bta_thread(FROM_HERE, Bind(&HearingAid::SetVolume,
+                                     Unretained(HearingAid::Get()), volume));
+  }
+
+  void Cleanup(void) {
+    DVLOG(2) << __func__;
+    do_in_bta_thread(FROM_HERE, Bind(&HearingAid::CleanUp));
+  }
+
+ private:
+  HearingAidCallbacks* callbacks;
+};
+
+}  // namespace
+
+HearingAidInterface* btif_hearing_aid_get_interface() {
+  if (!hearingAidInstance)
+    hearingAidInstance.reset(new HearingAidInterfaceImpl());
+
+  return hearingAidInstance.get();
+}
diff --git a/btif/src/btif_storage.cc b/btif/src/btif_storage.cc
index 47aceed..d7a90ea 100644
--- a/btif/src/btif_storage.cc
+++ b/btif/src/btif_storage.cc
@@ -40,7 +40,9 @@
 #include <time.h>
 
 #include "bt_common.h"
+#include "bta_closure_api.h"
 #include "bta_hd_api.h"
+#include "bta_hearing_aid_api.h"
 #include "bta_hh_api.h"
 #include "btif_api.h"
 #include "btif_config.h"
@@ -54,6 +56,7 @@
 #include "osi/include/log.h"
 #include "osi/include/osi.h"
 
+using base::Bind;
 using bluetooth::Uuid;
 
 /*******************************************************************************
@@ -1352,6 +1355,12 @@
   return BT_STATUS_SUCCESS;
 }
 
+/** Loads information about bonded hearing aid devices */
+void btif_storage_load_bonded_hearing_aids() {}
+
+/** Deletes the bonded hearing aid device info from NVRAM */
+void btif_storage_remove_hearing_aid(const RawAddress& address) {}
+
 /*******************************************************************************
  *
  * Function         btif_storage_is_restricted_device
diff --git a/include/hardware/bluetooth.h b/include/hardware/bluetooth.h
index dd7b8e1..e48e560 100644
--- a/include/hardware/bluetooth.h
+++ b/include/hardware/bluetooth.h
@@ -48,6 +48,7 @@
 #define BT_PROFILE_GATT_ID "gatt"
 #define BT_PROFILE_AV_RC_ID "avrcp"
 #define BT_PROFILE_AV_RC_CTRL_ID "avrcp_ctrl"
+#define BT_PROFILE_HEARING_AID_ID "hearing_aid"
 
 /** Bluetooth test interface IDs */
 #define BT_TEST_INTERFACE_MCAP_ID "mcap_test"
diff --git a/include/hardware/bt_av.h b/include/hardware/bt_av.h
index 3ef4c2c..8a8accd 100644
--- a/include/hardware/bt_av.h
+++ b/include/hardware/bt_av.h
@@ -89,7 +89,9 @@
   BTAV_A2DP_CODEC_SAMPLE_RATE_88200 = 0x1 << 2,
   BTAV_A2DP_CODEC_SAMPLE_RATE_96000 = 0x1 << 3,
   BTAV_A2DP_CODEC_SAMPLE_RATE_176400 = 0x1 << 4,
-  BTAV_A2DP_CODEC_SAMPLE_RATE_192000 = 0x1 << 5
+  BTAV_A2DP_CODEC_SAMPLE_RATE_192000 = 0x1 << 5,
+  BTAV_A2DP_CODEC_SAMPLE_RATE_16000 = 0x1 << 6,
+  BTAV_A2DP_CODEC_SAMPLE_RATE_24000 = 0x1 << 7
 } btav_a2dp_codec_sample_rate_t;
 
 typedef enum {
@@ -178,6 +180,12 @@
     AppendCapability(sample_rate_str,
                      (sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_192000),
                      "192000");
+    AppendCapability(sample_rate_str,
+                     (sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_16000),
+                     "16000");
+    AppendCapability(sample_rate_str,
+                     (sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_24000),
+                     "24000");
 
     std::string bits_per_sample_str;
     AppendCapability(bits_per_sample_str,
diff --git a/include/hardware/bt_hearing_aid.h b/include/hardware/bt_hearing_aid.h
new file mode 100644
index 0000000..3b53d37
--- /dev/null
+++ b/include/hardware/bt_hearing_aid.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef ANDROID_INCLUDE_BT_HEARING_AID_H
+#define ANDROID_INCLUDE_BT_HEARING_AID_H
+
+#include <hardware/bluetooth.h>
+
+namespace bluetooth {
+namespace hearing_aid {
+
+enum class ConnectionState {
+  DISCONNECTED = 0,
+  CONNECTING,
+  CONNECTED,
+  DISCONNECTING
+};
+
+class HearingAidCallbacks {
+ public:
+  virtual ~HearingAidCallbacks() = default;
+
+  /** Callback for profile connection state change */
+  virtual void OnConnectionState(ConnectionState state,
+                                 const RawAddress& address) = 0;
+
+  /** Callback for device being available. Is executed when devices are loaded
+   * from storage on stack bringup, and when new device is connected to profile.
+   * Main purpose of this callback is to keep its users informed of device
+   * capabilities and hiSyncId.
+   */
+  virtual void OnDeviceAvailable(uint8_t capabilities, uint64_t hiSyncId,
+                                 const RawAddress& address) = 0;
+};
+
+class HearingAidInterface {
+ public:
+  virtual ~HearingAidInterface() = default;
+
+  /** Register the Hearing Aid callbacks */
+  virtual void Init(HearingAidCallbacks* callbacks) = 0;
+
+  /** Connect to Hearing Aid */
+  virtual void Connect(const RawAddress& address) = 0;
+
+  /** Disconnect from Hearing Aid */
+  virtual void Disconnect(const RawAddress& address) = 0;
+
+  /** Set the volume */
+  virtual void SetVolume(int8_t volume) = 0;
+
+  /** Closes the interface. */
+  virtual void Cleanup(void) = 0;
+};
+
+}  // namespace hearing_aid
+}  // namespace bluetooth
+
+#endif /* ANDROID_INCLUDE_BT_HEARING_AID_H */
diff --git a/stack/a2dp/a2dp_aac.cc b/stack/a2dp/a2dp_aac.cc
index 93307c8..d147192 100644
--- a/stack/a2dp/a2dp_aac.cc
+++ b/stack/a2dp/a2dp_aac.cc
@@ -835,6 +835,8 @@
       break;
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       break;
   }
@@ -1055,6 +1057,8 @@
       break;
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
       codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_sbc.cc b/stack/a2dp/a2dp_sbc.cc
index 400d6d9..112626f 100644
--- a/stack/a2dp/a2dp_sbc.cc
+++ b/stack/a2dp/a2dp_sbc.cc
@@ -990,6 +990,8 @@
     case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       break;
   }
@@ -1175,6 +1177,8 @@
     case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
       codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_vendor_aptx.cc b/stack/a2dp/a2dp_vendor_aptx.cc
index cf136e3..89378a0 100644
--- a/stack/a2dp/a2dp_vendor_aptx.cc
+++ b/stack/a2dp/a2dp_vendor_aptx.cc
@@ -489,6 +489,8 @@
     case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       break;
   }
@@ -642,6 +644,8 @@
     case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
       codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_vendor_aptx_hd.cc b/stack/a2dp/a2dp_vendor_aptx_hd.cc
index 907712c..5b9105a 100644
--- a/stack/a2dp/a2dp_vendor_aptx_hd.cc
+++ b/stack/a2dp/a2dp_vendor_aptx_hd.cc
@@ -506,6 +506,8 @@
     case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       break;
   }
@@ -659,6 +661,8 @@
     case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
       codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_vendor_ldac.cc b/stack/a2dp/a2dp_vendor_ldac.cc
index 7f76e1f..b6b37be 100644
--- a/stack/a2dp/a2dp_vendor_ldac.cc
+++ b/stack/a2dp/a2dp_vendor_ldac.cc
@@ -654,6 +654,8 @@
         return true;
       }
       break;
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       break;
   }
@@ -875,6 +877,8 @@
         codec_capability_.sample_rate = codec_user_config_.sample_rate;
         codec_config_.sample_rate = codec_user_config_.sample_rate;
       }
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+    case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
     case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
       codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
       codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/udrv/include/uipc.h b/udrv/include/uipc.h
index 332beca..bd1c8bf 100644
--- a/udrv/include/uipc.h
+++ b/udrv/include/uipc.h
@@ -18,6 +18,8 @@
 #ifndef UIPC_H
 #define UIPC_H
 
+#include <mutex>
+
 #define UIPC_CH_ID_AV_CTRL 0
 #define UIPC_CH_ID_AV_AUDIO 1
 #define UIPC_CH_NUM 2
@@ -37,13 +39,6 @@
   UIPC_TX_DATA_READY_EVT = 0x0010
 } tUIPC_EVENT;
 
-/* UIPC users */
-typedef enum {
-  UIPC_USER_A2DP = 0,
-  UIPC_USER_HEARING_AID = 1,
-  UIPC_USER_NUM = 2
-} tUIPC_USER;
-
 /*
  * UIPC IOCTL Requests
  */
@@ -62,12 +57,33 @@
 
 const char* dump_uipc_event(tUIPC_EVENT event);
 
+typedef struct {
+  int srvfd;
+  int fd;
+  int read_poll_tmo_ms;
+  int task_evt_flags; /* event flags pending to be processed in read task */
+  tUIPC_RCV_CBACK* cback;
+} tUIPC_CHAN;
+
+struct tUIPC_STATE {
+  pthread_t tid; /* main thread id */
+  int running;
+  std::recursive_mutex mutex;
+
+  fd_set active_set;
+  fd_set read_set;
+  int max_fd;
+  int signal_fds[2];
+
+  tUIPC_CHAN ch[UIPC_CH_NUM];
+};
+
 /**
  * Initialize UIPC module
  *
  * @param user User ID who uses UIPC
  */
-void UIPC_Init(void*, int user);
+std::unique_ptr<tUIPC_STATE> UIPC_Init();
 
 /**
  * Open a UIPC channel
@@ -76,15 +92,14 @@
  * @param p_cback Callback handler
  * @return true on success, otherwise false
  */
-bool UIPC_Open(tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback);
+bool UIPC_Open(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback);
 
 /**
  * Closes a channel in UIPC or the entire UIPC module
  *
  * @param ch_id Channel ID; if ch_id is UIPC_CH_ID_ALL, then cleanup UIPC
- * @param user User ID who uses UIPC
  */
-void UIPC_Close(tUIPC_CH_ID ch_id, int user);
+void UIPC_Close(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id);
 
 /**
  * Send a message over UIPC
@@ -95,8 +110,8 @@
  * @param msglen Message length
  * @return true on success, otherwise false
  */
-bool UIPC_Send(tUIPC_CH_ID ch_id, uint16_t msg_evt, const uint8_t* p_buf,
-               uint16_t msglen);
+bool UIPC_Send(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint16_t msg_evt,
+               const uint8_t* p_buf, uint16_t msglen);
 
 /**
  * Read a message from UIPC
@@ -107,8 +122,8 @@
  * @param len Bytes to read
  * @return true on success, otherwise false
  */
-uint32_t UIPC_Read(tUIPC_CH_ID ch_id, uint16_t* p_msg_evt, uint8_t* p_buf,
-                   uint32_t len);
+uint32_t UIPC_Read(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint16_t* p_msg_evt,
+                   uint8_t* p_buf, uint32_t len);
 
 /**
  * Control the UIPC parameter
@@ -118,6 +133,7 @@
  * @param param Optional parameters
  * @return true on success, otherwise false
  */
-bool UIPC_Ioctl(tUIPC_CH_ID ch_id, uint32_t request, void* param);
+bool UIPC_Ioctl(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint32_t request,
+                void* param);
 
 #endif /* UIPC_H */
diff --git a/udrv/ulinux/uipc.cc b/udrv/ulinux/uipc.cc
index 78fdac8..73c7cb2 100644
--- a/udrv/ulinux/uipc.cc
+++ b/udrv/ulinux/uipc.cc
@@ -75,39 +75,10 @@
   UIPC_TASK_FLAG_DISCONNECT_CHAN = 0x1,
 } tUIPC_TASK_FLAGS;
 
-typedef struct {
-  int srvfd;
-  int fd;
-  int read_poll_tmo_ms;
-  int task_evt_flags; /* event flags pending to be processed in read task */
-  tUIPC_RCV_CBACK* cback;
-} tUIPC_CHAN;
-
-typedef struct {
-  pthread_t tid; /* main thread id */
-  int running;
-  std::recursive_mutex mutex;
-
-  fd_set active_set;
-  fd_set read_set;
-  int max_fd;
-  int signal_fds[2];
-
-  tUIPC_CHAN ch[UIPC_CH_NUM];
-  std::set<int> active_users;
-} tUIPC_MAIN;
-
-/*****************************************************************************
- *  Static variables
- *****************************************************************************/
-
-static tUIPC_MAIN uipc_main;
-
 /*****************************************************************************
  *  Static functions
  *****************************************************************************/
-
-static int uipc_close_ch_locked(tUIPC_CH_ID ch_id);
+static int uipc_close_ch_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id);
 
 /*****************************************************************************
  *  Externs
@@ -207,29 +178,29 @@
  *
  ****************************************************************************/
 
-static int uipc_main_init(void) {
+static int uipc_main_init(tUIPC_STATE& uipc) {
   int i;
 
   BTIF_TRACE_EVENT("### uipc_main_init ###");
 
-  uipc_main.tid = 0;
-  uipc_main.running = 0;
-  memset(&uipc_main.active_set, 0, sizeof(uipc_main.active_set));
-  memset(&uipc_main.read_set, 0, sizeof(uipc_main.read_set));
-  uipc_main.max_fd = 0;
-  memset(&uipc_main.signal_fds, 0, sizeof(uipc_main.signal_fds));
-  memset(&uipc_main.ch, 0, sizeof(uipc_main.ch));
+  uipc.tid = 0;
+  uipc.running = 0;
+  memset(&uipc.active_set, 0, sizeof(uipc.active_set));
+  memset(&uipc.read_set, 0, sizeof(uipc.read_set));
+  uipc.max_fd = 0;
+  memset(&uipc.signal_fds, 0, sizeof(uipc.signal_fds));
+  memset(&uipc.ch, 0, sizeof(uipc.ch));
 
   /* setup interrupt socket pair */
-  if (socketpair(AF_UNIX, SOCK_STREAM, 0, uipc_main.signal_fds) < 0) {
+  if (socketpair(AF_UNIX, SOCK_STREAM, 0, uipc.signal_fds) < 0) {
     return -1;
   }
 
-  FD_SET(uipc_main.signal_fds[0], &uipc_main.active_set);
-  uipc_main.max_fd = MAX(uipc_main.max_fd, uipc_main.signal_fds[0]);
+  FD_SET(uipc.signal_fds[0], &uipc.active_set);
+  uipc.max_fd = MAX(uipc.max_fd, uipc.signal_fds[0]);
 
   for (i = 0; i < UIPC_CH_NUM; i++) {
-    tUIPC_CHAN* p = &uipc_main.ch[i];
+    tUIPC_CHAN* p = &uipc.ch[i];
     p->srvfd = UIPC_DISCONNECTED;
     p->fd = UIPC_DISCONNECTED;
     p->task_evt_flags = 0;
@@ -239,107 +210,104 @@
   return 0;
 }
 
-void uipc_main_cleanup(void) {
+void uipc_main_cleanup(tUIPC_STATE& uipc) {
   int i;
 
   BTIF_TRACE_EVENT("uipc_main_cleanup");
 
-  close(uipc_main.signal_fds[0]);
-  close(uipc_main.signal_fds[1]);
+  close(uipc.signal_fds[0]);
+  close(uipc.signal_fds[1]);
 
   /* close any open channels */
-  for (i = 0; i < UIPC_CH_NUM; i++) uipc_close_ch_locked(i);
-
-  uipc_main.active_users.clear();
+  for (i = 0; i < UIPC_CH_NUM; i++) uipc_close_ch_locked(uipc, i);
 }
 
 /* check pending events in read task */
-static void uipc_check_task_flags_locked(void) {
+static void uipc_check_task_flags_locked(tUIPC_STATE& uipc) {
   int i;
 
   for (i = 0; i < UIPC_CH_NUM; i++) {
-    if (uipc_main.ch[i].task_evt_flags & UIPC_TASK_FLAG_DISCONNECT_CHAN) {
-      uipc_main.ch[i].task_evt_flags &= ~UIPC_TASK_FLAG_DISCONNECT_CHAN;
-      uipc_close_ch_locked(i);
+    if (uipc.ch[i].task_evt_flags & UIPC_TASK_FLAG_DISCONNECT_CHAN) {
+      uipc.ch[i].task_evt_flags &= ~UIPC_TASK_FLAG_DISCONNECT_CHAN;
+      uipc_close_ch_locked(uipc, i);
     }
 
     /* add here */
   }
 }
 
-static int uipc_check_fd_locked(tUIPC_CH_ID ch_id) {
+static int uipc_check_fd_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
   if (ch_id >= UIPC_CH_NUM) return -1;
 
-  // BTIF_TRACE_EVENT("CHECK SRVFD %d (ch %d)", uipc_main.ch[ch_id].srvfd,
+  // BTIF_TRACE_EVENT("CHECK SRVFD %d (ch %d)", uipc.ch[ch_id].srvfd,
   // ch_id);
 
-  if (SAFE_FD_ISSET(uipc_main.ch[ch_id].srvfd, &uipc_main.read_set)) {
+  if (SAFE_FD_ISSET(uipc.ch[ch_id].srvfd, &uipc.read_set)) {
     BTIF_TRACE_EVENT("INCOMING CONNECTION ON CH %d", ch_id);
 
     // Close the previous connection
-    if (uipc_main.ch[ch_id].fd != UIPC_DISCONNECTED) {
-      BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc_main.ch[ch_id].fd);
-      close(uipc_main.ch[ch_id].fd);
-      FD_CLR(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
-      uipc_main.ch[ch_id].fd = UIPC_DISCONNECTED;
+    if (uipc.ch[ch_id].fd != UIPC_DISCONNECTED) {
+      BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc.ch[ch_id].fd);
+      close(uipc.ch[ch_id].fd);
+      FD_CLR(uipc.ch[ch_id].fd, &uipc.active_set);
+      uipc.ch[ch_id].fd = UIPC_DISCONNECTED;
     }
 
-    uipc_main.ch[ch_id].fd = accept_server_socket(uipc_main.ch[ch_id].srvfd);
+    uipc.ch[ch_id].fd = accept_server_socket(uipc.ch[ch_id].srvfd);
 
-    BTIF_TRACE_EVENT("NEW FD %d", uipc_main.ch[ch_id].fd);
+    BTIF_TRACE_EVENT("NEW FD %d", uipc.ch[ch_id].fd);
 
-    if ((uipc_main.ch[ch_id].fd >= 0) && uipc_main.ch[ch_id].cback) {
+    if ((uipc.ch[ch_id].fd >= 0) && uipc.ch[ch_id].cback) {
       /*  if we have a callback we should add this fd to the active set
           and notify user with callback event */
-      BTIF_TRACE_EVENT("ADD FD %d TO ACTIVE SET", uipc_main.ch[ch_id].fd);
-      FD_SET(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
-      uipc_main.max_fd = MAX(uipc_main.max_fd, uipc_main.ch[ch_id].fd);
+      BTIF_TRACE_EVENT("ADD FD %d TO ACTIVE SET", uipc.ch[ch_id].fd);
+      FD_SET(uipc.ch[ch_id].fd, &uipc.active_set);
+      uipc.max_fd = MAX(uipc.max_fd, uipc.ch[ch_id].fd);
     }
 
-    if (uipc_main.ch[ch_id].fd < 0) {
+    if (uipc.ch[ch_id].fd < 0) {
       BTIF_TRACE_ERROR("FAILED TO ACCEPT CH %d", ch_id);
       return -1;
     }
 
-    if (uipc_main.ch[ch_id].cback)
-      uipc_main.ch[ch_id].cback(ch_id, UIPC_OPEN_EVT);
+    if (uipc.ch[ch_id].cback) uipc.ch[ch_id].cback(ch_id, UIPC_OPEN_EVT);
   }
 
-  // BTIF_TRACE_EVENT("CHECK FD %d (ch %d)", uipc_main.ch[ch_id].fd, ch_id);
+  // BTIF_TRACE_EVENT("CHECK FD %d (ch %d)", uipc.ch[ch_id].fd, ch_id);
 
-  if (SAFE_FD_ISSET(uipc_main.ch[ch_id].fd, &uipc_main.read_set)) {
+  if (SAFE_FD_ISSET(uipc.ch[ch_id].fd, &uipc.read_set)) {
     // BTIF_TRACE_EVENT("INCOMING DATA ON CH %d", ch_id);
 
-    if (uipc_main.ch[ch_id].cback)
-      uipc_main.ch[ch_id].cback(ch_id, UIPC_RX_DATA_READY_EVT);
+    if (uipc.ch[ch_id].cback)
+      uipc.ch[ch_id].cback(ch_id, UIPC_RX_DATA_READY_EVT);
   }
   return 0;
 }
 
-static void uipc_check_interrupt_locked(void) {
-  if (SAFE_FD_ISSET(uipc_main.signal_fds[0], &uipc_main.read_set)) {
+static void uipc_check_interrupt_locked(tUIPC_STATE& uipc) {
+  if (SAFE_FD_ISSET(uipc.signal_fds[0], &uipc.read_set)) {
     char sig_recv = 0;
-    OSI_NO_INTR(recv(uipc_main.signal_fds[0], &sig_recv, sizeof(sig_recv),
-                     MSG_WAITALL));
+    OSI_NO_INTR(
+        recv(uipc.signal_fds[0], &sig_recv, sizeof(sig_recv), MSG_WAITALL));
   }
 }
 
-static inline void uipc_wakeup_locked(void) {
+static inline void uipc_wakeup_locked(tUIPC_STATE& uipc) {
   char sig_on = 1;
   BTIF_TRACE_EVENT("UIPC SEND WAKE UP");
 
-  OSI_NO_INTR(send(uipc_main.signal_fds[1], &sig_on, sizeof(sig_on), 0));
+  OSI_NO_INTR(send(uipc.signal_fds[1], &sig_on, sizeof(sig_on), 0));
 }
 
-static int uipc_setup_server_locked(tUIPC_CH_ID ch_id, const char* name,
-                                    tUIPC_RCV_CBACK* cback) {
+static int uipc_setup_server_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
+                                    const char* name, tUIPC_RCV_CBACK* cback) {
   int fd;
 
   BTIF_TRACE_EVENT("SETUP CHANNEL SERVER %d", ch_id);
 
   if (ch_id >= UIPC_CH_NUM) return -1;
 
-  std::lock_guard<std::recursive_mutex> guard(uipc_main.mutex);
+  std::lock_guard<std::recursive_mutex> guard(uipc.mutex);
 
   fd = create_server_socket(name);
 
@@ -349,27 +317,27 @@
   }
 
   BTIF_TRACE_EVENT("ADD SERVER FD TO ACTIVE SET %d", fd);
-  FD_SET(fd, &uipc_main.active_set);
-  uipc_main.max_fd = MAX(uipc_main.max_fd, fd);
+  FD_SET(fd, &uipc.active_set);
+  uipc.max_fd = MAX(uipc.max_fd, fd);
 
-  uipc_main.ch[ch_id].srvfd = fd;
-  uipc_main.ch[ch_id].cback = cback;
-  uipc_main.ch[ch_id].read_poll_tmo_ms = DEFAULT_READ_POLL_TMO_MS;
+  uipc.ch[ch_id].srvfd = fd;
+  uipc.ch[ch_id].cback = cback;
+  uipc.ch[ch_id].read_poll_tmo_ms = DEFAULT_READ_POLL_TMO_MS;
 
   /* trigger main thread to update read set */
-  uipc_wakeup_locked();
+  uipc_wakeup_locked(uipc);
 
   return 0;
 }
 
-static void uipc_flush_ch_locked(tUIPC_CH_ID ch_id) {
+static void uipc_flush_ch_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
   char buf[UIPC_FLUSH_BUFFER_SIZE];
   struct pollfd pfd;
 
   pfd.events = POLLIN;
-  pfd.fd = uipc_main.ch[ch_id].fd;
+  pfd.fd = uipc.ch[ch_id].fd;
 
-  if (uipc_main.ch[ch_id].fd == UIPC_DISCONNECTED) {
+  if (uipc.ch[ch_id].fd == UIPC_DISCONNECTED) {
     BTIF_TRACE_EVENT("%s() - fd disconnected. Exiting", __func__);
     return;
   }
@@ -401,65 +369,65 @@
   }
 }
 
-static void uipc_flush_locked(tUIPC_CH_ID ch_id) {
+static void uipc_flush_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
   if (ch_id >= UIPC_CH_NUM) return;
 
   switch (ch_id) {
     case UIPC_CH_ID_AV_CTRL:
-      uipc_flush_ch_locked(UIPC_CH_ID_AV_CTRL);
+      uipc_flush_ch_locked(uipc, UIPC_CH_ID_AV_CTRL);
       break;
 
     case UIPC_CH_ID_AV_AUDIO:
-      uipc_flush_ch_locked(UIPC_CH_ID_AV_AUDIO);
+      uipc_flush_ch_locked(uipc, UIPC_CH_ID_AV_AUDIO);
       break;
   }
 }
 
-static int uipc_close_ch_locked(tUIPC_CH_ID ch_id) {
+static int uipc_close_ch_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
   int wakeup = 0;
 
   BTIF_TRACE_EVENT("CLOSE CHANNEL %d", ch_id);
 
   if (ch_id >= UIPC_CH_NUM) return -1;
 
-  if (uipc_main.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
-    BTIF_TRACE_EVENT("CLOSE SERVER (FD %d)", uipc_main.ch[ch_id].srvfd);
-    close(uipc_main.ch[ch_id].srvfd);
-    FD_CLR(uipc_main.ch[ch_id].srvfd, &uipc_main.active_set);
-    uipc_main.ch[ch_id].srvfd = UIPC_DISCONNECTED;
+  if (uipc.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
+    BTIF_TRACE_EVENT("CLOSE SERVER (FD %d)", uipc.ch[ch_id].srvfd);
+    close(uipc.ch[ch_id].srvfd);
+    FD_CLR(uipc.ch[ch_id].srvfd, &uipc.active_set);
+    uipc.ch[ch_id].srvfd = UIPC_DISCONNECTED;
     wakeup = 1;
   }
 
-  if (uipc_main.ch[ch_id].fd != UIPC_DISCONNECTED) {
-    BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc_main.ch[ch_id].fd);
-    close(uipc_main.ch[ch_id].fd);
-    FD_CLR(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
-    uipc_main.ch[ch_id].fd = UIPC_DISCONNECTED;
+  if (uipc.ch[ch_id].fd != UIPC_DISCONNECTED) {
+    BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc.ch[ch_id].fd);
+    close(uipc.ch[ch_id].fd);
+    FD_CLR(uipc.ch[ch_id].fd, &uipc.active_set);
+    uipc.ch[ch_id].fd = UIPC_DISCONNECTED;
     wakeup = 1;
   }
 
   /* notify this connection is closed */
-  if (uipc_main.ch[ch_id].cback)
-    uipc_main.ch[ch_id].cback(ch_id, UIPC_CLOSE_EVT);
+  if (uipc.ch[ch_id].cback) uipc.ch[ch_id].cback(ch_id, UIPC_CLOSE_EVT);
 
   /* trigger main thread update if something was updated */
-  if (wakeup) uipc_wakeup_locked();
+  if (wakeup) uipc_wakeup_locked(uipc);
 
   return 0;
 }
 
-void uipc_close_locked(tUIPC_CH_ID ch_id) {
-  if (uipc_main.ch[ch_id].srvfd == UIPC_DISCONNECTED) {
+void uipc_close_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
+  if (uipc.ch[ch_id].srvfd == UIPC_DISCONNECTED) {
     BTIF_TRACE_EVENT("CHANNEL %d ALREADY CLOSED", ch_id);
     return;
   }
 
   /* schedule close on this channel */
-  uipc_main.ch[ch_id].task_evt_flags |= UIPC_TASK_FLAG_DISCONNECT_CHAN;
-  uipc_wakeup_locked();
+  uipc.ch[ch_id].task_evt_flags |= UIPC_TASK_FLAG_DISCONNECT_CHAN;
+  uipc_wakeup_locked(uipc);
 }
 
-static void* uipc_read_task(UNUSED_ATTR void* arg) {
+static void* uipc_read_task(void* arg) {
+  tUIPC_STATE& uipc = *((tUIPC_STATE*)arg);
   int ch_id;
   int result;
 
@@ -467,11 +435,10 @@
 
   raise_priority_a2dp(TASK_UIPC_READ);
 
-  while (uipc_main.running) {
-    uipc_main.read_set = uipc_main.active_set;
+  while (uipc.running) {
+    uipc.read_set = uipc.active_set;
 
-    result =
-        select(uipc_main.max_fd + 1, &uipc_main.read_set, NULL, NULL, NULL);
+    result = select(uipc.max_fd + 1, &uipc.read_set, NULL, NULL, NULL);
 
     if (result == 0) {
       BTIF_TRACE_EVENT("select timeout");
@@ -485,40 +452,40 @@
     }
 
     {
-      std::lock_guard<std::recursive_mutex> guard(uipc_main.mutex);
+      std::lock_guard<std::recursive_mutex> guard(uipc.mutex);
 
       /* clear any wakeup interrupt */
-      uipc_check_interrupt_locked();
+      uipc_check_interrupt_locked(uipc);
 
       /* check pending task events */
-      uipc_check_task_flags_locked();
+      uipc_check_task_flags_locked(uipc);
 
       /* make sure we service audio channel first */
-      uipc_check_fd_locked(UIPC_CH_ID_AV_AUDIO);
+      uipc_check_fd_locked(uipc, UIPC_CH_ID_AV_AUDIO);
 
       /* check for other connections */
       for (ch_id = 0; ch_id < UIPC_CH_NUM; ch_id++) {
-        if (ch_id != UIPC_CH_ID_AV_AUDIO) uipc_check_fd_locked(ch_id);
+        if (ch_id != UIPC_CH_ID_AV_AUDIO) uipc_check_fd_locked(uipc, ch_id);
       }
     }
   }
 
   BTIF_TRACE_EVENT("UIPC READ THREAD EXITING");
 
-  uipc_main_cleanup();
+  uipc_main_cleanup(uipc);
 
-  uipc_main.tid = 0;
+  uipc.tid = 0;
 
   BTIF_TRACE_EVENT("UIPC READ THREAD DONE");
 
   return nullptr;
 }
 
-int uipc_start_main_server_thread(void) {
-  uipc_main.running = 1;
+int uipc_start_main_server_thread(tUIPC_STATE& uipc) {
+  uipc.running = 1;
 
-  if (pthread_create(&uipc_main.tid, (const pthread_attr_t*)NULL,
-                     uipc_read_task, nullptr) < 0) {
+  if (pthread_create(&uipc.tid, (const pthread_attr_t*)NULL, uipc_read_task,
+                     &uipc) < 0) {
     BTIF_TRACE_ERROR("uipc_thread_create pthread_create failed:%d", errno);
     return -1;
   }
@@ -527,19 +494,19 @@
 }
 
 /* blocking call */
-void uipc_stop_main_server_thread(void) {
+void uipc_stop_main_server_thread(tUIPC_STATE& uipc) {
   /* request shutdown of read thread */
   {
-    std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
-    uipc_main.running = 0;
-    uipc_wakeup_locked();
+    std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+    uipc.running = 0;
+    uipc_wakeup_locked(uipc);
   }
 
   /* wait until read thread is fully terminated */
   /* tid might hold pointer value where it's value
      is negative vaule with singed bit is set, so
      corrected the logic to check zero or non zero */
-  if (uipc_main.tid) pthread_join(uipc_main.tid, NULL);
+  if (uipc.tid) pthread_join(uipc.tid, NULL);
 }
 
 /*******************************************************************************
@@ -551,21 +518,16 @@
  ** Returns          void
  **
  ******************************************************************************/
-
-void UIPC_Init(UNUSED_ATTR void* p_data, int user) {
+std::unique_ptr<tUIPC_STATE> UIPC_Init() {
+  std::unique_ptr<tUIPC_STATE> uipc = std::make_unique<tUIPC_STATE>();
   BTIF_TRACE_DEBUG("UIPC_Init");
-  if (user < 0 || user >= UIPC_USER_NUM) {
-    BTIF_TRACE_ERROR("UIPC_Close : invalid user ID %d", user);
-    return;
-  }
-  std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
-  auto result_insert = uipc_main.active_users.insert(user);
-  if ((uipc_main.active_users.size() != 1) || !result_insert.second) {
-    return;
-  }
 
-  uipc_main_init();
-  uipc_start_main_server_thread();
+  std::lock_guard<std::recursive_mutex> lock(uipc->mutex);
+
+  uipc_main_init(*uipc);
+  uipc_start_main_server_thread(*uipc);
+
+  return uipc;
 }
 
 /*******************************************************************************
@@ -577,27 +539,27 @@
  ** Returns          true in case of success, false in case of failure.
  **
  ******************************************************************************/
-bool UIPC_Open(tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback) {
+bool UIPC_Open(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback) {
   BTIF_TRACE_DEBUG("UIPC_Open : ch_id %d, p_cback %x", ch_id, p_cback);
 
-  std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
+  std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
 
   if (ch_id >= UIPC_CH_NUM) {
     return false;
   }
 
-  if (uipc_main.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
+  if (uipc.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
     BTIF_TRACE_EVENT("CHANNEL %d ALREADY OPEN", ch_id);
     return 0;
   }
 
   switch (ch_id) {
     case UIPC_CH_ID_AV_CTRL:
-      uipc_setup_server_locked(ch_id, A2DP_CTRL_PATH, p_cback);
+      uipc_setup_server_locked(uipc, ch_id, A2DP_CTRL_PATH, p_cback);
       break;
 
     case UIPC_CH_ID_AV_AUDIO:
-      uipc_setup_server_locked(ch_id, A2DP_DATA_PATH, p_cback);
+      uipc_setup_server_locked(uipc, ch_id, A2DP_DATA_PATH, p_cback);
       break;
   }
 
@@ -613,30 +575,18 @@
  ** Returns          void
  **
  ******************************************************************************/
-
-void UIPC_Close(tUIPC_CH_ID ch_id, int user) {
+void UIPC_Close(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
   BTIF_TRACE_DEBUG("UIPC_Close : ch_id %d", ch_id);
-  if (user < 0 || user >= UIPC_USER_NUM) {
-    BTIF_TRACE_ERROR("UIPC_Close : invalid user ID %d", user);
-    return;
-  }
+
   /* special case handling uipc shutdown */
   if (ch_id != UIPC_CH_ID_ALL) {
-    std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
-    uipc_close_locked(ch_id);
-    return;
-  }
-
-  if (uipc_main.active_users.erase(user) == 0) {
-    return;
-  }
-
-  if (!uipc_main.active_users.empty()) {
+    std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+    uipc_close_locked(uipc, ch_id);
     return;
   }
 
   BTIF_TRACE_DEBUG("UIPC_Close : waiting for shutdown to complete");
-  uipc_stop_main_server_thread();
+  uipc_stop_main_server_thread(uipc);
   BTIF_TRACE_DEBUG("UIPC_Close : shutdown complete");
 }
 
@@ -649,14 +599,15 @@
  ** Returns          true in case of success, false in case of failure.
  **
  ******************************************************************************/
-bool UIPC_Send(tUIPC_CH_ID ch_id, UNUSED_ATTR uint16_t msg_evt,
-               const uint8_t* p_buf, uint16_t msglen) {
+bool UIPC_Send(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
+               UNUSED_ATTR uint16_t msg_evt, const uint8_t* p_buf,
+               uint16_t msglen) {
   BTIF_TRACE_DEBUG("UIPC_Send : ch_id:%d %d bytes", ch_id, msglen);
 
-  std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
+  std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
 
   ssize_t ret;
-  OSI_NO_INTR(ret = write(uipc_main.ch[ch_id].fd, p_buf, msglen));
+  OSI_NO_INTR(ret = write(uipc.ch[ch_id].fd, p_buf, msglen));
   if (ret < 0) {
     BTIF_TRACE_ERROR("failed to write (%s)", strerror(errno));
   }
@@ -674,10 +625,11 @@
  **
  ******************************************************************************/
 
-uint32_t UIPC_Read(tUIPC_CH_ID ch_id, UNUSED_ATTR uint16_t* p_msg_evt,
-                   uint8_t* p_buf, uint32_t len) {
+uint32_t UIPC_Read(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
+                   UNUSED_ATTR uint16_t* p_msg_evt, uint8_t* p_buf,
+                   uint32_t len) {
   int n_read = 0;
-  int fd = uipc_main.ch[ch_id].fd;
+  int fd = uipc.ch[ch_id].fd;
   struct pollfd pfd;
 
   if (ch_id >= UIPC_CH_NUM) {
@@ -698,10 +650,10 @@
        a read for more than poll timeout */
 
     int poll_ret;
-    OSI_NO_INTR(poll_ret = poll(&pfd, 1, uipc_main.ch[ch_id].read_poll_tmo_ms));
+    OSI_NO_INTR(poll_ret = poll(&pfd, 1, uipc.ch[ch_id].read_poll_tmo_ms));
     if (poll_ret == 0) {
       BTIF_TRACE_WARNING("poll timeout (%d ms)",
-                         uipc_main.ch[ch_id].read_poll_tmo_ms);
+                         uipc.ch[ch_id].read_poll_tmo_ms);
       break;
     }
     if (poll_ret < 0) {
@@ -714,8 +666,8 @@
 
     if (pfd.revents & (POLLHUP | POLLNVAL)) {
       BTIF_TRACE_WARNING("poll : channel detached remotely");
-      std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
-      uipc_close_locked(ch_id);
+      std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+      uipc_close_locked(uipc, ch_id);
       return 0;
     }
 
@@ -726,8 +678,8 @@
 
     if (n == 0) {
       BTIF_TRACE_WARNING("UIPC_Read : channel detached remotely");
-      std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
-      uipc_close_locked(ch_id);
+      std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+      uipc_close_locked(uipc, ch_id);
       return 0;
     }
 
@@ -752,37 +704,38 @@
  *
  ******************************************************************************/
 
-extern bool UIPC_Ioctl(tUIPC_CH_ID ch_id, uint32_t request, void* param) {
+extern bool UIPC_Ioctl(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint32_t request,
+                       void* param) {
   BTIF_TRACE_DEBUG("#### UIPC_Ioctl : ch_id %d, request %d ####", ch_id,
                    request);
-  std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
+  std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
 
   switch (request) {
     case UIPC_REQ_RX_FLUSH:
-      uipc_flush_locked(ch_id);
+      uipc_flush_locked(uipc, ch_id);
       break;
 
     case UIPC_REG_CBACK:
       // BTIF_TRACE_EVENT("register callback ch %d srvfd %d, fd %d", ch_id,
-      // uipc_main.ch[ch_id].srvfd, uipc_main.ch[ch_id].fd);
-      uipc_main.ch[ch_id].cback = (tUIPC_RCV_CBACK*)param;
+      // uipc.ch[ch_id].srvfd, uipc.ch[ch_id].fd);
+      uipc.ch[ch_id].cback = (tUIPC_RCV_CBACK*)param;
       break;
 
     case UIPC_REG_REMOVE_ACTIVE_READSET:
       /* user will read data directly and not use select loop */
-      if (uipc_main.ch[ch_id].fd != UIPC_DISCONNECTED) {
+      if (uipc.ch[ch_id].fd != UIPC_DISCONNECTED) {
         /* remove this channel from active set */
-        FD_CLR(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
+        FD_CLR(uipc.ch[ch_id].fd, &uipc.active_set);
 
         /* refresh active set */
-        uipc_wakeup_locked();
+        uipc_wakeup_locked(uipc);
       }
       break;
 
     case UIPC_SET_READ_POLL_TMO:
-      uipc_main.ch[ch_id].read_poll_tmo_ms = (intptr_t)param;
+      uipc.ch[ch_id].read_poll_tmo_ms = (intptr_t)param;
       BTIF_TRACE_EVENT("UIPC_SET_READ_POLL_TMO : CH %d, TMO %d ms", ch_id,
-                       uipc_main.ch[ch_id].read_poll_tmo_ms);
+                       uipc.ch[ch_id].read_poll_tmo_ms);
       break;
 
     default: