Merge "Add Connect and Disconnect methods to LowEnergyClient"
diff --git a/service/ipc/binder/bluetooth_low_energy_binder_server.cpp b/service/ipc/binder/bluetooth_low_energy_binder_server.cpp
index f20e9c4..04c8450 100644
--- a/service/ipc/binder/bluetooth_low_energy_binder_server.cpp
+++ b/service/ipc/binder/bluetooth_low_energy_binder_server.cpp
@@ -168,6 +168,12 @@
   return true;
 }
 
+void BluetoothLowEnergyBinderServer::OnConnectionState(
+      bluetooth::LowEnergyClient* client, int status,
+      const char* address, bool connected) {
+  VLOG(2) << __func__ << " address: " << address << " connected: " << connected;
+}
+
 void BluetoothLowEnergyBinderServer::OnScanResult(
     bluetooth::LowEnergyClient* client,
     const bluetooth::ScanResult& result) {
diff --git a/service/ipc/binder/bluetooth_low_energy_binder_server.h b/service/ipc/binder/bluetooth_low_energy_binder_server.h
index 666fb77..ed4a476 100644
--- a/service/ipc/binder/bluetooth_low_energy_binder_server.h
+++ b/service/ipc/binder/bluetooth_low_energy_binder_server.h
@@ -60,6 +60,8 @@
   bool StopMultiAdvertising(int client_id) override;
 
   // bluetooth::LowEnergyClient::Delegate overrides:
+  void OnConnectionState(bluetooth::LowEnergyClient* client, int status,
+                         const char* address, bool connected) override;
   void OnScanResult(bluetooth::LowEnergyClient* client,
                     const bluetooth::ScanResult& result) override;
 
diff --git a/service/low_energy_client.cpp b/service/low_energy_client.cpp
index b3f9d64..eb46d4b 100644
--- a/service/low_energy_client.cpp
+++ b/service/low_energy_client.cpp
@@ -19,6 +19,7 @@
 #include <base/logging.h>
 
 #include "service/adapter.h"
+#include "service/common/bluetooth/util/address_helper.h"
 #include "service/logging_helpers.h"
 #include "stack/include/bt_types.h"
 #include "stack/include/hcidefs.h"
@@ -107,7 +108,7 @@
 
 bool ProcessServiceData(const uint8_t* data,
         uint8_t uuid_len,
-        HALAdvertiseData* out_data){
+        HALAdvertiseData* out_data) {
   size_t field_len = data[0];
 
   // Minimum packet size should be equal to the uuid length + 1 to include
@@ -142,7 +143,7 @@
     // through uuid field
     VLOG(1) << "More than one UUID entry not allowed";
     return false;
-  } // else do nothing as UUID is already properly assigned
+  }  // else do nothing as UUID is already properly assigned
 
   // Use + uuid_len + 2 here in order to skip over a
   // uuid contained in the beggining of the field
@@ -334,6 +335,49 @@
     StopScan();
 }
 
+bool LowEnergyClient::Connect(std::string address, bool is_direct) {
+  VLOG(2) << __func__ << "Address: " << address << " is_direct: " << is_direct;
+
+  bt_bdaddr_t bda;
+  util::BdAddrFromString(address, &bda);
+
+  bt_status_t status = hal::BluetoothGattInterface::Get()->
+      GetClientHALInterface()->connect(client_id_, &bda, is_direct,
+                                       BT_TRANSPORT_LE);
+  if (status != BT_STATUS_SUCCESS) {
+    LOG(ERROR) << "HAL call to connect failed";
+    return false;
+  }
+
+  return true;
+}
+
+bool LowEnergyClient::Disconnect(std::string address) {
+  VLOG(2) << __func__ << "Address: " << address;
+
+  bt_bdaddr_t bda;
+  util::BdAddrFromString(address, &bda);
+
+  std::map<const bt_bdaddr_t, int>::iterator conn_id;
+  {
+    lock_guard<mutex> lock(connection_fields_lock_);
+    conn_id = connection_ids_.find(bda);
+    if (conn_id == connection_ids_.end()) {
+      LOG(WARNING) << "Can't disconnect, no existing connection to " << address;
+      return false;
+    }
+  }
+
+  bt_status_t status = hal::BluetoothGattInterface::Get()->
+      GetClientHALInterface()->disconnect(client_id_, &bda, conn_id->second);
+  if (status != BT_STATUS_SUCCESS) {
+    LOG(ERROR) << "HAL call to disconnect failed";
+    return false;
+  }
+
+  return true;
+}
+
 void LowEnergyClient::SetDelegate(Delegate* delegate) {
   lock_guard<mutex> lock(delegate_mutex_);
   delegate_ = delegate;
@@ -513,6 +557,46 @@
   delegate_->OnScanResult(this, result);
 }
 
