OSDN Git Service

service: Support GattServer notifications/indications
authorArman Uguray <armansito@google.com>
Thu, 1 Oct 2015 23:36:38 +0000 (16:36 -0700)
committerArman Uguray <armansito@google.com>
Mon, 5 Oct 2015 23:50:11 +0000 (16:50 -0700)
Added outgoing notification/indication support to
bluetooth::GattServer and implemented the related Binder API.

Bug: 24245347
Change-Id: I2d5cea90137e5d771f969163591b50aee1ad171c

15 files changed:
service/example/heart_rate/heart_rate_server.cpp
service/example/heart_rate/heart_rate_server.h
service/gatt_server.cpp
service/gatt_server.h
service/hal/bluetooth_gatt_interface.cpp
service/hal/bluetooth_gatt_interface.h
service/hal/fake_bluetooth_gatt_interface.cpp
service/hal/fake_bluetooth_gatt_interface.h
service/ipc/binder/IBluetoothGattServer.cpp
service/ipc/binder/IBluetoothGattServer.h
service/ipc/binder/IBluetoothGattServerCallback.cpp
service/ipc/binder/IBluetoothGattServerCallback.h
service/ipc/binder/bluetooth_gatt_server_binder_server.cpp
service/ipc/binder/bluetooth_gatt_server_binder_server.h
service/test/gatt_server_unittest.cpp

index 6f4a691..df58309 100644 (file)
@@ -210,4 +210,9 @@ void HeartRateServer::OnExecuteWriteRequest(
   // TODO(armansito): Implement.
 }
 
+void HeartRateServer::OnNotificationSent(
+    const std::string& device_address, int status) {
+  // TODO(armansito): Implement.
+}
+
 }  // namespace heart_rate
