Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1cf59992 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

vc: Extend VCP with support for volume control offset service

This patch adds support to handle volume control offset service.
This can be use by peer device to
1) use it to control balance in case of coordinated audio sets (e.g.
earbuds): This feature is exposed in the API
2) expose multiple output speakers and add a way to control the audio
offset compared to global volume: This feature is not exposed in the API yet

Bug: 150670922
Sponsor: @jpawlowski
Test: atest --host bluetooth_vc_test
Change-Id: I7c722048a27ff6c1c55395d05e8c3b6e7a68f3e9
parent 1e33487f
Loading
Loading
Loading
Loading
+250 −0
Original line number Diff line number Diff line
@@ -34,6 +34,10 @@ namespace android {
static jmethodID method_onConnectionStateChanged;
static jmethodID method_onVolumeStateChanged;
static jmethodID method_onGroupVolumeStateChanged;
static jmethodID method_onDeviceAvailable;
static jmethodID method_onExtAudioOutVolumeOffsetChanged;
static jmethodID method_onExtAudioOutLocationChanged;
static jmethodID method_onExtAudioOutDescriptionChanged;

static VolumeControlInterface* sVolumeControlInterface = nullptr;
static std::shared_timed_mutex interface_mutex;
@@ -98,6 +102,100 @@ class VolumeControlCallbacksImpl : public VolumeControlCallbacks {
                                 method_onGroupVolumeStateChanged, (jint)volume,
                                 (jboolean)mute, group_id, (jboolean)isAutonomous);
  }

  void OnDeviceAvailable(const RawAddress& bd_addr,
                         uint8_t num_offsets) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new jbyteArray bd addr for onDeviceAvailable";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDeviceAvailable,
                                 (jint)num_offsets, addr.get());
  }

  void OnExtAudioOutVolumeOffsetChanged(const RawAddress& bd_addr,
                                        uint8_t ext_output_id,
                                        int16_t offset) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new jbyteArray bd addr for "
                    "OnExtAudioOutVolumeOffsetChanged";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);
    sCallbackEnv->CallVoidMethod(mCallbacksObj,
                                 method_onExtAudioOutVolumeOffsetChanged,
                                 (jint)ext_output_id, (jint)offset, addr.get());
  }

  void OnExtAudioOutLocationChanged(const RawAddress& bd_addr,
                                    uint8_t ext_output_id,
                                    uint32_t location) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new jbyteArray bd addr for "
                    "OnExtAudioOutLocationChanged";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);
    sCallbackEnv->CallVoidMethod(
        mCallbacksObj, method_onExtAudioOutLocationChanged, (jint)ext_output_id,
        (jint)location, addr.get());
  }

  void OnExtAudioOutDescriptionChanged(const RawAddress& bd_addr,
                                       uint8_t ext_output_id,
                                       std::string descr) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new jbyteArray bd addr for "
                    "OnExtAudioOutDescriptionChanged";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);
    jstring description = sCallbackEnv->NewStringUTF(descr.c_str());
    sCallbackEnv->CallVoidMethod(mCallbacksObj,
                                 method_onExtAudioOutDescriptionChanged,
                                 (jint)ext_output_id, description, addr.get());
  }
};

static VolumeControlCallbacksImpl sVolumeControlCallbacks;
@@ -112,6 +210,18 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
  method_onGroupVolumeStateChanged =
      env->GetMethodID(clazz, "onGroupVolumeStateChanged", "(IZIZ)V");

  method_onDeviceAvailable =
      env->GetMethodID(clazz, "onDeviceAvailable", "(I[B)V");

  method_onExtAudioOutVolumeOffsetChanged =
      env->GetMethodID(clazz, "onExtAudioOutVolumeOffsetChanged", "(II[B)V");

  method_onExtAudioOutLocationChanged =
      env->GetMethodID(clazz, "onExtAudioOutLocationChanged", "(II[B)V");

  method_onExtAudioOutDescriptionChanged = env->GetMethodID(
      clazz, "onExtAudioOutDescriptionChanged", "(ILjava/lang/String;[B)V");

  LOG(INFO) << __func__ << ": succeeds";
}

@@ -249,6 +359,134 @@ static void setVolumeGroupNative(JNIEnv* env, jobject object, jint group_id,
  sVolumeControlInterface->SetVolume(group_id, volume);
}