+void LowEnergyClient::ConnectCallback(
+      hal::BluetoothGattInterface* gatt_iface, int conn_id, int status,
+      int client_id, const bt_bdaddr_t& bda) {
+  if (client_id != client_id_)
+    return;
+
+  VLOG(1) << __func__ << "client_id: " << client_id << " status: " << status;
+
+  {
+    lock_guard<mutex> lock(connection_fields_lock_);
+    auto success = connection_ids_.emplace(bda, conn_id);
+    if (!success.second) {
+      LOG(ERROR) << __func__ << " Insertion into connection_ids_ failed!";
+    }
+  }
+
+  if (delegate_)
+    delegate_->OnConnectionState(this, status, BtAddrString(&bda).c_str(),
+                                 true);
+}
+
+void LowEnergyClient::DisconnectCallback(
+      hal::BluetoothGattInterface* gatt_iface, int conn_id, int status,
+      int client_id, const bt_bdaddr_t& bda) {
+  if (client_id != client_id_)
+    return;
+
+  VLOG(1) << __func__ << " client_id: " << client_id << " status: " << status;
+  {
+    lock_guard<mutex> lock(connection_fields_lock_);
+    if (!connection_ids_.erase(bda)) {
+      LOG(ERROR) << __func__ << " Erasing from connection_ids_ failed!";
+    }
+  }
+
+  if (delegate_)
+    delegate_->OnConnectionState(this, status, BtAddrString(&bda).c_str(),
+                                 false);
+}
+
 void LowEnergyClient::MultiAdvEnableCallback(
     hal::BluetoothGattInterface* gatt_iface,
     int client_id, int status) {
diff --git a/service/low_energy_client.h b/service/low_energy_client.h
index c1e787d..700ad9c 100644
--- a/service/low_energy_client.h
+++ b/service/low_energy_client.h
@@ -35,6 +35,12 @@
 
 namespace bluetooth {
 
+struct ConnComparator {
+    bool operator()(const bt_bdaddr_t& a, const bt_bdaddr_t& b) const {
+        return memcmp(&a, &b, sizeof(bt_bdaddr_t)) < 0;
+    }
+};
+
 class Adapter;
 
 // A LowEnergyClient represents an application's handle to perform various
@@ -55,6 +61,10 @@
     virtual void OnScanResult(LowEnergyClient* client,
                               const ScanResult& scan_result) = 0;
 
+    // Called asynchronously to notify the delegate of connection state change
+    virtual void OnConnectionState(LowEnergyClient* client, int status,
+                                   const char* address, bool connected) = 0;
+
    private:
     DISALLOW_COPY_AND_ASSIGN(Delegate);
   };
@@ -70,6 +80,15 @@
   // Callback type used to return the result of asynchronous operations below.
   using StatusCallback = std::function<void(BLEStatus)>;
 
+  // Initiates a BLE connection do device with address |address|. If
+  // |is_direct| is set, use direct connect procedure. Return true on success
+  //, false otherwise.
+  bool Connect(std::string address, bool is_direct);
+
+  // Disconnect from previously connected BLE device with address |address|.
+  // Return true on success, false otherwise.
+  bool Disconnect(std::string address);
+
   // Initiates a BLE device scan for this client using the given |settings| and
   // |filters|. See the documentation for ScanSettings and ScanFilter for how
   // these parameters can be configured. Return true on success, false
@@ -102,7 +121,7 @@
 
   // Returns the current advertising settings.
   const AdvertiseSettings& advertise_settings() const {
-   return advertise_settings_;
+    return advertise_settings_;
   }
 
   // Returns the current scan settings.
@@ -123,6 +142,13 @@
   void ScanResultCallback(
       hal::BluetoothGattInterface* gatt_iface,
       const bt_bdaddr_t& bda, int rssi, uint8_t* adv_data) override;
+
+  void ConnectCallback(
+      hal::BluetoothGattInterface* gatt_iface, int conn_id, int status,
+      int client_id, const bt_bdaddr_t& bda) override;
+  void DisconnectCallback(
+      hal::BluetoothGattInterface* gatt_iface, int conn_id, int status,
+      int client_id, const bt_bdaddr_t& bda) override;
   void MultiAdvEnableCallback(
       hal::BluetoothGattInterface* gatt_iface,
       int client_id, int status) override;
@@ -190,6 +216,12 @@
   std::mutex delegate_mutex_;
   Delegate* delegate_;
 
+  // Protects device connection related members below.
+  std::mutex connection_fields_lock_;
+
+  // Maps bluetooth address to connection id
+  std::map<const bt_bdaddr_t, int, ConnComparator> connection_ids_;
+
   DISALLOW_COPY_AND_ASSIGN(LowEnergyClient);
 };
 
diff --git a/service/test/low_energy_client_unittest.cpp b/service/test/low_energy_client_unittest.cpp
index 6f99d3f..70bbd2a 100644
--- a/service/test/low_energy_client_unittest.cpp
+++ b/service/test/low_energy_client_unittest.cpp
@@ -27,6 +27,9 @@
 
 using ::testing::_;
 using ::testing::Return;