index f70673d..e67212f 100644 (file)
@@ -64,6 +64,8 @@ class HeartRateServer : public ipc::binder::BnBluetoothGattServerCallback {
   void OnExecuteWriteRequest(
       const std::string& device_address,
       int request_id, bool is_execute) override;
+  void OnNotificationSent(const std::string& device_address,
+                          int status) override;
 
   std::mutex mutex_;
 
index 0a4ba0a..0517684 100644 (file)
@@ -366,6 +366,85 @@ bool GattServer::SendResponse(
   return true;
 }
 
+bool GattServer::SendNotification(
+    const std::string& device_address,
+    const GattIdentifier& characteristic_id,
+    bool confirm,
+    const std::vector<uint8_t>& value,
+    const GattCallback& callback) {
+  VLOG(1) << " - server_if: " << server_if_
+          << " device_address: " << device_address
+          << " confirm: " << confirm;
+  lock_guard<mutex> lock(mutex_);
+
+  bt_bdaddr_t addr;
+  if (!util::BdAddrFromString(device_address, &addr)) {
+    LOG(ERROR) << "Invalid device address given: " << device_address;
+    return false;
+  }
+
+  // Get the connection IDs for which we will send this notification.
+  auto conn_iter = conn_addr_map_.find(device_address);
+  if (conn_iter == conn_addr_map_.end()) {
+    LOG(ERROR) << "No known connections for device with address: "
+               << device_address;
+    return false;
+  }
+
+  // Make sure that |characteristic_id| matches a valid attribute handle.
+  auto handle_iter = id_to_handle_map_.find(characteristic_id);
+  if (handle_iter == id_to_handle_map_.end()) {
+    LOG(ERROR) << "Unknown characteristic";
+    return false;
+  }
+
+  std::shared_ptr<PendingIndication> pending_ind(
+      new PendingIndication(callback));
+
+  // Send the notification/indication on all matching connections.
+  int send_count = 0;
+  for (auto conn : conn_iter->second) {
+    // Make sure that one isn't already pending for this connection.
+    if (pending_indications_.find(conn->conn_id) !=
+        pending_indications_.end()) {
+      VLOG(1) << "A" << (confirm ? "n indication" : " notification")
+              << " is already pending for connection: " << conn->conn_id;
+      continue;
+    }
+
+    // The HAL API takes char* rather const char* for |value|, so we have to
+    // cast away the const.
+    // TODO(armansito): Make HAL accept const char*.
+    bt_status_t status = hal::BluetoothGattInterface::Get()->
+        GetServerHALInterface()->send_indication(
+            server_if_,
+            handle_iter->second,
+            conn->conn_id,
+            value.size(),
+            confirm,
+            reinterpret_cast<char*>(const_cast<uint8_t*>(value.data())));
+
+    // Increment the send count if this was successful. We don't immediately
+    // fail if the HAL returned an error. It's better to report success as long
+    // as we sent out at least one notification to this device as
+    // multi-transport GATT connections from the same BD_ADDR will be rare
+    // enough already.
+    if (status != BT_STATUS_SUCCESS)
+      continue;
+
+    send_count++;
+    pending_indications_[conn->conn_id] = pending_ind;
+  }
+
+  if (send_count == 0) {
+    LOG(ERROR) << "Failed to send notifications/indications to device: "
+               << device_address;
+    return false;
+  }
+
+  return true;
+}
+
 void GattServer::ConnectionCallback(
     hal::BluetoothGattInterface* /* gatt_iface */,
     int conn_id, int server_if,
@@ -711,6 +790,32 @@ void GattServer::RequestExecWriteCallback(
   delegate_->OnExecuteWriteRequest(this, device_address, trans_id, exec_write);
 }
 
+void GattServer::IndicationSentCallback(
+    hal::BluetoothGattInterface* /* gatt_iface */,
+    int conn_id, int status) {
+  VLOG(1) << __func__ << " conn_id: " << conn_id << " status: " << status;
+  lock_guard<mutex> lock(mutex_);
+
+  const auto& pending_ind_iter = pending_indications_.find(conn_id);
+  if (pending_ind_iter == pending_indications_.end()) {
+    VLOG(1) << "Unknown connection: " << conn_id;
+    return;
+  }
+
+  std::shared_ptr<PendingIndication> pending_ind = pending_ind_iter->second;
+  pending_indications_.erase(pending_ind_iter);
+
+  if (status == BT_STATUS_SUCCESS)
+    pending_ind->has_success = true;
+
+  // Invoke it if this was the last reference to the confirmation callback.
+  if (pending_ind.unique() && pending_ind->callback) {
+    pending_ind->callback(
+        pending_ind->has_success ?
+        GATT_ERROR_NONE : static_cast<GATTError>(status));
+  }
+}
+
 void GattServer::NotifyEndCallbackAndClearData(
     BLEStatus status, const GattIdentifier& id) {
   VLOG(1) << __func__ << " status: " << status;
index 35cc587..f11c7c9 100644 (file)
@@ -125,6 +125,7 @@ class GattServer : public BluetoothClientInstance,
   // operation.
   using ResultCallback =
       std::function<void(BLEStatus status, const GattIdentifier& id)>;
+  using GattCallback = std::function<void(GATTError error)>;
 
   // Starts a new GATT service declaration for the service with the given
   // parameters. In the case of an error, for example If a service declaration
@@ -174,6 +175,22 @@ class GattServer : public BluetoothClientInstance,
                     GATTError error, int offset,
                     const std::vector<uint8_t>& value);
 
+  // Sends an ATT Handle-Value Notification to the device with BD_ADDR
+  // |device_address| for the characteristic with ID |characteristic_id| and
+  // value |value|. If |confirm| is true, then an ATT Handle-Value Indication
+  // will be sent instead, which requires the remote to confirm receipt. Returns
+  // false if there was an immediate error in initiating the notification
+  // procedure. Otherwise, returns true and reports the asynchronous result of
+  // the operation in |callback|.
+  //
+  // If |confirm| is true, then |callback| will be run when the remote device
+  // sends a ATT Handle-Value Confirmation packet. Otherwise, it will be run as
+  // soon as the notification has been sent out.
+  bool SendNotification(const std::string& device_address,
+                        const GattIdentifier& characteristic_id,
+                        bool confirm, const std::vector<uint8_t>& value,
+                        const GattCallback& callback);
+
  private:
   friend class GattServerFactory;
 
@@ -216,6 +233,15 @@ class GattServer : public BluetoothClientInstance,
     bt_bdaddr_t bdaddr;
   };
 
+  // Used to keep track of a pending Handle-Value indication.
+  struct PendingIndication {
+    PendingIndication(const GattCallback& callback)
+        : has_success(false), callback(callback) {}
+
+    bool has_success;
+    GattCallback callback;
+  };
+
   // Constructor shouldn't be called directly as instance are meant to be
   // obtained from the factory.
   GattServer(const UUID& uuid, int server_if);
@@ -274,6 +300,9 @@ class GattServer : public BluetoothClientInstance,
       hal::BluetoothGattInterface* gatt_iface,
       int conn_id, int trans_id,
       const bt_bdaddr_t& bda, int exec_write) override;