/* Native methods for exterbak audio outputs */
static jboolean getExtAudioOutVolumeOffsetNative(JNIEnv* env, jobject object,
                                                 jbyteArray address,
                                                 jint ext_output_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sVolumeControlInterface) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sVolumeControlInterface->GetExtAudioOutVolumeOffset(*tmpraw, ext_output_id);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static jboolean setExtAudioOutVolumeOffsetNative(JNIEnv* env, jobject object,
                                                 jbyteArray address,
                                                 jint ext_output_id,
                                                 jint offset) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sVolumeControlInterface) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sVolumeControlInterface->SetExtAudioOutVolumeOffset(*tmpraw, ext_output_id,
                                                      offset);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static jboolean getExtAudioOutLocationNative(JNIEnv* env, jobject object,
                                             jbyteArray address,
                                             jint ext_output_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sVolumeControlInterface) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sVolumeControlInterface->GetExtAudioOutLocation(*tmpraw, ext_output_id);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static jboolean setExtAudioOutLocationNative(JNIEnv* env, jobject object,
                                             jbyteArray address,
                                             jint ext_output_id,
                                             jint location) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sVolumeControlInterface) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sVolumeControlInterface->SetExtAudioOutLocation(*tmpraw, ext_output_id,
                                                  location);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static jboolean getExtAudioOutDescriptionNative(JNIEnv* env, jobject object,
                                                jbyteArray address,
                                                jint ext_output_id) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sVolumeControlInterface) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sVolumeControlInterface->GetExtAudioOutDescription(*tmpraw, ext_output_id);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static jboolean setExtAudioOutDescriptionNative(JNIEnv* env, jobject object,
                                                jbyteArray address,
                                                jint ext_output_id,
                                                jstring descr) {
  LOG(INFO) << __func__;
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sVolumeControlInterface) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  std::string description;
  if (descr != nullptr) {
    const char* value = env->GetStringUTFChars(descr, nullptr);
    description = std::string(value);
    env->ReleaseStringUTFChars(descr, value);
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sVolumeControlInterface->SetExtAudioOutDescription(*tmpraw, ext_output_id,
                                                     description);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static JNINativeMethod sMethods[] = {
    {"classInitNative", "()V", (void*)classInitNative},
    {"initNative", "()V", (void*)initNative},
@@ -258,6 +496,18 @@ static JNINativeMethod sMethods[] = {
     (void*)disconnectVolumeControlNative},
    {"setVolumeNative", "([BI)V", (void*)setVolumeNative},
    {"setVolumeGroupNative", "(II)V", (void*)setVolumeGroupNative},
    {"getExtAudioOutVolumeOffsetNative", "([BI)Z",
     (void*)getExtAudioOutVolumeOffsetNative},
    {"setExtAudioOutVolumeOffsetNative", "([BII)Z",
     (void*)setExtAudioOutVolumeOffsetNative},
    {"getExtAudioOutLocationNative", "([BI)Z",
     (void*)getExtAudioOutLocationNative},
    {"setExtAudioOutLocationNative", "([BII)Z",
     (void*)setExtAudioOutLocationNative},
    {"getExtAudioOutDescriptionNative", "([BI)Z",
     (void*)getExtAudioOutDescriptionNative},
    {"setExtAudioOutDescriptionNative", "([BILjava/lang/String;)Z",
     (void*)setExtAudioOutDescriptionNative},
};

int register_com_android_bluetooth_vc(JNIEnv* env) {
+18 −0
Original line number Diff line number Diff line
@@ -39,4 +39,22 @@ class VolumeControl {
  virtual void Disconnect(const RawAddress& address) = 0;
  virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
                         uint8_t volume) = 0;
  /* Volume Offset Control Service (VOCS) */
  virtual void SetExtAudioOutVolumeOffset(const RawAddress& address,
                                          uint8_t ext_output_id,
                                          int16_t offset) = 0;
  virtual void GetExtAudioOutVolumeOffset(const RawAddress& address,
                                          uint8_t ext_output_id) = 0;

  /* Location as per Bluetooth Assigned Numbers.*/
  virtual void SetExtAudioOutLocation(const RawAddress& address,
                                      uint8_t ext_output_id,
                                      uint32_t location) = 0;
  virtual void GetExtAudioOutLocation(const RawAddress& address,
                                      uint8_t ext_output_id) = 0;
  virtual void GetExtAudioOutDescription(const RawAddress& address,
                                         uint8_t ext_output_id) = 0;
  virtual void SetExtAudioOutDescription(const RawAddress& address,
                                         uint8_t ext_output_id,
                                         std::string descr) = 0;
};
+181 −0
Original line number Diff line number Diff line
@@ -41,6 +41,14 @@ void VolumeControlDevice::Disconnect(tGATT_IF gatt_if) {
      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
                                           volume_flags_handle);

    for (const VolumeOffset& of : audio_offsets.volume_offsets) {
      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
                                           of.audio_descr_handle);
      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
                                           of.audio_location_handle);
      BTA_GATTC_DeregisterForNotifications(gatt_if, address, of.state_handle);
    }

    BtaGattQueue::Clean(connection_id);
    BTA_GATTC_Close(connection_id);
    connection_id = GATT_INVALID_CONN_ID;