+using ::testing::Pointee;
+using ::testing::DoAll;
+using ::testing::Invoke;
 
 namespace bluetooth {
 namespace {
@@ -74,7 +77,7 @@
 
 class TestDelegate : public LowEnergyClient::Delegate {
  public:
-  TestDelegate() : scan_result_count_(0) {
+  TestDelegate() : scan_result_count_(0), connection_state_count_(0) {
   }
 
   ~TestDelegate() override = default;
@@ -82,6 +85,14 @@
   int scan_result_count() const { return scan_result_count_; }
   const ScanResult& last_scan_result() const { return last_scan_result_; }
 
+  int connection_state_count() const { return connection_state_count_; }
+
+  void OnConnectionState(LowEnergyClient* client, int status,
+                         const char* address, bool connected)  {
+    ASSERT_TRUE(client);
+    connection_state_count_++;
+  }
+
   void OnScanResult(LowEnergyClient* client, const ScanResult& scan_result) {
     ASSERT_TRUE(client);
     scan_result_count_++;
@@ -92,6 +103,8 @@
   int scan_result_count_;
   ScanResult last_scan_result_;
 
+  int connection_state_count_;
+
   DISALLOW_COPY_AND_ASSIGN(TestDelegate);
 };
 
@@ -248,7 +261,7 @@
     ASSERT_FALSE(le_client_->IsStoppingAdvertising());
   }
 
-  void AdvertiseDataTestHelper(AdvertiseData data, std::function<void(BLEStatus)> callback){
+  void AdvertiseDataTestHelper(AdvertiseData data, std::function<void(BLEStatus)> callback) {
     AdvertiseSettings settings;
     EXPECT_TRUE(le_client_->StartAdvertising(
         settings, data, AdvertiseData(), callback));
@@ -835,7 +848,7 @@
   EXPECT_EQ(service_data, adv_handler->service_data());
   EXPECT_EQ(uuid_32bit_canonical, adv_handler->uuid_data());
 
-  //Service data and UUID where the UUID for dont match, should fail
+  // Service data and UUID where the UUID for dont match, should fail
   EXPECT_TRUE(le_client_->StartAdvertising(
               settings, service_uuid_mismatch, AdvertiseData(), callback));
   fake_hal_gatt_iface_->NotifyMultiAdvEnableCallback(
@@ -856,7 +869,7 @@
   // Adapter is not enabled.
   EXPECT_FALSE(le_client_->StartScan(settings, filters));
 
-  //TODO(jpawlowski): add tests checking settings and filter parsing when
+  // TODO(jpawlowski): add tests checking settings and filter parsing when
   // implemented
 
   // These should succeed and result in a HAL call
@@ -937,5 +950,60 @@
   le_client_->SetDelegate(nullptr);
 }
 
+MATCHER_P(BitEq, x, std::string(negation ? "isn't" : "is") +
+                        " bitwise equal to " + ::testing::PrintToString(x)) {
+  static_assert(sizeof(x) == sizeof(arg), "Size mismatch");
+  return std::memcmp(&arg, &x, sizeof(x)) == 0;
+}
+
+TEST_F(LowEnergyClientPostRegisterTest, Connect) {
+  const bt_bdaddr_t kTestAddress = {
+    { 0x01, 0x02, 0x03, 0x0A, 0x0B, 0x0C }
+  };
+  const char kTestAddressStr[] = "01:02:03:0A:0B:0C";
+  const bool kTestDirect = false;
+  const int connId = 12;
+
+  TestDelegate delegate;
+  le_client_->SetDelegate(&delegate);
+
+  // TODO(jpawlowski): NotifyConnectCallback should be called after returning
+  // success, fix it when it becomes important.
+  // These should succeed and result in a HAL call
+  EXPECT_CALL(*mock_handler_, Connect(le_client_->GetInstanceId(),
+          Pointee(BitEq(kTestAddress)), kTestDirect, BT_TRANSPORT_LE))
+      .Times(1)
+      .WillOnce(DoAll(
+        Invoke([&](int client_id, const bt_bdaddr_t *bd_addr, bool is_direct,
+                   int transport){
+          fake_hal_gatt_iface_->NotifyConnectCallback(connId, BT_STATUS_SUCCESS,
+                                                      client_id, *bd_addr);
+        }),
+        Return(BT_STATUS_SUCCESS)));
+
+  EXPECT_TRUE(le_client_->Connect(kTestAddressStr, kTestDirect));
+  EXPECT_EQ(1, delegate.connection_state_count());
+
+  // TODO(jpawlowski): same as above
+  // These should succeed and result in a HAL call
+  EXPECT_CALL(*mock_handler_, Disconnect(le_client_->GetInstanceId(),
+        Pointee(BitEq(kTestAddress)), connId))
+      .Times(1)
+      .WillOnce(DoAll(
+        Invoke([&](int client_id, const bt_bdaddr_t *bd_addr, int connId){
+          fake_hal_gatt_iface_->NotifyDisconnectCallback(connId,
+                                                         BT_STATUS_SUCCESS,
+                                                         client_id, *bd_addr);
+        }),
+        Return(BT_STATUS_SUCCESS)));
+
+  EXPECT_TRUE(le_client_->Disconnect(kTestAddressStr));
+  EXPECT_EQ(2, delegate.connection_state_count());
+
+  le_client_->SetDelegate(nullptr);
+  ::testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
+}
+
+
 }  // namespace
 }  // namespace bluetooth