+  void IndicationSentCallback(
+      hal::BluetoothGattInterface* gatt_iface,
+      int conn_id, int status) override;
 
   // Helper function that notifies and clears the pending callback.
   void NotifyEndCallbackAndClearData(BLEStatus status,
@@ -316,6 +345,13 @@ class GattServer : public BluetoothClientInstance,
   std::unordered_map<std::string, std::vector<std::shared_ptr<Connection>>>
       conn_addr_map_;
 
+  // Connections for which a Handle-Value indication is pending. Since there can
+  // be multiple indications to the same device (in the case of a dual-mode
+  // device with simulatenous BR/EDR & LE GATT connections), we also keep track
+  // of whether there has been at least one successful confirmation.
+  std::unordered_map<int, std::shared_ptr<PendingIndication>>
+      pending_indications_;
+
   // Raw handle to the Delegate, which must outlive this GattServer instance.
   Delegate* delegate_;
 
index d4ea097..8eee886 100644 (file)
@@ -233,6 +233,15 @@ void RequestExecWriteCallback(int conn_id, int trans_id,
       g_interface, conn_id, trans_id, *bda, exec_write));
 }
 
+void IndicationSentCallback(int conn_id, int status) {
+  lock_guard<mutex> lock(g_instance_lock);
+  VLOG(2) << __func__ << " - conn_id: " << conn_id << " status: " << status;
+  VERIFY_INTERFACE_OR_RETURN();
+
+  FOR_EACH_SERVER_OBSERVER(IndicationSentCallback(
+      g_interface, conn_id, status));
+}
+
 // The HAL Bluetooth GATT client interface callbacks. These signal a mixture of
 // GATT client-role and GAP events.
 const btgatt_client_callbacks_t gatt_client_callbacks = {
@@ -285,7 +294,7 @@ const btgatt_server_callbacks_t gatt_server_callbacks = {
     RequestWriteCallback,
     RequestExecWriteCallback,
     nullptr,  // response_confirmation_cb,
-    nullptr,  // indication_sent_cb
+    IndicationSentCallback,
     nullptr,  // congestion_cb
     nullptr,  // mtu_changed_cb
 };
@@ -542,7 +551,7 @@ void BluetoothGattInterface::ServerObserver::RequestWriteCallback(
 }
 
 void BluetoothGattInterface::ServerObserver::RequestExecWriteCallback(
-    BluetoothGattInterface* gatt_iface,
+    BluetoothGattInterface* /* gatt_iface */,
     int /* conn_id */,
     int /* trans_id */,
     const bt_bdaddr_t& /* bda */,
@@ -550,6 +559,13 @@ void BluetoothGattInterface::ServerObserver::RequestExecWriteCallback(
   // Do nothing.
 }
 
+void BluetoothGattInterface::ServerObserver::IndicationSentCallback(
+    BluetoothGattInterface* /* gatt_iface */,
+    int /* conn_id */,
+    int /* status */) {
+  // Do nothing.
+}
+
 // static
 bool BluetoothGattInterface::Initialize() {
   lock_guard<mutex> lock(g_instance_lock);
index 8dfba6e..ce781e5 100644 (file)
@@ -136,6 +136,9 @@ class BluetoothGattInterface {
         int conn_id, int trans_id,
         const bt_bdaddr_t& bda, int exec_write);
 
+    virtual void IndicationSentCallback(
+        BluetoothGattInterface* gatt_iface, int conn_id, int status);
+
     // TODO(armansito): Complete the list of callbacks.
   };
 
index 8ab2a41..41183aa 100644 (file)
@@ -131,6 +131,16 @@ bt_status_t FakeDeleteService(int server_if, int srvc_handle) {
   return BT_STATUS_FAIL;
 }
 
+bt_status_t FakeSendIndication(int server_if, int attribute_handle,
+                               int conn_id, int len, int confirm,
+                               char* value) {
+  if (g_server_handler)
+    return g_server_handler->SendIndication(server_if, attribute_handle,
+                                            conn_id, len, confirm, value);
+
+  return BT_STATUS_FAIL;
+}
+
 bt_status_t FakeSendResponse(int conn_id, int trans_id, int status,
                              btgatt_response_t* response) {
   if (g_server_handler)
@@ -191,7 +201,7 @@ btgatt_server_interface_t fake_btgatts_iface = {
   FakeStartService,
   nullptr,  // stop_service
   FakeDeleteService,
-  nullptr,  // send_indication
+  FakeSendIndication,
   FakeSendResponse,
 };
 
@@ -325,6 +335,12 @@ void FakeBluetoothGattInterface::NotifyRequestExecWriteCallback(
       RequestExecWriteCallback(this, conn_id, trans_id, bda, exec_write));
 }
 
+void FakeBluetoothGattInterface::NotifyIndicationSentCallback(
+    int conn_id, int status) {
+  FOR_EACH_OBSERVER(ServerObserver, server_observers_,
+                    IndicationSentCallback(this, conn_id, status));
+}
+
 void FakeBluetoothGattInterface::AddClientObserver(ClientObserver* observer) {
   CHECK(observer);
   client_observers_.AddObserver(observer);
index 7b24928..03f25f9 100644 (file)
@@ -67,6 +67,9 @@ class FakeBluetoothGattInterface : public BluetoothGattInterface {
     virtual bt_status_t StartService(
         int server_if, int srvc_handle, int transport) = 0;
     virtual bt_status_t DeleteService(int server_if, int srvc_handle) = 0;
+    virtual bt_status_t SendIndication(int server_if, int attribute_handle,
+                                       int conn_id, int len, int confirm,
+                                       char* value) = 0;
     virtual bt_status_t SendResponse(int conn_id, int trans_id, int status,
                                      btgatt_response_t* response) = 0;
   };
@@ -113,6 +116,7 @@ class FakeBluetoothGattInterface : public BluetoothGattInterface {
                                   bool need_rsp, bool is_prep, uint8_t* value);
   void NotifyRequestExecWriteCallback(int conn_id, int trans_id,
                                       const bt_bdaddr_t& bda, int exec_write);
+  void NotifyIndicationSentCallback(int conn_id, int status);
 
   // BluetoothGattInterface overrides:
   void AddClientObserver(ClientObserver* observer) override;
index 4d5a5df..84c0035 100644 (file)
@@ -145,6 +145,28 @@ status_t BnBluetoothGattServer::onTransact(
 
     return android::NO_ERROR;
   }
+  case SEND_NOTIFICATION_TRANSACTION: {
+    int server_if = data.readInt32();
+    std::string device_address = data.readCString();
+    auto char_id = CreateGattIdentifierFromParcel(data);
+    CHECK(char_id);
+    bool confirm = data.readInt32();
+
+    std::vector<uint8_t> value;
+    int value_len = data.readInt32();
+    if (value_len != -1) {
+      uint8_t bytes[value_len];
+      data.read(bytes, value_len);
+      value.insert(value.begin(), bytes, bytes + value_len);
+    }
+
+    bool result = SendNotification(server_if, device_address, *char_id, confirm,
+                                   value);
+
+    reply->writeInt32(result);
+
+    return android::NO_ERROR;
+  }
   default:
     return BBinder::onTransact(code, data, reply, flags);
   }
@@ -289,6 +311,27 @@ bool BpBluetoothGattServer::SendResponse(
   return reply.readInt32();
 }
 
+bool BpBluetoothGattServer::SendNotification(
+    int server_if,
+    const std::string& device_address,
+    const bluetooth::GattIdentifier& characteristic_id,
+    bool confirm,
+    const std::vector<uint8_t>& value) {
+  Parcel data, reply;
+
+  data.writeInterfaceToken(IBluetoothGattServer::getInterfaceDescriptor());
+  data.writeInt32(server_if);
+  data.writeCString(device_address.c_str());
+  WriteGattIdentifierToParcel(characteristic_id, &data);
+  data.writeInt32(confirm);
+  data.writeByteArray(value.size(), value.data());
+
+  remote()->transact(IBluetoothGattServer::SEND_NOTIFICATION_TRANSACTION,
+                     data, &reply);
+
+  return reply.readInt32();
+}
+
 IMPLEMENT_META_INTERFACE(BluetoothGattServer,
                          IBluetoothGattServer::kServiceName);
 
index 7e07330..904d212 100644 (file)
@@ -83,6 +83,13 @@ class IBluetoothGattServer : public android::IInterface {
       int status, int offset,
       const std::vector<uint8_t>& value) = 0;
 
+  virtual bool SendNotification(
+      int server_if,
+      const std::string& device_address,
+      const bluetooth::GattIdentifier& characteristic_id,
+      bool confirm,
+      const std::vector<uint8_t>& value) = 0;
+
   // TODO(armansito): Complete the API definition.
 
  private:
@@ -136,6 +143,12 @@ class BpBluetoothGattServer
       int request_id,
       int status, int offset,
       const std::vector<uint8_t>& value) override;
+  bool SendNotification(
+      int server_if,
+      const std::string& device_address,
+      const bluetooth::GattIdentifier& characteristic_id,
+      bool confirm,
+      const std::vector<uint8_t>& value) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(BpBluetoothGattServer);
index de9d0ba..057f3fa 100644 (file)
@@ -133,6 +133,13 @@ status_t BnBluetoothGattServerCallback::onTransact(
     OnExecuteWriteRequest(device_address, request_id, is_exec);
     return android::NO_ERROR;
   }
+  case ON_NOTIFICATION_SENT_TRANSACTION: {
+    std::string device_address = data.readCString();
+    int status = data.readInt32();
+
+    OnNotificationSent(device_address, status);
+    return android::NO_ERROR;
+  }
   default:
     return BBinder::onTransact(code, data, reply, flags);
   }