@@ -108,6 +116,50 @@ bool VolumeControlDevice::set_volume_control_service_handles(
  return false;
}

void VolumeControlDevice::set_volume_offset_control_service_handles(
    const gatt::Service& service) {
  VolumeOffset offset = VolumeOffset(service.handle);

  for (const gatt::Characteristic& chrc : service.characteristics) {
    if (chrc.uuid == kVolumeOffsetStateUuid) {
      offset.state_handle = chrc.value_handle;
      offset.state_ccc_handle = find_ccc_handle(chrc.value_handle);

    } else if (chrc.uuid == kVolumeOffsetLocationUuid) {
      offset.audio_location_handle = chrc.value_handle;
      offset.audio_location_ccc_handle = find_ccc_handle(chrc.value_handle);
      offset.audio_location_writable =
          chrc.properties & GATT_CHAR_PROP_BIT_WRITE_NR;

    } else if (chrc.uuid == kVolumeOffsetControlPointUuid) {
      offset.control_point_handle = chrc.value_handle;

    } else if (chrc.uuid == kVolumeOffsetOutputDescriptionUuid) {
      offset.audio_descr_handle = chrc.value_handle;
      offset.audio_descr_ccc_handle = find_ccc_handle(chrc.value_handle);
      offset.audio_descr_writable =
          chrc.properties & GATT_CHAR_PROP_BIT_WRITE_NR;

    } else {
      LOG(WARNING) << __func__ << ": unknown characteristic=" << chrc.uuid;
    }
  }

  // Check if all mandatory attributes are present
  if (GATT_HANDLE_IS_VALID(offset.state_handle) &&
      GATT_HANDLE_IS_VALID(offset.state_ccc_handle) &&
      GATT_HANDLE_IS_VALID(offset.audio_location_handle) &&
      /* audio_location_ccc_handle is optional */
      GATT_HANDLE_IS_VALID(offset.control_point_handle) &&
      GATT_HANDLE_IS_VALID(offset.audio_descr_handle)
      /* audio_descr_ccc_handle is optional */) {
    audio_offsets.Add(offset);
    LOG(INFO) << "Offset added id=" << loghex(offset.id);
  } else {
    LOG(WARNING) << "Ignoring offset handle=" << loghex(service.handle);
  }
}

bool VolumeControlDevice::UpdateHandles(void) {
  ResetHandles();

@@ -124,6 +176,20 @@ bool VolumeControlDevice::UpdateHandles(void) {
      LOG(INFO) << "Found VCS, handle=" << loghex(service.handle);
      vcs_found = set_volume_control_service_handles(service);
      if (!vcs_found) break;

      for (auto const& included : service.included_services) {
        const gatt::Service* service =
            BTA_GATTC_GetOwningService(connection_id, included.start_handle);
        if (service == nullptr) continue;

        if (included.uuid == kVolumeOffsetUuid) {
          LOG(INFO) << "Found VOCS, handle=" << loghex(service->handle);
          set_volume_offset_control_service_handles(*service);

        } else {
          LOG(WARNING) << __func__ << ": unknown service=" << service->uuid;
        }
      }
    }
  }

@@ -141,6 +207,8 @@ void VolumeControlDevice::ResetHandles(void) {
  volume_control_point_handle = 0;
  volume_flags_handle = 0;
  volume_flags_ccc_handle = 0;

  if (audio_offsets.Size() != 0) audio_offsets.Clear();
}