@@ -279,6 +286,22 @@ void BpBluetoothGattServerCallback::OnExecuteWriteRequest(
       IBinder::FLAG_ONEWAY);
 }
 
+void BpBluetoothGattServerCallback::OnNotificationSent(
+    const std::string& device_address,
+    int status) {
+  Parcel data, reply;
+
+  data.writeInterfaceToken(
+      IBluetoothGattServerCallback::getInterfaceDescriptor());
+  data.writeCString(device_address.c_str());
+  data.writeInt32(status);
+
+  remote()->transact(
+      IBluetoothGattServerCallback::ON_NOTIFICATION_SENT_TRANSACTION,
+      data, &reply,
+      IBinder::FLAG_ONEWAY);
+}
+
 IMPLEMENT_META_INTERFACE(BluetoothGattServerCallback,
                          IBluetoothGattServerCallback::kServiceName);
 
index ca3e036..ee90d0f 100644 (file)
@@ -85,7 +85,8 @@ namespace binder {
       const std::string& device_address,
       int request_id, bool is_execute) = 0;
 
-  // TODO(armansito): Complete the API definition.
+  virtual void OnNotificationSent(const std::string& device_address,
+                                  int status) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(IBluetoothGattServerCallback);
@@ -142,6 +143,8 @@ class BpBluetoothGattServerCallback
   void OnExecuteWriteRequest(
       const std::string& device_address,
       int request_id, bool is_execute) override;
+  void OnNotificationSent(const std::string& device_address,
+                          int status) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(BpBluetoothGattServerCallback);
index 0f7e850..631d6e4 100644 (file)
@@ -187,6 +187,51 @@ bool BluetoothGattServerBinderServer::SendResponse(
       offset, value);
 }
 
+bool BluetoothGattServerBinderServer::SendNotification(
+    int server_if,
+    const std::string& device_address,
+    const bluetooth::GattIdentifier& characteristic_id,
+    bool confirm,
+    const std::vector<uint8_t>& value) {
+  VLOG(2) << __func__;
+  std::lock_guard<std::mutex> lock(*maps_lock());
+
+  auto gatt_server = GetGattServer(server_if);
+  if (!gatt_server) {
+    LOG(ERROR) << "Unknown server_if: " << server_if;
+    return false;
+  }
+
+  // Create a weak pointer and pass that to the callback to prevent a potential
+  // use after free.
+  android::wp<BluetoothGattServerBinderServer> weak_ptr_to_this(this);
+  auto callback = [=](bluetooth::GATTError error) {
+    auto sp_to_this = weak_ptr_to_this.promote();
+    if (!sp_to_this.get()) {
+      VLOG(2) << "BluetoothLowEnergyBinderServer was deleted";
+      return;
+    }
+
+    std::lock_guard<std::mutex> lock(*maps_lock());
+
+    auto gatt_cb = GetGattServerCallback(server_if);
+    if (!gatt_cb.get()) {
+      VLOG(2) << "The callback was deleted";
+      return;
+    }
+
+    gatt_cb->OnNotificationSent(device_address, error);
+  };
+
+  if (!gatt_server->SendNotification(device_address, characteristic_id,
+                                     confirm, value, callback)) {
+    LOG(ERROR) << "Failed to send notification";
+    return false;
+  }
+
+  return true;
+}
+
 void BluetoothGattServerBinderServer::OnCharacteristicReadRequest(
     bluetooth::GattServer* gatt_server,
     const std::string& device_address,
index ae90af9..869a66f 100644 (file)
@@ -57,6 +57,12 @@ class BluetoothGattServerBinderServer : public BnBluetoothGattServer,
   bool SendResponse(int server_if, const std::string& device_address,
                     int request_id, int status, int offset,
                     const std::vector<uint8_t>& value) override;
+  bool SendNotification(
+      int server_if,
+      const std::string& device_address,
+      const bluetooth::GattIdentifier& characteristic_id,
+      bool confirm,
+      const std::vector<uint8_t>& value) override;
 
   // bluetooth::GattServer::Delegate overrides:
   void OnCharacteristicReadRequest(
index fb1b5eb..c7a660a 100644 (file)
@@ -41,6 +41,7 @@ class MockGattHandler
   MOCK_METHOD4(AddDescriptor, bt_status_t(int, int, bt_uuid_t*, int));
   MOCK_METHOD3(StartService, bt_status_t(int, int, int));
   MOCK_METHOD2(DeleteService, bt_status_t(int, int));
+  MOCK_METHOD6(SendIndication, bt_status_t(int, int, int, int, int, char*));
   MOCK_METHOD4(SendResponse, bt_status_t(int, int, int, btgatt_response_t*));
 
  private:
@@ -1202,5 +1203,134 @@ TEST_F(GattServerPostRegisterTest, RequestWrite) {
   gatt_server_->SetDelegate(nullptr);
 }
 
+TEST_F(GattServerPostRegisterTest, SendNotification) {
+  SetUpTestService();
+
+  const std::string kTestAddress0 = "01:23:45:67:89:AB";
+  const std::string kTestAddress1 = "cd:ef:01:23:45:67";
+  const std::string kInvalidAddress = "thingamajig blabbidyboop";
+  const int kConnId0 = 0;
+  const int kConnId1 = 1;
+  std::vector<uint8_t> value;
+  bt_bdaddr_t hal_addr0;
+  ASSERT_TRUE(util::BdAddrFromString(kTestAddress0, &hal_addr0));
+
+  // Set up two connections with the same address.
+  fake_hal_gatt_iface_->NotifyServerConnectionCallback(
+      kConnId0, kDefaultServerId, true, hal_addr0);
+  fake_hal_gatt_iface_->NotifyServerConnectionCallback(
+      kConnId1, kDefaultServerId, true, hal_addr0);
+
+  // Set up a test callback.
+  GATTError gatt_error;
+  int callback_count = 0;
+  auto callback = [&](GATTError in_error) {
+    gatt_error = in_error;
+    callback_count++;
+  };
+
+  // Bad device address.
+  EXPECT_FALSE(gatt_server_->SendNotification(
+      kInvalidAddress,
+      test_char_id_, false, value, callback));
+
+  // Bad connection.
+  EXPECT_FALSE(gatt_server_->SendNotification(
+      kTestAddress1,
+      test_char_id_, false, value, callback));
+
+  // We should get a HAL call for each connection for this address. The calls
+  // fail.
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId0,
+                             value.size(), 0, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_FAIL));
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId1,
+                             value.size(), 0, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_FAIL));
+  EXPECT_FALSE(gatt_server_->SendNotification(
+      kTestAddress0,
+      test_char_id_, false, value, callback));
+
+  // One of the calls succeeds.
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId0,
+                             value.size(), 0, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId1,
+                             value.size(), 0, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_FAIL));
+  EXPECT_TRUE(gatt_server_->SendNotification(
+      kTestAddress0,
+      test_char_id_, false, value, callback));
+
+  // One of the connections is already pending so there should be only one call.
+  // This one we send with confirm=true.
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId1,
+                             value.size(), 1, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_TRUE(gatt_server_->SendNotification(
+      kTestAddress0,
+      test_char_id_, true, value, callback));
+
+  // Calls are already pending.
+  EXPECT_FALSE(gatt_server_->SendNotification(
+      kTestAddress0, test_char_id_, true, value, callback));
+
+  // Trigger one confirmation callback. We should get calls for two callbacks
+  // since we have two separate calls pending.
+  fake_hal_gatt_iface_->NotifyIndicationSentCallback(
+      kConnId0, BT_STATUS_SUCCESS);
+  fake_hal_gatt_iface_->NotifyIndicationSentCallback(
+      kConnId1, BT_STATUS_SUCCESS);
+  EXPECT_EQ(2, callback_count);
+  EXPECT_EQ(GATT_ERROR_NONE, gatt_error);
+
+  callback_count = 0;
+
+  // Restart. Both calls succeed now.
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId0,
+                             value.size(), 0, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_CALL(*mock_handler_,
+              SendIndication(kDefaultServerId, char_handle_, kConnId1,
+                             value.size(), 0, nullptr))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_TRUE(gatt_server_->SendNotification(
+      kTestAddress0,
+      test_char_id_, false, value, callback));
+
+  // Trigger one confirmation callback. The callback we passed should still be
+  // pending. The first callback is for the wrong connection ID.
+  fake_hal_gatt_iface_->NotifyIndicationSentCallback(
+      kConnId0 + 50, BT_STATUS_FAIL);
+  fake_hal_gatt_iface_->NotifyIndicationSentCallback(
+      kConnId0, BT_STATUS_SUCCESS);
+  EXPECT_EQ(0, callback_count);
+
+  // This should be ignored since |kConnId0| was already processed.
+  fake_hal_gatt_iface_->NotifyIndicationSentCallback(
+      kConnId0, BT_STATUS_SUCCESS);
+  EXPECT_EQ(0, callback_count);
+
+  // Run the callback with failure. Since the previous callback reported
+  // success, we should report success.
+  fake_hal_gatt_iface_->NotifyIndicationSentCallback(
+      kConnId1, BT_STATUS_SUCCESS);
+  EXPECT_EQ(1, callback_count);
+  EXPECT_EQ(GATT_ERROR_NONE, gatt_error);
+}
+
 }  // namespace
 }  // namespace bluetooth