void VolumeControlDevice::ControlPointOperation(uint8_t opcode,
@@ -194,6 +262,18 @@ bool VolumeControlDevice::EnqueueInitialRequests(
    return false;
  }

  for (auto const& offset : audio_offsets.volume_offsets) {
    handles_pending.insert(offset.state_handle);
    handles_pending.insert(offset.state_ccc_handle);
    if (!subscribe_for_notifications(gatt_if, offset.state_handle,
                                     offset.state_ccc_handle, cccd_write_cb)) {
      return false;
    }

    BtaGattQueue::ReadCharacteristic(connection_id, offset.state_handle,
                                     chrc_read_cb, nullptr);
  }

  BtaGattQueue::ReadCharacteristic(connection_id, volume_state_handle,
                                   chrc_read_cb, nullptr);

@@ -213,6 +293,12 @@ void VolumeControlDevice::EnqueueRemainingRequests(
      {volume_flags_handle, volume_flags_ccc_handle},
  };

  for (auto const& offset : audio_offsets.volume_offsets) {
    handle_pairs[offset.audio_location_handle] =
        offset.audio_location_ccc_handle;
    handle_pairs[offset.audio_descr_handle] = offset.audio_descr_ccc_handle;
  }

  for (auto const& handles : handle_pairs) {
    if (GATT_HANDLE_IS_VALID(handles.second)) {
      subscribe_for_notifications(gatt_if, handles.first, handles.second,
@@ -230,6 +316,101 @@ bool VolumeControlDevice::VerifyReady(uint16_t handle) {
  return device_ready;
}

void VolumeControlDevice::GetExtAudioOutVolumeOffset(uint8_t ext_output_id,
                                                     GATT_READ_OP_CB cb,
                                                     void* cb_data) {
  VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
  if (!offset) {
    LOG(ERROR) << __func__ << ": no such offset!";
    return;
  }

  BtaGattQueue::ReadCharacteristic(connection_id, offset->state_handle, cb,
                                   cb_data);
}

void VolumeControlDevice::GetExtAudioOutLocation(uint8_t ext_output_id,
                                                 GATT_READ_OP_CB cb,
                                                 void* cb_data) {
  VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
  if (!offset) {
    LOG(ERROR) << __func__ << ": no such offset!";
    return;
  }

  BtaGattQueue::ReadCharacteristic(connection_id, offset->audio_location_handle,
                                   cb, cb_data);
}

void VolumeControlDevice::SetExtAudioOutLocation(uint8_t ext_output_id,
                                                 uint32_t location) {
  VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
  if (!offset) {
    LOG(ERROR) << __func__ << ": no such offset!";
    return;
  }

  if (!offset->audio_location_writable) {
    LOG(WARNING) << __func__ << ": not writable";
    return;
  }

  std::vector<uint8_t> value(4);
  uint8_t* ptr = value.data();
  UINT32_TO_STREAM(ptr, location);
  BtaGattQueue::WriteCharacteristic(connection_id,
                                    offset->audio_location_handle, value,
                                    GATT_WRITE_NO_RSP, nullptr, nullptr);
}

void VolumeControlDevice::GetExtAudioOutDescription(uint8_t ext_output_id,
                                                    GATT_READ_OP_CB cb,
                                                    void* cb_data) {
  VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
  if (!offset) {
    LOG(ERROR) << __func__ << ": no such offset!";
    return;
  }

  BtaGattQueue::ReadCharacteristic(connection_id, offset->audio_descr_handle,
                                   cb, cb_data);
}

void VolumeControlDevice::SetExtAudioOutDescription(uint8_t ext_output_id,
                                                    std::string& descr) {
  VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
  if (!offset) {
    LOG(ERROR) << __func__ << ": no such offset!";
    return;
  }

  if (!offset->audio_descr_writable) {
    LOG(WARNING) << __func__ << ": not writable";
    return;
  }

  std::vector<uint8_t> value(descr.begin(), descr.end());
  BtaGattQueue::WriteCharacteristic(connection_id, offset->audio_descr_handle,
                                    value, GATT_WRITE_NO_RSP, nullptr, nullptr);
}

void VolumeControlDevice::ExtAudioOutControlPointOperation(
    uint8_t ext_output_id, uint8_t opcode, const std::vector<uint8_t>* arg,
    GATT_WRITE_OP_CB cb, void* cb_data) {
  VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
  if (!offset) {
    LOG(ERROR) << __func__ << ": no such offset!";
    return;
  }

  std::vector<uint8_t> set_value({opcode, offset->change_counter});
  if (arg != nullptr)
    set_value.insert(set_value.end(), (*arg).begin(), (*arg).end());

  BtaGattQueue::WriteCharacteristic(connection_id, offset->control_point_handle,
                                    set_value, GATT_WRITE, cb, cb_data);
}

bool VolumeControlDevice::IsEncryptionEnabled() {
  return BTM_IsEncrypted(address, BT_TRANSPORT_LE);
}
+40 −3
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ class VolumeControlDevice {
  uint16_t volume_flags_handle;
  uint16_t volume_flags_ccc_handle;

  VolumeOffsets audio_offsets;

  bool device_ready; /* Set when device read server status and registgered for
                        notifications */

@@ -83,7 +85,25 @@ class VolumeControlDevice {

  inline std::string ToString() { return address.ToString(); }

  void DebugDump(int fd) { dprintf(fd, "%s\n", this->ToString().c_str()); }
  void DebugDump(int fd) {
    std::stringstream stream;
    stream << "   == device address: " << address << " == \n";

    if (connection_id == GATT_INVALID_CONN_ID)
      stream << "    Not connected\n";
    else
      stream << "    Connected. Conn_id = " << connection_id << "\n";

    stream << "    volume: " << +volume << "\n"
           << "    mute: " << +mute << "\n"
           << "    flags: " << +flags << "\n"
           << "    device read: " << device_ready << "\n"
           << "    first_connection_: " << first_connection << "\n"
           << "    connecting_actively_: " << connecting_actively << "\n";

    dprintf(fd, "%s", stream.str().c_str());
    audio_offsets.Dump(fd);
  }

  bool IsConnected() { return connection_id != GATT_INVALID_CONN_ID; }

@@ -97,6 +117,17 @@ class VolumeControlDevice {

  void ControlPointOperation(uint8_t opcode, const std::vector<uint8_t>* arg,
                             GATT_WRITE_OP_CB cb, void* cb_data);
  void GetExtAudioOutVolumeOffset(uint8_t ext_output_id, GATT_READ_OP_CB cb,
                                  void* cb_data);
  void SetExtAudioOutLocation(uint8_t ext_output_id, uint32_t location);
  void GetExtAudioOutLocation(uint8_t ext_output_id, GATT_READ_OP_CB cb,
                              void* cb_data);
  void GetExtAudioOutDescription(uint8_t ext_output_id, GATT_READ_OP_CB cb,
                                 void* cb_data);
  void SetExtAudioOutDescription(uint8_t ext_output_id, std::string& descr);
  void ExtAudioOutControlPointOperation(uint8_t ext_output_id, uint8_t opcode,
                                        const std::vector<uint8_t>* arg,
                                        GATT_WRITE_OP_CB cb, void* cb_data);
  bool IsEncryptionEnabled();

  bool EnableEncryption(tBTM_SEC_CALLBACK* callback);
@@ -118,6 +149,7 @@ class VolumeControlDevice {

  uint16_t find_ccc_handle(uint16_t chrc_handle);
  bool set_volume_control_service_handles(const gatt::Service& service);
  void set_volume_offset_control_service_handles(const gatt::Service& service);
  bool subscribe_for_notifications(tGATT_IF gatt_if, uint16_t handle,
                                   uint16_t ccc_handle, GATT_WRITE_OP_CB cb);
};
@@ -163,10 +195,15 @@ class VolumeControlDevices {
  void Clear() { devices_.clear(); }

  void DebugDump(int fd) {
    if (devices_.empty()) {
      dprintf(fd, "  No VC devices:\n");
    } else {
      dprintf(fd, "  Devices:\n");
      for (auto& device : devices_) {
        device.DebugDump(fd);
      }
    }
  }

  void Disconnect(tGATT_IF gatt_if) {
    for (auto& device : devices_) {
+227 −3

File changed.

Preview size limit exceeded, changes collapsed.

Loading