Merge "ClassicSecurity: Remove extra Event conversion"
diff --git a/audio_bluetooth_hw/audio_bluetooth_hw.cc b/audio_bluetooth_hw/audio_bluetooth_hw.cc
index 5d0b43a..887c4e3 100644
--- a/audio_bluetooth_hw/audio_bluetooth_hw.cc
+++ b/audio_bluetooth_hw/audio_bluetooth_hw.cc
@@ -102,7 +102,8 @@
 static int adev_dump(const audio_hw_device_t* device, int fd) { return 0; }
 
 static int adev_close(hw_device_t* device) {
-  free(device);
+  auto* bluetooth_device = reinterpret_cast<BluetoothAudioDevice*>(device);
+  delete bluetooth_device;
   return 0;
 }
 
@@ -111,7 +112,7 @@
   LOG(VERBOSE) << __func__ << ": name=[" << name << "]";
   if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) return -EINVAL;
 
-  auto bluetooth_audio_device = new BluetoothAudioDevice;
+  auto bluetooth_audio_device = new BluetoothAudioDevice{};
   struct audio_hw_device* adev = &bluetooth_audio_device->audio_device_;
   if (!adev) return -ENOMEM;
 
diff --git a/audio_bluetooth_hw/stream_apis.cc b/audio_bluetooth_hw/stream_apis.cc
index 7b01c32..d809b57 100644
--- a/audio_bluetooth_hw/stream_apis.cc
+++ b/audio_bluetooth_hw/stream_apis.cc
@@ -635,7 +635,7 @@
                             struct audio_stream_out** stream_out,
                             const char* address __unused) {
   *stream_out = nullptr;
-  auto* out = new BluetoothStreamOut;
+  auto* out = new BluetoothStreamOut{};
   if (!out->bluetooth_output_.SetUp(devices)) {
     delete out;
     return -EINVAL;
diff --git a/audio_bluetooth_hw/stream_apis.h b/audio_bluetooth_hw/stream_apis.h
index e319d95..c894d1e 100644
--- a/audio_bluetooth_hw/stream_apis.h
+++ b/audio_bluetooth_hw/stream_apis.h
@@ -46,7 +46,7 @@
 struct BluetoothStreamOut {
   // Must be the first member so it can be cast from audio_stream
   // or audio_stream_out pointer
-  audio_stream_out stream_out_;
+  audio_stream_out stream_out_{};
   ::android::bluetooth::audio::BluetoothAudioPortOut bluetooth_output_;
   int64_t last_write_time_us_;
   // Audio PCM Configs
@@ -66,7 +66,7 @@
 struct BluetoothAudioDevice {
   // Important: device must be first as an audio_hw_device* may be cast to
   // BluetoothAudioDevice* when the type is implicitly known.
-  audio_hw_device audio_device_;
+  audio_hw_device audio_device_{};
   // protect against device->output and stream_out from being inconsistent
   std::mutex mutex_;
   std::list<BluetoothStreamOut*> opened_stream_outs_ =
diff --git a/binder/android/bluetooth/IBluetoothA2dp.aidl b/binder/android/bluetooth/IBluetoothA2dp.aidl
index 3b406cc..39157e5 100644
--- a/binder/android/bluetooth/IBluetoothA2dp.aidl
+++ b/binder/android/bluetooth/IBluetoothA2dp.aidl
@@ -52,4 +52,5 @@
     int supportsOptionalCodecs(in BluetoothDevice device);
     int getOptionalCodecsEnabled(in BluetoothDevice device);
     oneway void setOptionalCodecsEnabled(in BluetoothDevice device, int value);
+    int getPriority(in BluetoothDevice device);
 }
diff --git a/binder/android/bluetooth/IBluetoothHeadset.aidl b/binder/android/bluetooth/IBluetoothHeadset.aidl
index bc8fa05..0edac14 100644
--- a/binder/android/bluetooth/IBluetoothHeadset.aidl
+++ b/binder/android/bluetooth/IBluetoothHeadset.aidl
@@ -63,4 +63,6 @@
     boolean setActiveDevice(in BluetoothDevice device);
     BluetoothDevice getActiveDevice();
     boolean isInbandRingingEnabled();
+    boolean setPriority(in BluetoothDevice device, int connectionPolicy);
+    int getPriority(in BluetoothDevice device);
 }
diff --git a/btif/co/bta_av_co.cc b/btif/co/bta_av_co.cc
index 60b407c..b2a02d9 100644
--- a/btif/co/bta_av_co.cc
+++ b/btif/co/bta_av_co.cc
@@ -1378,7 +1378,7 @@
   APPL_TRACE_DEBUG("%s: peer %s bta_av_handle: 0x%x delay:0x%x", __func__,
                    peer_address.ToString().c_str(), bta_av_handle, delay);
 
-  btif_av_set_audio_delay(delay);
+  btif_av_set_audio_delay(peer_address, delay);
 }
 
 void BtaAvCo::UpdateMtu(tBTA_AV_HNDL bta_av_handle,
diff --git a/btif/include/btif_av.h b/btif/include/btif_av.h
index 1c596f9..adbc333 100644
--- a/btif/include/btif_av.h
+++ b/btif/include/btif_av.h
@@ -163,9 +163,16 @@
 /**
  * Set the audio delay for the stream.
  *
+ * @param peer_address the address of the peer to report
  * @param delay the delay to set in units of 1/10ms
  */
-void btif_av_set_audio_delay(uint16_t delay);
+void btif_av_set_audio_delay(const RawAddress& peer_address, uint16_t delay);
+
+/**
+ * Get the audio delay for the stream.
+ *  @param  none
+ */
+uint16_t btif_av_get_audio_delay(void);
 
 /**
  * Reset the audio delay and count of audio bytes sent to zero.
diff --git a/btif/src/bluetooth.cc b/btif/src/bluetooth.cc
index 8dfe73a..56f0b9b 100644
--- a/btif/src/bluetooth.cc
+++ b/btif/src/bluetooth.cc
@@ -63,6 +63,7 @@
 #include "btsnoop.h"
 #include "btsnoop_mem.h"
 #include "common/address_obfuscator.h"
+#include "common/metric_id_allocator.h"
 #include "common/metrics.h"
 #include "device/include/interop.h"
 #include "main/shim/dumpsys.h"
@@ -460,6 +461,11 @@
       address);
 }
 
+static int get_metric_id(const RawAddress& address) {
+  return bluetooth::common::MetricIdAllocator::GetInstance().AllocateId(
+      address);
+}
+
 EXPORT_SYMBOL bt_interface_t bluetoothInterface = {
     sizeof(bluetoothInterface),
     init,
@@ -496,4 +502,5 @@
     interop_database_add,
     get_avrcp_service,
     obfuscate_address,
+    get_metric_id,
 };
diff --git a/btif/src/btif_a2dp_source.cc b/btif/src/btif_a2dp_source.cc
index 4ad9368..45b2e3f 100644
--- a/btif/src/btif_a2dp_source.cc
+++ b/btif/src/btif_a2dp_source.cc
@@ -397,6 +397,7 @@
   }
   if (bluetooth::audio::a2dp::is_hal_2_0_enabled()) {
     bluetooth::audio::a2dp::start_session();
+    bluetooth::audio::a2dp::set_remote_delay(btif_av_get_audio_delay());
     BluetoothMetricsLogger::GetInstance()->LogBluetoothSessionStart(
         bluetooth::common::CONNECTION_TECHNOLOGY_TYPE_BREDR, 0);
   } else if (btif_av_is_a2dp_offload_enabled()) {
diff --git a/btif/src/btif_av.cc b/btif/src/btif_av.cc
index 55350e0..4f6f0ca 100644
--- a/btif/src/btif_av.cc
+++ b/btif/src/btif_av.cc
@@ -282,6 +282,10 @@
 
   void SetSilence(bool silence) { is_silenced_ = silence; };
 
+  // AVDTP delay reporting in 1/10 milliseconds
+  void SetDelayReport(uint16_t delay) { delay_report_ = delay; };
+  uint16_t GetDelayReport() const { return delay_report_; };
+
   /**
    * Check whether any of the flags specified by the bitlags mask is set.
    *
@@ -330,6 +334,7 @@
   uint8_t flags_;
   bool self_initiated_connection_;
   bool is_silenced_;
+  uint16_t delay_report_;
 };
 
 class BtifAvSource {
@@ -864,7 +869,8 @@
       av_open_on_rc_timer_(nullptr),
       edr_(0),
       flags_(0),
-      self_initiated_connection_(false) {}
+      self_initiated_connection_(false),
+      delay_report_(0) {}
 
 BtifAvPeer::~BtifAvPeer() { alarm_free(av_open_on_rc_timer_); }
 
@@ -3260,6 +3266,7 @@
   dprintf(fd, "    Support 3Mbps: %s\n", peer.Is3Mbps() ? "true" : "false");
   dprintf(fd, "    Self Initiated Connection: %s\n",
           peer.SelfInitiatedConnection() ? "true" : "false");
+  dprintf(fd, "    Delay Reporting: %u\n", peer.GetDelayReport());
 }
 
 static void btif_debug_av_source_dump(int fd) {
@@ -3294,9 +3301,23 @@
   btif_debug_av_sink_dump(fd);
 }
 
-void btif_av_set_audio_delay(uint16_t delay) {
+void btif_av_set_audio_delay(const RawAddress& peer_address, uint16_t delay) {
   btif_a2dp_control_set_audio_delay(delay);
-  bluetooth::audio::a2dp::set_remote_delay(delay);
+  BtifAvPeer* peer = btif_av_find_peer(peer_address);
+  if (peer != nullptr && peer->IsSink()) {
+    peer->SetDelayReport(delay);
+    if (peer->IsActivePeer()) {
+      bluetooth::audio::a2dp::set_remote_delay(peer->GetDelayReport());
+    }
+  }
+}
+
+uint16_t btif_av_get_audio_delay() {
+  BtifAvPeer* peer = btif_av_find_active_peer();
+  if (peer != nullptr && peer->IsSink()) {
+    return peer->GetDelayReport();
+  }
+  return 0;
 }
 
 void btif_av_reset_audio_delay(void) { btif_a2dp_control_reset_audio_delay(); }
diff --git a/btif/src/btif_config.cc b/btif/src/btif_config.cc
index 924de43e..4b5c854 100644
--- a/btif/src/btif_config.cc
+++ b/btif/src/btif_config.cc
@@ -29,9 +29,11 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <functional>
 #include <mutex>
 #include <sstream>
 #include <string>
+#include <unordered_map>
 
 #include "bt_types.h"
 #include "btcore/include/module.h"
@@ -41,6 +43,7 @@
 //#include "btif_keystore.h"
 #include "btif_util.h"
 #include "common/address_obfuscator.h"
+#include "common/metric_id_allocator.h"
 #include "main/shim/config.h"
 #include "main/shim/shim.h"
 #include "osi/include/alarm.h"
@@ -50,6 +53,7 @@
 #include "osi/include/log.h"
 #include "osi/include/osi.h"
 #include "osi/include/properties.h"
+#include "raw_address.h"
 
 #define BT_CONFIG_SOURCE_TAG_NUM 1010001
 
@@ -68,8 +72,11 @@
 
 #define BT_CONFIG_METRICS_SECTION "Metrics"
 #define BT_CONFIG_METRICS_SALT_256BIT "Salt256Bit"
+#define BT_CONFIG_METRICS_ID_KEY "MetricsId"
+
 // using bluetooth::BtifKeystore;
 using bluetooth::common::AddressObfuscator;
+using bluetooth::common::MetricIdAllocator;
 
 // TODO(armansito): Find a better way than searching by a hardcoded path.
 #if defined(OS_GENERIC)
@@ -191,6 +198,63 @@
   AddressObfuscator::GetInstance()->Initialize(metrics_salt);
 }
 
+/**
+ * Initialize metric id allocator by reading metric_id from config by mac
+ * address. If there is no metric id for a mac address, then allocate it a new
+ * metric id.
+ */
+static void init_metric_id_allocator() {
+  std::unordered_map<RawAddress, int> paired_device_map;
+
+  // When user update the system, there will be devices paired with older
+  // version of android without a metric id.
+  std::vector<RawAddress> addresses_without_id;
+
+  for (auto& section : btif_config_sections()) {
+    auto& section_name = section.name;
+    RawAddress mac_address;
+    if (!RawAddress::FromString(section_name, mac_address)) {
+      continue;
+    }
+    // if the section name is a mac address
+    bool is_valid_id_found = false;
+    if (btif_config_exist(section_name, BT_CONFIG_METRICS_ID_KEY)) {
+      // there is one metric id under this mac_address
+      int id = 0;
+      btif_config_get_int(section_name, BT_CONFIG_METRICS_ID_KEY, &id);
+      if (MetricIdAllocator::IsValidId(id)) {
+        paired_device_map[mac_address] = id;
+        is_valid_id_found = true;
+      }
+    }
+    if (!is_valid_id_found) {
+      addresses_without_id.push_back(mac_address);
+    }
+  }
+
+  // Initialize MetricIdAllocator
+  MetricIdAllocator::Callback save_device_callback =
+      [](const RawAddress& address, const int id) {
+        return btif_config_set_int(address.ToString(), BT_CONFIG_METRICS_ID_KEY,
+                                   id);
+      };
+  MetricIdAllocator::Callback forget_device_callback =
+      [](const RawAddress& address, const int id) {
+        return btif_config_remove(address.ToString(), BT_CONFIG_METRICS_ID_KEY);
+      };
+  if (!MetricIdAllocator::GetInstance().Init(
+          paired_device_map, std::move(save_device_callback),
+          std::move(forget_device_callback))) {
+    LOG(FATAL) << __func__ << "Failed to initialize MetricIdAllocator";
+  }
+
+  // Add device_without_id
+  for (auto& address : addresses_without_id) {
+    MetricIdAllocator::GetInstance().AllocateId(address);
+    MetricIdAllocator::GetInstance().SaveDevice(address);
+  }
+}
+
 static std::recursive_mutex config_lock;  // protects operations on |config|.
 static std::unique_ptr<config_t> config;
 static alarm_t* config_timer;
@@ -262,6 +326,9 @@
   // Read or set metrics 256 bit hashing salt
   read_or_set_metrics_salt();
 
+  // Initialize MetricIdAllocator
+  init_metric_id_allocator();
+
   // TODO(sharvil): use a non-wake alarm for this once we have
   // API support for it. There's no need to wake the system to
   // write back to disk.
@@ -326,6 +393,7 @@
   config_timer = NULL;
 
   std::unique_lock<std::recursive_mutex> lock(config_lock);
+  MetricIdAllocator::GetInstance().Close();
   config.reset();
   return future_new_immediate(FUTURE_SUCCESS);
 }
diff --git a/btif/src/btif_dm.cc b/btif/src/btif_dm.cc
index 71489f7..8b8d829 100644
--- a/btif/src/btif_dm.cc
+++ b/btif/src/btif_dm.cc
@@ -60,6 +60,7 @@
 #include "btif_storage.h"
 #include "btif_util.h"
 #include "btu.h"
+#include "common/metric_id_allocator.h"
 #include "common/metrics.h"
 #include "device/include/controller.h"
 #include "device/include/interop.h"
@@ -74,6 +75,7 @@
 #include "stack_config.h"
 
 using bluetooth::Uuid;
+using bluetooth::common::MetricIdAllocator;
 /******************************************************************************
  *  Constants & Macros
  *****************************************************************************/
@@ -293,6 +295,15 @@
 static void btif_dm_send_bond_state_changed(RawAddress address, bt_bond_state_t bond_state) {
   do_in_jni_thread(FROM_HERE, base::BindOnce([](RawAddress address, bt_bond_state_t bond_state) {
     btif_stats_add_bond_event(address, BTIF_DM_FUNC_BOND_STATE_CHANGED, bond_state);
+    if (bond_state == BT_BOND_STATE_NONE) {
+      MetricIdAllocator::GetInstance().ForgetDevice(address);
+    } else if (bond_state == BT_BOND_STATE_BONDED) {
+      MetricIdAllocator::GetInstance().AllocateId(address);
+      if (!MetricIdAllocator::GetInstance().SaveDevice(address)) {
+        LOG(FATAL) << __func__ << ": Fail to save metric id for device "
+                   << address;
+      }
+    }
     HAL_CBACK(bt_hal_cbacks, bond_state_changed_cb, BT_STATUS_SUCCESS, &address, bond_state);
   }, address, bond_state));
 }
@@ -545,6 +556,15 @@
   BTIF_TRACE_DEBUG("%s: state=%d, prev_state=%d, sdp_attempts = %d", __func__,
                    state, pairing_cb.state, pairing_cb.sdp_attempts);
 
+  if (state == BT_BOND_STATE_NONE) {
+    MetricIdAllocator::GetInstance().ForgetDevice(bd_addr);
+  } else if (state == BT_BOND_STATE_BONDED) {
+    MetricIdAllocator::GetInstance().AllocateId(bd_addr);
+    if (!MetricIdAllocator::GetInstance().SaveDevice(bd_addr)) {
+      LOG(FATAL) << __func__ << ": Fail to save metric id for device "
+                 << bd_addr;
+    }
+  }
   auto tmp = bd_addr;
   HAL_CBACK(bt_hal_cbacks, bond_state_changed_cb, status, &tmp, state);
 
@@ -1191,6 +1211,7 @@
     // Do not call bond_state_changed_cb yet. Wait until remote service
     // discovery is complete
   } else {
+    bool is_bonded_device_removed = false;
     // Map the HCI fail reason  to  bt status
     switch (p_auth_cmpl->fail_reason) {
       case HCI_ERR_PAGE_TIMEOUT:
@@ -1209,14 +1230,16 @@
         break;
 
       case HCI_ERR_PAIRING_NOT_ALLOWED:
-        btif_storage_remove_bonded_device(&bd_addr);
+        is_bonded_device_removed =
+            (btif_storage_remove_bonded_device(&bd_addr) == BT_STATUS_SUCCESS);
         status = BT_STATUS_AUTH_REJECTED;
         break;
 
       /* map the auth failure codes, so we can retry pairing if necessary */
       case HCI_ERR_AUTH_FAILURE:
       case HCI_ERR_KEY_MISSING:
-        btif_storage_remove_bonded_device(&bd_addr);
+        is_bonded_device_removed =
+            (btif_storage_remove_bonded_device(&bd_addr) == BT_STATUS_SUCCESS);
         [[fallthrough]];
       case HCI_ERR_HOST_REJECT_SECURITY:
       case HCI_ERR_ENCRY_MODE_NOT_ACCEPTABLE:
@@ -1247,9 +1270,14 @@
       /* Remove Device as bonded in nvram as authentication failed */
       BTIF_TRACE_DEBUG("%s(): removing hid pointing device from nvram",
                        __func__);
-      btif_storage_remove_bonded_device(&bd_addr);
+      is_bonded_device_removed =
+          (btif_storage_remove_bonded_device(&bd_addr) == BT_STATUS_SUCCESS);
     }
-    bond_state_changed(status, bd_addr, state);
+    // Report bond state change to java only if we are bonding to a device or
+    // a device is removed from the pairing list.
+    if (pairing_cb.state == BT_BOND_STATE_BONDING || is_bonded_device_removed) {
+      bond_state_changed(status, bd_addr, state);
+    }
   }
 }
 
@@ -2414,6 +2442,11 @@
   BTIF_TRACE_EVENT("%s: accept=%d", __func__, accept);
 
   if (bluetooth::shim::is_gd_shim_enabled()) {
+    if (pin_code == nullptr) {
+      LOG_ERROR("Pin code must be not null with GD shim enabled");
+      return BT_STATUS_FAIL;
+    }
+
     uint8_t tmp_dev_type = 0;
     uint8_t tmp_addr_type = 0;
     BTM_ReadDevInfo(*bd_addr, &tmp_dev_type, &tmp_addr_type);
diff --git a/btif/src/btif_hf.cc b/btif/src/btif_hf.cc
index 04ad011..99063fd 100644
--- a/btif/src/btif_hf.cc
+++ b/btif/src/btif_hf.cc
@@ -319,12 +319,48 @@
     case BTA_AG_OPEN_EVT:
       // Check if an outoging connection is pending
       if (btif_hf_cb[idx].is_initiator) {
+        if ((p_data->open.status != BTA_AG_SUCCESS) &&
+            btif_hf_cb[idx].state != BTHF_CONNECTION_STATE_CONNECTING) {
+          if (p_data->open.bd_addr == btif_hf_cb[idx].connected_bda) {
+            LOG(WARNING) << __func__ << ": btif_hf_cb state["
+                         << p_data->open.status
+                         << "] is not expected, possible connection collision, "
+                            "ignoring AG open "
+                            "failure event for the same device "
+                         << p_data->open.bd_addr;
+          } else {
+            LOG(WARNING) << __func__ << ": btif_hf_cb state["
+                         << p_data->open.status
+                         << "] is not expected, possible connection collision, "
+                            "ignoring AG open failure "
+                            "event for the different devices btif_hf_cb bda: "
+                         << btif_hf_cb[idx].connected_bda
+                         << ", p_data bda: " << p_data->open.bd_addr
+                         << ", report disconnect state for p_data bda.";
+            bt_hf_callbacks->ConnectionStateCallback(
+                BTHF_CONNECTION_STATE_DISCONNECTED, &(p_data->open.bd_addr));
+          }
+          break;
+        }
+
         CHECK_EQ(btif_hf_cb[idx].state, BTHF_CONNECTION_STATE_CONNECTING)
             << "Control block must be in connecting state when initiating";
         CHECK(!btif_hf_cb[idx].connected_bda.IsEmpty())
             << "Remote device address must not be empty when initiating";
-        CHECK_EQ(btif_hf_cb[idx].connected_bda, p_data->open.bd_addr)
-            << "Incoming message's address must match expected one";
+        if (btif_hf_cb[idx].connected_bda != p_data->open.bd_addr) {
+          LOG(WARNING) << __func__
+                       << ": possible connection collision, ignore the "
+                          "outgoing connection for the "
+                          "different devices btif_hf_cb bda: "
+                       << btif_hf_cb[idx].connected_bda
+                       << ", p_data bda: " << p_data->open.bd_addr
+                       << ", report disconnect state for btif_hf_cb bda.";
+          bt_hf_callbacks->ConnectionStateCallback(
+              BTHF_CONNECTION_STATE_DISCONNECTED,
+              &(btif_hf_cb[idx].connected_bda));
+          reset_control_block(&btif_hf_cb[idx]);
+          btif_queue_advance();
+        }
       }
       if (p_data->open.status == BTA_AG_SUCCESS) {
         // In case this is an incoming connection
diff --git a/common/metric_id_allocator.cc b/common/metric_id_allocator.cc
index 2667fa6..005ef3a 100644
--- a/common/metric_id_allocator.cc
+++ b/common/metric_id_allocator.cc
@@ -27,9 +27,9 @@
 
 namespace common {
 
-const std::string MetricIdAllocator::LOG_TAG = "BluetoothMetricIdAllocator";
+const std::string MetricIdAllocator::LOGGING_TAG = "BluetoothMetricIdAllocator";
 const size_t MetricIdAllocator::kMaxNumUnpairedDevicesInMemory = 200;
-const size_t MetricIdAllocator::kMaxNumPairedDevicesInMemory = 400;
+const size_t MetricIdAllocator::kMaxNumPairedDevicesInMemory = 65000;
 const int MetricIdAllocator::kMinId = 1;
 const int MetricIdAllocator::kMaxId = 65534;  // 2^16 - 2
 
@@ -42,14 +42,13 @@
               "kMaxNumPairedDevicesInMemory + MaxNumUnpairedDevicesInMemory");
 
 MetricIdAllocator::MetricIdAllocator()
-    : paired_device_cache_(kMaxNumPairedDevicesInMemory, LOG_TAG,
-                           [this](RawAddress dummy, int to_remove) {
-                             this->id_set_.erase(to_remove);
+    : paired_device_cache_(kMaxNumPairedDevicesInMemory, LOGGING_TAG,
+                           [this](RawAddress mac_address, int id) {
+                             ForgetDevicePostprocess(mac_address, id);
                            }),
-      temporary_device_cache_(kMaxNumUnpairedDevicesInMemory, LOG_TAG,
-                              [this](RawAddress dummy, int to_remove) {
-                                this->id_set_.erase(to_remove);
-                              }) {}
+      temporary_device_cache_(
+          kMaxNumUnpairedDevicesInMemory, LOGGING_TAG,
+          [this](RawAddress dummy, int id) { this->id_set_.erase(id); }) {}
 
 bool MetricIdAllocator::Init(
     const std::unordered_map<RawAddress, int>& paired_device_map,
@@ -62,7 +61,7 @@
   // init paired_devices_map
   if (paired_device_map.size() > kMaxNumPairedDevicesInMemory) {
     LOG(FATAL)
-        << LOG_TAG
+        << LOGGING_TAG
         << "Paired device map is bigger than kMaxNumPairedDevicesInMemory";
     // fail loudly to let caller know
     return false;
@@ -71,7 +70,7 @@
   next_id_ = kMinId;
   for (const std::pair<RawAddress, int>& p : paired_device_map) {
     if (p.second < kMinId || p.second > kMaxId) {
-      LOG(FATAL) << LOG_TAG << "Invalid Bluetooth Metric Id in config";
+      LOG(FATAL) << LOGGING_TAG << "Invalid Bluetooth Metric Id in config";
     }
     paired_device_cache_.Put(p.first, p.second);
     id_set_.insert(p.second);
@@ -130,7 +129,7 @@
     next_id_++;
     if (next_id_ > kMaxId) {
       next_id_ = kMinId;
-      LOG(WARNING) << LOG_TAG << "Bluetooth metric id overflow.";
+      LOG(WARNING) << LOGGING_TAG << "Bluetooth metric id overflow.";
     }
   }
   id = next_id_++;
@@ -147,26 +146,55 @@
 bool MetricIdAllocator::SaveDevice(const RawAddress& mac_address) {
   std::lock_guard<std::mutex> lock(id_allocator_mutex_);
   int id = 0;
-  bool success = temporary_device_cache_.Get(mac_address, &id);
-  success &= temporary_device_cache_.Remove(mac_address);
-  if (success) {
-    paired_device_cache_.Put(mac_address, id);
-    success = save_id_callback_(mac_address, id);
+  if (paired_device_cache_.Get(mac_address, &id)) {
+    return true;
   }
-  return success;
+  if (!temporary_device_cache_.Get(mac_address, &id)) {
+    LOG(ERROR) << LOGGING_TAG
+               << "Failed to save device because device is not in "
+               << "temporary_device_cache_";
+    return false;
+  }
+  if (!temporary_device_cache_.Remove(mac_address)) {
+    LOG(ERROR) << LOGGING_TAG
+               << "Failed to remove device from temporary_device_cache_";
+    return false;
+  }
+  paired_device_cache_.Put(mac_address, id);
+  if (!save_id_callback_(mac_address, id)) {
+    LOG(ERROR) << LOGGING_TAG
+               << "Callback returned false after saving the device";
+    return false;
+  }
+  return true;
 }
 
 // call this function when a device is forgotten
-bool MetricIdAllocator::ForgetDevice(const RawAddress& mac_address) {
+void MetricIdAllocator::ForgetDevice(const RawAddress& mac_address) {
   std::lock_guard<std::mutex> lock(id_allocator_mutex_);
   int id = 0;
-  bool success = paired_device_cache_.Get(mac_address, &id);
-  success &= paired_device_cache_.Remove(mac_address);
-  if (success) {
-    id_set_.erase(id);
-    success = forget_device_callback_(mac_address, id);
+  if (!paired_device_cache_.Get(mac_address, &id)) {
+    LOG(ERROR) << LOGGING_TAG
+               << "Failed to forget device because device is not in "
+               << "paired_device_cache_";
+    return;
   }
-  return success;
+  if (!paired_device_cache_.Remove(mac_address)) {
+    LOG(ERROR) << LOGGING_TAG
+               << "Failed to remove device from paired_device_cache_";
+    return;
+  }
+  ForgetDevicePostprocess(mac_address, id);
+}
+
+bool MetricIdAllocator::IsValidId(const int id) {
+  return id >= kMinId && id <= kMaxId;
+}
+
+void MetricIdAllocator::ForgetDevicePostprocess(const RawAddress& mac_address,
+                                                const int id) {
+  id_set_.erase(id);
+  forget_device_callback_(mac_address, id);
 }
 
 }  // namespace common
diff --git a/common/metric_id_allocator.h b/common/metric_id_allocator.h
index f941fd0..63236e4 100644
--- a/common/metric_id_allocator.h
+++ b/common/metric_id_allocator.h
@@ -98,16 +98,25 @@
    * Delete the id for a device to be forgotten
    *
    * @param mac_address mac address of Bluetooth device
+   */
+  void ForgetDevice(const RawAddress& mac_address);
+
+  /**
+   * Check if an id is valid.
+   * The id should be less than or equal to kMaxId and bigger than or equal to
+   * kMinId
+   *
+   * @param mac_address mac address of Bluetooth device
    * @return true if delete successfully
    */
-  bool ForgetDevice(const RawAddress& mac_address);
+  static bool IsValidId(const int id);
 
  protected:
   // Singleton
   MetricIdAllocator();
 
  private:
-  static const std::string LOG_TAG;
+  static const std::string LOGGING_TAG;
   mutable std::mutex id_allocator_mutex_;
 
   LruCache<RawAddress, int> paired_device_cache_;
@@ -119,6 +128,8 @@
   Callback save_id_callback_;
   Callback forget_device_callback_;
 
+  void ForgetDevicePostprocess(const RawAddress& mac_address, const int id);
+
   // delete copy constructor for singleton
   MetricIdAllocator(MetricIdAllocator const&) = delete;
   MetricIdAllocator& operator=(MetricIdAllocator const&) = delete;
diff --git a/common/metric_id_allocator_unittest.cc b/common/metric_id_allocator_unittest.cc
index 416b671..ccc1576 100644
--- a/common/metric_id_allocator_unittest.cc
+++ b/common/metric_id_allocator_unittest.cc
@@ -165,14 +165,14 @@
   EXPECT_TRUE(allocator.SaveDevice(RawAddress({0, 0, 0, 0, 0, 3})));
   EXPECT_EQ(dummy, 176);
 
-  // should fail, since id had been saved
-  EXPECT_FALSE(allocator.SaveDevice(RawAddress({0, 0, 0, 0, 0, 0})));
+  // should be true but callback won't be called, since id had been saved
+  EXPECT_TRUE(allocator.SaveDevice(RawAddress({0, 0, 0, 0, 0, 0})));
   EXPECT_EQ(dummy, 176);
 
   // forget
-  EXPECT_FALSE(allocator.ForgetDevice(RawAddress({0, 0, 0, 0, 0, 1})));
+  allocator.ForgetDevice(RawAddress({0, 0, 0, 0, 0, 1}));
   EXPECT_EQ(dummy, 176);
-  EXPECT_TRUE(allocator.ForgetDevice(RawAddress({0, 0, 0, 0, 0, 2})));
+  allocator.ForgetDevice(RawAddress({0, 0, 0, 0, 0, 2}));
   EXPECT_EQ(dummy, 88);
 
   EXPECT_TRUE(allocator.Close());
@@ -183,7 +183,7 @@
   // preset a full map
   std::unordered_map<RawAddress, int> paired_device_map =
       generateAddresses(MetricIdAllocator::kMaxNumPairedDevicesInMemory);
-  int dummy = 22;
+  int dummy = 243;
   int* pointer = &dummy;
   MetricIdAllocator::Callback save_callback = [pointer](const RawAddress&,
                                                         const int) {
@@ -192,7 +192,7 @@
   };
   MetricIdAllocator::Callback forget_callback = [pointer](const RawAddress&,
                                                           const int) {
-    *pointer = *pointer / 2;
+    *pointer = *pointer / 3;
     return true;
   };
 
@@ -222,7 +222,8 @@
 
   // save it and make sure the callback is called
   EXPECT_TRUE(allocator.SaveDevice(kthAddress(key)));
-  EXPECT_EQ(dummy, 44);
+  EXPECT_EQ(dummy, 162);  // one key is evicted, another key is saved so *2/3
+
   // paired: 1, 2 ... 199, 200,
   // scanned:
 
@@ -233,13 +234,13 @@
   // key == 200
   // should fail, since id of device is not allocated
   EXPECT_FALSE(allocator.SaveDevice(kthAddress(key + 1)));
-  EXPECT_EQ(dummy, 44);
+  EXPECT_EQ(dummy, 162);
   // paired: 1, 2 ... 199, 200,
   // scanned: 0
 
   EXPECT_EQ(allocator.AllocateId(kthAddress(key + 1)), id++);
   EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 1)));
-  EXPECT_EQ(dummy, 88);
+  EXPECT_EQ(dummy, 108);  // one key is evicted, another key is saved so *2/3,
   // paired: 2 ... 199, 200, 201
   // scanned: 0
 
@@ -253,23 +254,24 @@
   // paired: 2 ... 199, 200, 201,
   // scanned: 0, 1, 202, 203
 
-  dummy = 44;
+  dummy = 9;
   EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 2)));
-  EXPECT_EQ(dummy, 88);
+  EXPECT_EQ(dummy, 6);  // one key is evicted, another key is saved so *2/3,
   EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 3)));
-  EXPECT_EQ(dummy, 176);
+  EXPECT_EQ(dummy, 4);  // one key is evicted, another key is saved so *2/3,
   // paired: 4 ... 199, 200, 201, 202, 203
   // scanned: 0, 1
 
-  // should fail, since id had been saved
-  EXPECT_FALSE(allocator.SaveDevice(kthAddress(key + 2)));
-  EXPECT_EQ(dummy, 176);
+  // should be true but callback won't be called, since id had been saved
+  EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 2)));
+  EXPECT_EQ(dummy, 4);
 
+  dummy = 27;
   // forget
-  EXPECT_FALSE(allocator.ForgetDevice(kthAddress(key + 200)));
-  EXPECT_EQ(dummy, 176);
-  EXPECT_TRUE(allocator.ForgetDevice(kthAddress(key + 2)));
-  EXPECT_EQ(dummy, 88);
+  allocator.ForgetDevice(kthAddress(key + 200));
+  EXPECT_EQ(dummy, 27);  // should fail, no such a key
+  allocator.ForgetDevice(kthAddress(key + 2));
+  EXPECT_EQ(dummy, 9);
   // paired: 4 ... 199, 200, 201, 203
   // scanned: 0, 1
 
@@ -281,25 +283,27 @@
   // scanned: 0, 1, 202, 204, 205
 
   EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 2)));
-  EXPECT_EQ(dummy, 176);
-  EXPECT_FALSE(allocator.SaveDevice(kthAddress(key + 3)));
-  EXPECT_EQ(dummy, 176);
+  EXPECT_EQ(dummy, 18);  // no key is evicted, a key is saved so *2,
+
+  // should be true but callback won't be called, since id had been saved
+  EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 3)));
+  EXPECT_EQ(dummy, 18);  // no such a key in scanned
   EXPECT_TRUE(allocator.SaveDevice(kthAddress(key + 4)));
-  EXPECT_EQ(dummy, 352);
-  // paired: 6 ... 199, 200, 201, 203, 202, 204
+  EXPECT_EQ(dummy, 12);  // one key is evicted, another key is saved so *2/3,
+  // paired: 5 6 ... 199, 200, 201, 203, 202, 204
   // scanned: 0, 1, 205
 
   // verify paired:
-  for (key = 6; key <= 199; key++) {
-    dummy = 10;
-    EXPECT_TRUE(allocator.ForgetDevice(kthAddress(key)));
-    EXPECT_EQ(dummy, 5);
+  for (key = 5; key <= 199; key++) {
+    dummy = 3;
+    allocator.ForgetDevice(kthAddress(key));
+    EXPECT_EQ(dummy, 1);
   }
   for (size_t k = MetricIdAllocator::kMaxNumPairedDevicesInMemory;
        k <= MetricIdAllocator::kMaxNumPairedDevicesInMemory + 4; k++) {
-    dummy = 10;
-    EXPECT_TRUE(allocator.ForgetDevice(kthAddress(k)));
-    EXPECT_EQ(dummy, 5);
+    dummy = 3;
+    allocator.ForgetDevice(kthAddress(k));
+    EXPECT_EQ(dummy, 1);
   }
 
   // verify scanned
@@ -397,14 +401,15 @@
   // make sure no deadlock
   std::vector<std::thread> workers;
   for (int key = 0;
-       key < static_cast<int>(MetricIdAllocator::kMaxNumPairedDevicesInMemory);
+       key <
+       static_cast<int>(MetricIdAllocator::kMaxNumUnpairedDevicesInMemory);
        key++) {
     workers.push_back(std::thread([key]() {
       auto& allocator = MetricIdAllocator::GetInstance();
       RawAddress fake_mac_address = kthAddress(key);
       allocator.AllocateId(fake_mac_address);
       EXPECT_TRUE(allocator.SaveDevice(fake_mac_address));
-      EXPECT_TRUE(allocator.ForgetDevice(fake_mac_address));
+      allocator.ForgetDevice(fake_mac_address);
     }));
   }
   for (auto& worker : workers) {
diff --git a/gd/Android.bp b/gd/Android.bp
index f4d0e62..6fb243f 100644
--- a/gd/Android.bp
+++ b/gd/Android.bp
@@ -143,6 +143,9 @@
     generated_headers: [
         "BluetoothGeneratedPackets_h",
         "BluetoothFacadeGeneratedStub_h",
+        // Needed here to guarantee that generated zip file is created before
+        // bluetooth_cert_tests.zip is packaged
+        "BluetoothFacadeAndCertGeneratedStub_py",
     ],
     generated_sources: [
         "BluetoothFacadeGeneratedStub_cc",
@@ -372,6 +375,7 @@
         "hci/facade/le_scanning_manager_facade.proto",
         "neighbor/facade/facade.proto",
         "l2cap/classic/facade.proto",
+        "l2cap/le/facade.proto",
         "security/facade.proto",
     ],
 }
@@ -407,6 +411,8 @@
         "hci/facade/le_scanning_manager_facade.pb.h",
         "l2cap/classic/facade.grpc.pb.h",
         "l2cap/classic/facade.pb.h",
+        "l2cap/le/facade.grpc.pb.h",
+        "l2cap/le/facade.pb.h",
         "neighbor/facade/facade.grpc.pb.h",
         "neighbor/facade/facade.pb.h",
         "security/facade.grpc.pb.h",
@@ -445,6 +451,8 @@
         "hci/facade/le_scanning_manager_facade.pb.cc",
         "l2cap/classic/facade.grpc.pb.cc",
         "l2cap/classic/facade.pb.cc",
+        "l2cap/le/facade.grpc.pb.cc",
+        "l2cap/le/facade.pb.cc",
         "neighbor/facade/facade.grpc.pb.cc",
         "neighbor/facade/facade.pb.cc",
         "security/facade.grpc.pb.cc",
@@ -459,24 +467,23 @@
         "protoc-gen-grpc-python-plugin",
         "soong_zip",
     ],
-    cmd: "mkdir -p $(genDir)/system/bt/gd && " +
-        "$(location aprotoc) -Isystem/bt/gd -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-python-plugin) $(in) --grpc_out=$(genDir)/system/bt/gd --python_out=$(genDir)/system/bt/gd && " +
-        "touch $(genDir)/system/bt/gd/facade/__init__.py && " +
-        "touch $(genDir)/system/bt/gd/hal/__init__.py && " +
-        "touch $(genDir)/system/bt/gd/hci/__init__.py && " +
-        "touch $(genDir)/system/bt/gd/hci/facade/__init__.py && " +
-        "touch $(genDir)/system/bt/gd/l2cap/classic/__init__.py && " +
-        "touch $(genDir)/system/bt/gd/neighbor/facade/__init__.py && " +
-        "touch $(genDir)/system/bt/gd/security/__init__.py && " +
-        "$(location soong_zip) -C $(genDir) -D $(genDir) -o $(out)",
+    cmd: "mkdir -p $(genDir)/files && " +
+        "$(location aprotoc) -Isystem/bt/gd -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-python-plugin) $(in) --grpc_out=$(genDir)/files --python_out=$(genDir)/files && " +
+        "mkdir -p $(genDir)/files/cert && " +
+        "touch $(genDir)/files/cert/__init__.py && " +
+        "touch $(genDir)/files/facade/__init__.py && " +
+        "touch $(genDir)/files/hal/__init__.py && " +
+        "touch $(genDir)/files/hci/__init__.py && " +
+        "touch $(genDir)/files/hci/facade/__init__.py && " +
+        "touch $(genDir)/files/l2cap/classic/__init__.py && " +
+        "touch $(genDir)/files/l2cap/le/__init__.py && " +
+        "touch $(genDir)/files/neighbor/facade/__init__.py && " +
+        "touch $(genDir)/files/security/__init__.py && " +
+        "$(location soong_zip) -C $(genDir)/files -D $(genDir)/files -o $(out)",
     srcs: [
         ":BluetoothFacadeProto",
     ],
     out: ["bluetooth_cert_generated_py.zip"],
-    dist: {
-        targets: ["bluetooth_stack_with_facade"],
-    },
-
 }
 
 cc_defaults {
diff --git a/gd/Android.mk b/gd/Android.mk
index 966e53b..1dc9af6 100644
--- a/gd/Android.mk
+++ b/gd/Android.mk
@@ -1,46 +1,94 @@
 LOCAL_PATH := $(call my-dir)
 
-bluetooth_cert_test_file_list := \
-    $(call all-named-files-under,*.py,.) \
-    $(call all-named-files-under,*.proto,cert facade hal hci/cert hci/facade l2cap/classic \
-	    l2cap/classic/cert neighbor/facade security) \
-    cert/all_cert_testcases
+LOCAL_cert_test_sources := \
+	$(call all-named-files-under,*.py,.) \
+	cert/all_cert_testcases
+LOCAL_cert_test_sources := \
+	$(filter-out gd_cert_venv% venv%, $(LOCAL_cert_test_sources))
+LOCAL_cert_test_sources := \
+	$(addprefix $(LOCAL_PATH)/, $(LOCAL_cert_test_sources))
 
-bluetooth_cert_test_file_list := $(addprefix $(LOCAL_PATH)/,$(bluetooth_cert_test_file_list))
+LOCAL_host_executables := \
+	$(HOST_OUT_EXECUTABLES)/bluetooth_stack_with_facade
 
-bluetooth_cert_test_file_list += \
-    $(HOST_OUT_EXECUTABLES)/bluetooth_stack_with_facade \
-    $(HOST_OUT_SHARED_LIBRARIES)/bluetooth_packets_python3.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libbase.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libbluetooth_gd.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libc++.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libchrome.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libevent-host.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libgrpc++_unsecure.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/liblog.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libz-host.so \
-    $(HOST_OUT_SHARED_LIBRARIES)/libprotobuf-cpp-full.so \
-    $(TARGET_OUT_EXECUTABLES)/bluetooth_stack_with_facade \
-    $(TARGET_OUT_SHARED_LIBRARIES)/libbluetooth_gd.so \
-    $(TARGET_OUT_SHARED_LIBRARIES)/libgrpc++_unsecure.so \
-    $(HOST_OUT_NATIVE_TESTS)/root-canal/root-canal
+LOCAL_host_root_canal_executables := \
+	$(HOST_OUT_NATIVE_TESTS)/root-canal/root-canal
 
-bluetooth_cert_env_provider_path := \
-    $(call intermediates-dir-for,PACKAGING,bluetooth_cert_test_package,HOST)/system/bt/gd/cert/environment_provider.py
+LOCAL_host_python_extension_libraries := \
+	$(HOST_OUT_SHARED_LIBRARIES)/bluetooth_packets_python3.so
 
-$(bluetooth_cert_env_provider_path):
-	@mkdir -p $(dir $@)
-	$(hide) echo "PRODUCT_DEVICE = \"$(PRODUCT_DEVICE)\"" > $@
+LOCAL_host_libraries := \
+	$(HOST_OUT_SHARED_LIBRARIES)/libbase.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libbluetooth_gd.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libc++.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libchrome.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libevent-host.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libgrpc++_unsecure.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/liblog.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libz-host.so \
+	$(HOST_OUT_SHARED_LIBRARIES)/libprotobuf-cpp-full.so
 
-bluetooth_cert_zip_path := \
-    $(call intermediates-dir-for,PACKAGING,bluetooth_cert_test_package,HOST)/bluetooth_cert_test.zip
+LOCAL_target_executables := \
+	$(TARGET_OUT_EXECUTABLES)/bluetooth_stack_with_facade
 
-$(bluetooth_cert_zip_path): PRIVATE_BLUETOOTH_CERT_TEST_FILE_LIST := $(bluetooth_cert_test_file_list)
+LOCAL_target_libraries := \
+	$(TARGET_OUT_SHARED_LIBRARIES)/libbluetooth_gd.so \
+	$(TARGET_OUT_SHARED_LIBRARIES)/libgrpc++_unsecure.so
 
-$(bluetooth_cert_zip_path): PRIVATE_BLUETOOTH_CERT_ENV_PROVIDER_PATH := $(bluetooth_cert_env_provider_path)
+bluetooth_cert_src_and_bin_zip := \
+	$(call intermediates-dir-for,PACKAGING,bluetooth_cert_src_and_bin,HOST)/bluetooth_cert_src_and_bin.zip
 
-$(bluetooth_cert_zip_path) : $(SOONG_ZIP) $(bluetooth_cert_env_provider_path) $(bluetooth_cert_test_file_list)
-	$(hide) $(SOONG_ZIP) -d -o $@ $(addprefix -f ,$(PRIVATE_BLUETOOTH_CERT_TEST_FILE_LIST)) \
-		-C $(call intermediates-dir-for,PACKAGING,bluetooth_cert_test_package,HOST) -f $(PRIVATE_BLUETOOTH_CERT_ENV_PROVIDER_PATH)
+# Assume 64-bit OS
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_cert_test_sources := $(LOCAL_cert_test_sources)
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_host_executables := $(LOCAL_host_executables)
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_host_root_canal_executables := $(LOCAL_host_root_canal_executables)
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_host_python_extension_libraries := $(LOCAL_host_python_extension_libraries)
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_host_libraries := $(LOCAL_host_libraries)
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_target_executables := $(LOCAL_target_executables)
+$(bluetooth_cert_src_and_bin_zip): PRIVATE_target_libraries := $(LOCAL_target_libraries)
+$(bluetooth_cert_src_and_bin_zip): $(SOONG_ZIP) $(LOCAL_cert_test_sources) \
+		$(LOCAL_host_executables) $(LOCAL_host_root_canal_executables) $(LOCAL_host_libraries) \
+		$(LOCAL_target_executables) $(LOCAL_target_libraries) $(LOCAL_host_python_extension_libraries)
+	$(hide) $(SOONG_ZIP) -d -o $@ \
+		-C system/bt/gd $(addprefix -f ,$(PRIVATE_cert_test_sources)) \
+		-C $(HOST_OUT_EXECUTABLES) $(addprefix -f ,$(PRIVATE_host_executables)) \
+		-C $(HOST_OUT_NATIVE_TESTS)/root-canal $(addprefix -f ,$(PRIVATE_host_root_canal_executables)) \
+		-C $(HOST_OUT_SHARED_LIBRARIES) $(addprefix -f ,$(PRIVATE_host_python_extension_libraries)) \
+		-P lib64 \
+		-C $(HOST_OUT_SHARED_LIBRARIES) $(addprefix -f ,$(PRIVATE_host_libraries)) \
+		-P target \
+		-C $(TARGET_OUT_EXECUTABLES) $(addprefix -f ,$(PRIVATE_target_executables)) \
+		-C $(TARGET_OUT_SHARED_LIBRARIES) $(addprefix -f ,$(PRIVATE_target_libraries))
 
-$(call dist-for-goals,bluetooth_stack_with_facade,$(bluetooth_cert_zip_path):bluetooth_cert_test.zip)
+# TODO: Find a better way to locate output from SOONG genrule()
+LOCAL_cert_generated_py_zip := \
+	$(SOONG_OUT_DIR)/.intermediates/system/bt/gd/BluetoothFacadeAndCertGeneratedStub_py/gen/bluetooth_cert_generated_py.zip
+
+LOCAL_acts_zip := $(HOST_OUT)/acts-dist/acts.zip
+
+bluetooth_cert_tests_py_package_zip := \
+	$(call intermediates-dir-for,PACKAGING,bluetooth_cert_tests_py_package,HOST)/bluetooth_cert_tests.zip
+
+$(bluetooth_cert_tests_py_package_zip): PRIVATE_cert_src_and_bin_zip := $(bluetooth_cert_src_and_bin_zip)
+$(bluetooth_cert_tests_py_package_zip): PRIVATE_acts_zip := $(LOCAL_acts_zip)
+$(bluetooth_cert_tests_py_package_zip): PRIVATE_cert_generated_py_zip := $(LOCAL_cert_generated_py_zip)
+$(bluetooth_cert_tests_py_package_zip): $(SOONG_ZIP) $(LOCAL_acts_zip) \
+		$(bluetooth_cert_src_and_bin_zip) $(bluetooth_cert_generated_py_zip)
+	@echo "Packaging Bluetooth Cert Tests into $@"
+	@rm -rf $(dir $@)bluetooth_cert_tests
+	@rm -rf $(dir $@)acts
+	@mkdir -p $(dir $@)bluetooth_cert_tests
+	@mkdir -p $(dir $@)acts
+	$(hide) unzip -o -q $(PRIVATE_acts_zip) "tools/test/connectivity/acts/framework/*" -d $(dir $@)acts
+	$(hide) unzip -o -q $(PRIVATE_cert_src_and_bin_zip) -d $(dir $@)bluetooth_cert_tests
+	$(hide) unzip -o -q $(PRIVATE_cert_generated_py_zip) -d $(dir $@)bluetooth_cert_tests
+	# Make all subdirectory of gd Python pacakages except lib64 and target
+	$(hide) for f in `find $(dir $@)bluetooth_cert_tests -type d -name "*" \
+					-not -path "$(dir $@)bluetooth_cert_tests/target*" \
+					-not -path "$(dir $@)bluetooth_cert_tests/lib64*"` \
+			; do (touch -a $$f/__init__.py) ; done
+	$(hide) $(SOONG_ZIP) -d -o $@ -C $(dir $@)bluetooth_cert_tests -D $(dir $@)bluetooth_cert_tests \
+		-P acts_framework \
+		-C $(dir $@)acts/tools/test/connectivity/acts/framework -D $(dir $@)acts/tools/test/connectivity/acts/framework
+
+$(call dist-for-goals,bluetooth_stack_with_facade,$(bluetooth_cert_tests_py_package_zip):bluetooth_cert_tests.zip)
\ No newline at end of file
diff --git a/gd/cert/all_cert_testcases b/gd/cert/all_cert_testcases
index a5bb929..e8b1d1f 100644
--- a/gd/cert/all_cert_testcases
+++ b/gd/cert/all_cert_testcases
@@ -10,3 +10,4 @@
 LeAclManagerTest
 StackTest
 L2capTest
+LeL2capTest
diff --git a/gd/cert/android_devices_config.json b/gd/cert/android_devices_config.json
index 7123c65..1c0ec78 100644
--- a/gd/cert/android_devices_config.json
+++ b/gd/cert/android_devices_config.json
@@ -12,6 +12,7 @@
                     "signal_port": "8894",
                     "label": "cert_stack",
                     "serial_number": "CERT",
+                    "name": "Cert Device",
                     "cmd":
                     [
                         "adb",
@@ -31,6 +32,7 @@
                     "signal_port": "8895",
                     "label": "stack_under_test",
                     "serial_number": "DUT",
+                    "name": "DUT Device",
                     "cmd":
                     [
                         "adb",
diff --git a/gd/cert/captures.py b/gd/cert/captures.py
index 2dff151..c16afec 100644
--- a/gd/cert/captures.py
+++ b/gd/cert/captures.py
@@ -17,7 +17,7 @@
 import bluetooth_packets_python3 as bt_packets
 from bluetooth_packets_python3 import hci_packets
 from bluetooth_packets_python3 import l2cap_packets
-from bluetooth_packets_python3.l2cap_packets import CommandCode
+from bluetooth_packets_python3.l2cap_packets import CommandCode, LeCommandCode
 from cert.capture import Capture
 from cert.matchers import L2capMatchers
 
@@ -47,6 +47,18 @@
                                     list(packet.event)))))
 
 
+def LeConnectionCompleteCapture():
+    return Capture(lambda packet: packet.event[0] == 0x3e
+                   and (packet.event[2] == 0x01 or packet.event[2] == 0x0a),
+        lambda packet: hci_packets.LeConnectionCompleteView(
+            hci_packets.LeMetaEventView(
+                            hci_packets.EventPacketView(
+                                bt_packets.PacketViewLittleEndian(
+                                    list(packet.event)
+                                )
+                            ))))
+
+
 class L2capCaptures(object):
 
     @staticmethod
@@ -60,3 +72,27 @@
         frame = L2capMatchers.control_frame_with_code(
             packet, CommandCode.CONNECTION_RESPONSE)
         return l2cap_packets.ConnectionResponseView(frame)
+
+    @staticmethod
+    def CreditBasedConnectionRequest(psm):
+        return Capture(
+            L2capMatchers.CreditBasedConnectionRequest(psm),
+            L2capCaptures._extract_credit_based_connection_request)
+
+    @staticmethod
+    def _extract_credit_based_connection_request(packet):
+        frame = L2capMatchers.le_control_frame_with_code(
+            packet, LeCommandCode.LE_CREDIT_BASED_CONNECTION_REQUEST)
+        return l2cap_packets.LeCreditBasedConnectionRequestView(frame)
+
+    @staticmethod
+    def CreditBasedConnectionResponse(scid):
+        return Capture(
+            L2capMatchers.CreditBasedConnectionResponse(scid),
+            L2capCaptures._extract_credit_based_connection_response)
+
+    @staticmethod
+    def _extract_credit_based_connection_response(packet):
+        frame = L2capMatchers.le_control_frame_with_code(
+            packet, LeCommandCode.LE_CREDIT_BASED_CONNECTION_RESPONSE)
+        return l2cap_packets.LeCreditBasedConnectionResponseView(frame)
diff --git a/gd/cert/cert_self_test.py b/gd/cert/cert_self_test.py
index cec7051..5041921 100644
--- a/gd/cert/cert_self_test.py
+++ b/gd/cert/cert_self_test.py
@@ -14,18 +14,18 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+from datetime import datetime, timedelta
 import logging
 import time
 
 from mobly import asserts
-from datetime import datetime, timedelta
-from acts.base_test import BaseTestClass
-from cert.event_stream import EventStream, FilteringEventStream
-from cert.truth import assertThat
 
-# Test packet nesting
+from acts.base_test import BaseTestClass
+
 from bluetooth_packets_python3 import hci_packets
 from bluetooth_packets_python3 import l2cap_packets
+from cert.event_stream import EventStream, FilteringEventStream
+from cert.truth import assertThat
 
 
 class BogusProto:
diff --git a/gd/cert/event_stream.py b/gd/cert/event_stream.py
index c8cee2f..fc9fb56 100644
--- a/gd/cert/event_stream.py
+++ b/gd/cert/event_stream.py
@@ -14,6 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+from abc import ABC, abstractmethod
+from concurrent.futures import ThreadPoolExecutor
 from datetime import datetime, timedelta
 import logging
 from queue import SimpleQueue, Empty
@@ -21,10 +23,8 @@
 from mobly import asserts
 
 from google.protobuf import text_format
-from concurrent.futures import ThreadPoolExecutor
-from grpc import RpcError
 
-from abc import ABC, abstractmethod
+from grpc import RpcError
 
 from cert.closable import Closable
 
diff --git a/gd/cert/gd_base_test_facade_only.py b/gd/cert/gd_base_test.py
similarity index 89%
rename from gd/cert/gd_base_test_facade_only.py
rename to gd/cert/gd_base_test.py
index 1412e64..284ffcd 100644
--- a/gd/cert/gd_base_test_facade_only.py
+++ b/gd/cert/gd_base_test.py
@@ -14,26 +14,19 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts import asserts
-from acts.base_test import BaseTestClass
-from facade import rootservice_pb2 as facade_rootservice
-
 import importlib
 import logging
 import os
 import signal
 import subprocess
 
-
-def is_subprocess_alive(process, timeout_seconds=1):
-    try:
-        process.wait(timeout=timeout_seconds)
-        return False
-    except subprocess.TimeoutExpired as exp:
-        return True
+from acts import asserts
+from acts.base_test import BaseTestClass
+from cert.os_utils import get_gd_root, is_subprocess_alive
+from facade import rootservice_pb2 as facade_rootservice
 
 
-class GdFacadeOnlyBaseTestClass(BaseTestClass):
+class GdBaseTestClass(BaseTestClass):
 
     def setup_class(self, dut_module, cert_module):
         self.dut_module = dut_module
@@ -49,9 +42,7 @@
             self.rootcanal_logs = open(rootcanal_logpath, 'w')
             rootcanal_config = self.controller_configs['rootcanal']
             rootcanal_hci_port = str(rootcanal_config.get("hci_port", "6402"))
-            rootcanal = os.path.join(
-                os.getcwd(),
-                "out/host/linux-x86/nativetest64/root-canal/root-canal")
+            rootcanal = os.path.join(get_gd_root(), "root-canal")
             self.rootcanal_process = subprocess.Popen(
                 [
                     rootcanal,
@@ -59,7 +50,7 @@
                     rootcanal_hci_port,
                     str(rootcanal_config.get("link_layer_port", "6403"))
                 ],
-                cwd=os.getcwd(),
+                cwd=get_gd_root(),
                 env=os.environ.copy(),
                 stdout=self.rootcanal_logs,
                 stderr=self.rootcanal_logs)
diff --git a/gd/cert/gd_device.py b/gd/cert/gd_device.py
index 77dbe8c..0d4c295 100644
--- a/gd/cert/gd_device.py
+++ b/gd/cert/gd_device.py
@@ -16,8 +16,11 @@
 
 import logging
 
-from facade import rootservice_pb2_grpc as facade_rootservice_pb2_grpc
+from google.protobuf import empty_pb2 as empty_proto
+
 from cert.gd_device_base import GdDeviceBase, replace_vars
+from cert.event_stream import EventStream
+from facade import rootservice_pb2_grpc as facade_rootservice_pb2_grpc
 from hal import facade_pb2_grpc as hal_facade_pb2_grpc
 from hci.facade import facade_pb2 as hci_facade
 from hci.facade import facade_pb2_grpc as hci_facade_pb2_grpc
@@ -26,11 +29,10 @@
 from hci.facade import le_acl_manager_facade_pb2_grpc
 from hci.facade import le_advertising_manager_facade_pb2_grpc
 from hci.facade import le_scanning_manager_facade_pb2_grpc
-from neighbor.facade import facade_pb2_grpc as neighbor_facade_pb2_grpc
 from l2cap.classic import facade_pb2_grpc as l2cap_facade_pb2_grpc
+from l2cap.le import facade_pb2_grpc as l2cap_le_facade_pb2_grpc
+from neighbor.facade import facade_pb2_grpc as neighbor_facade_pb2_grpc
 from security import facade_pb2_grpc as security_facade_pb2_grpc
-from google.protobuf import empty_pb2 as empty_proto
-from cert.event_stream import EventStream
 
 ACTS_CONTROLLER_CONFIG_NAME = "GdDevice"
 ACTS_CONTROLLER_REFERENCE_NAME = "gd_devices"
@@ -67,16 +69,17 @@
         devices.append(
             GdDevice(config["grpc_port"], config["grpc_root_server_port"],
                      config["signal_port"], resolved_cmd, config["label"],
-                     config.get("serial_number", "")))
+                     config.get("serial_number", ""), config.get("name", "")))
     return devices
 
 
 class GdDevice(GdDeviceBase):
 
     def __init__(self, grpc_port, grpc_root_server_port, signal_port, cmd,
-                 label, serial_number):
+                 label, serial_number, name):
         super().__init__(grpc_port, grpc_root_server_port, signal_port, cmd,
-                         label, ACTS_CONTROLLER_CONFIG_NAME, serial_number)
+                         label, ACTS_CONTROLLER_CONFIG_NAME, serial_number,
+                         name)
 
         # Facade stubs
         self.rootservice = facade_rootservice_pb2_grpc.RootFacadeStub(
@@ -91,6 +94,8 @@
         self.hci.send_command_with_status = self.__send_hci_command_with_status
         self.l2cap = l2cap_facade_pb2_grpc.L2capClassicModuleFacadeStub(
             self.grpc_channel)
+        self.l2cap_le = l2cap_le_facade_pb2_grpc.L2capLeModuleFacadeStub(
+            self.grpc_channel)
         self.hci_acl_manager = acl_manager_facade_pb2_grpc.AclManagerFacadeStub(
             self.grpc_channel)
         self.hci_le_acl_manager = le_acl_manager_facade_pb2_grpc.LeAclManagerFacadeStub(
diff --git a/gd/cert/gd_device_base.py b/gd/cert/gd_device_base.py
index 7c00cfc..2ec0d81 100644
--- a/gd/cert/gd_device_base.py
+++ b/gd/cert/gd_device_base.py
@@ -29,12 +29,7 @@
 
 import grpc
 
-from cert.environment_provider import PRODUCT_DEVICE
-from cert.gd_base_test_facade_only import is_subprocess_alive
-
-ANDROID_PRODUCT_OUT = os.path.join(
-    os.getcwd(), "out/dist/bluetooth_cert_test/out/target/product",
-    PRODUCT_DEVICE)
+from cert.os_utils import get_gd_root, is_subprocess_alive
 
 WAIT_CHANNEL_READY_TIMEOUT = 10
 WAIT_FOR_DEVICE_TIMEOUT = 180
@@ -49,8 +44,7 @@
         rootcanal_port = ""
     if serial_number == "DUT" or serial_number == "CERT":
         raise Exception("Did you forget to configure the serial number?")
-    android_host_out = os.path.join(os.getcwd(), "out/host/linux-x86")
-    return string.replace("$ANDROID_HOST_OUT", android_host_out) \
+    return string.replace("$GD_ROOT", get_gd_root()) \
                  .replace("$(grpc_port)", config.get("grpc_port")) \
                  .replace("$(grpc_root_server_port)", config.get("grpc_root_server_port")) \
                  .replace("$(rootcanal_port)", rootcanal_port) \
@@ -61,7 +55,7 @@
 class GdDeviceBase:
 
     def __init__(self, grpc_port, grpc_root_server_port, signal_port, cmd,
-                 label, type_identifier, serial_number):
+                 label, type_identifier, serial_number, name):
         self.label = label if label is not None else grpc_port
         # logging.log_path only exists when this is used in an ACTS test run.
         self.log_path_base = context.get_current_context().get_full_output_path(
@@ -78,6 +72,10 @@
                                         '%s_btsnoop_hci.log' % label)
             cmd.append("--btsnoop=" + btsnoop_path)
 
+        self.grpc_root_server_port = int(grpc_root_server_port)
+        self.grpc_port = int(grpc_port)
+        self.signal_port = int(signal_port)
+
         self.serial_number = serial_number
         if self.serial_number:
             self.adb = AdbProxy(self.serial_number)
@@ -87,35 +85,35 @@
                 msg="device %s cannot run as root after enabling verity" %
                 self.serial_number)
             self.adb.shell("date " + time.strftime("%m%d%H%M%Y.%S"))
-            self.adb.tcp_forward(int(grpc_port), int(grpc_port))
-            self.adb.tcp_forward(
-                int(grpc_root_server_port), int(grpc_root_server_port))
-            self.adb.reverse("tcp:%s tcp:%s" % (signal_port, signal_port))
+            self.tcp_forward_or_die(self.grpc_port, self.grpc_port)
+            self.tcp_forward_or_die(self.grpc_root_server_port,
+                                    self.grpc_root_server_port)
+            self.tcp_reverse_or_die(self.signal_port, self.signal_port)
             self.push_or_die(
-                os.path.join(ANDROID_PRODUCT_OUT,
-                             "system/bin/bluetooth_stack_with_facade"),
-                "system/bin")
+                os.path.join(get_gd_root(), "target",
+                             "bluetooth_stack_with_facade"), "system/bin")
             self.push_or_die(
-                os.path.join(ANDROID_PRODUCT_OUT,
-                             "system/lib64/libbluetooth_gd.so"), "system/lib64")
+                os.path.join(get_gd_root(), "target", "libbluetooth_gd.so"),
+                "system/lib64")
             self.push_or_die(
-                os.path.join(ANDROID_PRODUCT_OUT,
-                             "system/lib64/libgrpc++_unsecure.so"),
+                os.path.join(get_gd_root(), "target", "libgrpc++_unsecure.so"),
                 "system/lib64")
             self.ensure_no_output(self.adb.shell("logcat -c"))
             self.adb.shell("rm /data/misc/bluetooth/logs/btsnoop_hci.log")
             self.ensure_no_output(self.adb.shell("svc bluetooth disable"))
 
+        self.name = name
+
         tester_signal_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         tester_signal_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
                                         1)
-        socket_address = ('localhost', int(signal_port))
+        socket_address = ('localhost', self.signal_port)
         tester_signal_socket.bind(socket_address)
         tester_signal_socket.listen(1)
 
         self.backing_process = subprocess.Popen(
             cmd,
-            cwd=os.getcwd(),
+            cwd=get_gd_root(),
             env=os.environ.copy(),
             stdout=self.backing_process_logs,
             stderr=self.backing_process_logs)
@@ -131,7 +129,6 @@
 
         self.grpc_root_server_channel = grpc.insecure_channel(
             "localhost:" + grpc_root_server_port)
-        self.grpc_port = int(grpc_port)
         self.grpc_channel = grpc.insecure_channel("localhost:" + grpc_port)
 
     def clean_up(self):
@@ -146,6 +143,9 @@
                           (self.label, backing_process_return_code))
 
         if self.serial_number:
+            self.adb.remove_tcp_forward(self.grpc_port)
+            self.adb.remove_tcp_forward(self.grpc_root_server_port)
+            self.adb.reverse("--remove tcp:%d" % self.signal_port)
             self.adb.shell("logcat -d -f /data/misc/bluetooth/logs/system_log")
             self.adb.pull(
                 "/data/misc/bluetooth/logs/btsnoop_hci.log %s" % os.path.join(
@@ -191,6 +191,44 @@
                 (src_file_path, dst_file_path, e),
                 extras=e)
 
+    def tcp_forward_or_die(self, host_port, device_port):
+        """
+        Forward a TCP port from host to device or fail
+        :param host_port: host port, int, 0 for adb to assign one
+        :param device_port: device port, int
+        :return: host port int
+        """
+        error_or_port = self.adb.forward(
+            "tcp:%d tcp:%d" % (host_port, device_port), ignore_status=True)
+        if not error_or_port:
+            logging.debug("host port %d was already forwarded" % host_port)
+            return host_port
+        if not isinstance(error_or_port, int):
+            asserts.fail(
+                'Unable to forward host port %d to device port %d, error %s' %
+                (host_port, device_port, error_or_port))
+        return error_or_port
+
+    def tcp_reverse_or_die(self, device_port, host_port):
+        """
+        Forward a TCP port from device to host or fail
+        :param device_port: device port, int, 0 for adb to assign one
+        :param host_port: host port, int
+        :return: device port int
+        """
+        error_or_port = self.adb.reverse(
+            "tcp:%d tcp:%d" % (device_port, host_port))
+        if not error_or_port:
+            logging.debug("device port %d was already reversed" % device_port)
+            return device_port
+        try:
+            error_or_port = int(error_or_port)
+        except ValueError:
+            asserts.fail(
+                'Unable to reverse device port %d to host port %d, error %s' %
+                (device_port, host_port, error_or_port))
+        return error_or_port
+
     def ensure_verity_disabled(self):
         """Ensures that verity is enabled.
 
diff --git a/gd/cert/host_config.json b/gd/cert/host_config.json
index 9de9d70..3ffb547 100644
--- a/gd/cert/host_config.json
+++ b/gd/cert/host_config.json
@@ -18,9 +18,10 @@
                     "grpc_root_server_port": "8996",
                     "signal_port": "8994",
                     "label": "cert_stack",
+                    "name": "Cert Device",
                     "cmd":
                     [
-                        "$ANDROID_HOST_OUT/bin/bluetooth_stack_with_facade",
+                        "$GD_ROOT/bluetooth_stack_with_facade",
                         "--grpc-port=$(grpc_port)",
                         "--root-server-port=$(grpc_root_server_port)",
                         "--rootcanal-port=$(rootcanal_port)",
@@ -32,9 +33,10 @@
                     "grpc_root_server_port": "8997",
                     "signal_port": "8995",
                     "label": "stack_under_test",
+                    "name": "DUT Device",
                     "cmd":
                     [
-                        "$ANDROID_HOST_OUT/bin/bluetooth_stack_with_facade",
+                        "$GD_ROOT/bluetooth_stack_with_facade",
                         "--grpc-port=$(grpc_port)",
                         "--root-server-port=$(grpc_root_server_port)",
                         "--rootcanal-port=$(rootcanal_port)",
diff --git a/gd/cert/matchers.py b/gd/cert/matchers.py
index a30becf..805931b 100644
--- a/gd/cert/matchers.py
+++ b/gd/cert/matchers.py
@@ -16,10 +16,10 @@
 
 import bluetooth_packets_python3 as bt_packets
 from bluetooth_packets_python3 import l2cap_packets
-from bluetooth_packets_python3.l2cap_packets import CommandCode
+from bluetooth_packets_python3.l2cap_packets import CommandCode, LeCommandCode
 from bluetooth_packets_python3.l2cap_packets import ConnectionResponseResult
 from bluetooth_packets_python3.l2cap_packets import InformationRequestInfoType
-import logging
+from bluetooth_packets_python3.l2cap_packets import LeCreditBasedConnectionResponseResult
 
 
 class L2capMatchers(object):
@@ -53,22 +53,57 @@
         return lambda packet: L2capMatchers._is_control_frame_with_code(packet, CommandCode.COMMAND_REJECT)
 
     @staticmethod
+    def LeCommandReject():
+        return lambda packet: L2capMatchers._is_le_control_frame_with_code(packet, LeCommandCode.COMMAND_REJECT)
+
+    @staticmethod
+    def CreditBasedConnectionRequest(psm):
+        return lambda packet: L2capMatchers._is_matching_credit_based_connection_request(packet, psm)
+
+    @staticmethod
+    def CreditBasedConnectionResponse(
+            scid, result=LeCreditBasedConnectionResponseResult.SUCCESS):
+        return lambda packet: L2capMatchers._is_matching_credit_based_connection_response(packet, scid, result)
+
+    @staticmethod
+    def LeDisconnectionRequest(scid, dcid):
+        return lambda packet: L2capMatchers._is_matching_le_disconnection_request(packet, scid, dcid)
+
+    @staticmethod
+    def LeDisconnectionResponse(scid, dcid):
+        return lambda packet: L2capMatchers._is_matching_le_disconnection_response(packet, scid, dcid)
+
+    @staticmethod
     def SFrame(req_seq=None, f=None, s=None, p=None):
         return lambda packet: L2capMatchers._is_matching_supervisory_frame(packet, req_seq, f, s, p)
 
     @staticmethod
-    def IFrame(tx_seq=None, payload=None):
-        return lambda packet: L2capMatchers._is_matching_information_frame(packet, tx_seq, payload)
+    def IFrame(tx_seq=None, payload=None, f=None):
+        return lambda packet: L2capMatchers._is_matching_information_frame(packet, tx_seq, payload, f)
 
     @staticmethod
     def Data(payload):
         return lambda packet: packet.GetPayload().GetBytes() == payload
 
+    @staticmethod
+    def FirstLeIFrame(payload, sdu_size):
+        return lambda packet: L2capMatchers._is_matching_first_le_i_frame(packet, payload, sdu_size)
+
     # this is a hack - should be removed
     @staticmethod
     def PartialData(payload):
         return lambda packet: payload in packet.GetPayload().GetBytes()
 
+    # this is a hack - should be removed
+    @staticmethod
+    def PacketPayloadRawData(payload):
+        return lambda packet: payload in packet.payload
+
+    # this is a hack - should be removed
+    @staticmethod
+    def PacketPayloadWithMatchingPsm(psm):
+        return lambda packet: None if psm != packet.psm else packet
+
     @staticmethod
     def ExtractBasicFrame(scid):
         return lambda packet: L2capMatchers._basic_frame_for(packet, scid)
@@ -109,7 +144,7 @@
         return l2cap_packets.EnhancedSupervisoryFrameView(standard_frame)
 
     @staticmethod
-    def _is_matching_information_frame(packet, tx_seq, payload):
+    def _is_matching_information_frame(packet, tx_seq, payload, f):
         frame = L2capMatchers._information_frame(packet)
         if frame is None:
             return False
@@ -117,6 +152,8 @@
             return False
         if payload is not None and frame.GetPayload().GetBytes() != payload:
             return False
+        if f is not None and frame.GetF() != f:
+            return False
         return True
 
     @staticmethod
@@ -135,12 +172,24 @@
         return True
 
     @staticmethod
+    def _is_matching_first_le_i_frame(packet, payload, sdu_size):
+        first_le_i_frame = l2cap_packets.FirstLeInformationFrameView(packet)
+        return first_le_i_frame.GetPayload().GetBytes(
+        ) == payload and first_le_i_frame.GetL2capSduLength() == sdu_size
+
+    @staticmethod
     def _control_frame(packet):
         if packet.GetChannelId() != 1:
             return None
         return l2cap_packets.ControlView(packet.GetPayload())
 
     @staticmethod
+    def _le_control_frame(packet):
+        if packet.GetChannelId() != 5:
+            return None
+        return l2cap_packets.LeControlView(packet.GetPayload())
+
+    @staticmethod
     def control_frame_with_code(packet, code):
         frame = L2capMatchers._control_frame(packet)
         if frame is None or frame.GetCode() != code:
@@ -148,10 +197,22 @@
         return frame
 
     @staticmethod
+    def le_control_frame_with_code(packet, code):
+        frame = L2capMatchers._le_control_frame(packet)
+        if frame is None or frame.GetCode() != code:
+            return None
+        return frame
+
+    @staticmethod
     def _is_control_frame_with_code(packet, code):
         return L2capMatchers.control_frame_with_code(packet, code) is not None
 
     @staticmethod
+    def _is_le_control_frame_with_code(packet, code):
+        return L2capMatchers.le_control_frame_with_code(packet,
+                                                        code) is not None
+
+    @staticmethod
     def _is_matching_connection_response(packet, scid):
         frame = L2capMatchers.control_frame_with_code(
             packet, CommandCode.CONNECTION_RESPONSE)
@@ -173,6 +234,26 @@
         ) == dcid
 
     @staticmethod
+    def _is_matching_le_disconnection_response(packet, scid, dcid):
+        frame = L2capMatchers.le_control_frame_with_code(
+            packet, LeCommandCode.DISCONNECTION_RESPONSE)
+        if frame is None:
+            return False
+        response = l2cap_packets.LeDisconnectionResponseView(frame)
+        return response.GetSourceCid() == scid and response.GetDestinationCid(
+        ) == dcid
+
+    @staticmethod
+    def _is_matching_le_disconnection_request(packet, scid, dcid):
+        frame = L2capMatchers.le_control_frame_with_code(
+            packet, LeCommandCode.DISCONNECTION_REQUEST)
+        if frame is None:
+            return False
+        request = l2cap_packets.LeDisconnectionRequestView(frame)
+        return request.GetSourceCid() == scid and request.GetDestinationCid(
+        ) == dcid
+
+    @staticmethod
     def _information_response_with_type(packet, info_type):
         frame = L2capMatchers.control_frame_with_code(
             packet, CommandCode.INFORMATION_RESPONSE)
@@ -203,3 +284,23 @@
         ) != supports_fixed_channels:
             return False
         return True
+
+    @staticmethod
+    def _is_matching_credit_based_connection_request(packet, psm):
+        frame = L2capMatchers.le_control_frame_with_code(
+            packet, LeCommandCode.LE_CREDIT_BASED_CONNECTION_REQUEST)
+        if frame is None:
+            return False
+        request = l2cap_packets.LeCreditBasedConnectionRequestView(frame)
+        return request.GetLePsm() == psm
+
+    @staticmethod
+    def _is_matching_credit_based_connection_response(packet, scid, result):
+        frame = L2capMatchers.le_control_frame_with_code(
+            packet, LeCommandCode.LE_CREDIT_BASED_CONNECTION_RESPONSE)
+        if frame is None:
+            return False
+        response = l2cap_packets.LeCreditBasedConnectionResponseView(frame)
+        return response.GetResult() == result and (
+            result != LeCreditBasedConnectionResponseResult.SUCCESS or
+            response.GetDestinationCid() != 0)
diff --git a/gd/cert/os_utils.py b/gd/cert/os_utils.py
new file mode 100644
index 0000000..45e32f9
--- /dev/null
+++ b/gd/cert/os_utils.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - 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.
+
+from pathlib import Path
+import subprocess
+
+
+def is_subprocess_alive(process, timeout_seconds=1):
+    """
+    Check if a process is alive for at least timeout_seconds
+    :param process: a Popen object that represent a subprocess
+    :param timeout_seconds: process needs to be alive for at least
+           timeout_seconds
+    :return: True if process is alive for at least timeout_seconds
+    """
+    try:
+        process.wait(timeout=timeout_seconds)
+        return False
+    except subprocess.TimeoutExpired as exp:
+        return True
+
+
+def get_gd_root():
+    """
+    Return the root of the GD test library
+
+    GD root is the parent directory of cert
+    :return: root directory string of gd test library
+    """
+    return str(Path(__file__).absolute().parents[1])
diff --git a/gd/cert/pts.json b/gd/cert/pts.json
index 76a27be..b266c5a 100644
--- a/gd/cert/pts.json
+++ b/gd/cert/pts.json
@@ -13,6 +13,7 @@
                     "signal_port": "8895",
                     "label": "stack_under_test",
                     "serial_number": "DUT",
+                    "name": "Cert Device",
                     "cmd":
                     [
                         "adb",
diff --git a/gd/cert/pts_base_test.py b/gd/cert/pts_base_test.py
index 906bccf..2b1b555 100644
--- a/gd/cert/pts_base_test.py
+++ b/gd/cert/pts_base_test.py
@@ -17,10 +17,6 @@
 from acts.base_test import BaseTestClass
 
 import importlib
-import logging
-import os
-import signal
-import subprocess
 
 
 class PTSBaseTestClass(BaseTestClass):
diff --git a/gd/cert/py_acl_manager.py b/gd/cert/py_acl_manager.py
index aa3e810..d193a8d 100644
--- a/gd/cert/py_acl_manager.py
+++ b/gd/cert/py_acl_manager.py
@@ -16,16 +16,12 @@
 
 from google.protobuf import empty_pb2 as empty_proto
 from cert.event_stream import EventStream
-from cert.event_stream import FilteringEventStream
 from cert.event_stream import IEventStream
-from cert.captures import ReadBdAddrCompleteCapture
 from cert.captures import ConnectionCompleteCapture
-from cert.captures import ConnectionRequestCapture
 from cert.closable import Closable
 from cert.closable import safeClose
 from bluetooth_packets_python3 import hci_packets
 from cert.truth import assertThat
-from hci.facade import facade_pb2 as hci_facade
 from hci.facade import acl_manager_facade_pb2 as acl_manager_facade
 
 
diff --git a/gd/cert/py_l2cap.py b/gd/cert/py_l2cap.py
index 0246dfa..f26e0c9 100644
--- a/gd/cert/py_l2cap.py
+++ b/gd/cert/py_l2cap.py
@@ -14,8 +14,17 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+from google.protobuf import empty_pb2 as empty_proto
+
 from l2cap.classic import facade_pb2 as l2cap_facade_pb2
+from l2cap.le import facade_pb2 as l2cap_le_facade_pb2
 from bluetooth_packets_python3 import l2cap_packets
+from cert.event_stream import FilteringEventStream
+from cert.event_stream import EventStream, IEventStream
+from cert.closable import Closable, safeClose
+from cert.truth import assertThat
+from cert.matchers import L2capMatchers
+from facade import common_pb2 as common
 
 
 class PyL2capChannel(object):
@@ -29,11 +38,14 @@
             l2cap_facade_pb2.DynamicChannelPacket(psm=0x33, payload=payload))
 
 
-class PyL2cap(object):
+class PyL2cap(Closable):
 
     def __init__(self, device):
         self._device = device
 
+    def close(self):
+        pass
+
     def open_channel(self,
                      psm=0x33,
                      mode=l2cap_facade_pb2.RetransmissionFlowControlMode.BASIC):
@@ -43,3 +55,76 @@
             l2cap_facade_pb2.SetEnableDynamicChannelRequest(
                 psm=psm, retransmission_mode=mode))
         return PyL2capChannel(self._device, psm)
+
+
+class PyLeL2capChannel(IEventStream):
+
+    def __init__(self, device, psm, l2cap_stream):
+        self._device = device
+        self._psm = psm
+        self._le_l2cap_stream = l2cap_stream
+        self._our_le_l2cap_view = FilteringEventStream(
+            self._le_l2cap_stream,
+            L2capMatchers.PacketPayloadWithMatchingPsm(self._psm))
+
+    def get_event_queue(self):
+        return self._our_le_l2cap_view.get_event_queue()
+
+    def send(self, payload):
+        self._device.l2cap_le.SendDynamicChannelPacket(
+            l2cap_le_facade_pb2.DynamicChannelPacket(psm=0x33, payload=payload))
+
+
+class CreditBasedConnectionResponseFutureWrapper(object):
+    """
+    The future object returned when we send a connection request from DUT. Can be used to get connection status and
+    create the corresponding PyLeL2capChannel object later
+    """
+
+    def __init__(self, grpc_response_future, device, psm, le_l2cap_stream):
+        self._grpc_response_future = grpc_response_future
+        self._device = device
+        self._psm = psm
+        self._le_l2cap_stream = le_l2cap_stream
+
+    def get_status(self):
+        return l2cap_packets.LeCreditBasedConnectionResponseResult(
+            self._grpc_response_future.result().status)
+
+    def get_channel(self):
+        assertThat(self.get_status()).isEqualTo(
+            l2cap_packets.LeCreditBasedConnectionResponseResult.SUCCESS)
+        return PyLeL2capChannel(self._device, self._psm, self._le_l2cap_stream)
+
+
+class PyLeL2cap(Closable):
+
+    def __init__(self, device):
+        self._device = device
+        self._le_l2cap_stream = EventStream(
+            self._device.l2cap_le.FetchL2capData(empty_proto.Empty()))
+
+    def close(self):
+        safeClose(self._le_l2cap_stream)
+
+    def register_coc(self, psm=0x33):
+        self._device.l2cap_le.SetDynamicChannel(
+            l2cap_le_facade_pb2.SetEnableDynamicChannelRequest(
+                psm=psm, enable=True))
+        return PyLeL2capChannel(self._device, psm, self._le_l2cap_stream)
+
+    def connect_coc_to_cert(self, psm=0x33):
+        """
+        Send open LE COC request to CERT. Get a future for connection result, to be used after CERT accepts request
+        """
+        self.register_coc(psm)
+        # TODO: Update CERT device random address in ACL manager
+        response_future = self._device.l2cap_le.OpenDynamicChannel.future(
+            l2cap_le_facade_pb2.OpenDynamicChannelRequest(
+                psm=psm,
+                remote=common.BluetoothAddressWithType(
+                    address=common.BluetoothAddress(
+                        address=b"22:33:ff:ff:11:00"))))
+
+        return CreditBasedConnectionResponseFutureWrapper(
+            response_future, self._device, psm, self._le_l2cap_stream)
diff --git a/gd/cert/py_le_acl_manager.py b/gd/cert/py_le_acl_manager.py
new file mode 100644
index 0000000..a7ee3d0
--- /dev/null
+++ b/gd/cert/py_le_acl_manager.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - 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.
+
+from google.protobuf import empty_pb2 as empty_proto
+
+from cert.event_stream import EventStream
+from cert.event_stream import IEventStream
+from cert.captures import ConnectionCompleteCapture
+from cert.captures import LeConnectionCompleteCapture
+from cert.closable import Closable
+from cert.closable import safeClose
+from bluetooth_packets_python3 import hci_packets
+from cert.truth import assertThat
+from hci.facade import le_acl_manager_facade_pb2 as le_acl_manager_facade
+
+
+class PyLeAclManagerAclConnection(IEventStream, Closable):
+
+    def __init__(self, device, acl_stream, remote_addr, handle):
+        """
+        An abstract representation for an LE ACL connection in GD certification test
+        :param device: The GD device
+        :param acl_stream: The ACL stream for this connection
+        :param remote_addr: Remote device address
+        :param handle: Connection handle
+        """
+        self.device = device
+        self.handle = handle
+        # todo enable filtering after sorting out handles
+        #self.our_acl_stream = FilteringEventStream(acl_stream, None)
+        self.our_acl_stream = acl_stream
+
+        if remote_addr:
+            remote_addr_bytes = bytes(
+                remote_addr,
+                'utf8') if type(remote_addr) is str else bytes(remote_addr)
+            self.connection_event_stream = EventStream(
+                self.device.hci_le_acl_manager.CreateConnection(
+                    le_acl_manager_facade.LeConnectionMsg(
+                        address_type=int(
+                            hci_packets.AddressType.RANDOM_DEVICE_ADDRESS),
+                        address=remote_addr_bytes)))
+        else:
+            self.connection_event_stream = None
+
+    def close(self):
+        safeClose(self.connection_event_stream)
+
+    def wait_for_connection_complete(self):
+        connection_complete = LeConnectionCompleteCapture()
+        assertThat(self.connection_event_stream).emits(connection_complete)
+        self.handle = connection_complete.get().GetConnectionHandle()
+
+    def send(self, data):
+        self.device.hci_le_acl_manager.SendAclData(
+            le_acl_manager_facade.LeAclData(
+                handle=self.handle, payload=bytes(data)))
+
+    def get_event_queue(self):
+        return self.our_acl_stream.get_event_queue()
+
+
+class PyLeAclManager(Closable):
+
+    def __init__(self, device):
+        """
+        LE ACL Manager for GD Certification test
+        :param device: The GD device
+        """
+        self.device = device
+
+        self.le_acl_stream = EventStream(
+            self.device.hci_le_acl_manager.FetchAclData(empty_proto.Empty()))
+        self.incoming_connection_stream = None
+
+    def close(self):
+        safeClose(self.le_acl_stream)
+        safeClose(self.incoming_connection_stream)
+
+    # temporary, until everyone is migrated
+    def get_le_acl_stream(self):
+        return self.le_acl_stream
+
+    def listen_for_incoming_connections(self):
+        self.incoming_connection_stream = EventStream(
+            self.device.hci_le_acl_manager.FetchIncomingConnection(
+                empty_proto.Empty()))
+
+    def initiate_connection(self, remote_addr):
+        return PyLeAclManagerAclConnection(self.device, self.le_acl_stream,
+                                           remote_addr, None)
+
+    def accept_connection(self):
+        connection_complete = ConnectionCompleteCapture()
+        assertThat(self.incoming_connection_stream).emits(connection_complete)
+        handle = connection_complete.get().GetConnectionHandle()
+        return PyLeAclManagerAclConnection(self.device, self.le_acl_stream,
+                                           None, handle)
diff --git a/gd/cert/python3.8-gd b/gd/cert/python3.8-gd
deleted file mode 100755
index 6aa24b2..0000000
--- a/gd/cert/python3.8-gd
+++ /dev/null
@@ -1,4 +0,0 @@
-#! /bin/bash
-BLUETOOTH_CERT_TEST_ENV=$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test_env
-PYTHONPATH=$BLUETOOTH_CERT_TEST_ENV/out/host/linux-x86/lib64:$BLUETOOTH_CERT_TEST_ENV/system/bt/gd:$PYTHONPATH \
- python3.8 "$@"
diff --git a/gd/cert/run b/gd/cert/run
index 9634691..afa5057 100755
--- a/gd/cert/run
+++ b/gd/cert/run
@@ -12,6 +12,7 @@
 
 TEST_CONFIG="$ANDROID_BUILD_TOP/system/bt/gd/cert/android_devices_config.json"
 TEST_FILTER="-tf $ANDROID_BUILD_TOP/system/bt/gd/cert/all_cert_testcases"
+REUSE_VENV=false
 
 POSITIONAL=()
 while [[ $# -gt 0 ]]
@@ -26,6 +27,19 @@
     TEST_CONFIG=$ANDROID_BUILD_TOP/system/bt/gd/cert/host_config.json
     shift # past argument
     ;;
+    --test_file=*)
+    TEST_FILTER="-tc ${key#*=}"
+    shift # past argument
+    ;;
+    --test_config=*)
+    TEST_CONFIG="${key#*=}"
+    shift # past argument
+    ;;
+    # This will speed up the test by overwriting existing venv
+    --reuse_venv)
+    REUSE_VENV=true
+    shift # past argument
+    ;;
     *)    # unknown option
     POSITIONAL+=("$1") # save it in an array for later
     shift # past argument
@@ -34,21 +48,40 @@
 done
 set -- "${POSITIONAL[@]}" # restore positional parameters
 
-BLUETOOTH_CERT_TEST_ENV=$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test_env
-rm -rf BLUETOOTH_CERT_TEST_ENV
-mkdir -p $BLUETOOTH_CERT_TEST_ENV
+CERT_TEST_VENV=$ANDROID_BUILD_TOP/out/dist/bluetooth_venv
 
-unzip -o -q $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py.zip \
-   -d $BLUETOOTH_CERT_TEST_ENV
+if [ "$REUSE_VENV" != true ] ; then
+  rm -rf $CERT_TEST_VENV
+fi
 
-unzip -o -q $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test.zip \
-   -d $BLUETOOTH_CERT_TEST_ENV
+python3.8 -m virtualenv --python `which python3.8` $CERT_TEST_VENV
+if [[ $? -ne 0 ]] ; then
+    echo "Error setting up virtualenv"
+    return 1
+fi
 
-pushd .
-cd $ANDROID_BUILD_TOP
-PYTHONPATH=$BLUETOOTH_CERT_TEST_ENV/out/host/linux-x86/lib64:$BLUETOOTH_CERT_TEST_ENV/system/bt/gd:$PYTHONPATH \
-    python3.8 `which act.py`\
-      -c $TEST_CONFIG \
-      $TEST_FILTER \
-      -tp $ANDROID_BUILD_TOP/system/bt/gd
-popd
\ No newline at end of file
+unzip -o -q $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_tests.zip -d $CERT_TEST_VENV/acts
+if [[ $? -ne 0 ]] ; then
+    echo "Error unzipping bluetooth_cert_tests.zip"
+    return 1
+fi
+
+$CERT_TEST_VENV/bin/python $CERT_TEST_VENV/acts/setup.py install
+if [[ $? -ne 0 ]] ; then
+    echo "Error installing GD libraries"
+    return 1
+fi
+
+$CERT_TEST_VENV/bin/python -c "
+import bluetooth_packets_python3 as bp3
+bp3.BaseStruct
+"
+if [[ $? -ne 0 ]] ; then
+  echo "Setup failed as bluetooth_packets_python3 cannot be imported"
+  return 1
+fi
+
+$CERT_TEST_VENV/bin/python $CERT_TEST_VENV/bin/act.py \
+    -c $TEST_CONFIG \
+    $TEST_FILTER \
+    -tp $CERT_TEST_VENV/acts
diff --git a/gd/cert/run_pts_l2cap.sh b/gd/cert/run_pts_l2cap.sh
index 92fc7cd..356b31f 100755
--- a/gd/cert/run_pts_l2cap.sh
+++ b/gd/cert/run_pts_l2cap.sh
@@ -1,9 +1,5 @@
 #! /bin/bash
 
-unzip -u $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py.zip -d $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py
-
-# For bluetooth_packets_python3
-pushd .
-cd $ANDROID_BUILD_TOP
-python3.8-gd `which act.py` -c $ANDROID_BUILD_TOP/system/bt/gd/cert/pts.json -tf $ANDROID_BUILD_TOP/system/bt/gd/cert/pts_l2cap_testcase -tp $ANDROID_BUILD_TOP/system/bt/gd
-popd
\ No newline at end of file
+source $ANDROID_BUILD_TOP/system/bt/cert/run \
+  --test_config=$ANDROID_BUILD_TOP/system/bt/gd/cert/pts.json \
+  --test_file=$ANDROID_BUILD_TOP/system/bt/gd/cert/pts_l2cap_testcase
\ No newline at end of file
diff --git a/gd/cert/set_up_acts.sh b/gd/cert/set_up_acts.sh
deleted file mode 100755
index 60bfdd2..0000000
--- a/gd/cert/set_up_acts.sh
+++ /dev/null
@@ -1,109 +0,0 @@
-#! /bin/bash
-#
-# Script to setup environment to execute bluetooth certification stack
-#
-# for more info, see go/acts
-
-## Android build main build setup script relative to top level android source root
-BUILD_SETUP=./build/envsetup.sh
-
-function UsageAndroidTree {
-    cat<<EOF
-Ensure invoked from within the android source tree
-EOF
-}
-
-function UsageSourcedNotExecuted {
-    cat<<EOF
-Ensure script is SOURCED and not executed to persist the build setup
-e.g.
-source $0
-EOF
-}
-
-function UpFind {
-    while [[ $PWD != / ]] ; do
-        rc=$(find "$PWD" -maxdepth 1 "$@")
-        if [ -n "$rc" ]; then
-            echo $(dirname "$rc")
-            return
-        fi
-        cd ..
-    done
-}
-
-function SetUpAndroidBuild {
-    pushd .
-    android_root=$(UpFind -name out -type d)
-    if [[ -z $android_root ]] ; then
-        UsageAndroidTree
-        return
-    fi
-    echo "Found android root $android_root"
-    cd $android_root && . $BUILD_SETUP
-    echo "Sourced build setup rules"
-    cd $android_root && lunch
-    popd
-}
-
-function SetupPython38 {
-    echo "Setting up python3.8"
-    sudo apt-get install python3.8-dev
-}
-
-function CompileBluetoothPacketsPython3 {
-    echo "bluetooth_packets_python3 is not found, compiling"
-    croot
-    make -j bluetooth_packets_python3
-}
-
-if [[ "${BASH_SOURCE[0]}" == "${0}" ]] ; then
-    UsageSourcedNotExecuted
-    return 1
-fi
-
-if [[ -z "$ANDROID_BUILD_TOP" ]] ; then
-    SetUpAndroidBuild
-fi
-
-## Check python3.8 is installed properly
-## Need Python 3.8 because bluetooth_packets_python3 is compiled against
-## Python 3.8 headers
-dpkg -l python3.8-dev > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
-    SetupPython38
-fi
-
-## Check bluetooth_packets_python3 is compiled succssfully
-PYTHONPATH=$PYTHONPATH:$ANDROID_BUILD_TOP/out/host/linux-x86/lib64 python3.8 -c "
-import bluetooth_packets_python3 as bp3
-bp3.BaseStruct
-"
-if [[ $? -ne 0 ]] ; then
-  pushd .
-  CompileBluetoothPacketsPython3
-  popd
-  python3.8 -c "
-import bluetooth_packets_python3 as bp3
-bp3.BaseStruct
-"
-  if [[ $? -ne 0 ]] ; then
-    echo "Setup failed as bluetooth_packets_python3 cannot be found"
-  else
-    echo "Found bluetooth_packets_python3 after compilation"
-  fi
-else
-  echo "Found bluetooth_packets_python3"
-fi
-
-## All is good now so go ahead with the acts setup
-pushd .
-cd $ANDROID_BUILD_TOP/tools/test/connectivity/acts/framework/
-sudo python3.8 setup.py develop
-if [[ $? -eq 0 ]] ; then
-    echo "cert setup complete"
-else
-    echo "cert setup failed"
-fi
-popd
-
diff --git a/gd/cert/set_up_virtualenv.sh b/gd/cert/set_up_virtualenv.sh
index 50f3d48..0c07691 100644
--- a/gd/cert/set_up_virtualenv.sh
+++ b/gd/cert/set_up_virtualenv.sh
@@ -5,9 +5,9 @@
 # Usage
 #  1. cd system/bt/gd
 #  2. source cert/set_up_virtualenv.sh
-#  3. source gd_cert_venv/bin/activate
-#  4. [run tests, do development, hack]
-#  5. deactivate (or just close the terminal window)
+#  4. [run tests, do development, hack] using vevn/bin/python
+#
+# Note: Just use the virtualized Python binary, no need to activate
 
 ## Android build main build setup script relative to top level android source root
 BUILD_SETUP=./build/envsetup.sh
@@ -61,12 +61,6 @@
     sudo apt-get install python3-pip
 }
 
-function CompileBluetoothPacketsPython3 {
-    echo "bluetooth_packets_python3 is not found, compiling"
-    croot
-    make -j bluetooth_packets_python3
-}
-
 # Deactivate existing virtual environment, if any, ignore errors
 deactivate > /dev/null 2>&1
 
@@ -102,120 +96,49 @@
     SetUpAndroidBuild
 fi
 
-## Check bluetooth_packets_python3 is compiled succssfully
-$ANDROID_BUILD_TOP/system/bt/gd/cert/python3.8-gd -c "
-import bluetooth_packets_python3 as bp3
-bp3.BaseStruct
-"
-if [[ $? -ne 0 ]] ; then
-  pushd .
-  CompileBluetoothPacketsPython3
-  popd
-  $ANDROID_BUILD_TOP/system/bt/gd/cert/python3.8-gd -c "
-import bluetooth_packets_python3 as bp3
-bp3.BaseStruct
-"
-  if [[ $? -ne 0 ]] ; then
-    echo "Setup failed as bluetooth_packets_python3 cannot be found"
-    return 1
-  else
-    echo "Found bluetooth_packets_python3 after compilation"
-  fi
-else
-  echo "Found bluetooth_packets_python3"
-fi
-
 ## Compile and unzip test artifacts
-if [[ ! -f "$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py.zip" || ! -f "$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test.zip" ]]; then
-    echo "bluetooth_cert_generated_py.zip OR bluetooth_cert_test.zip is not found, compiling"
-    m -j dist bluetooth_stack_with_facade
-    if [[ ! -f "$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py.zip" || ! -f "$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test.zip" ]]; then
-        echo "Failed to compile bluetooth_stack_with_facade"
-        return 1
-    fi
-fi
-unzip -u $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py.zip -d $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py
+echo "Compiling bluetooth_stack_with_facade ..."
+$ANDROID_BUILD_TOP/build/soong/soong_ui.bash --build-mode --"all-modules" --dir="$(pwd)" dist bluetooth_stack_with_facade
 if [[ $? -ne 0 ]] ; then
-    echo "Failed to unzip bluetooth_cert_generated_py.zip"
+    echo "Failed to compile bluetooth_stack_with_facade"
     return 1
 fi
-unzip -u $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test.zip -d $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test
-if [[ $? -ne 0 ]] ; then
-    echo "Failed to unzip bluetooth_cert_test.zip"
+if [[ ! -f "$ANDROID_BUILD_TOP/out/dist/bluetooth_cert_tests.zip" ]]; then
+    echo "Cannot find bluetooth_cert_tests.zip after compilation"
     return 1
 fi
 
-# Set-up virtualenv
-pushd .
-cd $ANDROID_BUILD_TOP/system/bt/gd
-virtualenv -p python3.8 gd_cert_venv
+CERT_TEST_VENV=$ANDROID_BUILD_TOP/out/dist/bluetooth_venv
+
+rm -rf $CERT_TEST_VENV
+
+python3.8 -m virtualenv --python `which python3.8` $CERT_TEST_VENV
 if [[ $? -ne 0 ]] ; then
     echo "Error setting up virtualenv"
-    popd
     return 1
 fi
-popd
 
-# Set up artifacts
-pushd .
-cd $ANDROID_BUILD_TOP/system/bt/gd/gd_cert_venv/lib/python3.8/site-packages
-# Python generated code
-ln -sfT $ANDROID_BUILD_TOP/tools/test/connectivity/acts/framework/acts acts
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test/system/bt/gd/cert cert
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py/system/bt/gd/facade facade
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py/system/bt/gd/hal hal
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py/system/bt/gd/hci hci
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py/system/bt/gd/l2cap l2cap
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py/system/bt/gd/neighbor neighbor
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_generated_py/system/bt/gd/security security
-# Native libraries
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test/out/host/linux-x86/lib64/bluetooth_packets_python3.so bluetooth_packets_python3.so
-# Per systrace, Python only load from python3.8/lib64 directory for plugin imported native libraries
-mkdir -p $ANDROID_BUILD_TOP/system/bt/gd/gd_cert_venv/lib/python3.8/lib64
-cd $ANDROID_BUILD_TOP/system/bt/gd/gd_cert_venv/lib/python3.8/lib64
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test/out/host/linux-x86/lib64/libc++.so libc++.so
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test/out/host/linux-x86/lib64/libbluetooth_gd.so libbluetooth_gd.so
-ln -sfT $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_test/out/host/linux-x86/lib64/libgrpc++_unsecure.so libgrpc++_unsecure.so
-# Binaries
-cd $ANDROID_BUILD_TOP/system/bt/gd/gd_cert_venv/bin
-ln -sfT $ANDROID_BUILD_TOP/out/host/linux-x86/bin/bluetooth_stack_with_facade bluetooth_stack_with_facade
-ln -sfT $ANDROID_BUILD_TOP/out/host/linux-x86/nativetest64/root-canal/root-canal root-canal
-popd
-
-# Activate virtualenv
-pushd .
-source $ANDROID_BUILD_TOP/system/bt/gd/gd_cert_venv/bin/activate
+unzip -o -q $ANDROID_BUILD_TOP/out/dist/bluetooth_cert_tests.zip -d $CERT_TEST_VENV/acts
 if [[ $? -ne 0 ]] ; then
-    echo "Failed to activate virtualenv"
-    deactivate
-    popd
-    return 1
-fi
-popd
-if [[ -z "$ANDROID_BUILD_TOP" ]] ; then
-    echo "Failed to inherit Android build environment"
-    deactivate
+    echo "Error unzipping bluetooth_cert_tests.zip"
     return 1
 fi
 
-## Set up ACTS
-# sudo is no longer needed since we are in a virtual environment
-python3.8 $ANDROID_BUILD_TOP/tools/test/connectivity/acts/framework/setup.py develop
+$CERT_TEST_VENV/bin/python $CERT_TEST_VENV/acts/setup.py install
 if [[ $? -ne 0 ]] ; then
-    echo "ACTS setup failed"
-    deactivate
+    echo "Error installing GD libraries"
     return 1
 fi
 
-pip3 install protobuf
+$CERT_TEST_VENV/bin/python -c "
+import bluetooth_packets_python3 as bp3
+bp3.BaseStruct
+"
 if [[ $? -ne 0 ]] ; then
-    echo "Failed to install protobuf"
-    deactivate
-    return 1
+  echo "Setup failed as bluetooth_packets_python3 cannot be imported"
+  return 1
 fi
 
-deactivate
-
 echo ""
 echo "Please mark GD root directory as \"Project Sources and Headers\" in IDE"
 echo "If still seeing errors, invalidate cached and restart"
diff --git a/gd/cert/truth.py b/gd/cert/truth.py
index c1ea1c0..d1dfb7a 100644
--- a/gd/cert/truth.py
+++ b/gd/cert/truth.py
@@ -14,21 +14,18 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import time
 from datetime import timedelta
 
 from mobly.asserts import assert_true
 from mobly.asserts import assert_false
-
 from mobly import signals
+
 from cert.event_stream import IEventStream
 from cert.event_stream import NOT_FOR_YOU_assert_event_occurs
 from cert.event_stream import NOT_FOR_YOU_assert_all_events_occur
 from cert.event_stream import NOT_FOR_YOU_assert_none_matching
 from cert.event_stream import NOT_FOR_YOU_assert_none
 
-import sys, traceback
-
 
 class ObjectSubject(object):
 
diff --git a/gd/facade/grpc_root_server.cc b/gd/facade/grpc_root_server.cc
index 6a8e5de..7e37b5b 100644
--- a/gd/facade/grpc_root_server.cc
+++ b/gd/facade/grpc_root_server.cc
@@ -32,6 +32,7 @@
 #include "hci/le_advertising_manager.h"
 #include "hci/le_scanning_manager.h"
 #include "l2cap/classic/facade.h"
+#include "l2cap/le/facade.h"
 #include "neighbor/connectability.h"
 #include "neighbor/discoverability.h"
 #include "neighbor/facade/facade.h"
@@ -88,9 +89,11 @@
         break;
       case BluetoothModule::L2CAP:
         modules.add<::bluetooth::hci::facade::ControllerFacadeModule>();
+        modules.add<::bluetooth::hci::facade::LeAdvertisingManagerFacadeModule>();
         modules.add<::bluetooth::neighbor::facade::NeighborFacadeModule>();
         modules.add<::bluetooth::facade::ReadOnlyPropertyServerModule>();
         modules.add<::bluetooth::l2cap::classic::L2capClassicModuleFacadeModule>();
+        modules.add<::bluetooth::l2cap::le::L2capLeModuleFacadeModule>();
         modules.add<::bluetooth::hci::facade::HciLayerFacadeModule>();
         break;
       case BluetoothModule::SECURITY:
diff --git a/gd/hal/cert/simple_hal_test.py b/gd/hal/cert/simple_hal_test.py
index 91f2faf..6b053e2 100644
--- a/gd/hal/cert/simple_hal_test.py
+++ b/gd/hal/cert/simple_hal_test.py
@@ -16,7 +16,7 @@
 
 from datetime import timedelta
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from cert.truth import assertThat
 from google.protobuf import empty_pb2
@@ -26,7 +26,7 @@
 import bluetooth_packets_python3 as bt_packets
 
 
-class SimpleHalTest(GdFacadeOnlyBaseTestClass):
+class SimpleHalTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='HAL', cert_module='HAL')
diff --git a/gd/hci/acl_connection_interface.h b/gd/hci/acl_connection_interface.h
new file mode 100644
index 0000000..ae22e29
--- /dev/null
+++ b/gd/hci/acl_connection_interface.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 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 "common/callback.h"
+#include "hci/hci_packets.h"
+#include "os/utils.h"
+
+namespace bluetooth {
+namespace hci {
+
+class AclConnectionInterface {
+ public:
+  AclConnectionInterface() = default;
+  virtual ~AclConnectionInterface() = default;
+  DISALLOW_COPY_AND_ASSIGN(AclConnectionInterface);
+
+  virtual void EnqueueCommand(std::unique_ptr<ConnectionManagementCommandBuilder> command,
+                              common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) = 0;
+
+  virtual void EnqueueCommand(std::unique_ptr<ConnectionManagementCommandBuilder> command,
+                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) = 0;
+
+  static constexpr EventCode AclConnectionEvents[] = {
+      EventCode::CONNECTION_PACKET_TYPE_CHANGED,
+      EventCode::ROLE_CHANGE,
+      EventCode::CONNECTION_COMPLETE,
+      EventCode::DISCONNECTION_COMPLETE,
+      EventCode::CONNECTION_REQUEST,
+      EventCode::CONNECTION_PACKET_TYPE_CHANGED,
+      EventCode::AUTHENTICATION_COMPLETE,
+      EventCode::READ_CLOCK_OFFSET_COMPLETE,
+      EventCode::MODE_CHANGE,
+      EventCode::QOS_SETUP_COMPLETE,
+      EventCode::ROLE_CHANGE,
+      EventCode::FLOW_SPECIFICATION_COMPLETE,
+      EventCode::FLUSH_OCCURRED,
+      EventCode::READ_REMOTE_SUPPORTED_FEATURES_COMPLETE,
+      EventCode::READ_REMOTE_EXTENDED_FEATURES_COMPLETE,
+      EventCode::READ_REMOTE_VERSION_INFORMATION_COMPLETE,
+      EventCode::ENCRYPTION_CHANGE,
+      EventCode::LINK_SUPERVISION_TIMEOUT_CHANGED,
+  };
+};
+}  // namespace hci
+}  // namespace bluetooth
diff --git a/gd/hci/acl_manager.cc b/gd/hci/acl_manager.cc
index 892bcf3..50cb130 100644
--- a/gd/hci/acl_manager.cc
+++ b/gd/hci/acl_manager.cc
@@ -26,6 +26,7 @@
 #include "hci/acl_fragmenter.h"
 #include "hci/controller.h"
 #include "hci/hci_layer.h"
+#include "security/security_module.h"
 
 namespace bluetooth {
 namespace hci {
@@ -154,7 +155,7 @@
   }
 };
 
-struct AclManager::impl {
+struct AclManager::impl : public security::ISecurityManagerListener {
   impl(const AclManager& acl_manager) : acl_manager_(acl_manager) {}
 
   void Start() {
@@ -212,8 +213,6 @@
     hci_layer_->RegisterEventHandler(EventCode::READ_REMOTE_VERSION_INFORMATION_COMPLETE,
                                      Bind(&impl::on_read_remote_version_information_complete, common::Unretained(this)),
                                      handler_);
-    hci_layer_->RegisterEventHandler(EventCode::ENCRYPTION_CHANGE,
-                                     Bind(&impl::on_encryption_change, common::Unretained(this)), handler_);
     hci_layer_->RegisterEventHandler(EventCode::LINK_SUPERVISION_TIMEOUT_CHANGED,
                                      Bind(&impl::on_link_supervision_timeout_changed, common::Unretained(this)),
                                      handler_);
@@ -237,6 +236,7 @@
     hci_queue_end_ = nullptr;
     handler_ = nullptr;
     hci_layer_ = nullptr;
+    security_manager_.reset();
   }
 
   void incoming_acl_credits(uint16_t handle, uint16_t credits) {
@@ -588,8 +588,11 @@
     }
   }
 
-  void on_encryption_change(EventPacketView packet) {
-    EncryptionChangeView encryption_change_view = EncryptionChangeView::Create(packet);
+  void OnDeviceBonded(bluetooth::hci::AddressWithType device) override {}
+  void OnDeviceUnbonded(bluetooth::hci::AddressWithType device) override {}
+  void OnDeviceBondFailed(bluetooth::hci::AddressWithType device) override {}
+
+  void OnEncryptionStateChanged(EncryptionChangeView encryption_change_view) override {
     if (!encryption_change_view.IsValid()) {
       LOG_ERROR("Received on_encryption_change with invalid packet");
       return;
@@ -1190,6 +1193,11 @@
         handler_);
   }
 
+  void set_security_module(security::SecurityModule* security_module) {
+    security_manager_ = security_module->GetSecurityManager();
+    security_manager_->RegisterCallbackListener(this, handler_);
+  }
+
   void accept_connection(Address address) {
     auto role = AcceptConnectionRequestRole::BECOME_MASTER;  // We prefer to be master
     hci_layer_->EnqueueCommand(AcceptConnectionRequestBuilder::Create(address, role),
@@ -1427,9 +1435,10 @@
   }
 
   void handle_le_connection_update(uint16_t handle, uint16_t conn_interval_min, uint16_t conn_interval_max,
-                                   uint16_t conn_latency, uint16_t supervision_timeout) {
+                                   uint16_t conn_latency, uint16_t supervision_timeout, uint16_t min_ce_length,
+                                   uint16_t max_ce_length) {
     auto packet = LeConnectionUpdateBuilder::Create(handle, conn_interval_min, conn_interval_max, conn_latency,
-                                                    supervision_timeout, kMinimumCeLength, kMaximumCeLength);
+                                                    supervision_timeout, min_ce_length, max_ce_length);
     hci_layer_->EnqueueCommand(std::move(packet), common::BindOnce([](CommandStatusView status) {
                                  ASSERT(status.IsValid());
                                  ASSERT(status.GetCommandOpCode() == OpCode::LE_CREATE_CONNECTION);
@@ -1869,8 +1878,9 @@
   }
 
   bool LeConnectionUpdate(uint16_t handle, uint16_t conn_interval_min, uint16_t conn_interval_max,
-                          uint16_t conn_latency, uint16_t supervision_timeout,
-                          common::OnceCallback<void(ErrorCode)> done_callback, os::Handler* handler) {
+                          uint16_t conn_latency, uint16_t supervision_timeout, uint16_t min_ce_length,
+                          uint16_t max_ce_length, common::OnceCallback<void(ErrorCode)> done_callback,
+                          os::Handler* handler) {
     auto& connection = check_and_get_connection(handle);
     if (connection.is_disconnected_) {
       LOG_INFO("Already disconnected");
@@ -1889,7 +1899,7 @@
       return false;
     }
     handler_->Post(BindOnce(&impl::handle_le_connection_update, common::Unretained(this), handle, conn_interval_min,
-                            conn_interval_max, conn_latency, supervision_timeout));
+                            conn_interval_max, conn_latency, supervision_timeout, min_ce_length, max_ce_length));
     return true;
   }
 
@@ -1913,6 +1923,7 @@
   std::map<uint16_t, acl_connection>::iterator current_connection_pair_;
 
   HciLayer* hci_layer_ = nullptr;
+  std::unique_ptr<security::SecurityManager> security_manager_;
   os::Handler* handler_ = nullptr;
   ConnectionCallbacks* client_callbacks_ = nullptr;
   os::Handler* client_handler_ = nullptr;
@@ -2074,10 +2085,11 @@
 }
 
 bool AclConnection::LeConnectionUpdate(uint16_t conn_interval_min, uint16_t conn_interval_max, uint16_t conn_latency,
-                                       uint16_t supervision_timeout,
+                                       uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length,
                                        common::OnceCallback<void(ErrorCode)> done_callback, os::Handler* handler) {
   return manager_->pimpl_->LeConnectionUpdate(handle_, conn_interval_min, conn_interval_max, conn_latency,
-                                              supervision_timeout, std::move(done_callback), handler);
+                                              supervision_timeout, min_ce_length, max_ce_length,
+                                              std::move(done_callback), handler);
 }
 
 void AclConnection::Finish() {
@@ -2140,6 +2152,10 @@
                               default_link_policy_settings));
 }
 
+void AclManager::SetSecurityModule(security::SecurityModule* security_module) {
+  GetHandler()->Post(BindOnce(&impl::set_security_module, common::Unretained(pimpl_.get()), security_module));
+}
+
 void AclManager::ListDependencies(ModuleList* list) {
   list->add<HciLayer>();
   list->add<Controller>();
diff --git a/gd/hci/acl_manager.h b/gd/hci/acl_manager.h
index 9d24835..38be104 100644
--- a/gd/hci/acl_manager.h
+++ b/gd/hci/acl_manager.h
@@ -28,6 +28,11 @@
 #include "os/handler.h"
 
 namespace bluetooth {
+
+namespace security {
+class SecurityModule;
+}
+
 namespace hci {
 
 class AclManager;
@@ -149,8 +154,8 @@
 
   // LE ACL Method
   virtual bool LeConnectionUpdate(uint16_t conn_interval_min, uint16_t conn_interval_max, uint16_t conn_latency,
-                                  uint16_t supervision_timeout, common::OnceCallback<void(ErrorCode)> done_callback,
-                                  os::Handler* handler);
+                                  uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length,
+                                  common::OnceCallback<void(ErrorCode)> done_callback, os::Handler* handler);
 
   // Ask AclManager to clean me up. Must invoke after on_disconnect is called
   virtual void Finish();
@@ -239,6 +244,9 @@
   virtual void ReadDefaultLinkPolicySettings();
   virtual void WriteDefaultLinkPolicySettings(uint16_t default_link_policy_settings);
 
+  // In order to avoid circular dependency use setter rather than module dependency.
+  virtual void SetSecurityModule(security::SecurityModule* security_module);
+
   static const ModuleFactory Factory;
 
  protected:
diff --git a/gd/hci/acl_manager_test.cc b/gd/hci/acl_manager_test.cc
index 13d156f..4fb71a7 100644
--- a/gd/hci/acl_manager_test.cc
+++ b/gd/hci/acl_manager_test.cc
@@ -588,7 +588,7 @@
   std::promise<ErrorCode> promise;
   auto future = promise.get_future();
   connection->LeConnectionUpdate(
-      0x0006, 0x0C80, 0x0000, 0x000A,
+      0x0006, 0x0C80, 0x0000, 0x000A, 0, 0,
       common::BindOnce([](std::promise<ErrorCode> promise, ErrorCode code) { promise.set_value(code); },
                        std::move(promise)),
       client_handler_);
diff --git a/gd/hci/cert/acl_manager_test.py b/gd/hci/cert/acl_manager_test.py
index abc2dfb..143f701 100644
--- a/gd/hci/cert/acl_manager_test.py
+++ b/gd/hci/cert/acl_manager_test.py
@@ -18,7 +18,7 @@
 import sys
 import logging
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from cert.truth import assertThat
 from google.protobuf import empty_pb2 as empty_proto
@@ -35,12 +35,12 @@
 from cert.py_acl_manager import PyAclManager
 
 
-class AclManagerTest(GdFacadeOnlyBaseTestClass):
+class AclManagerTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='HCI_INTERFACES', cert_module='HCI')
 
-    # todo: move into GdFacadeOnlyBaseTestClass, based on modules inited
+    # todo: move into GdBaseTestClass, based on modules inited
     def setup_test(self):
         super().setup_test()
         self.cert_hci = PyHci(self.cert)
diff --git a/gd/hci/cert/controller_test.py b/gd/hci/cert/controller_test.py
index 7f6ef2e..6e6030f 100644
--- a/gd/hci/cert/controller_test.py
+++ b/gd/hci/cert/controller_test.py
@@ -16,14 +16,14 @@
 
 import time
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.truth import assertThat
 from google.protobuf import empty_pb2 as empty_proto
 from facade import rootservice_pb2 as facade_rootservice
 from hci.facade import controller_facade_pb2 as controller_facade
 
 
-class ControllerTest(GdFacadeOnlyBaseTestClass):
+class ControllerTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(
diff --git a/gd/hci/cert/direct_hci_test.py b/gd/hci/cert/direct_hci_test.py
index 836c076..8c15456 100644
--- a/gd/hci/cert/direct_hci_test.py
+++ b/gd/hci/cert/direct_hci_test.py
@@ -19,7 +19,7 @@
 import sys
 import logging
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from google.protobuf import empty_pb2 as empty_proto
 from facade import rootservice_pb2 as facade_rootservice
@@ -29,7 +29,7 @@
 import bluetooth_packets_python3 as bt_packets
 
 
-class DirectHciTest(GdFacadeOnlyBaseTestClass):
+class DirectHciTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='HCI', cert_module='HAL')
diff --git a/gd/hci/cert/le_acl_manager_test.py b/gd/hci/cert/le_acl_manager_test.py
index 7de7e99..a90a3de 100644
--- a/gd/hci/cert/le_acl_manager_test.py
+++ b/gd/hci/cert/le_acl_manager_test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from google.protobuf import empty_pb2 as empty_proto
 from facade import rootservice_pb2 as facade_rootservice
@@ -26,7 +26,7 @@
 from bluetooth_packets_python3 import hci_packets
 
 
-class LeAclManagerTest(GdFacadeOnlyBaseTestClass):
+class LeAclManagerTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='HCI_INTERFACES', cert_module='HCI')
diff --git a/gd/hci/cert/le_advertising_manager_test.py b/gd/hci/cert/le_advertising_manager_test.py
index 6a35099..722fb10 100644
--- a/gd/hci/cert/le_advertising_manager_test.py
+++ b/gd/hci/cert/le_advertising_manager_test.py
@@ -18,7 +18,7 @@
 import sys
 import logging
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from google.protobuf import empty_pb2 as empty_proto
 from facade import rootservice_pb2 as facade_rootservice
@@ -29,7 +29,7 @@
 from facade import common_pb2 as common
 
 
-class LeAdvertisingManagerTest(GdFacadeOnlyBaseTestClass):
+class LeAdvertisingManagerTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='HCI_INTERFACES', cert_module='HCI')
diff --git a/gd/hci/cert/le_scanning_manager_test.py b/gd/hci/cert/le_scanning_manager_test.py
index 95319a1..41e4790 100644
--- a/gd/hci/cert/le_scanning_manager_test.py
+++ b/gd/hci/cert/le_scanning_manager_test.py
@@ -18,7 +18,7 @@
 import sys
 import logging
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from google.protobuf import empty_pb2 as empty_proto
 from facade import rootservice_pb2 as facade_rootservice
@@ -29,7 +29,7 @@
 from facade import common_pb2 as common
 
 
-class LeScanningManagerTest(GdFacadeOnlyBaseTestClass):
+class LeScanningManagerTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(
diff --git a/gd/hci/hci_layer.cc b/gd/hci/hci_layer.cc
index 462ae01..167cc20 100644
--- a/gd/hci/hci_layer.cc
+++ b/gd/hci/hci_layer.cc
@@ -97,19 +97,52 @@
 }
 }  // namespace
 
-class SecurityInterfaceImpl : public SecurityInterface {
+class AclConnectionManagerInterfaceImpl : public AclConnectionInterface {
  public:
-  SecurityInterfaceImpl(HciLayer& hci) : hci_(hci) {}
-  virtual ~SecurityInterfaceImpl() = default;
+  explicit AclConnectionManagerInterfaceImpl(HciLayer& hci) : hci_(hci) {}
+  ~AclConnectionManagerInterfaceImpl() override = default;
 
-  virtual void EnqueueCommand(std::unique_ptr<SecurityCommandBuilder> command,
-                              common::OnceCallback<void(CommandCompleteView)> on_complete,
-                              os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<ConnectionManagementCommandBuilder> command,
+                      common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_complete), handler);
   }
 
-  virtual void EnqueueCommand(std::unique_ptr<SecurityCommandBuilder> command,
-                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<ConnectionManagementCommandBuilder> command,
+                      common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
+    hci_.EnqueueCommand(std::move(command), std::move(on_status), handler);
+  }
+  HciLayer& hci_;
+};
+
+class SecurityInterfaceImpl : public SecurityInterface {
+ public:
+  explicit SecurityInterfaceImpl(HciLayer& hci) : hci_(hci) {}
+  ~SecurityInterfaceImpl() override = default;
+
+  void EnqueueCommand(std::unique_ptr<SecurityCommandBuilder> command,
+                      common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) override {
+    hci_.EnqueueCommand(std::move(command), std::move(on_complete), handler);
+  }
+
+  void EnqueueCommand(std::unique_ptr<SecurityCommandBuilder> command,
+                      common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
+    hci_.EnqueueCommand(std::move(command), std::move(on_status), handler);
+  }
+  HciLayer& hci_;
+};
+
+class LeAclConnectionManagerInterfaceImpl : public LeAclConnectionInterface {
+ public:
+  explicit LeAclConnectionManagerInterfaceImpl(HciLayer& hci) : hci_(hci) {}
+  ~LeAclConnectionManagerInterfaceImpl() override = default;
+
+  void EnqueueCommand(std::unique_ptr<LeConnectionManagementCommandBuilder> command,
+                      common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) override {
+    hci_.EnqueueCommand(std::move(command), std::move(on_complete), handler);
+  }
+
+  void EnqueueCommand(std::unique_ptr<LeConnectionManagementCommandBuilder> command,
+                      common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_status), handler);
   }
   HciLayer& hci_;
@@ -117,17 +150,16 @@
 
 class LeSecurityInterfaceImpl : public LeSecurityInterface {
  public:
-  LeSecurityInterfaceImpl(HciLayer& hci) : hci_(hci) {}
-  virtual ~LeSecurityInterfaceImpl() = default;
+  explicit LeSecurityInterfaceImpl(HciLayer& hci) : hci_(hci) {}
+  ~LeSecurityInterfaceImpl() override = default;
 
-  virtual void EnqueueCommand(std::unique_ptr<LeSecurityCommandBuilder> command,
-                              common::OnceCallback<void(CommandCompleteView)> on_complete,
-                              os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<LeSecurityCommandBuilder> command,
+                      common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_complete), handler);
   }
 
-  virtual void EnqueueCommand(std::unique_ptr<LeSecurityCommandBuilder> command,
-                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<LeSecurityCommandBuilder> command,
+                      common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_status), handler);
   }
   HciLayer& hci_;
@@ -135,17 +167,16 @@
 
 class LeAdvertisingInterfaceImpl : public LeAdvertisingInterface {
  public:
-  LeAdvertisingInterfaceImpl(HciLayer& hci) : hci_(hci) {}
-  virtual ~LeAdvertisingInterfaceImpl() = default;
+  explicit LeAdvertisingInterfaceImpl(HciLayer& hci) : hci_(hci) {}
+  ~LeAdvertisingInterfaceImpl() override = default;
 
-  virtual void EnqueueCommand(std::unique_ptr<LeAdvertisingCommandBuilder> command,
-                              common::OnceCallback<void(CommandCompleteView)> on_complete,
-                              os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<LeAdvertisingCommandBuilder> command,
+                      common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_complete), handler);
   }
 
-  virtual void EnqueueCommand(std::unique_ptr<LeAdvertisingCommandBuilder> command,
-                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<LeAdvertisingCommandBuilder> command,
+                      common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_status), handler);
   }
   HciLayer& hci_;
@@ -153,17 +184,16 @@
 
 class LeScanningInterfaceImpl : public LeScanningInterface {
  public:
-  LeScanningInterfaceImpl(HciLayer& hci) : hci_(hci) {}
-  virtual ~LeScanningInterfaceImpl() = default;
+  explicit LeScanningInterfaceImpl(HciLayer& hci) : hci_(hci) {}
+  ~LeScanningInterfaceImpl() override = default;
 
-  virtual void EnqueueCommand(std::unique_ptr<LeScanningCommandBuilder> command,
-                              common::OnceCallback<void(CommandCompleteView)> on_complete,
-                              os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<LeScanningCommandBuilder> command,
+                      common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_complete), handler);
   }
 
-  virtual void EnqueueCommand(std::unique_ptr<LeScanningCommandBuilder> command,
-                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
+  void EnqueueCommand(std::unique_ptr<LeScanningCommandBuilder> command,
+                      common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) override {
     hci_.EnqueueCommand(std::move(command), std::move(on_status), handler);
   }
   HciLayer& hci_;
@@ -382,8 +412,7 @@
                                      os::Handler* handler) {
     ASSERT_LOG(event_handlers_.count(event_code) == 0, "Can not register a second handler for event_code %02hhx (%s)",
                event_code, EventCodeText(event_code).c_str());
-    EventHandler to_save(event_handler, handler);
-    event_handlers_[event_code] = to_save;
+    event_handlers_[event_code] = EventHandler(event_handler, handler);
   }
 
   void UnregisterEventHandler(EventCode event_code) {
@@ -406,8 +435,7 @@
     ASSERT_LOG(subevent_handlers_.count(subevent_code) == 0,
                "Can not register a second handler for subevent_code %02hhx (%s)", subevent_code,
                SubeventCodeText(subevent_code).c_str());
-    SubeventHandler to_save(subevent_handler, handler);
-    subevent_handlers_[subevent_code] = to_save;
+    subevent_handlers_[subevent_code] = SubeventHandler(subevent_handler, handler);
   }
 
   void UnregisterLeEventHandler(SubeventCode subevent_code) {
@@ -426,6 +454,8 @@
   HciLayer& module_;
 
   // Interfaces
+  AclConnectionManagerInterfaceImpl acl_connection_manager_interface_{module_};
+  LeAclConnectionManagerInterfaceImpl le_acl_connection_manager_interface_{module_};
   SecurityInterfaceImpl security_interface{module_};
   LeSecurityInterfaceImpl le_security_interface{module_};
   LeAdvertisingInterfaceImpl le_advertising_interface{module_};
@@ -483,6 +513,24 @@
   impl_->UnregisterLeEventHandler(subevent_code);
 }
 
+AclConnectionInterface* HciLayer::GetAclConnectionInterface(common::Callback<void(EventPacketView)> event_handler,
+                                                            common::Callback<void(uint16_t, ErrorCode)> on_disconnect,
+                                                            os::Handler* handler) {
+  for (const auto event : AclConnectionInterface::AclConnectionEvents) {
+    RegisterEventHandler(event, event_handler, handler);
+  }
+  return &impl_->acl_connection_manager_interface_;
+}
+
+LeAclConnectionInterface* HciLayer::GetLeAclConnectionInterface(
+    common::Callback<void(LeMetaEventView)> event_handler, common::Callback<void(uint16_t, ErrorCode)> on_disconnect,
+    os::Handler* handler) {
+  for (const auto event : LeAclConnectionInterface::LeConnectionManagementEvents) {
+    RegisterLeEventHandler(event, event_handler, handler);
+  }
+  return &impl_->le_acl_connection_manager_interface_;
+}
+
 SecurityInterface* HciLayer::GetSecurityInterface(common::Callback<void(EventPacketView)> event_handler,
                                                   os::Handler* handler) {
   for (const auto event : SecurityInterface::SecurityEvents) {
diff --git a/gd/hci/hci_layer.h b/gd/hci/hci_layer.h
index 3e299d4..50686cd 100644
--- a/gd/hci/hci_layer.h
+++ b/gd/hci/hci_layer.h
@@ -24,7 +24,9 @@
 #include "common/bidi_queue.h"
 #include "common/callback.h"
 #include "hal/hci_hal.h"
+#include "hci/acl_connection_interface.h"
 #include "hci/hci_packets.h"
+#include "hci/le_acl_connection_interface.h"
 #include "hci/le_advertising_interface.h"
 #include "hci/le_scanning_interface.h"
 #include "hci/le_security_interface.h"
@@ -64,6 +66,14 @@
   LeSecurityInterface* GetLeSecurityInterface(common::Callback<void(LeMetaEventView)> event_handler,
                                               os::Handler* handler);
 
+  AclConnectionInterface* GetAclConnectionInterface(common::Callback<void(EventPacketView)> event_handler,
+                                                    common::Callback<void(uint16_t, hci::ErrorCode)> on_disconnect,
+                                                    os::Handler* handler);
+
+  LeAclConnectionInterface* GetLeAclConnectionInterface(common::Callback<void(LeMetaEventView)> event_handler,
+                                                        common::Callback<void(uint16_t, hci::ErrorCode)> on_disconnect,
+                                                        os::Handler* handler);
+
   LeAdvertisingInterface* GetLeAdvertisingInterface(common::Callback<void(LeMetaEventView)> event_handler,
                                                     os::Handler* handler);
 
diff --git a/gd/hci/hci_packets.pdl b/gd/hci/hci_packets.pdl
index 3658c98..f37383a 100644
--- a/gd/hci/hci_packets.pdl
+++ b/gd/hci/hci_packets.pdl
@@ -1034,7 +1034,7 @@
   bd_addr : Address,
 }
 
-packet ReadRemoteSupportedFeatures : DiscoveryCommand (op_code = READ_REMOTE_SUPPORTED_FEATURES) {
+packet ReadRemoteSupportedFeatures : ConnectionManagementCommand (op_code = READ_REMOTE_SUPPORTED_FEATURES) {
   connection_handle : 12,
   _reserved_ : 4,
 }
@@ -1042,7 +1042,7 @@
 packet ReadRemoteSupportedFeaturesStatus : CommandStatus (command_op_code = READ_REMOTE_SUPPORTED_FEATURES) {
 }
 
-packet ReadRemoteExtendedFeatures : DiscoveryCommand (op_code = READ_REMOTE_EXTENDED_FEATURES) {
+packet ReadRemoteExtendedFeatures : ConnectionManagementCommand (op_code = READ_REMOTE_EXTENDED_FEATURES) {
   connection_handle : 12,
   _reserved_ : 4,
   page_number : 8,
@@ -1051,7 +1051,7 @@
 packet ReadRemoteExtendedFeaturesStatus : CommandStatus (command_op_code = READ_REMOTE_EXTENDED_FEATURES) {
 }
 
-packet ReadRemoteVersionInformation : DiscoveryCommand (op_code = READ_REMOTE_VERSION_INFORMATION) {
+packet ReadRemoteVersionInformation : ConnectionManagementCommand (op_code = READ_REMOTE_VERSION_INFORMATION) {
   connection_handle : 12,
   _reserved_ : 4,
 }
diff --git a/gd/hci/le_acl_connection_interface.h b/gd/hci/le_acl_connection_interface.h
new file mode 100644
index 0000000..627461e
--- /dev/null
+++ b/gd/hci/le_acl_connection_interface.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 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 "common/callback.h"
+#include "hci/hci_packets.h"
+#include "os/handler.h"
+#include "os/utils.h"
+
+namespace bluetooth {
+namespace hci {
+
+class LeAclConnectionInterface {
+ public:
+  LeAclConnectionInterface() = default;
+  virtual ~LeAclConnectionInterface() = default;
+  DISALLOW_COPY_AND_ASSIGN(LeAclConnectionInterface);
+
+  virtual void EnqueueCommand(std::unique_ptr<LeConnectionManagementCommandBuilder> command,
+                              common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) = 0;
+
+  virtual void EnqueueCommand(std::unique_ptr<LeConnectionManagementCommandBuilder> command,
+                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) = 0;
+
+  static constexpr SubeventCode LeConnectionManagementEvents[] = {
+      SubeventCode::CONNECTION_COMPLETE,
+      SubeventCode::ENHANCED_CONNECTION_COMPLETE,
+      SubeventCode::CONNECTION_UPDATE_COMPLETE,
+  };
+};
+}  // namespace hci
+}  // namespace bluetooth
diff --git a/gd/hci/security_interface.h b/gd/hci/security_interface.h
index 1258122..0d945b0 100644
--- a/gd/hci/security_interface.h
+++ b/gd/hci/security_interface.h
@@ -36,6 +36,7 @@
                               common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) = 0;
 
   static constexpr hci::EventCode SecurityEvents[] = {
+      hci::EventCode::ENCRYPTION_CHANGE,
       hci::EventCode::CHANGE_CONNECTION_LINK_KEY_COMPLETE,
       hci::EventCode::MASTER_LINK_KEY_COMPLETE,
       hci::EventCode::RETURN_LINK_KEYS,
diff --git a/gd/l2cap/Android.bp b/gd/l2cap/Android.bp
index c04a9d3..ff68c10 100644
--- a/gd/l2cap/Android.bp
+++ b/gd/l2cap/Android.bp
@@ -70,6 +70,7 @@
     name: "BluetoothFacade_l2cap_layer",
     srcs: [
         "classic/facade.cc",
+        "le/facade.cc",
     ],
 }
 
diff --git a/gd/l2cap/classic/cert/l2cap_test.py b/gd/l2cap/classic/cert/l2cap_test.py
index 30b8466..06f5296 100644
--- a/gd/l2cap/classic/cert/l2cap_test.py
+++ b/gd/l2cap/classic/cert/l2cap_test.py
@@ -17,7 +17,7 @@
 from datetime import timedelta
 from mobly import asserts
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from cert.truth import assertThat
 from cert.closable import safeClose
@@ -44,7 +44,7 @@
 SAMPLE_PACKET = l2cap_packets.CommandRejectNotUnderstoodBuilder(1)
 
 
-class L2capTest(GdFacadeOnlyBaseTestClass):
+class L2capTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='L2CAP', cert_module='HCI_INTERFACES')
@@ -63,6 +63,7 @@
 
     def teardown_test(self):
         self.cert_l2cap.close()
+        self.dut_l2cap.close()
         super().teardown_test()
 
     def cert_send_b_frame(self, b_frame):
@@ -105,17 +106,6 @@
         dut_channel.send(b'abc')
         assertThat(cert_channel).emits(L2capMatchers.Data(b'abc'))
 
-    def test_fixed_channel(self):
-        self._setup_link_from_cert()
-
-        self.dut.l2cap.RegisterChannel(
-            l2cap_facade_pb2.RegisterChannelRequest(channel=2))
-        asserts.skip("FIXME: Not working")
-        self.dut.l2cap.SendL2capPacket(
-            l2cap_facade_pb2.L2capPacket(channel=2, payload=b"123"))
-
-        assertThat(self.cert_channel).emits(L2capMatchers.PartialData(b'123'))
-
     def test_receive_packet_from_unknown_channel(self):
         self._setup_link_from_cert()
 
@@ -610,6 +600,57 @@
             L2capMatchers.IFrame(tx_seq=0, payload=b'abc'),
             L2capMatchers.IFrame(tx_seq=1, payload=b'abc')).inOrder()
 
+    def test_respond_to_srej_p_set(self):
+        """
+        L2CAP/ERM/BV-14-C [Respond to S-Frame [SREJ] POLL Bit Set]
+        """
+        self._setup_link_from_cert()
+        self.cert_l2cap.turn_on_ertm(tx_window_size=3, max_transmit=2)
+
+        (dut_channel, cert_channel) = self._open_channel(
+            scid=0x41, psm=0x33, use_ertm=True)
+
+        for _ in range(4):
+            dut_channel.send(b'abc')
+        assertThat(cert_channel).emits(
+            L2capMatchers.IFrame(tx_seq=0, payload=b'abc'),
+            L2capMatchers.IFrame(tx_seq=1, payload=b'abc'),
+            L2capMatchers.IFrame(tx_seq=2, payload=b'abc')).inOrder()
+
+        cert_channel.send_s_frame(
+            req_seq=1, p=Poll.POLL, s=SupervisoryFunction.SELECT_REJECT)
+
+        assertThat(cert_channel).emits(
+            L2capMatchers.IFrame(
+                tx_seq=1, payload=b'abc', f=Final.POLL_RESPONSE),
+            L2capMatchers.IFrame(tx_seq=3, payload=b'abc')).inOrder()
+
+    def test_respond_to_srej_p_clear(self):
+        """
+        L2CAP/ERM/BV-15-C [Respond to S-Frame [SREJ] POLL Bit Clear]
+        """
+        self._setup_link_from_cert()
+        self.cert_l2cap.turn_on_ertm(tx_window_size=3, max_transmit=2)
+
+        (dut_channel, cert_channel) = self._open_channel(
+            scid=0x41, psm=0x33, use_ertm=True)
+
+        for _ in range(4):
+            dut_channel.send(b'abc')
+        assertThat(cert_channel).emits(
+            L2capMatchers.IFrame(tx_seq=0, payload=b'abc'),
+            L2capMatchers.IFrame(tx_seq=1, payload=b'abc'),
+            L2capMatchers.IFrame(tx_seq=2, payload=b'abc')).inOrder()
+
+        cert_channel.send_s_frame(
+            req_seq=1, s=SupervisoryFunction.SELECT_REJECT)
+        assertThat(cert_channel).emits(
+            L2capMatchers.IFrame(tx_seq=1, payload=b'abc', f=Final.NOT_SET))
+        cert_channel.send_s_frame(
+            req_seq=3, s=SupervisoryFunction.RECEIVER_READY)
+        assertThat(cert_channel).emits(
+            L2capMatchers.IFrame(tx_seq=3, payload=b'abc', f=Final.NOT_SET))
+
     def test_receive_s_frame_rr_final_bit_set(self):
         """
         L2CAP/ERM/BV-18-C [Receive S-Frame [RR] Final Bit = 1]
diff --git a/gd/l2cap/classic/facade.cc b/gd/l2cap/classic/facade.cc
index 64e0068..35a518f 100644
--- a/gd/l2cap/classic/facade.cc
+++ b/gd/l2cap/classic/facade.cc
@@ -25,7 +25,6 @@
 #include "l2cap/classic/facade.grpc.pb.h"
 #include "l2cap/classic/facade.h"
 #include "l2cap/classic/l2cap_classic_module.h"
-#include "l2cap/l2cap_packets.h"
 #include "os/log.h"
 #include "packet/raw_builder.h"
 
@@ -57,20 +56,6 @@
     return pending_connection_close_.RunLoop(context, writer);
   }
 
-  ::grpc::Status SendL2capPacket(::grpc::ServerContext* context, const classic::L2capPacket* request,
-                                 SendL2capPacketResult* response) override {
-    std::unique_lock<std::mutex> lock(channel_map_mutex_);
-    if (fixed_channel_helper_map_.find(request->channel()) == fixed_channel_helper_map_.end()) {
-      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not registered");
-    }
-    std::vector<uint8_t> packet(request->payload().begin(), request->payload().end());
-    if (!fixed_channel_helper_map_[request->channel()]->SendPacket(packet)) {
-      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not open");
-    }
-    response->set_result_type(SendL2capPacketResultType::OK);
-    return ::grpc::Status::OK;
-  }
-
   ::grpc::Status SendDynamicChannelPacket(::grpc::ServerContext* context, const DynamicChannelPacket* request,
                                           ::google::protobuf::Empty* response) override {
     std::unique_lock<std::mutex> lock(channel_map_mutex_);
@@ -116,103 +101,6 @@
     return status;
   }
 
-  ::grpc::Status RegisterChannel(::grpc::ServerContext* context, const classic::RegisterChannelRequest* request,
-                                 ::google::protobuf::Empty* response) override {
-    std::unique_lock<std::mutex> lock(channel_map_mutex_);
-    if (fixed_channel_helper_map_.find(request->channel()) != fixed_channel_helper_map_.end()) {
-      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Already registered");
-    }
-    fixed_channel_helper_map_.emplace(request->channel(), std::make_unique<L2capFixedChannelHelper>(
-                                                              this, l2cap_layer_, facade_handler_, request->channel()));
-
-    return ::grpc::Status::OK;
-  }
-
-  class L2capFixedChannelHelper {
-   public:
-    L2capFixedChannelHelper(L2capClassicModuleFacadeService* service, L2capClassicModule* l2cap_layer,
-                            os::Handler* handler, Cid cid)
-        : facade_service_(service), l2cap_layer_(l2cap_layer), handler_(handler), cid_(cid) {
-      fixed_channel_manager_ = l2cap_layer_->GetFixedChannelManager();
-      fixed_channel_manager_->RegisterService(
-          cid, {},
-          common::BindOnce(&L2capFixedChannelHelper::on_l2cap_service_registration_complete, common::Unretained(this)),
-          common::Bind(&L2capFixedChannelHelper::on_connection_open, common::Unretained(this)), handler_);
-    }
-
-    void on_l2cap_service_registration_complete(FixedChannelManager::RegistrationResult registration_result,
-                                                std::unique_ptr<FixedChannelService> service) {
-      service_ = std::move(service);
-    }
-
-    void on_connection_open(std::unique_ptr<FixedChannel> channel) {
-      ConnectionCompleteEvent event;
-      event.mutable_remote()->set_address(channel->GetDevice().ToString());
-      facade_service_->pending_connection_complete_.OnIncomingEvent(event);
-      channel_ = std::move(channel);
-      channel_->RegisterOnCloseCallback(
-          facade_service_->facade_handler_,
-          common::BindOnce(&L2capFixedChannelHelper::on_close_callback, common::Unretained(this)));
-      {
-        std::unique_lock<std::mutex> lock(facade_service_->channel_map_mutex_);
-        if (facade_service_->fetch_l2cap_data_) {
-          channel_->GetQueueUpEnd()->RegisterDequeue(
-              facade_service_->facade_handler_,
-              common::Bind(&L2capFixedChannelHelper::on_incoming_packet, common::Unretained(this)));
-        }
-      }
-    }
-
-    bool SendPacket(const std::vector<uint8_t>& packet) {
-      if (channel_ == nullptr) {
-        LOG_WARN("Channel is not open");
-        return false;
-      }
-      std::unique_lock<std::mutex> lock(facade_service_->channel_map_mutex_);
-      channel_->GetQueueUpEnd()->RegisterEnqueue(
-          handler_, common::Bind(&L2capFixedChannelHelper::enqueue_callback, common::Unretained(this), packet));
-      return true;
-    }
-
-    void on_close_callback(hci::ErrorCode error_code) {
-      {
-        std::unique_lock<std::mutex> lock(facade_service_->channel_map_mutex_);
-        if (facade_service_->fetch_l2cap_data_) {
-          channel_->GetQueueUpEnd()->UnregisterDequeue();
-        }
-      }
-      channel_ = nullptr;
-      classic::ConnectionCloseEvent event;
-      event.mutable_remote()->set_address(channel_->GetDevice().ToString());
-      event.set_reason(static_cast<uint32_t>(error_code));
-      facade_service_->pending_connection_close_.OnIncomingEvent(event);
-    }
-
-    void on_incoming_packet() {
-      auto packet = channel_->GetQueueUpEnd()->TryDequeue();
-      std::string data = std::string(packet->begin(), packet->end());
-      L2capPacket l2cap_data;
-      l2cap_data.set_channel(cid_);
-      l2cap_data.set_payload(data);
-      facade_service_->pending_l2cap_data_.OnIncomingEvent(l2cap_data);
-    }
-
-    std::unique_ptr<packet::BasePacketBuilder> enqueue_callback(const std::vector<uint8_t>& packet) {
-      auto packet_one = std::make_unique<packet::RawBuilder>();
-      packet_one->AddOctets(packet);
-      channel_->GetQueueUpEnd()->UnregisterEnqueue();
-      return packet_one;
-    };
-
-    L2capClassicModuleFacadeService* facade_service_;
-    L2capClassicModule* l2cap_layer_;
-    os::Handler* handler_;
-    std::unique_ptr<FixedChannelManager> fixed_channel_manager_;
-    std::unique_ptr<FixedChannelService> service_;
-    std::unique_ptr<FixedChannel> channel_ = nullptr;
-    Cid cid_;
-  };
-
   ::grpc::Status SetDynamicChannel(::grpc::ServerContext* context, const SetEnableDynamicChannelRequest* request,
                                    google::protobuf::Empty* response) override {
     dynamic_channel_helper_map_.emplace(
@@ -266,7 +154,7 @@
     // invoked from Facade Handler
     void on_connection_open(std::unique_ptr<DynamicChannel> channel) {
       ConnectionCompleteEvent event;
-      event.mutable_remote()->set_address(channel->GetDevice().ToString());
+      event.mutable_remote()->set_address(channel->GetDevice().GetAddress().ToString());
       facade_service_->pending_connection_complete_.OnIncomingEvent(event);
       {
         std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
@@ -287,7 +175,7 @@
         channel_->GetQueueUpEnd()->UnregisterDequeue();
       }
       classic::ConnectionCloseEvent event;
-      event.mutable_remote()->set_address(channel_->GetDevice().ToString());
+      event.mutable_remote()->set_address(channel_->GetDevice().GetAddress().ToString());
       event.set_reason(static_cast<uint32_t>(error_code));
       facade_service_->pending_connection_close_.OnIncomingEvent(event);
       channel_ = nullptr;
@@ -348,7 +236,6 @@
   L2capClassicModule* l2cap_layer_;
   ::bluetooth::os::Handler* facade_handler_;
   std::mutex channel_map_mutex_;
-  std::map<Cid, std::unique_ptr<L2capFixedChannelHelper>> fixed_channel_helper_map_;
   std::map<Psm, std::unique_ptr<L2capDynamicChannelHelper>> dynamic_channel_helper_map_;
   bool fetch_l2cap_data_ = false;
   ::bluetooth::grpc::GrpcEventQueue<classic::ConnectionCompleteEvent> pending_connection_complete_{
diff --git a/gd/l2cap/classic/facade.proto b/gd/l2cap/classic/facade.proto
index 758f568..851d5ea 100644
--- a/gd/l2cap/classic/facade.proto
+++ b/gd/l2cap/classic/facade.proto
@@ -6,9 +6,6 @@
 import "facade/common.proto";
 
 service L2capClassicModuleFacade {
-  rpc RegisterChannel(RegisterChannelRequest) returns (google.protobuf.Empty) {
-    // Testing Android Bluetooth stack only. Optional for other stack.
-  }
   rpc FetchConnectionComplete(google.protobuf.Empty) returns (stream ConnectionCompleteEvent) {
     // Testing Android Bluetooth stack only. Optional for other stack.
   }
@@ -17,7 +14,6 @@
   }
   rpc OpenChannel(OpenChannelRequest) returns (google.protobuf.Empty) {}
   rpc CloseChannel(CloseChannelRequest) returns (google.protobuf.Empty) {}
-  rpc SendL2capPacket(L2capPacket) returns (SendL2capPacketResult) {}
   rpc FetchL2capData(google.protobuf.Empty) returns (stream L2capPacket) {}
   rpc SetDynamicChannel(SetEnableDynamicChannelRequest) returns (google.protobuf.Empty) {}
   rpc SendDynamicChannelPacket(DynamicChannelPacket) returns (google.protobuf.Empty) {}
diff --git a/gd/l2cap/classic/internal/signalling_manager.cc b/gd/l2cap/classic/internal/signalling_manager.cc
index 8012db3..9193270 100644
--- a/gd/l2cap/classic/internal/signalling_manager.cc
+++ b/gd/l2cap/classic/internal/signalling_manager.cc
@@ -58,8 +58,7 @@
 }
 
 void ClassicSignallingManager::OnCommandReject(CommandRejectView command_reject_view) {
-  if (command_just_sent_.signal_id_ != command_reject_view.GetIdentifier() ||
-      command_just_sent_.command_code_ != command_reject_view.GetCode()) {
+  if (command_just_sent_.signal_id_ != command_reject_view.GetIdentifier()) {
     LOG_WARN("Unexpected command reject: no pending request");
     return;
   }
diff --git a/gd/l2cap/dynamic_channel.cc b/gd/l2cap/dynamic_channel.cc
index f8b64e6..da599a9 100644
--- a/gd/l2cap/dynamic_channel.cc
+++ b/gd/l2cap/dynamic_channel.cc
@@ -21,7 +21,7 @@
 namespace bluetooth {
 namespace l2cap {
 
-hci::Address DynamicChannel::GetDevice() const {
+hci::AddressWithType DynamicChannel::GetDevice() const {
   return impl_->GetDevice();
 }
 
diff --git a/gd/l2cap/dynamic_channel.h b/gd/l2cap/dynamic_channel.h
index b7496f8..745fa37 100644
--- a/gd/l2cap/dynamic_channel.h
+++ b/gd/l2cap/dynamic_channel.h
@@ -42,7 +42,7 @@
     ASSERT(l2cap_handler_ != nullptr);
   }
 
-  hci::Address GetDevice() const;
+  hci::AddressWithType GetDevice() const;
 
   /**
    * Register close callback. If close callback is registered, when a channel is closed, the channel's resource will
diff --git a/gd/l2cap/internal/dynamic_channel_impl.cc b/gd/l2cap/internal/dynamic_channel_impl.cc
index 947a32f..3a0918a 100644
--- a/gd/l2cap/internal/dynamic_channel_impl.cc
+++ b/gd/l2cap/internal/dynamic_channel_impl.cc
@@ -40,8 +40,8 @@
   ASSERT(l2cap_handler_ != nullptr);
 }
 
-hci::Address DynamicChannelImpl::GetDevice() const {
-  return device_.GetAddress();
+hci::AddressWithType DynamicChannelImpl::GetDevice() const {
+  return device_;
 }
 
 void DynamicChannelImpl::RegisterOnCloseCallback(os::Handler* user_handler,
diff --git a/gd/l2cap/internal/dynamic_channel_impl.h b/gd/l2cap/internal/dynamic_channel_impl.h
index 856e21d..c980012 100644
--- a/gd/l2cap/internal/dynamic_channel_impl.h
+++ b/gd/l2cap/internal/dynamic_channel_impl.h
@@ -38,7 +38,7 @@
 
   virtual ~DynamicChannelImpl() = default;
 
-  hci::Address GetDevice() const;
+  hci::AddressWithType GetDevice() const;
 
   virtual void RegisterOnCloseCallback(os::Handler* user_handler, DynamicChannel::OnCloseCallback on_close_callback);
 
diff --git a/gd/l2cap/internal/dynamic_channel_impl_test.cc b/gd/l2cap/internal/dynamic_channel_impl_test.cc
index 677b504..ee38430 100644
--- a/gd/l2cap/internal/dynamic_channel_impl_test.cc
+++ b/gd/l2cap/internal/dynamic_channel_impl_test.cc
@@ -65,7 +65,7 @@
   EXPECT_CALL(mock_classic_link, GetDevice()).WillRepeatedly(Return(device));
   DynamicChannelImpl dynamic_channel_impl(0x01, kFirstDynamicChannel, kFirstDynamicChannel, &mock_classic_link,
                                           l2cap_handler_);
-  EXPECT_EQ(device.GetAddress(), dynamic_channel_impl.GetDevice());
+  EXPECT_EQ(device, dynamic_channel_impl.GetDevice());
 }
 
 TEST_F(L2capClassicDynamicChannelImplTest, close_triggers_callback) {
diff --git a/gd/l2cap/internal/enhanced_retransmission_mode_channel_data_controller.cc b/gd/l2cap/internal/enhanced_retransmission_mode_channel_data_controller.cc
index dc98836..ca1160a 100644
--- a/gd/l2cap/internal/enhanced_retransmission_mode_channel_data_controller.cc
+++ b/gd/l2cap/internal/enhanced_retransmission_mode_channel_data_controller.cc
@@ -411,6 +411,7 @@
         remote_busy_ = false;
         pass_to_tx(req_seq, f);
         retransmit_requested_i_frame(req_seq, p);
+        send_pending_i_frames();
         if (p_bit_outstanding()) {
           srej_actioned_ = true;
           srej_save_req_seq_ = req_seq;
diff --git a/gd/l2cap/internal/le_credit_based_channel_data_controller.cc b/gd/l2cap/internal/le_credit_based_channel_data_controller.cc
index 943cb17..6769763 100644
--- a/gd/l2cap/internal/le_credit_based_channel_data_controller.cc
+++ b/gd/l2cap/internal/le_credit_based_channel_data_controller.cc
@@ -102,6 +102,7 @@
 }
 
 std::unique_ptr<packet::BasePacketBuilder> LeCreditBasedDataController::GetNextPacket() {
+  ASSERT(!pdu_queue_.empty());
   auto next = std::move(pdu_queue_.front());
   pdu_queue_.pop();
   return next;
@@ -123,6 +124,7 @@
   credits_ = total_credits;
   if (pending_frames_count_ > 0 && credits_ >= pending_frames_count_) {
     scheduler_->OnPacketsReady(cid_, pending_frames_count_);
+    pending_frames_count_ = 0;
     credits_ -= pending_frames_count_;
   } else if (pending_frames_count_ > 0) {
     scheduler_->OnPacketsReady(cid_, credits_);
diff --git a/gd/l2cap/l2cap_packet_test.cc b/gd/l2cap/l2cap_packet_test.cc
index 33752e0..7a413f4 100644
--- a/gd/l2cap/l2cap_packet_test.cc
+++ b/gd/l2cap/l2cap_packet_test.cc
@@ -141,5 +141,15 @@
   RunConfigurationRequestReflectionFuzzTest(bluetooth_gd_fuzz_test_5747922062802944,
                                             sizeof(bluetooth_gd_fuzz_test_5747922062802944));
 }
+
+TEST(L2capFuzzRegressions, ConfigurationRequestFuzz_5202709231697920) {
+  uint8_t bluetooth_gd_fuzz_test_5747922062802944[] = {
+      0x04, 0x01, 0x45, 0x45, 0x05, 0x01, 0x01, 0x45, 0x05, 0x01,
+  };
+
+  RunConfigurationRequestReflectionFuzzTest(bluetooth_gd_fuzz_test_5747922062802944,
+                                            sizeof(bluetooth_gd_fuzz_test_5747922062802944));
+}
+
 }  // namespace l2cap
 }  // namespace bluetooth
diff --git a/gd/l2cap/le/cert/cert_le_l2cap.py b/gd/l2cap/le/cert/cert_le_l2cap.py
new file mode 100644
index 0000000..63142c9
--- /dev/null
+++ b/gd/l2cap/le/cert/cert_le_l2cap.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - 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.
+
+from cert.closable import Closable
+from cert.closable import safeClose
+from cert.py_le_acl_manager import PyLeAclManager
+from cert.truth import assertThat
+import bluetooth_packets_python3 as bt_packets
+from bluetooth_packets_python3 import l2cap_packets
+from bluetooth_packets_python3.l2cap_packets import LeCommandCode
+from bluetooth_packets_python3.l2cap_packets import LeCreditBasedConnectionResponseResult
+from cert.event_stream import FilteringEventStream
+from cert.event_stream import IEventStream
+from cert.matchers import L2capMatchers
+from cert.captures import L2capCaptures
+
+
+class CertLeL2capChannel(IEventStream):
+
+    def __init__(self, device, scid, dcid, acl_stream, acl, control_channel):
+        self._device = device
+        self._scid = scid
+        self._dcid = dcid
+        self._acl_stream = acl_stream
+        self._acl = acl
+        self._control_channel = control_channel
+        self._our_acl_view = FilteringEventStream(
+            acl_stream, L2capMatchers.ExtractBasicFrame(scid))
+
+    def get_event_queue(self):
+        return self._our_acl_view.get_event_queue()
+
+    def send(self, packet):
+        frame = l2cap_packets.BasicFrameBuilder(self._dcid, packet)
+        self._acl.send(frame.Serialize())
+
+    def send_first_le_i_frame(self, sdu_size, packet):
+        frame = l2cap_packets.FirstLeInformationFrameBuilder(
+            self._dcid, sdu_size, packet)
+        self._acl.send(frame.Serialize())
+
+    def disconnect_and_verify(self):
+        assertThat(self._scid).isNotEqualTo(1)
+        self._control_channel.send(
+            l2cap_packets.DisconnectionRequestBuilder(1, self._dcid,
+                                                      self._scid))
+
+        assertThat(self._control_channel).emits(
+            L2capMatchers.LeDisconnectionResponse(self._scid, self._dcid))
+
+    def verify_disconnect_request(self):
+        assertThat(self._control_channel).emits(
+            L2capMatchers.LeDisconnectionRequest(self._dcid, self._scid))
+
+    def send_credits(self, num_credits):
+        self._control_channel.send(
+            l2cap_packets.LeFlowControlCreditBuilder(2, self._scid,
+                                                     num_credits))
+
+
+class CertLeL2cap(Closable):
+
+    def __init__(self, device):
+        self._device = device
+        self._le_acl_manager = PyLeAclManager(device)
+        self._le_acl = None
+
+        self.control_table = {
+            LeCommandCode.DISCONNECTION_REQUEST:
+            self._on_disconnection_request_default,
+            LeCommandCode.DISCONNECTION_RESPONSE:
+            self._on_disconnection_response_default,
+        }
+
+        self.scid_to_dcid = {}
+
+    def close(self):
+        self._le_acl_manager.close()
+        safeClose(self._le_acl)
+
+    def connect_le_acl(self, remote_addr):
+        self._le_acl = self._le_acl_manager.initiate_connection(remote_addr)
+        self._le_acl.wait_for_connection_complete()
+        self.control_channel = CertLeL2capChannel(
+            self._device,
+            5,
+            5,
+            self._get_acl_stream(),
+            self._le_acl,
+            control_channel=None)
+        self._get_acl_stream().register_callback(self._handle_control_packet)
+
+    def open_channel(self,
+                     signal_id,
+                     psm,
+                     scid,
+                     mtu=1000,
+                     mps=100,
+                     initial_credit=6):
+        self.control_channel.send(
+            l2cap_packets.LeCreditBasedConnectionRequestBuilder(
+                signal_id, psm, scid, mtu, mps, initial_credit))
+
+        response = L2capCaptures.CreditBasedConnectionResponse(scid)
+        assertThat(self.control_channel).emits(response)
+        return CertLeL2capChannel(self._device, scid,
+                                  response.get().GetDestinationCid(),
+                                  self._get_acl_stream(), self._le_acl,
+                                  self.control_channel)
+
+    def verify_and_respond_open_channel_from_remote(
+            self, psm=0x33,
+            result=LeCreditBasedConnectionResponseResult.SUCCESS):
+        request = L2capCaptures.CreditBasedConnectionRequest(psm)
+        assertThat(self.control_channel).emits(request)
+        (scid, dcid) = self._respond_connection_request_default(
+            request.get(), result)
+        return CertLeL2capChannel(self._device, scid, dcid,
+                                  self._get_acl_stream(), self._le_acl,
+                                  self.control_channel)
+
+    def verify_and_reject_open_channel_from_remote(self, psm=0x33):
+        request = L2capCaptures.CreditBasedConnectionRequest(psm)
+        assertThat(self.control_channel).emits(request)
+        sid = request.get().GetIdentifier()
+        reject = l2cap_packets.LeCommandRejectNotUnderstoodBuilder(sid)
+        self.control_channel.send(reject)
+
+    def _respond_connection_request_default(
+            self, request,
+            result=LeCreditBasedConnectionResponseResult.SUCCESS):
+        sid = request.GetIdentifier()
+        their_scid = request.GetSourceCid()
+        mtu = request.GetMtu()
+        mps = request.GetMps()
+        initial_credits = request.GetInitialCredits()
+        # Here we use the same value - their scid as their scid
+        our_scid = their_scid
+        our_dcid = their_scid
+        response = l2cap_packets.LeCreditBasedConnectionResponseBuilder(
+            sid, our_scid, mtu, mps, initial_credits, result)
+        self.control_channel.send(response)
+        return (our_scid, our_dcid)
+
+    # prefer to use channel abstraction instead, if at all possible
+    def send_acl(self, packet):
+        self._acl.send(packet.Serialize())
+
+    def get_control_channel(self):
+        return self.control_channel
+
+    def _get_acl_stream(self):
+        return self._le_acl_manager.get_le_acl_stream()
+
+    def _on_disconnection_request_default(self, request):
+        disconnection_request = l2cap_packets.LeDisconnectionRequestView(
+            request)
+        sid = disconnection_request.GetIdentifier()
+        scid = disconnection_request.GetSourceCid()
+        dcid = disconnection_request.GetDestinationCid()
+        response = l2cap_packets.LeDisconnectionResponseBuilder(sid, dcid, scid)
+        self.control_channel.send(response)
+
+    def _on_disconnection_response_default(self, request):
+        disconnection_response = l2cap_packets.LeDisconnectionResponseView(
+            request)
+
+    def _handle_control_packet(self, l2cap_packet):
+        packet_bytes = l2cap_packet.payload
+        l2cap_view = l2cap_packets.BasicFrameView(
+            bt_packets.PacketViewLittleEndian(list(packet_bytes)))
+        if l2cap_view.GetChannelId() != 5:
+            return
+        request = l2cap_packets.LeControlView(l2cap_view.GetPayload())
+        fn = self.control_table.get(request.GetCode())
+        if fn is not None:
+            fn(request)
+        return
diff --git a/gd/l2cap/le/cert/le_l2cap_test.py b/gd/l2cap/le/cert/le_l2cap_test.py
new file mode 100644
index 0000000..f742681
--- /dev/null
+++ b/gd/l2cap/le/cert/le_l2cap_test.py
@@ -0,0 +1,315 @@
+#
+#   Copyright 2020 - 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.
+
+import time
+from datetime import timedelta
+from mobly import asserts
+
+from cert.gd_base_test import GdBaseTestClass
+from cert.event_stream import EventStream
+from cert.truth import assertThat
+from cert.closable import safeClose
+from cert.py_l2cap import PyLeL2cap
+from cert.py_acl_manager import PyAclManager
+from cert.matchers import L2capMatchers
+from facade import common_pb2 as common
+from facade import rootservice_pb2 as facade_rootservice
+from google.protobuf import empty_pb2 as empty_proto
+from l2cap.le import facade_pb2 as l2cap_facade_pb2
+from neighbor.facade import facade_pb2 as neighbor_facade
+from hci.facade import acl_manager_facade_pb2 as acl_manager_facade
+from hci.facade import le_advertising_manager_facade_pb2 as le_advertising_facade
+import bluetooth_packets_python3 as bt_packets
+from bluetooth_packets_python3 import hci_packets, l2cap_packets
+from bluetooth_packets_python3.l2cap_packets import LeCreditBasedConnectionResponseResult
+from l2cap.le.cert.cert_le_l2cap import CertLeL2cap
+
+# Assemble a sample packet. TODO: Use RawBuilder
+SAMPLE_PACKET = l2cap_packets.CommandRejectNotUnderstoodBuilder(1)
+
+
+class LeL2capTest(GdBaseTestClass):
+
+    def setup_class(self):
+        super().setup_class(dut_module='L2CAP', cert_module='HCI_INTERFACES')
+
+    def setup_test(self):
+        super().setup_test()
+
+        self.dut.address = self.dut.hci_controller.GetMacAddressSimple()
+        self.cert.address = self.cert.controller_read_only_property.ReadLocalAddress(
+            empty_proto.Empty()).address
+        self.cert_address = common.BluetoothAddress(address=self.cert.address)
+
+        self.dut_l2cap = PyLeL2cap(self.dut)
+        self.cert_l2cap = CertLeL2cap(self.cert)
+
+    def teardown_test(self):
+        self.cert_l2cap.close()
+        self.dut_l2cap.close()
+        super().teardown_test()
+
+    def _setup_link_from_cert(self):
+        # DUT Advertises
+        gap_name = hci_packets.GapData()
+        gap_name.data_type = hci_packets.GapDataType.COMPLETE_LOCAL_NAME
+        gap_name.data = list(bytes(b'Im_The_DUT'))
+        gap_data = le_advertising_facade.GapDataMsg(
+            data=bytes(gap_name.Serialize()))
+        config = le_advertising_facade.AdvertisingConfig(
+            advertisement=[gap_data],
+            random_address=common.BluetoothAddress(
+                address=bytes(b'0D:05:04:03:02:01')),
+            interval_min=512,
+            interval_max=768,
+            event_type=le_advertising_facade.AdvertisingEventType.ADV_IND,
+            address_type=common.RANDOM_DEVICE_ADDRESS,
+            peer_address_type=common.PUBLIC_DEVICE_OR_IDENTITY_ADDRESS,
+            peer_address=common.BluetoothAddress(
+                address=bytes(b'A6:A5:A4:A3:A2:A1')),
+            channel_map=7,
+            filter_policy=le_advertising_facade.AdvertisingFilterPolicy.
+            ALL_DEVICES)
+        request = le_advertising_facade.CreateAdvertiserRequest(config=config)
+        create_response = self.dut.hci_le_advertising_manager.CreateAdvertiser(
+            request)
+        self.cert_l2cap.connect_le_acl(bytes(b'0D:05:04:03:02:01'))
+
+    def _open_channel_from_cert(self,
+                                signal_id=1,
+                                scid=0x0101,
+                                psm=0x33,
+                                mtu=1000,
+                                mps=100,
+                                initial_credit=6):
+
+        dut_channel = self.dut_l2cap.register_coc(psm)
+        cert_channel = self.cert_l2cap.open_channel(signal_id, psm, scid, mtu,
+                                                    mps, initial_credit)
+
+        return (dut_channel, cert_channel)
+
+    def _open_channel_from_dut(self, psm=0x33):
+        response_future = self.dut_l2cap.connect_coc_to_cert(psm)
+        cert_channel = self.cert_l2cap.verify_and_respond_open_channel_from_remote(
+            psm)
+        dut_channel = response_future.get_channel()
+        return (dut_channel, cert_channel)
+
+    def test_reject_connection_parameter_update_request(self):
+        """
+        L2CAP/LE/CPU/BI-02-C
+        """
+        self._setup_link_from_cert()
+        self.cert_l2cap.get_control_channel().send(
+            l2cap_packets.ConnectionParameterUpdateRequestBuilder(
+                2, 100, 100, 512, 100))
+        assertThat(self.cert_l2cap.get_control_channel()).emits(
+            L2capMatchers.LeCommandReject())
+
+    def test_segmentation(self):
+        """
+        L2CAP/COS/CFC/BV-01-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert(
+            mtu=1000, mps=102)
+        dut_channel.send(b'hello' * 20 + b'world')
+        # The first LeInformation packet contains 2 bytes of SDU size.
+        # The packet is divided into first 100 bytes from 'hellohello....'
+        # and remaining 5 bytes 'world'
+        assertThat(cert_channel).emits(
+            L2capMatchers.FirstLeIFrame(b'hello' * 20, sdu_size=105),
+            L2capMatchers.Data(b'world')).inOrder()
+
+    def test_no_segmentation(self):
+        """
+        L2CAP/COS/CFC/BV-02-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert(
+            mtu=1000, mps=202)
+        dut_channel.send(b'hello' * 40)
+        assertThat(cert_channel).emits(
+            L2capMatchers.FirstLeIFrame(b'hello' * 40, sdu_size=200))
+
+    def test_reassembling(self):
+        """
+        L2CAP/COS/CFC/BV-03-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert()
+        sdu_size_for_two_sample_packet = 12
+        cert_channel.send_first_le_i_frame(sdu_size_for_two_sample_packet,
+                                           SAMPLE_PACKET)
+        cert_channel.send(SAMPLE_PACKET)
+        assertThat(dut_channel).emits(
+            L2capMatchers.PacketPayloadRawData(b'\x01\x01\x02\x00\x00\x00' * 2))
+
+    def test_data_receiving(self):
+        """
+        L2CAP/COS/CFC/BV-04-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert()
+        cert_channel.send_first_le_i_frame(6, SAMPLE_PACKET)
+        assertThat(dut_channel).emits(
+            L2capMatchers.PacketPayloadRawData(b'\x01\x01\x02\x00\x00\x00'))
+
+    def test_multiple_channels_with_interleaved_data_streams(self):
+        """
+        L2CAP/COS/CFC/BV-05-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel_x, cert_channel_x) = self._open_channel_from_cert(
+            signal_id=1, scid=0x0103, psm=0x33)
+        (dut_channel_y, cert_channel_y) = self._open_channel_from_cert(
+            signal_id=2, scid=0x0105, psm=0x35)
+        (dut_channel_z, cert_channel_z) = self._open_channel_from_cert(
+            signal_id=3, scid=0x0107, psm=0x37)
+        cert_channel_y.send_first_le_i_frame(6, SAMPLE_PACKET)
+        cert_channel_z.send_first_le_i_frame(6, SAMPLE_PACKET)
+        cert_channel_y.send_first_le_i_frame(6, SAMPLE_PACKET)
+        cert_channel_z.send_first_le_i_frame(6, SAMPLE_PACKET)
+        cert_channel_y.send_first_le_i_frame(6, SAMPLE_PACKET)
+        # TODO: We should assert two events in order, but it got stuck
+        assertThat(dut_channel_y).emits(
+            L2capMatchers.PacketPayloadRawData(b'\x01\x01\x02\x00\x00\x00'),
+            at_least_times=3)
+        assertThat(dut_channel_z).emits(
+            L2capMatchers.PacketPayloadRawData(b'\x01\x01\x02\x00\x00\x00'),
+            L2capMatchers.PacketPayloadRawData(
+                b'\x01\x01\x02\x00\x00\x00')).inOrder()
+        cert_channel_z.send_first_le_i_frame(6, SAMPLE_PACKET)
+        assertThat(dut_channel_z).emits(
+            L2capMatchers.PacketPayloadRawData(b'\x01\x01\x02\x00\x00\x00'))
+
+    def test_reject_unknown_command_in_le_sigling_channel(self):
+        """
+        L2CAP/LE/REJ/BI-01-C
+        """
+        self._setup_link_from_cert()
+        self.cert_l2cap.get_control_channel().send(
+            l2cap_packets.InformationRequestBuilder(
+                2, l2cap_packets.InformationRequestInfoType.
+                EXTENDED_FEATURES_SUPPORTED))
+        assertThat(self.cert_l2cap.get_control_channel()).emits(
+            L2capMatchers.LeCommandReject())
+
+    def test_credit_based_connection_request_unsupported_le_psm(self):
+        """
+        L2CAP/LE/CFC/BV-05-C
+        """
+        self._setup_link_from_cert()
+        self.cert_l2cap.get_control_channel().send(
+            l2cap_packets.LeCreditBasedConnectionRequestBuilder(
+                1, 0x34, 0x0101, 2000, 1000, 1000))
+        assertThat(self.cert_l2cap.get_control_channel()).emits(
+            L2capMatchers.CreditBasedConnectionResponse(
+                0x0101,
+                result=LeCreditBasedConnectionResponseResult.
+                LE_PSM_NOT_SUPPORTED))
+
+    def test_credit_exchange_receiving_incremental_credits(self):
+        """
+        L2CAP/LE/CFC/BV-06-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel,
+         cert_channel) = self._open_channel_from_cert(initial_credit=0)
+        for _ in range(4):
+            dut_channel.send(b'hello')
+        cert_channel.send_credits(1)
+        assertThat(cert_channel).emits(
+            L2capMatchers.FirstLeIFrame(b'hello', sdu_size=5))
+        cert_channel.send_credits(1)
+        assertThat(cert_channel).emits(
+            L2capMatchers.FirstLeIFrame(b'hello', sdu_size=5))
+        cert_channel.send_credits(2)
+        assertThat(cert_channel).emits(
+            L2capMatchers.FirstLeIFrame(b'hello', sdu_size=5),
+            L2capMatchers.FirstLeIFrame(b'hello', sdu_size=5))
+
+    def test_le_credit_based_connection_request_legacy_peer(self):
+        """
+        L2CAP/LE/CFC/BV-01-C
+        """
+        self._setup_link_from_cert()
+        response_future = self.dut_l2cap.connect_coc_to_cert(psm=0x33)
+        self.cert_l2cap.verify_and_reject_open_channel_from_remote(psm=0x33)
+        assertThat(response_future.get_status()).isNotEqualTo(
+            LeCreditBasedConnectionResponseResult.SUCCESS)
+
+    def test_le_credit_based_connection_request_on_supported_le_psm(self):
+        """
+        L2CAP/LE/CFC/BV-02-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_dut()
+        cert_channel.send_first_le_i_frame(6, SAMPLE_PACKET)
+        assertThat(dut_channel).emits(
+            L2capMatchers.PacketPayloadRawData(b'\x01\x01\x02\x00\x00\x00'))
+
+    def test_credit_based_connection_response_on_supported_le_psm(self):
+        """
+        L2CAP/LE/CFC/BV-03-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert()
+        dut_channel.send(b'hello')
+        assertThat(cert_channel).emits(
+            L2capMatchers.FirstLeIFrame(b'hello', sdu_size=5))
+
+    def test_credit_based_connection_request_on_an_unsupported_le_psm(self):
+        """
+        L2CAP/LE/CFC/BV-04-C
+        """
+        self._setup_link_from_cert()
+        response_future = self.dut_l2cap.connect_coc_to_cert(psm=0x33)
+        self.cert_l2cap.verify_and_respond_open_channel_from_remote(
+            psm=0x33,
+            result=LeCreditBasedConnectionResponseResult.LE_PSM_NOT_SUPPORTED)
+        assertThat(response_future.get_status()).isEqualTo(
+            LeCreditBasedConnectionResponseResult.LE_PSM_NOT_SUPPORTED)
+
+    def test_request_refused_due_to_unacceptable_parameters_initiator(self):
+        """
+        L2CAP/LE/CFC/BV-21-C
+        """
+        self._setup_link_from_cert()
+        response_future = self.dut_l2cap.connect_coc_to_cert(psm=0x33)
+        self.cert_l2cap.verify_and_respond_open_channel_from_remote(
+            psm=0x33,
+            result=LeCreditBasedConnectionResponseResult.UNACCEPTABLE_PARAMETERS
+        )
+        assertThat(response_future.get_status()).isEqualTo(
+            LeCreditBasedConnectionResponseResult.UNACCEPTABLE_PARAMETERS)
+
+    def test_credit_exchange_exceed_initial_credits(self):
+        """
+        L2CAP/LE/CFC/BI-01-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert()
+        cert_channel.send_credits(65535)
+        cert_channel.verify_disconnect_request()
+
+    def test_disconnection_response(self):
+        """
+        L2CAP/LE/CFC/BV-09-C
+        """
+        self._setup_link_from_cert()
+        (dut_channel, cert_channel) = self._open_channel_from_cert()
+        cert_channel.disconnect_and_verify()
diff --git a/gd/l2cap/le/dynamic_channel_manager.h b/gd/l2cap/le/dynamic_channel_manager.h
index b57ec2c..a1b99a6 100644
--- a/gd/l2cap/le/dynamic_channel_manager.h
+++ b/gd/l2cap/le/dynamic_channel_manager.h
@@ -50,7 +50,8 @@
   struct ConnectionResult {
     ConnectionResultCode connection_result_code = ConnectionResultCode::SUCCESS;
     hci::ErrorCode hci_error = hci::ErrorCode::SUCCESS;
-    ConnectionResponseResult l2cap_connection_response_result = ConnectionResponseResult::SUCCESS;
+    LeCreditBasedConnectionResponseResult l2cap_connection_response_result =
+        LeCreditBasedConnectionResponseResult::SUCCESS;
   };
   /**
    * OnConnectionFailureCallback(std::string failure_reason);
diff --git a/gd/l2cap/le/facade.cc b/gd/l2cap/le/facade.cc
new file mode 100644
index 0000000..168d957
--- /dev/null
+++ b/gd/l2cap/le/facade.cc
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2020 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 "l2cap/le/facade.h"
+
+#include "grpc/grpc_event_queue.h"
+#include "l2cap/le/dynamic_channel.h"
+#include "l2cap/le/dynamic_channel_manager.h"
+#include "l2cap/le/dynamic_channel_service.h"
+#include "l2cap/le/facade.grpc.pb.h"
+#include "l2cap/le/l2cap_le_module.h"
+#include "l2cap/psm.h"
+#include "packet/raw_builder.h"
+
+namespace bluetooth {
+namespace l2cap {
+namespace le {
+
+class L2capLeModuleFacadeService : public L2capLeModuleFacade::Service {
+ public:
+  L2capLeModuleFacadeService(L2capLeModule* l2cap_layer, os::Handler* facade_handler)
+      : l2cap_layer_(l2cap_layer), facade_handler_(facade_handler) {
+    ASSERT(l2cap_layer_ != nullptr);
+    ASSERT(facade_handler_ != nullptr);
+  }
+
+  ::grpc::Status FetchL2capData(::grpc::ServerContext* context, const ::google::protobuf::Empty* request,
+                                ::grpc::ServerWriter<::bluetooth::l2cap::le::L2capPacket>* writer) override {
+    return pending_l2cap_data_.RunLoop(context, writer);
+  }
+
+  ::grpc::Status OpenDynamicChannel(::grpc::ServerContext* context, const OpenDynamicChannelRequest* request,
+                                    OpenDynamicChannelResponse* response) override {
+    auto service_helper = dynamic_channel_helper_map_.find(request->psm());
+    if (service_helper == dynamic_channel_helper_map_.end()) {
+      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
+    }
+    request->remote();
+    hci::Address peer_address;
+    ASSERT(hci::Address::FromString(request->remote().address().address(), peer_address));
+    // TODO: Support different address type
+    hci::AddressWithType peer(peer_address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
+    service_helper->second->Connect(peer);
+    response->set_status(
+        static_cast<int>(service_helper->second->channel_open_fail_reason_.l2cap_connection_response_result));
+    return ::grpc::Status::OK;
+  }
+
+  ::grpc::Status CloseDynamicChannel(::grpc::ServerContext* context, const CloseDynamicChannelRequest* request,
+                                     ::google::protobuf::Empty* response) override {
+    auto service_helper = dynamic_channel_helper_map_.find(request->psm());
+    if (service_helper == dynamic_channel_helper_map_.end()) {
+      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
+    }
+    if (service_helper->second->channel_ == nullptr) {
+      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not open");
+    }
+    service_helper->second->channel_->Close();
+    return ::grpc::Status::OK;
+  }
+
+  ::grpc::Status SetDynamicChannel(::grpc::ServerContext* context,
+                                   const ::bluetooth::l2cap::le::SetEnableDynamicChannelRequest* request,
+                                   ::google::protobuf::Empty* response) override {
+    if (request->enable()) {
+      dynamic_channel_helper_map_.emplace(request->psm(), std::make_unique<L2capDynamicChannelHelper>(
+                                                              this, l2cap_layer_, facade_handler_, request->psm()));
+      return ::grpc::Status::OK;
+    } else {
+      auto service_helper = dynamic_channel_helper_map_.find(request->psm());
+      if (service_helper == dynamic_channel_helper_map_.end()) {
+        return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
+      }
+      service_helper->second->service_->Unregister(common::BindOnce([] {}), facade_handler_);
+      return ::grpc::Status::OK;
+    }
+  }
+
+  ::grpc::Status SendDynamicChannelPacket(::grpc::ServerContext* context,
+                                          const ::bluetooth::l2cap::le::DynamicChannelPacket* request,
+                                          ::google::protobuf::Empty* response) override {
+    std::unique_lock<std::mutex> lock(channel_map_mutex_);
+    if (dynamic_channel_helper_map_.find(request->psm()) == dynamic_channel_helper_map_.end()) {
+      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
+    }
+    std::vector<uint8_t> packet(request->payload().begin(), request->payload().end());
+    if (!dynamic_channel_helper_map_[request->psm()]->SendPacket(packet)) {
+      return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not open");
+    }
+    return ::grpc::Status::OK;
+  }
+
+  class L2capDynamicChannelHelper {
+   public:
+    L2capDynamicChannelHelper(L2capLeModuleFacadeService* service, L2capLeModule* l2cap_layer, os::Handler* handler,
+                              Psm psm)
+        : facade_service_(service), l2cap_layer_(l2cap_layer), handler_(handler), psm_(psm) {
+      dynamic_channel_manager_ = l2cap_layer_->GetDynamicChannelManager();
+      dynamic_channel_manager_->RegisterService(
+          psm, {}, {},
+          common::BindOnce(&L2capDynamicChannelHelper::on_l2cap_service_registration_complete,
+                           common::Unretained(this)),
+          common::Bind(&L2capDynamicChannelHelper::on_connection_open, common::Unretained(this)), handler_);
+    }
+
+    ~L2capDynamicChannelHelper() {
+      if (channel_ != nullptr) {
+        channel_->GetQueueUpEnd()->UnregisterDequeue();
+        channel_ = nullptr;
+      }
+    }
+
+    void Connect(hci::AddressWithType address) {
+      dynamic_channel_manager_->ConnectChannel(
+          address, {}, psm_, common::Bind(&L2capDynamicChannelHelper::on_connection_open, common::Unretained(this)),
+          common::Bind(&L2capDynamicChannelHelper::on_connect_fail, common::Unretained(this)), handler_);
+      std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
+      if (!channel_open_cv_.wait_for(lock, std::chrono::seconds(2), [this] { return channel_ != nullptr; })) {
+        LOG_WARN("Channel is not open for psm %d", psm_);
+      }
+    }
+
+    void disconnect() {
+      channel_->Close();
+    }
+
+    void on_l2cap_service_registration_complete(DynamicChannelManager::RegistrationResult registration_result,
+                                                std::unique_ptr<DynamicChannelService> service) {
+      if (registration_result != DynamicChannelManager::RegistrationResult::SUCCESS) {
+        LOG_ERROR("Service registration failed");
+      } else {
+        service_ = std::move(service);
+      }
+    }
+
+    // invoked from Facade Handler
+    void on_connection_open(std::unique_ptr<DynamicChannel> channel) {
+      {
+        std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
+        channel_ = std::move(channel);
+      }
+      channel_open_cv_.notify_all();
+      channel_->RegisterOnCloseCallback(
+          facade_service_->facade_handler_,
+          common::BindOnce(&L2capDynamicChannelHelper::on_close_callback, common::Unretained(this)));
+      channel_->GetQueueUpEnd()->RegisterDequeue(
+          facade_service_->facade_handler_,
+          common::Bind(&L2capDynamicChannelHelper::on_incoming_packet, common::Unretained(this)));
+    }
+
+    void on_close_callback(hci::ErrorCode error_code) {
+      {
+        std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
+        channel_->GetQueueUpEnd()->UnregisterDequeue();
+      }
+      channel_ = nullptr;
+    }
+
+    void on_connect_fail(DynamicChannelManager::ConnectionResult result) {
+      {
+        std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
+        channel_ = nullptr;
+        channel_open_fail_reason_ = result;
+      }
+      channel_open_cv_.notify_all();
+    }
+
+    void on_incoming_packet() {
+      auto packet = channel_->GetQueueUpEnd()->TryDequeue();
+      std::string data = std::string(packet->begin(), packet->end());
+      L2capPacket l2cap_data;
+      l2cap_data.set_psm(psm_);
+      l2cap_data.set_payload(data);
+      facade_service_->pending_l2cap_data_.OnIncomingEvent(l2cap_data);
+    }
+
+    bool SendPacket(std::vector<uint8_t> packet) {
+      if (channel_ == nullptr) {
+        std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
+        if (!channel_open_cv_.wait_for(lock, std::chrono::seconds(2), [this] { return channel_ != nullptr; })) {
+          LOG_WARN("Channel is not open for psm %d", psm_);
+          return false;
+        }
+      }
+      std::promise<void> promise;
+      auto future = promise.get_future();
+      channel_->GetQueueUpEnd()->RegisterEnqueue(
+          handler_, common::Bind(&L2capDynamicChannelHelper::enqueue_callback, common::Unretained(this), packet,
+                                 common::Passed(std::move(promise))));
+      auto status = future.wait_for(std::chrono::milliseconds(500));
+      if (status != std::future_status::ready) {
+        LOG_ERROR("Can't send packet because the previous packet wasn't sent yet");
+        return false;
+      }
+      return true;
+    }
+
+    std::unique_ptr<packet::BasePacketBuilder> enqueue_callback(std::vector<uint8_t> packet,
+                                                                std::promise<void> promise) {
+      auto packet_one = std::make_unique<packet::RawBuilder>(2000);
+      packet_one->AddOctets(packet);
+      channel_->GetQueueUpEnd()->UnregisterEnqueue();
+      promise.set_value();
+      return packet_one;
+    };
+
+    L2capLeModuleFacadeService* facade_service_;
+    L2capLeModule* l2cap_layer_;
+    os::Handler* handler_;
+    std::unique_ptr<DynamicChannelManager> dynamic_channel_manager_;
+    std::unique_ptr<DynamicChannelService> service_;
+    std::unique_ptr<DynamicChannel> channel_ = nullptr;
+    Psm psm_;
+    DynamicChannelManager::ConnectionResult channel_open_fail_reason_;
+    std::condition_variable channel_open_cv_;
+    std::mutex channel_open_cv_mutex_;
+  };
+
+  L2capLeModule* l2cap_layer_;
+  os::Handler* facade_handler_;
+  std::mutex channel_map_mutex_;
+  std::map<Psm, std::unique_ptr<L2capDynamicChannelHelper>> dynamic_channel_helper_map_;
+  ::bluetooth::grpc::GrpcEventQueue<L2capPacket> pending_l2cap_data_{"FetchL2capData"};
+};
+
+void L2capLeModuleFacadeModule::ListDependencies(ModuleList* list) {
+  ::bluetooth::grpc::GrpcFacadeModule::ListDependencies(list);
+  list->add<l2cap::le::L2capLeModule>();
+}
+
+void L2capLeModuleFacadeModule::Start() {
+  ::bluetooth::grpc::GrpcFacadeModule::Start();
+  service_ = new L2capLeModuleFacadeService(GetDependency<l2cap::le::L2capLeModule>(), GetHandler());
+}
+
+void L2capLeModuleFacadeModule::Stop() {
+  delete service_;
+  ::bluetooth::grpc::GrpcFacadeModule::Stop();
+}
+
+::grpc::Service* L2capLeModuleFacadeModule::GetService() const {
+  return service_;
+}
+
+const ModuleFactory L2capLeModuleFacadeModule::Factory =
+    ::bluetooth::ModuleFactory([]() { return new L2capLeModuleFacadeModule(); });
+
+}  // namespace le
+}  // namespace l2cap
+}  // namespace bluetooth
diff --git a/gd/l2cap/le/facade.h b/gd/l2cap/le/facade.h
new file mode 100644
index 0000000..0f811c8
--- /dev/null
+++ b/gd/l2cap/le/facade.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 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 <grpc++/grpc++.h>
+
+#include "grpc/grpc_module.h"
+
+namespace bluetooth {
+namespace l2cap {
+namespace le {
+
+class L2capLeModuleFacadeService;
+
+class L2capLeModuleFacadeModule : public ::bluetooth::grpc::GrpcFacadeModule {
+ public:
+  static const ModuleFactory Factory;
+
+  void ListDependencies(ModuleList* list) override;
+
+  void Start() override;
+
+  void Stop() override;
+
+  ::grpc::Service* GetService() const override;
+
+ private:
+  L2capLeModuleFacadeService* service_;
+};
+
+}  // namespace le
+}  // namespace l2cap
+}  // namespace bluetooth
diff --git a/gd/l2cap/le/facade.proto b/gd/l2cap/le/facade.proto
new file mode 100644
index 0000000..2a1aeae
--- /dev/null
+++ b/gd/l2cap/le/facade.proto
@@ -0,0 +1,51 @@
+syntax = "proto3";
+
+package bluetooth.l2cap.le;
+
+import "google/protobuf/empty.proto";
+import "facade/common.proto";
+
+service L2capLeModuleFacade {
+  rpc FetchL2capData(google.protobuf.Empty) returns (stream L2capPacket) {}
+  rpc FetchDynamicChannelOpenEvent(google.protobuf.Empty) returns (stream DynamicChannelOpenEvent) {}
+  // Initiate a credit based connection request and block until response is received for up to some timeout (2s)
+  rpc OpenDynamicChannel(OpenDynamicChannelRequest) returns (OpenDynamicChannelResponse) {}
+  rpc CloseDynamicChannel(CloseDynamicChannelRequest) returns (google.protobuf.Empty) {}
+  rpc SetDynamicChannel(SetEnableDynamicChannelRequest) returns (google.protobuf.Empty) {}
+  rpc SendDynamicChannelPacket(DynamicChannelPacket) returns (google.protobuf.Empty) {}
+}
+
+message L2capPacket {
+  uint32 psm = 1;
+  bytes payload = 2;
+}
+
+message DynamicChannelOpenEvent {
+  uint32 psm = 1;
+  uint32 connection_response_result = 2;
+}
+
+message OpenDynamicChannelRequest {
+  facade.BluetoothAddressWithType remote = 1;
+  uint32 psm = 2;
+}
+
+message OpenDynamicChannelResponse {
+  uint32 status = 1;
+}
+
+message CloseDynamicChannelRequest {
+  facade.BluetoothAddressWithType remote = 1;
+  uint32 psm = 2;
+}
+
+message SetEnableDynamicChannelRequest {
+  uint32 psm = 1;
+  bool enable = 2;
+}
+
+message DynamicChannelPacket {
+  facade.BluetoothAddressWithType remote = 1;
+  uint32 psm = 2;
+  bytes payload = 3;
+}
diff --git a/gd/l2cap/le/fixed_channel_manager.h b/gd/l2cap/le/fixed_channel_manager.h
index 4583310..17410e4 100644
--- a/gd/l2cap/le/fixed_channel_manager.h
+++ b/gd/l2cap/le/fixed_channel_manager.h
@@ -17,7 +17,6 @@
 
 #include <string>
 
-#include "hci/acl_manager.h"
 #include "hci/address_with_type.h"
 #include "l2cap/cid.h"
 #include "l2cap/le/fixed_channel.h"
diff --git a/gd/l2cap/le/internal/link.cc b/gd/l2cap/le/internal/link.cc
index 37305fb..adeaec3 100644
--- a/gd/l2cap/le/internal/link.cc
+++ b/gd/l2cap/le/internal/link.cc
@@ -30,6 +30,9 @@
 namespace le {
 namespace internal {
 
+static constexpr uint16_t kDefaultMinimumCeLength = 0x0002;
+static constexpr uint16_t kDefaultMaximumCeLength = 0x0C00;
+
 Link::Link(os::Handler* l2cap_handler, std::unique_ptr<hci::AclConnection> acl_connection,
            l2cap::internal::ParameterProvider* parameter_provider,
            DynamicChannelServiceManagerImpl* dynamic_service_manager,
@@ -55,13 +58,29 @@
   acl_connection_->Disconnect(hci::DisconnectReason::REMOTE_USER_TERMINATED_CONNECTION);
 }
 
-void Link::UpdateConnectionParameter(SignalId signal_id, uint16_t conn_interval_min, uint16_t conn_interval_max,
-                                     uint16_t conn_latency, uint16_t supervision_timeout) {
+void Link::UpdateConnectionParameterFromRemote(SignalId signal_id, uint16_t conn_interval_min,
+                                               uint16_t conn_interval_max, uint16_t conn_latency,
+                                               uint16_t supervision_timeout) {
   acl_connection_->LeConnectionUpdate(
-      conn_interval_min, conn_interval_max, conn_latency, supervision_timeout,
+      conn_interval_min, conn_interval_max, conn_latency, supervision_timeout, kDefaultMinimumCeLength,
+      kDefaultMaximumCeLength,
       common::BindOnce(&Link::on_connection_update_complete, common::Unretained(this), signal_id), l2cap_handler_);
 }
 
+void Link::SendConnectionParameterUpdate(uint16_t conn_interval_min, uint16_t conn_interval_max, uint16_t conn_latency,
+                                         uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length) {
+  if (acl_connection_->GetRole() == hci::Role::SLAVE) {
+    // TODO: If both LL master and slave support 4.1, use HCI command directly
+    signalling_manager_.SendConnectionParameterUpdateRequest(conn_interval_min, conn_interval_max, conn_latency,
+                                                             supervision_timeout);
+    return;
+  }
+  acl_connection_->LeConnectionUpdate(
+      conn_interval_min, conn_interval_max, conn_latency, supervision_timeout, min_ce_length, max_ce_length,
+      common::BindOnce(&Link::on_connection_update_complete, common::Unretained(this), kInvalidSignalId),
+      l2cap_handler_);
+}
+
 std::shared_ptr<FixedChannelImpl> Link::AllocateFixedChannel(Cid cid, SecurityPolicy security_policy) {
   auto channel = fixed_channel_allocator_.AllocateChannel(cid, security_policy);
   data_pipeline_manager_.AttachChannel(cid, channel, l2cap::internal::DataPipelineManager::ChannelMode::BASIC);
@@ -82,7 +101,9 @@
     return;
   }
   auto reserved_cid = ReserveDynamicChannel();
-  signalling_manager_.SendConnectionRequest(psm, reserved_cid, pending_dynamic_channel_connection.configuration_.mtu);
+  auto mtu = pending_dynamic_channel_connection.configuration_.mtu;
+  local_cid_to_pending_dynamic_channel_connection_map_[reserved_cid] = std::move(pending_dynamic_channel_connection);
+  signalling_manager_.SendConnectionRequest(psm, reserved_cid, mtu);
 }
 
 void Link::SendDisconnectionRequest(Cid local_cid, Cid remote_cid) {
@@ -93,8 +114,18 @@
   signalling_manager_.SendDisconnectRequest(local_cid, remote_cid);
 }
 
-void Link::OnOutgoingConnectionRequestFail(Cid local_cid) {
-  local_cid_to_pending_dynamic_channel_connection_map_.erase(local_cid);
+void Link::OnOutgoingConnectionRequestFail(Cid local_cid, LeCreditBasedConnectionResponseResult response_result) {
+  if (local_cid_to_pending_dynamic_channel_connection_map_.find(local_cid) !=
+      local_cid_to_pending_dynamic_channel_connection_map_.end()) {
+    // TODO(hsz): Currently we only notify the client when the remote didn't send connection response SUCCESS.
+    //  Should we notify the client when the link failed to establish?
+    DynamicChannelManager::ConnectionResult result{
+        .connection_result_code = DynamicChannelManager::ConnectionResultCode::FAIL_L2CAP_ERROR,
+        .hci_error = hci::ErrorCode::SUCCESS,
+        .l2cap_connection_response_result = response_result,
+    };
+    NotifyChannelFail(local_cid, result);
+  }
   dynamic_channel_allocator_.FreeChannel(local_cid);
 }
 
@@ -122,12 +153,6 @@
   return channel;
 }
 
-DynamicChannelConfigurationOption Link::GetConfigurationForInitialConfiguration(Cid cid) {
-  ASSERT(local_cid_to_pending_dynamic_channel_connection_map_.find(cid) !=
-         local_cid_to_pending_dynamic_channel_connection_map_.end());
-  return local_cid_to_pending_dynamic_channel_connection_map_[cid].configuration_;
-}
-
 void Link::FreeDynamicChannel(Cid cid) {
   if (dynamic_channel_allocator_.FindChannelByCid(cid) == nullptr) {
     return;
@@ -159,12 +184,11 @@
   local_cid_to_pending_dynamic_channel_connection_map_.erase(cid);
 }
 
-void Link::NotifyChannelFail(Cid cid) {
+void Link::NotifyChannelFail(Cid cid, DynamicChannelManager::ConnectionResult result) {
   ASSERT(local_cid_to_pending_dynamic_channel_connection_map_.find(cid) !=
          local_cid_to_pending_dynamic_channel_connection_map_.end());
   auto& pending_dynamic_channel_connection = local_cid_to_pending_dynamic_channel_connection_map_[cid];
   // TODO(cmanton) Pass proper connection falure result to user
-  DynamicChannelManager::ConnectionResult result;
   pending_dynamic_channel_connection.handler_->Post(
       common::BindOnce(std::move(pending_dynamic_channel_connection.on_fail_callback_), result));
   local_cid_to_pending_dynamic_channel_connection_map_.erase(cid);
@@ -183,6 +207,9 @@
 }
 
 void Link::on_connection_update_complete(SignalId signal_id, hci::ErrorCode error_code) {
+  if (!signal_id.IsValid()) {
+    return;
+  }
   ConnectionParameterUpdateResponseResult result = (error_code == hci::ErrorCode::SUCCESS)
                                                        ? ConnectionParameterUpdateResponseResult::ACCEPTED
                                                        : ConnectionParameterUpdateResponseResult::REJECTED;
diff --git a/gd/l2cap/le/internal/link.h b/gd/l2cap/le/internal/link.h
index 3c90856..be63d69 100644
--- a/gd/l2cap/le/internal/link.h
+++ b/gd/l2cap/le/internal/link.h
@@ -72,8 +72,13 @@
   virtual void Disconnect();
 
   // Handles connection parameter update request from remote
-  virtual void UpdateConnectionParameter(SignalId signal_id, uint16_t conn_interval_min, uint16_t conn_interval_max,
-                                         uint16_t conn_latency, uint16_t supervision_timeout);
+  virtual void UpdateConnectionParameterFromRemote(SignalId signal_id, uint16_t conn_interval_min,
+                                                   uint16_t conn_interval_max, uint16_t conn_latency,
+                                                   uint16_t supervision_timeout);
+
+  virtual void SendConnectionParameterUpdate(uint16_t conn_interval_min, uint16_t conn_interval_max,
+                                             uint16_t conn_latency, uint16_t supervision_timeout,
+                                             uint16_t min_ce_length, uint16_t max_ce_length);
 
   // FixedChannel methods
 
@@ -90,7 +95,7 @@
   void SendDisconnectionRequest(Cid local_cid, Cid remote_cid) override;
 
   // Invoked by signalling manager to indicate an outgoing connection request failed and link shall free resources
-  virtual void OnOutgoingConnectionRequestFail(Cid local_cid);
+  virtual void OnOutgoingConnectionRequestFail(Cid local_cid, LeCreditBasedConnectionResponseResult result);
 
   virtual std::shared_ptr<l2cap::internal::DynamicChannelImpl> AllocateDynamicChannel(Psm psm, Cid remote_cid,
                                                                                       SecurityPolicy security_policy);
@@ -98,15 +103,13 @@
   virtual std::shared_ptr<l2cap::internal::DynamicChannelImpl> AllocateReservedDynamicChannel(
       Cid reserved_cid, Psm psm, Cid remote_cid, SecurityPolicy security_policy);
 
-  virtual DynamicChannelConfigurationOption GetConfigurationForInitialConfiguration(Cid cid);
-
   virtual void FreeDynamicChannel(Cid cid);
 
   // Check how many channels are acquired or in use, if zero, start tear down timer, if non-zero, cancel tear down timer
   virtual void RefreshRefCount();
 
   void NotifyChannelCreation(Cid cid, std::unique_ptr<DynamicChannel> user_channel);
-  void NotifyChannelFail(Cid cid);
+  void NotifyChannelFail(Cid cid, DynamicChannelManager::ConnectionResult result);
 
   virtual std::string ToString() {
     return GetDevice().ToString();
@@ -131,6 +134,9 @@
   os::Alarm link_idle_disconnect_alarm_{l2cap_handler_};
   DISALLOW_COPY_AND_ASSIGN(Link);
 
+  // Received connection update complete from ACL manager. SignalId is bound to a valid number when we need to send a
+  // response to remote. If SignalId is bound to an invalid number, we don't send a response to remote, because the
+  // connection update request is not from remote LL slave.
   void on_connection_update_complete(SignalId signal_id, hci::ErrorCode error_code);
 };
 
diff --git a/gd/l2cap/le/internal/link_manager.h b/gd/l2cap/le/internal/link_manager.h
index 9e9c0e8..b759e7b 100644
--- a/gd/l2cap/le/internal/link_manager.h
+++ b/gd/l2cap/le/internal/link_manager.h
@@ -28,6 +28,7 @@
 #include "l2cap/internal/parameter_provider.h"
 #include "l2cap/internal/scheduler.h"
 #include "l2cap/le/fixed_channel_manager.h"
+#include "l2cap/le/internal/dynamic_channel_service_manager_impl.h"
 #include "l2cap/le/internal/fixed_channel_service_manager_impl.h"
 #include "l2cap/le/internal/link.h"
 
@@ -39,9 +40,10 @@
 class LinkManager : public hci::LeConnectionCallbacks {
  public:
   LinkManager(os::Handler* l2cap_handler, hci::AclManager* acl_manager, FixedChannelServiceManagerImpl* service_manager,
+              DynamicChannelServiceManagerImpl* dynamic_service_manager,
               l2cap::internal::ParameterProvider* parameter_provider)
       : l2cap_handler_(l2cap_handler), acl_manager_(acl_manager), fixed_channel_service_manager_(service_manager),
-        parameter_provider_(parameter_provider) {
+        dynamic_channel_service_manager_(dynamic_service_manager), parameter_provider_(parameter_provider) {
     acl_manager_->RegisterLeCallbacks(this, l2cap_handler_);
   }
 
diff --git a/gd/l2cap/le/internal/link_manager_test.cc b/gd/l2cap/le/internal/link_manager_test.cc
index cbef3f3..8360c51 100644
--- a/gd/l2cap/le/internal/link_manager_test.cc
+++ b/gd/l2cap/le/internal/link_manager_test.cc
@@ -96,7 +96,7 @@
   os::Handler* hci_callback_handler = nullptr;
   EXPECT_CALL(mock_acl_manager, RegisterLeCallbacks(_, _))
       .WillOnce(DoAll(SaveArg<0>(&hci_le_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
-  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager,
+  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager, nullptr,
                               mock_parameter_provider_);
   EXPECT_EQ(hci_le_connection_callbacks, &le_link_manager);
   EXPECT_EQ(hci_callback_handler, l2cap_handler_);
@@ -194,7 +194,7 @@
   os::Handler* hci_callback_handler = nullptr;
   EXPECT_CALL(mock_acl_manager, RegisterLeCallbacks(_, _))
       .WillOnce(DoAll(SaveArg<0>(&hci_le_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
-  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager,
+  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager, nullptr,
                               mock_parameter_provider_);
   EXPECT_EQ(hci_le_connection_callbacks, &le_link_manager);
   EXPECT_EQ(hci_callback_handler, l2cap_handler_);
@@ -229,7 +229,7 @@
   os::Handler* hci_callback_handler = nullptr;
   EXPECT_CALL(mock_acl_manager, RegisterLeCallbacks(_, _))
       .WillOnce(DoAll(SaveArg<0>(&hci_le_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
-  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager,
+  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager, nullptr,
                               mock_parameter_provider_);
   EXPECT_EQ(hci_le_connection_callbacks, &le_link_manager);
   EXPECT_EQ(hci_callback_handler, l2cap_handler_);
@@ -276,7 +276,7 @@
   os::Handler* hci_callback_handler = nullptr;
   EXPECT_CALL(mock_acl_manager, RegisterLeCallbacks(_, _))
       .WillOnce(DoAll(SaveArg<0>(&hci_le_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
-  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager,
+  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager, nullptr,
                               mock_parameter_provider_);
   EXPECT_EQ(hci_le_connection_callbacks, &le_link_manager);
   EXPECT_EQ(hci_callback_handler, l2cap_handler_);
@@ -358,7 +358,7 @@
   os::Handler* hci_callback_handler = nullptr;
   EXPECT_CALL(mock_acl_manager, RegisterLeCallbacks(_, _))
       .WillOnce(DoAll(SaveArg<0>(&hci_le_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
-  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager,
+  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager, nullptr,
                               mock_parameter_provider_);
   EXPECT_EQ(hci_le_connection_callbacks, &le_link_manager);
   EXPECT_EQ(hci_callback_handler, l2cap_handler_);
@@ -442,7 +442,7 @@
   os::Handler* hci_callback_handler = nullptr;
   EXPECT_CALL(mock_acl_manager, RegisterLeCallbacks(_, _))
       .WillOnce(DoAll(SaveArg<0>(&hci_le_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
-  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager,
+  LinkManager le_link_manager(l2cap_handler_, &mock_acl_manager, &mock_le_fixed_channel_service_manager, nullptr,
                               mock_parameter_provider_);
   EXPECT_EQ(hci_le_connection_callbacks, &le_link_manager);
   EXPECT_EQ(hci_callback_handler, l2cap_handler_);
diff --git a/gd/l2cap/le/internal/signalling_manager.cc b/gd/l2cap/le/internal/signalling_manager.cc
index c9d978c..59dbdb1 100644
--- a/gd/l2cap/le/internal/signalling_manager.cc
+++ b/gd/l2cap/le/internal/signalling_manager.cc
@@ -56,9 +56,8 @@
 }
 
 void LeSignallingManager::SendConnectionRequest(Psm psm, Cid local_cid, Mtu mtu) {
-  PendingCommand pending_command = {
-      next_signal_id_, LeCommandCode::LE_CREDIT_BASED_CONNECTION_REQUEST, psm, local_cid, {}, mtu, link_->GetMps(),
-      link_->GetInitialCredit()};
+  PendingCommand pending_command = PendingCommand::CreditBasedConnectionRequest(
+      next_signal_id_, psm, local_cid, mtu, link_->GetMps(), link_->GetInitialCredit());
   next_signal_id_++;
   pending_commands_.push(pending_command);
   if (pending_commands_.size() == 1) {
@@ -66,9 +65,8 @@
   }
 }
 
-void LeSignallingManager::SendDisconnectRequest(Cid local_cid, Cid remote_cid) {
-  PendingCommand pending_command = {
-      next_signal_id_, LeCommandCode::DISCONNECTION_REQUEST, {}, local_cid, remote_cid, {}, {}, {}};
+void LeSignallingManager::SendDisconnectRequest(Cid scid, Cid dcid) {
+  PendingCommand pending_command = PendingCommand::DisconnectionRequest(next_signal_id_, scid, dcid);
   next_signal_id_++;
   pending_commands_.push(pending_command);
   if (pending_commands_.size() == 1) {
@@ -78,7 +76,13 @@
 
 void LeSignallingManager::SendConnectionParameterUpdateRequest(uint16_t interval_min, uint16_t interval_max,
                                                                uint16_t slave_latency, uint16_t timeout_multiplier) {
-  LOG_ERROR("Not implemented");
+  PendingCommand pending_command = PendingCommand::ConnectionParameterUpdate(
+      next_signal_id_, interval_min, interval_max, slave_latency, timeout_multiplier);
+  next_signal_id_++;
+  pending_commands_.push(pending_command);
+  if (pending_commands_.size() == 1) {
+    handle_send_next_command();
+  }
 }
 
 void LeSignallingManager::SendConnectionParameterUpdateResponse(SignalId signal_id,
@@ -99,23 +103,55 @@
 
 void LeSignallingManager::OnCommandReject(LeCommandRejectView command_reject_view) {
   auto signal_id = command_reject_view.GetIdentifier();
-  if (signal_id != command_just_sent_.signal_id_ || command_just_sent_.command_code_ != command_reject_view.GetCode()) {
+  if (signal_id != command_just_sent_.signal_id_) {
     LOG_WARN("Unexpected response: no pending request");
     return;
   }
   alarm_.Cancel();
+  if (command_just_sent_.command_code_ == LeCommandCode::LE_CREDIT_BASED_CONNECTION_REQUEST) {
+    link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_,
+                                           LeCreditBasedConnectionResponseResult::NO_RESOURCES_AVAILABLE);
+  }
   handle_send_next_command();
 
   LOG_WARN("Command rejected");
 }
 
-void LeSignallingManager::OnConnectionParameterUpdateRequest(uint16_t interval_min, uint16_t interval_max,
-                                                             uint16_t slave_latency, uint16_t timeout_multiplier) {
-  LOG_ERROR("Not implemented");
+void LeSignallingManager::OnConnectionParameterUpdateRequest(SignalId signal_id, uint16_t interval_min,
+                                                             uint16_t interval_max, uint16_t slave_latency,
+                                                             uint16_t timeout_multiplier) {
+  if (link_->GetRole() == hci::Role::SLAVE) {
+    LOG_WARN("Received request from LL master");
+    auto builder = LeCommandRejectNotUnderstoodBuilder::Create(signal_id.Value());
+    enqueue_buffer_->Enqueue(std::move(builder), handler_);
+    return;
+  }
+  if (interval_min < 6 || interval_min > 3200 || interval_max < 6 || interval_max > 3200 || slave_latency >= 500 ||
+      timeout_multiplier < 10 || timeout_multiplier > 3200) {
+    LOG_WARN("Received invalid connection parameter update request from LL master");
+    auto builder = ConnectionParameterUpdateResponseBuilder::Create(signal_id.Value(),
+                                                                    ConnectionParameterUpdateResponseResult::REJECTED);
+    enqueue_buffer_->Enqueue(std::move(builder), handler_);
+    return;
+  }
+  link_->UpdateConnectionParameterFromRemote(signal_id, interval_min, interval_max, slave_latency, timeout_multiplier);
 }
 
-void LeSignallingManager::OnConnectionParameterUpdateResponse(ConnectionParameterUpdateResponseResult result) {
-  LOG_ERROR("Not implemented");
+void LeSignallingManager::OnConnectionParameterUpdateResponse(SignalId signal_id,
+                                                              ConnectionParameterUpdateResponseResult result) {
+  if (signal_id != command_just_sent_.signal_id_) {
+    LOG_WARN("Unexpected response: no pending request");
+    return;
+  }
+  if (command_just_sent_.command_code_ != LeCommandCode::CONNECTION_PARAMETER_UPDATE_REQUEST) {
+    LOG_WARN("Unexpected response: no pending request");
+    return;
+  }
+  alarm_.Cancel();
+  command_just_sent_.signal_id_ = kInitialSignalId;
+  if (result != ConnectionParameterUpdateResponseResult::ACCEPTED) {
+    LOG_ERROR("Connection parameter update is not accepted");
+  }
 }
 
 void LeSignallingManager::OnConnectionRequest(SignalId signal_id, Psm psm, Cid remote_cid, Mtu mtu, uint16_t mps,
@@ -161,7 +197,7 @@
 
     return;
   }
-  send_connection_response(signal_id, remote_cid, local_mtu, local_mps, link_->GetInitialCredit(),
+  send_connection_response(signal_id, new_channel->GetCid(), local_mtu, local_mps, link_->GetInitialCredit(),
                            LeCreditBasedConnectionResponseResult::SUCCESS);
   auto* data_controller = reinterpret_cast<l2cap::internal::LeCreditBasedDataController*>(
       data_pipeline_manager_->GetDataController(new_channel->GetCid()));
@@ -186,7 +222,7 @@
   command_just_sent_.signal_id_ = kInitialSignalId;
   if (result != LeCreditBasedConnectionResponseResult::SUCCESS) {
     LOG_WARN("Connection failed: %s", LeCreditBasedConnectionResponseResultText(result).data());
-    link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_);
+    link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_, result);
     handle_send_next_command();
     return;
   }
@@ -194,7 +230,8 @@
       link_->AllocateReservedDynamicChannel(command_just_sent_.source_cid_, command_just_sent_.psm_, remote_cid, {});
   if (new_channel == nullptr) {
     LOG_WARN("Can't allocate dynamic channel");
-    link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_);
+    link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_,
+                                           LeCreditBasedConnectionResponseResult::NO_RESOURCES_AVAILABLE);
     handle_send_next_command();
     return;
   }
@@ -223,7 +260,7 @@
   link_->FreeDynamicChannel(cid);
 }
 
-void LeSignallingManager::OnDisconnectionResponse(SignalId signal_id, Cid cid, Cid remote_cid) {
+void LeSignallingManager::OnDisconnectionResponse(SignalId signal_id, Cid remote_cid, Cid cid) {
   if (signal_id != command_just_sent_.signal_id_ ||
       command_just_sent_.command_code_ != LeCommandCode::DISCONNECTION_REQUEST) {
     LOG_WARN("Unexpected response: no pending request");
@@ -285,8 +322,9 @@
         return;
       }
       OnConnectionParameterUpdateRequest(
-          parameter_update_req_view.GetIntervalMin(), parameter_update_req_view.GetIntervalMax(),
-          parameter_update_req_view.GetSlaveLatency(), parameter_update_req_view.GetTimeoutMultiplier());
+          parameter_update_req_view.GetIdentifier(), parameter_update_req_view.GetIntervalMin(),
+          parameter_update_req_view.GetIntervalMax(), parameter_update_req_view.GetSlaveLatency(),
+          parameter_update_req_view.GetTimeoutMultiplier());
       return;
     }
     case LeCommandCode::CONNECTION_PARAMETER_UPDATE_RESPONSE: {
@@ -295,7 +333,8 @@
       if (!parameter_update_rsp_view.IsValid()) {
         return;
       }
-      OnConnectionParameterUpdateResponse(parameter_update_rsp_view.GetResult());
+      OnConnectionParameterUpdateResponse(parameter_update_rsp_view.GetIdentifier(),
+                                          parameter_update_rsp_view.GetResult());
       return;
     }
     case LeCommandCode::LE_CREDIT_BASED_CONNECTION_REQUEST: {
@@ -372,7 +411,8 @@
   }
   switch (command_just_sent_.command_code_) {
     case LeCommandCode::CONNECTION_PARAMETER_UPDATE_REQUEST: {
-      link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_);
+      link_->OnOutgoingConnectionRequestFail(command_just_sent_.source_cid_,
+                                             LeCreditBasedConnectionResponseResult::NO_RESOURCES_AVAILABLE);
       break;
     }
     default:
@@ -405,6 +445,12 @@
       alarm_.Schedule(common::BindOnce(&LeSignallingManager::on_command_timeout, common::Unretained(this)), kTimeout);
       break;
     }
+    case LeCommandCode::CONNECTION_PARAMETER_UPDATE_REQUEST: {
+      auto builder = ConnectionParameterUpdateRequestBuilder::Create(command_just_sent_.signal_id_.Value(), 0, 0, 0, 0);
+      enqueue_buffer_->Enqueue(std::move(builder), handler_);
+      alarm_.Schedule(common::BindOnce(&LeSignallingManager::on_command_timeout, common::Unretained(this)), kTimeout);
+      break;
+    }
     default: {
       LOG_WARN("Unsupported command code 0x%x", static_cast<int>(command_just_sent_.command_code_));
     }
diff --git a/gd/l2cap/le/internal/signalling_manager.h b/gd/l2cap/le/internal/signalling_manager.h
index f218168..1f9d7d3 100644
--- a/gd/l2cap/le/internal/signalling_manager.h
+++ b/gd/l2cap/le/internal/signalling_manager.h
@@ -49,6 +49,44 @@
   Mtu mtu_;
   uint16_t mps_;
   uint16_t credits_;
+  uint16_t interval_min_;
+  uint16_t interval_max_;
+  uint16_t slave_latency_;
+  uint16_t timeout_multiplier_;
+
+  static PendingCommand CreditBasedConnectionRequest(SignalId signal_id, Psm psm, Cid scid, Mtu mtu, uint16_t mps,
+                                                     uint16_t initial_credits) {
+    PendingCommand pending_command;
+    pending_command.signal_id_ = signal_id;
+    pending_command.command_code_ = LeCommandCode::LE_CREDIT_BASED_CONNECTION_REQUEST;
+    pending_command.psm_ = psm;
+    pending_command.source_cid_ = scid;
+    pending_command.mtu_ = mtu;
+    pending_command.mps_ = mps;
+    pending_command.credits_ = initial_credits;
+    return pending_command;
+  }
+
+  static PendingCommand DisconnectionRequest(SignalId signal_id, Cid scid, Cid dcid) {
+    PendingCommand pending_command;
+    pending_command.signal_id_ = signal_id;
+    pending_command.command_code_ = LeCommandCode::DISCONNECTION_REQUEST;
+    pending_command.source_cid_ = scid;
+    pending_command.destination_cid_ = dcid;
+    return pending_command;
+  }
+
+  static PendingCommand ConnectionParameterUpdate(SignalId signal_id, uint16_t interval_min, uint16_t interval_max,
+                                                  uint16_t slave_latency, uint16_t timeout_multiplier) {
+    PendingCommand pending_command;
+    pending_command.signal_id_ = signal_id;
+    pending_command.command_code_ = LeCommandCode::CONNECTION_PARAMETER_UPDATE_REQUEST;
+    pending_command.interval_min_ = interval_min;
+    pending_command.interval_max_ = interval_max;
+    pending_command.slave_latency_ = slave_latency;
+    pending_command.timeout_multiplier_ = timeout_multiplier;
+    return pending_command;
+  }
 };
 
 class Link;
@@ -65,6 +103,7 @@
 
   void SendDisconnectRequest(Cid local_cid, Cid remote_cid);
 
+  // Note: Since Core 4.1, LL slave can send this through HCI command.
   void SendConnectionParameterUpdateRequest(uint16_t interval_min, uint16_t interval_max, uint16_t slave_latency,
                                             uint16_t timeout_multiplier);
 
@@ -76,9 +115,9 @@
 
   void OnCommandReject(LeCommandRejectView command_reject_view);
 
-  void OnConnectionParameterUpdateRequest(uint16_t interval_min, uint16_t interval_max, uint16_t slave_latency,
-                                          uint16_t timeout_multiplier);
-  void OnConnectionParameterUpdateResponse(ConnectionParameterUpdateResponseResult result);
+  void OnConnectionParameterUpdateRequest(SignalId signal_id, uint16_t interval_min, uint16_t interval_max,
+                                          uint16_t slave_latency, uint16_t timeout_multiplier);
+  void OnConnectionParameterUpdateResponse(SignalId signal_id, ConnectionParameterUpdateResponseResult result);
 
   void OnConnectionRequest(SignalId signal_id, Psm psm, Cid remote_cid, Mtu mtu, uint16_t mps,
                            uint16_t initial_credits);
diff --git a/gd/l2cap/le/l2cap_le_module.cc b/gd/l2cap/le/l2cap_le_module.cc
index 4959547..cc7b588 100644
--- a/gd/l2cap/le/l2cap_le_module.cc
+++ b/gd/l2cap/le/l2cap_le_module.cc
@@ -13,21 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#define LOG_TAG "l2cap2"
 
 #include <memory>
 
-#include "common/bidi_queue.h"
 #include "hci/acl_manager.h"
-#include "hci/address.h"
-#include "hci/hci_layer.h"
-#include "hci/hci_packets.h"
 #include "l2cap/internal/parameter_provider.h"
+#include "l2cap/le/internal/dynamic_channel_service_manager_impl.h"
 #include "l2cap/le/internal/fixed_channel_service_manager_impl.h"
 #include "l2cap/le/internal/link_manager.h"
 #include "module.h"
 #include "os/handler.h"
-#include "os/log.h"
 
 #include "l2cap/le/l2cap_le_module.h"
 
@@ -44,8 +39,9 @@
   hci::AclManager* acl_manager_;
   l2cap::internal::ParameterProvider parameter_provider_;
   internal::FixedChannelServiceManagerImpl fixed_channel_service_manager_impl_{l2cap_handler_};
+  internal::DynamicChannelServiceManagerImpl dynamic_channel_service_manager_impl_{l2cap_handler_};
   internal::LinkManager link_manager_{l2cap_handler_, acl_manager_, &fixed_channel_service_manager_impl_,
-                                      &parameter_provider_};
+                                      &dynamic_channel_service_manager_impl_, &parameter_provider_};
 };
 
 L2capLeModule::L2capLeModule() {}
@@ -72,6 +68,11 @@
                                                                       &pimpl_->link_manager_, pimpl_->l2cap_handler_));
 }
 
+std::unique_ptr<DynamicChannelManager> L2capLeModule::GetDynamicChannelManager() {
+  return std::unique_ptr<DynamicChannelManager>(new DynamicChannelManager(
+      &pimpl_->dynamic_channel_service_manager_impl_, &pimpl_->link_manager_, pimpl_->l2cap_handler_));
+}
+
 }  // namespace le
 }  // namespace l2cap
 }  // namespace bluetooth
diff --git a/gd/l2cap/le/l2cap_le_module.h b/gd/l2cap/le/l2cap_le_module.h
index 4ed3d9f..b9d76d5 100644
--- a/gd/l2cap/le/l2cap_le_module.h
+++ b/gd/l2cap/le/l2cap_le_module.h
@@ -17,6 +17,7 @@
 
 #include <memory>
 
+#include "l2cap/le/dynamic_channel_manager.h"
 #include "l2cap/le/fixed_channel_manager.h"
 #include "module.h"
 
@@ -34,6 +35,11 @@
    */
   virtual std::unique_ptr<FixedChannelManager> GetFixedChannelManager();
 
+  /**
+   * Get the api to the LE dynamic channel l2cap module
+   */
+  virtual std::unique_ptr<DynamicChannelManager> GetDynamicChannelManager();
+
   static const ModuleFactory Factory;
 
  protected:
diff --git a/gd/neighbor/cert/neighbor_test.py b/gd/neighbor/cert/neighbor_test.py
index b83812f..65741d8 100644
--- a/gd/neighbor/cert/neighbor_test.py
+++ b/gd/neighbor/cert/neighbor_test.py
@@ -18,7 +18,7 @@
 import sys
 import logging
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from google.protobuf import empty_pb2 as empty_proto
 from facade import rootservice_pb2 as facade_rootservice
@@ -29,7 +29,7 @@
 import bluetooth_packets_python3 as bt_packets
 
 
-class NeighborTest(GdFacadeOnlyBaseTestClass):
+class NeighborTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='HCI_INTERFACES', cert_module='HCI')
diff --git a/gd/packet/parser/README b/gd/packet/parser/README
index 9006c94..6915538 100644
--- a/gd/packet/parser/README
+++ b/gd/packet/parser/README
@@ -14,11 +14,11 @@
 
 Checksum types
   checksum MyChecksumClass : 16 "path/to/the/class/"
-  Checksum fields need to implement the following three static methods:
-    static void Initialize(MyChecksumClass&);
-    static void AddByte(MyChecksumClass&, uint8_t);
+  Checksum fields need to implement the following three methods:
+    void Initialize(MyChecksumClass&);
+    void AddByte(MyChecksumClass&, uint8_t);
     // Assuming a 16-bit (uint16_t) checksum:
-    static uint16_t GetChecksum(MyChecksumClass&);
+    uint16_t GetChecksum(MyChecksumClass&);
 -------------
  LIMITATIONS
 -------------
@@ -57,3 +57,4 @@
   static void Serialize(const Type&, MutableView&);
   static std::optional<size_t> Size(Iterator);
   static Type Parse(Iterator);
+  std::string ToString();
\ No newline at end of file
diff --git a/gd/packet/parser/custom_type_checker.h b/gd/packet/parser/custom_type_checker.h
index 5d99622..31b4660 100644
--- a/gd/packet/parser/custom_type_checker.h
+++ b/gd/packet/parser/custom_type_checker.h
@@ -36,14 +36,17 @@
   template <class C, bool little_endian, std::optional<Iterator<little_endian>> (*)(C* vec, Iterator<little_endian> it)>
   struct ParseChecker {};
 
+  template <class C, std::string (C::*)() const>
+  struct ToStringChecker {};
+
   template <class C, bool little_endian>
   static int Test(SerializeChecker<C, &C::Serialize>*, SizeChecker<C, &C::size>*,
-                  ParseChecker<C, little_endian, &C::Parse>*);
+                  ParseChecker<C, little_endian, &C::Parse>*, ToStringChecker<C, &C::ToString>*);
 
   template <class C, bool little_endian>
   static char Test(...);
 
-  static constexpr bool value = (sizeof(Test<T, packet_little_endian>(0, 0, 0)) == sizeof(int));
+  static constexpr bool value = (sizeof(Test<T, packet_little_endian>(0, 0, 0, 0)) == sizeof(int));
 };
 }  // namespace packet
 }  // namespace bluetooth
diff --git a/gd/packet/parser/fields/array_field.cc b/gd/packet/parser/fields/array_field.cc
index 37dea16..c66252e 100644
--- a/gd/packet/parser/fields/array_field.cc
+++ b/gd/packet/parser/fields/array_field.cc
@@ -175,3 +175,23 @@
 const PacketField* ArrayField::GetElementField() const {
   return element_field_;
 }
+
+void ArrayField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << "\"ARRAY[\";";
+  s << "/* " << element_field_->GetDataType() << "   " << element_field_->GetFieldType() << " */";
+
+  std::string arr_idx = "arridx_" + accessor;
+  std::string arr_size = std::to_string(array_size_);
+  s << "for (size_t index = 0; index < " << arr_size << "; index++) {";
+  std::string element_accessor = "(" + accessor + "[index])";
+  s << "ss << ((index == 0) ? \"\" : \", \") << ";
+
+  if (element_field_->GetFieldType() == CustomField::kFieldType) {
+    s << element_accessor << ".ToString()";
+  } else {
+    element_field_->GenStringRepresentation(s, element_accessor);
+  }
+
+  s << ";}";
+  s << "ss << \"]\"";
+}
diff --git a/gd/packet/parser/fields/array_field.h b/gd/packet/parser/fields/array_field.h
index dd49f26..e31e8de 100644
--- a/gd/packet/parser/fields/array_field.h
+++ b/gd/packet/parser/fields/array_field.h
@@ -62,6 +62,8 @@
 
   virtual const PacketField* GetElementField() const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
   const std::string name_;
 
   const PacketField* element_field_{nullptr};
diff --git a/gd/packet/parser/fields/body_field.cc b/gd/packet/parser/fields/body_field.cc
index 30abf6e..632456f 100644
--- a/gd/packet/parser/fields/body_field.cc
+++ b/gd/packet/parser/fields/body_field.cc
@@ -71,3 +71,7 @@
 void BodyField::GenValidator(std::ostream&) const {
   // Do nothing
 }
+
+void BodyField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << "\"BODY REPRENTATION_UNIMPLEMENTED " << accessor << " \"";
+}
diff --git a/gd/packet/parser/fields/body_field.h b/gd/packet/parser/fields/body_field.h
index 656a935..d8c4ee0 100644
--- a/gd/packet/parser/fields/body_field.h
+++ b/gd/packet/parser/fields/body_field.h
@@ -50,6 +50,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
   // Body fields can only be dynamically sized.
   const SizeField* size_field_{nullptr};
 };
diff --git a/gd/packet/parser/fields/checksum_field.cc b/gd/packet/parser/fields/checksum_field.cc
index 4647992..67863c2 100644
--- a/gd/packet/parser/fields/checksum_field.cc
+++ b/gd/packet/parser/fields/checksum_field.cc
@@ -58,3 +58,8 @@
 void ChecksumField::GenValidator(std::ostream&) const {
   // Done in packet_def.cc
 }
+
+void ChecksumField::GenStringRepresentation(std::ostream& s, std::string) const {
+  // TODO: there is currently no way to get checksum value
+  s << "\"CHECKSUM\"";
+}
diff --git a/gd/packet/parser/fields/checksum_field.h b/gd/packet/parser/fields/checksum_field.h
index c15f023..0e0a4c2 100644
--- a/gd/packet/parser/fields/checksum_field.h
+++ b/gd/packet/parser/fields/checksum_field.h
@@ -46,6 +46,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   std::string type_name_;
 };
diff --git a/gd/packet/parser/fields/checksum_start_field.cc b/gd/packet/parser/fields/checksum_start_field.cc
index 5531c5a..3f87db2 100644
--- a/gd/packet/parser/fields/checksum_start_field.cc
+++ b/gd/packet/parser/fields/checksum_start_field.cc
@@ -61,3 +61,7 @@
 std::string ChecksumStartField::GetStartedFieldName() const {
   return started_field_name_;
 }
+
+void ChecksumStartField::GenStringRepresentation(std::ostream&, std::string) const {
+  // Print nothing for checksum start
+}
diff --git a/gd/packet/parser/fields/checksum_start_field.h b/gd/packet/parser/fields/checksum_start_field.h
index c63806b..8c61a82 100644
--- a/gd/packet/parser/fields/checksum_start_field.h
+++ b/gd/packet/parser/fields/checksum_start_field.h
@@ -51,6 +51,8 @@
 
   virtual std::string GetStartedFieldName() const;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   std::string started_field_name_;
 };
diff --git a/gd/packet/parser/fields/custom_field.cc b/gd/packet/parser/fields/custom_field.cc
index 4e387f8..fd39714 100644
--- a/gd/packet/parser/fields/custom_field.cc
+++ b/gd/packet/parser/fields/custom_field.cc
@@ -91,3 +91,7 @@
 void CustomField::GenValidator(std::ostream&) const {
   // Do nothing.
 }
+
+void CustomField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << accessor << "->ToString()";
+}
diff --git a/gd/packet/parser/fields/custom_field.h b/gd/packet/parser/fields/custom_field.h
index 621a3c8..7ba1b0f 100644
--- a/gd/packet/parser/fields/custom_field.h
+++ b/gd/packet/parser/fields/custom_field.h
@@ -49,6 +49,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   std::string type_name_;
 };
diff --git a/gd/packet/parser/fields/custom_field_fixed_size.cc b/gd/packet/parser/fields/custom_field_fixed_size.cc
index 687d48d..54463cd 100644
--- a/gd/packet/parser/fields/custom_field_fixed_size.cc
+++ b/gd/packet/parser/fields/custom_field_fixed_size.cc
@@ -62,3 +62,8 @@
 void CustomFieldFixedSize::GenValidator(std::ostream&) const {
   // Do nothing.
 }
+
+void CustomFieldFixedSize::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  // We assume that custom fields will have a ToString() method
+  s << accessor << ".ToString()";
+}
diff --git a/gd/packet/parser/fields/custom_field_fixed_size.h b/gd/packet/parser/fields/custom_field_fixed_size.h
index 97acff9..c53f0b0 100644
--- a/gd/packet/parser/fields/custom_field_fixed_size.h
+++ b/gd/packet/parser/fields/custom_field_fixed_size.h
@@ -41,5 +41,7 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
   std::string type_name_;
 };
diff --git a/gd/packet/parser/fields/enum_field.cc b/gd/packet/parser/fields/enum_field.cc
index 3080d43..8fe56c2 100644
--- a/gd/packet/parser/fields/enum_field.cc
+++ b/gd/packet/parser/fields/enum_field.cc
@@ -51,3 +51,7 @@
 void EnumField::GenValidator(std::ostream&) const {
   // Do nothing
 }
+
+void EnumField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << GetDataType() << "Text(" << accessor << ")";
+}
diff --git a/gd/packet/parser/fields/enum_field.h b/gd/packet/parser/fields/enum_field.h
index 11c64e0..f413553 100644
--- a/gd/packet/parser/fields/enum_field.h
+++ b/gd/packet/parser/fields/enum_field.h
@@ -41,6 +41,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   EnumDef enum_def_;
   std::string value_;
diff --git a/gd/packet/parser/fields/fixed_scalar_field.cc b/gd/packet/parser/fields/fixed_scalar_field.cc
index 9bfdf0e..e0ad2dd 100644
--- a/gd/packet/parser/fields/fixed_scalar_field.cc
+++ b/gd/packet/parser/fields/fixed_scalar_field.cc
@@ -33,3 +33,7 @@
 void FixedScalarField::GenValue(std::ostream& s) const {
   s << value_;
 }
+
+void FixedScalarField::GenStringRepresentation(std::ostream& s, std::string) const {
+  s << "+" << value_;
+}
diff --git a/gd/packet/parser/fields/fixed_scalar_field.h b/gd/packet/parser/fields/fixed_scalar_field.h
index 0070f6c..0112b27 100644
--- a/gd/packet/parser/fields/fixed_scalar_field.h
+++ b/gd/packet/parser/fields/fixed_scalar_field.h
@@ -33,6 +33,8 @@
 
   virtual std::string GetDataType() const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
   static const std::string field_type;
 
  private:
diff --git a/gd/packet/parser/fields/packet_field.cc b/gd/packet/parser/fields/packet_field.cc
index 7b0bdb7..b51999f 100644
--- a/gd/packet/parser/fields/packet_field.cc
+++ b/gd/packet/parser/fields/packet_field.cc
@@ -100,3 +100,7 @@
 const PacketField* PacketField::GetElementField() const {
   return nullptr;
 }
+
+void PacketField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << "\"REPRESENTATION_UNIMPLEMENTED " << GetFieldType() << " " << accessor << "\"";
+}
diff --git a/gd/packet/parser/fields/packet_field.h b/gd/packet/parser/fields/packet_field.h
index d9cc019..55653bd 100644
--- a/gd/packet/parser/fields/packet_field.h
+++ b/gd/packet/parser/fields/packet_field.h
@@ -106,6 +106,9 @@
   // Get field of nested elements if this is a container field, nullptr if none
   virtual const PacketField* GetElementField() const;
 
+  // Return string representation of this field, that can be displayed for debugging or logging purposes
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const;
+
   std::string GetDebugName() const override;
 
   ParseLocation GetLocation() const override;
diff --git a/gd/packet/parser/fields/payload_field.cc b/gd/packet/parser/fields/payload_field.cc
index c075996..57a1822 100644
--- a/gd/packet/parser/fields/payload_field.cc
+++ b/gd/packet/parser/fields/payload_field.cc
@@ -107,3 +107,8 @@
 void PayloadField::GenValidator(std::ostream&) const {
   // Do nothing
 }
+
+void PayloadField::GenStringRepresentation(std::ostream& s, std::string) const {
+  // TODO: we should parse the child packets
+  s << "\"PAYLOAD[]\"";
+}
diff --git a/gd/packet/parser/fields/payload_field.h b/gd/packet/parser/fields/payload_field.h
index 11e6267..b4ead5b 100644
--- a/gd/packet/parser/fields/payload_field.h
+++ b/gd/packet/parser/fields/payload_field.h
@@ -54,6 +54,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
   // Payload fields can only be dynamically sized.
   const SizeField* size_field_;
   // Only used if the size of the payload is based on another field.
diff --git a/gd/packet/parser/fields/scalar_field.cc b/gd/packet/parser/fields/scalar_field.cc
index 320534a..1421273 100644
--- a/gd/packet/parser/fields/scalar_field.cc
+++ b/gd/packet/parser/fields/scalar_field.cc
@@ -129,3 +129,7 @@
 void ScalarField::GenValidator(std::ostream&) const {
   // Do nothing
 }
+
+void ScalarField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << "+" << accessor;
+}
diff --git a/gd/packet/parser/fields/scalar_field.h b/gd/packet/parser/fields/scalar_field.h
index 65f897e..e15d6c1 100644
--- a/gd/packet/parser/fields/scalar_field.h
+++ b/gd/packet/parser/fields/scalar_field.h
@@ -49,6 +49,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   const int size_;
 };
diff --git a/gd/packet/parser/fields/size_field.cc b/gd/packet/parser/fields/size_field.cc
index 0a1b80b..ce2c899 100644
--- a/gd/packet/parser/fields/size_field.cc
+++ b/gd/packet/parser/fields/size_field.cc
@@ -61,3 +61,7 @@
 std::string SizeField::GetSizedFieldName() const {
   return sized_field_name_;
 }
+
+void SizeField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << accessor;
+}
diff --git a/gd/packet/parser/fields/size_field.h b/gd/packet/parser/fields/size_field.h
index b6e8639..2d40c42 100644
--- a/gd/packet/parser/fields/size_field.h
+++ b/gd/packet/parser/fields/size_field.h
@@ -46,6 +46,8 @@
 
   virtual std::string GetSizedFieldName() const;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   int size_;
   std::string sized_field_name_;
diff --git a/gd/packet/parser/fields/struct_field.cc b/gd/packet/parser/fields/struct_field.cc
index fbdafcf..5ada67a 100644
--- a/gd/packet/parser/fields/struct_field.cc
+++ b/gd/packet/parser/fields/struct_field.cc
@@ -83,3 +83,7 @@
 void StructField::GenValidator(std::ostream&) const {
   // Do nothing
 }
+
+void StructField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << accessor << ".ToString()";
+}
diff --git a/gd/packet/parser/fields/struct_field.h b/gd/packet/parser/fields/struct_field.h
index 1f4f100..9d1d463 100644
--- a/gd/packet/parser/fields/struct_field.h
+++ b/gd/packet/parser/fields/struct_field.h
@@ -49,6 +49,8 @@
 
   virtual void GenValidator(std::ostream&) const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
  private:
   std::string type_name_;
 
diff --git a/gd/packet/parser/fields/vector_field.cc b/gd/packet/parser/fields/vector_field.cc
index 6990d30..7f37dbe 100644
--- a/gd/packet/parser/fields/vector_field.cc
+++ b/gd/packet/parser/fields/vector_field.cc
@@ -230,3 +230,22 @@
 const PacketField* VectorField::GetElementField() const {
   return element_field_;
 }
+
+void VectorField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
+  s << "\"VECTOR[\";";
+
+  std::string arr_idx = "arridx_" + accessor;
+  std::string vec_size = accessor + ".size()";
+  s << "for (size_t index = 0; index < " << vec_size << "; index++) {";
+  std::string element_accessor = "(" + accessor + "[index])";
+  s << "ss << ((index == 0) ? \"\" : \", \") << ";
+
+  if (element_field_->GetFieldType() == CustomField::kFieldType) {
+    s << element_accessor << ".ToString()";
+  } else {
+    element_field_->GenStringRepresentation(s, element_accessor);
+  }
+
+  s << ";}";
+  s << "ss << \"]\"";
+}
diff --git a/gd/packet/parser/fields/vector_field.h b/gd/packet/parser/fields/vector_field.h
index b2ae95d..827f718 100644
--- a/gd/packet/parser/fields/vector_field.h
+++ b/gd/packet/parser/fields/vector_field.h
@@ -67,6 +67,8 @@
 
   virtual const PacketField* GetElementField() const override;
 
+  virtual void GenStringRepresentation(std::ostream& s, std::string accessor) const override;
+
   const std::string name_;
 
   const PacketField* element_field_{nullptr};
diff --git a/gd/packet/parser/main.cc b/gd/packet/parser/main.cc
index 8b6737a..82deacf 100644
--- a/gd/packet/parser/main.cc
+++ b/gd/packet/parser/main.cc
@@ -121,6 +121,7 @@
   out_file << "#pragma once\n";
   out_file << "\n\n";
   out_file << "#include <stdint.h>\n";
+  out_file << "#include <sstream>\n";
   out_file << "#include <string>\n";
   out_file << "#include <functional>\n";
   out_file << "\n\n";
diff --git a/gd/packet/parser/packet_def.cc b/gd/packet/parser/packet_def.cc
index c37b63b..b2eb199 100644
--- a/gd/packet/parser/packet_def.cc
+++ b/gd/packet/parser/packet_def.cc
@@ -62,6 +62,10 @@
   GenValidator(s);
   s << "\n";
 
+  s << " public:";
+  GenParserToString(s);
+  s << "\n";
+
   s << " protected:\n";
   // Constructor from a View
   if (parent_ != nullptr) {
@@ -283,6 +287,35 @@
   }
 }
 
+void PacketDef::GenParserToString(std::ostream& s) const {
+  s << "virtual std::string ToString() " << (parent_ != nullptr ? " override" : "") << " {";
+  s << "std::stringstream ss;";
+  s << "ss << std::showbase << std::hex << \"" << name_ << " { \";";
+
+  if (fields_.size() > 0) {
+    s << "ss << \"\" ";
+    bool firstfield = true;
+    for (const auto& field : fields_) {
+      if (field->GetFieldType() == ReservedField::kFieldType || field->GetFieldType() == FixedScalarField::kFieldType ||
+          field->GetFieldType() == ChecksumStartField::kFieldType)
+        continue;
+
+      s << (firstfield ? " << \"" : " << \", ") << field->GetName() << " = \" << ";
+
+      field->GenStringRepresentation(s, field->GetGetterFunctionName() + "()");
+
+      if (firstfield) {
+        firstfield = false;
+      }
+    }
+    s << ";";
+  }
+
+  s << "ss << \" }\";";
+  s << "return ss.str();";
+  s << "}\n";
+}
+
 void PacketDef::GenBuilderDefinition(std::ostream& s) const {
   s << "class " << name_ << "Builder";
   if (parent_ != nullptr) {
diff --git a/gd/packet/parser/packet_def.h b/gd/packet/parser/packet_def.h
index e8acdc3..91be98d 100644
--- a/gd/packet/parser/packet_def.h
+++ b/gd/packet/parser/packet_def.h
@@ -39,6 +39,8 @@
 
   void GenValidator(std::ostream& s) const;
 
+  void GenParserToString(std::ostream& s) const;
+
   TypeDef::Type GetDefinitionType() const;
 
   void GenBuilderDefinition(std::ostream& s) const;
diff --git a/gd/packet/parser/struct_def.cc b/gd/packet/parser/struct_def.cc
index ebd8b45..b174836 100644
--- a/gd/packet/parser/struct_def.cc
+++ b/gd/packet/parser/struct_def.cc
@@ -45,6 +45,37 @@
   s << "}";
 }
 
+void StructDef::GenToString(std::ostream& s) const {
+  s << "std::string ToString() {";
+  s << "std::stringstream ss;";
+  s << "ss << std::hex << std::showbase << \"" << name_ << " { \";";
+
+  if (fields_.size() > 0) {
+    s << "ss";
+    bool firstfield = true;
+    for (const auto& field : fields_) {
+      if (field->GetFieldType() == ReservedField::kFieldType ||
+          field->GetFieldType() == ChecksumStartField::kFieldType ||
+          field->GetFieldType() == FixedScalarField::kFieldType || field->GetFieldType() == CountField::kFieldType ||
+          field->GetFieldType() == SizeField::kFieldType)
+        continue;
+
+      s << (firstfield ? " << \"" : " << \", ") << field->GetName() << " = \" << ";
+
+      field->GenStringRepresentation(s, field->GetName() + "_");
+
+      if (firstfield) {
+        firstfield = false;
+      }
+    }
+    s << ";";
+  }
+
+  s << "ss << \" }\";";
+  s << "return ss.str();";
+  s << "}\n";
+}
+
 void StructDef::GenParse(std::ostream& s) const {
   std::string iterator = (is_little_endian_ ? "Iterator<kLittleEndian>" : "Iterator<!kLittleEndian>");
 
@@ -81,11 +112,7 @@
 
   if (!fields_.HasBody()) {
     s << "size_t end_index = struct_begin_it.NumBytesRemaining();";
-    if (parent_ != nullptr) {
-      s << "if (end_index < " << GetSize().bytes() << " - to_fill->" << parent_->name_ << "::size())";
-    } else {
-      s << "if (end_index < " << GetSize().bytes() << ")";
-    }
+    s << "if (end_index < " << GetSize().bytes() << ")";
     s << "{ return struct_begin_it.Subrange(0,0);}";
   }
 
@@ -127,7 +154,7 @@
       s << "}";
     }
   }
-  s << "return struct_begin_it + to_fill->" << name_ << "::size();";
+  s << "return struct_begin_it + to_fill->size();";
   s << "}";
 }
 
@@ -175,6 +202,9 @@
   GenSpecialize(s);
   s << "\n";
 
+  GenToString(s);
+  s << "\n";
+
   GenMembers(s);
   for (const auto& field : fields_) {
     if (field->GetFieldType() == CountField::kFieldType || field->GetFieldType() == SizeField::kFieldType) {
diff --git a/gd/packet/parser/struct_def.h b/gd/packet/parser/struct_def.h
index 74c1b04..b4c890b 100644
--- a/gd/packet/parser/struct_def.h
+++ b/gd/packet/parser/struct_def.h
@@ -36,6 +36,8 @@
 
   void GenSpecialize(std::ostream& s) const;
 
+  void GenToString(std::ostream& s) const;
+
   void GenParse(std::ostream& s) const;
 
   void GenParseFunctionPrototype(std::ostream& s) const;
diff --git a/gd/packet/parser/test/generated_packet_test.cc b/gd/packet/parser/test/generated_packet_test.cc
index 4feb11e..45b3ec8 100644
--- a/gd/packet/parser/test/generated_packet_test.cc
+++ b/gd/packet/parser/test/generated_packet_test.cc
@@ -1899,6 +1899,68 @@
   ASSERT_EQ(1, view.GetAnArray().size());
 }
 
+TEST(GeneratedPacketTest, testToStringOutput) {
+  std::vector<TwoRelatedNumbersBe> count_array;
+  for (uint8_t i = 1; i < 5; i++) {
+    TwoRelatedNumbersBe trn;
+    trn.id_ = i;
+    trn.count_ = 0x0102 * i;
+    count_array.push_back(trn);
+  }
+
+  auto packet = ArrayOfStructBeBuilder::Create(count_array);
+
+  ASSERT_EQ(array_of_struct_be.size(), packet->size());
+
+  std::shared_ptr<std::vector<uint8_t>> packet_bytes = std::make_shared<std::vector<uint8_t>>();
+  BitInserter it(*packet_bytes);
+  packet->Serialize(it);
+
+  ASSERT_EQ(array_of_struct_be.size(), packet_bytes->size());
+  for (size_t i = 0; i < array_of_struct_be.size(); i++) {
+    ASSERT_EQ(array_of_struct_be[i], packet_bytes->at(i));
+  }
+
+  PacketView<!kLittleEndian> packet_bytes_view(packet_bytes);
+  auto view = ArrayOfStructBeView::Create(packet_bytes_view);
+  ASSERT_TRUE(view.IsValid());
+
+  ASSERT_EQ(
+      "ArrayOfStructBe { array_count = 0x4, array = VECTOR[TwoRelatedNumbersBe { id = 0x1, count = 0x102 }, "
+      "TwoRelatedNumbersBe { id = 0x2, count = 0x204 }, TwoRelatedNumbersBe { id = 0x3, count = 0x306 }, "
+      "TwoRelatedNumbersBe { id = 0x4, count = 0x408 }] }",
+      view.ToString());
+}
+
+TEST(GeneratedPacketTest, testToStringOneFixedTypesStruct) {
+  StructWithFixedTypes swf;
+  swf.four_bits_ = FourBits::FIVE;
+  swf.id_ = 0x0d;
+  swf.array_ = {{0x01, 0x02, 0x03}};
+  swf.six_bytes_ = SixBytes{{0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6}};
+
+  auto packet = OneFixedTypesStructBuilder::Create(swf);
+  ASSERT_EQ(one_fixed_types_struct.size(), packet->size());
+
+  std::shared_ptr<std::vector<uint8_t>> packet_bytes = std::make_shared<std::vector<uint8_t>>();
+  BitInserter it(*packet_bytes);
+  packet->Serialize(it);
+
+  ASSERT_EQ(one_fixed_types_struct.size(), packet_bytes->size());
+  for (size_t i = 0; i < one_fixed_types_struct.size(); i++) {
+    ASSERT_EQ(one_fixed_types_struct[i], packet_bytes->at(i));
+  }
+
+  PacketView<kLittleEndian> packet_bytes_view(packet_bytes);
+  auto view = OneFixedTypesStructView::Create(packet_bytes_view);
+  ASSERT_TRUE(view.IsValid());
+
+  ASSERT_EQ(
+      "OneFixedTypesStruct { one = StructWithFixedTypes { four_bits = FIVE, id = 0xd, array = ARRAY[0x1, 0x2, 0x3], "
+      "example_checksum = CHECKSUM, six_bytes = SixBytes } }",
+      view.ToString());
+}
+
 }  // namespace parser
 }  // namespace packet
 }  // namespace bluetooth
diff --git a/gd/packet/parser/test/six_bytes.h b/gd/packet/parser/test/six_bytes.h
index 0f26324..04b0599 100644
--- a/gd/packet/parser/test/six_bytes.h
+++ b/gd/packet/parser/test/six_bytes.h
@@ -53,6 +53,9 @@
   bool operator!=(const SixBytes& rhs) const {
     return !(*this == rhs);
   }
+  std::string ToString() const {
+    return "SixBytes";
+  }
 };
 
 }  // namespace test
diff --git a/gd/packet/parser/test/variable.h b/gd/packet/parser/test/variable.h
index c452a37..920c14f 100644
--- a/gd/packet/parser/test/variable.h
+++ b/gd/packet/parser/test/variable.h
@@ -62,6 +62,10 @@
     *instance = ss.str();
     return it;
   }
+
+  std::string ToString() const {
+    return data;
+  }
 };
 
 }  // namespace test
diff --git a/gd/security/cert/simple_security_test.py b/gd/security/cert/simple_security_test.py
index cd4847b..57a1ffa 100644
--- a/gd/security/cert/simple_security_test.py
+++ b/gd/security/cert/simple_security_test.py
@@ -19,7 +19,7 @@
 import sys
 import logging
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 from cert.event_stream import EventStream
 from google.protobuf import empty_pb2 as empty_proto
 from facade import common_pb2 as common
@@ -34,7 +34,7 @@
 import bluetooth_packets_python3 as bt_packets
 
 
-class SimpleSecurityTest(GdFacadeOnlyBaseTestClass):
+class SimpleSecurityTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='SECURITY', cert_module='L2CAP')
diff --git a/gd/security/channel/security_manager_channel.cc b/gd/security/channel/security_manager_channel.cc
index 81bdda1..c07446c 100644
--- a/gd/security/channel/security_manager_channel.cc
+++ b/gd/security/channel/security_manager_channel.cc
@@ -57,6 +57,7 @@
   if (fixed_channel_service_ != nullptr) {
     fixed_channel_service_->Unregister(common::Bind(&SecurityManagerChannel::OnUnregistered, common::Unretained(this)),
                                        handler_);
+    fixed_channel_service_.reset();
   }
 }
 
@@ -132,7 +133,6 @@
             result.connection_result_code);
   auto entry = fixed_channel_map_.find(address);
   if (entry != fixed_channel_map_.end()) {
-    entry->second->Release();
     entry->second.reset();
     fixed_channel_map_.erase(entry);
   }
@@ -144,7 +144,6 @@
   LOG_ERROR("Connection closed due to: %s", hci::ErrorCodeText(error_code).c_str());
   auto entry = fixed_channel_map_.find(address);
   if (entry != fixed_channel_map_.end()) {
-    entry->second->Release();
     entry->second.reset();
     fixed_channel_map_.erase(entry);
   }
diff --git a/gd/security/channel/security_manager_channel_unittest.cc b/gd/security/channel/security_manager_channel_unittest.cc
index b0fa13e..8e60ba1 100644
--- a/gd/security/channel/security_manager_channel_unittest.cc
+++ b/gd/security/channel/security_manager_channel_unittest.cc
@@ -545,6 +545,13 @@
   ASSERT_EQ(OpCode::DELETE_STORED_LINK_KEY, packet_view.GetOpCode());
 }
 
+TEST_F(SecurityManagerChannelTest, recv_encryption_change) {
+  uint16_t connection_handle = 0x0;
+  hci_layer_->IncomingEvent(
+      hci::EncryptionChangeBuilder::Create(hci::ErrorCode::SUCCESS, connection_handle, hci::EncryptionEnabled::ON));
+  ASSERT_TRUE(callback_->receivedEncryptionChange);
+}
+
 TEST_F(SecurityManagerChannelTest, recv_encryption_key_refresh) {
   uint16_t connection_handle = 0x0;
   hci_layer_->IncomingEvent(
diff --git a/gd/security/facade.cc b/gd/security/facade.cc
index a941b40..cfb7445 100644
--- a/gd/security/facade.cc
+++ b/gd/security/facade.cc
@@ -164,6 +164,8 @@
     bond_events_.OnIncomingEvent(bonded);
   }
 
+  void OnEncryptionStateChanged(hci::EncryptionChangeView encryption_change_view) override {}
+
   void OnDeviceUnbonded(hci::AddressWithType peer) override {
     LOG_INFO("%s", peer.ToString().c_str());
     BondMsg unbonded;
diff --git a/gd/security/internal/security_manager_impl.cc b/gd/security/internal/security_manager_impl.cc
index a45b376..ebe1ee7 100644
--- a/gd/security/internal/security_manager_impl.cc
+++ b/gd/security/internal/security_manager_impl.cc
@@ -159,6 +159,13 @@
   }
 }
 
+void SecurityManagerImpl::NotifyEncryptionStateChanged(hci::EncryptionChangeView encryption_change_view) {
+  for (auto& iter : listeners_) {
+    iter.second->Post(common::Bind(&ISecurityManagerListener::OnEncryptionStateChanged, common::Unretained(iter.first),
+                                   encryption_change_view));
+  }
+}
+
 template <class T>
 void SecurityManagerImpl::HandleEvent(T packet) {
   ASSERT(packet.IsValid());
@@ -178,7 +185,7 @@
         security_database_.FindOrCreate(hci::AddressWithType{bd_addr, hci::AddressType::PUBLIC_DEVICE_ADDRESS});
     auto authentication_requirements = hci::AuthenticationRequirements::NO_BONDING;
     DispatchPairingHandler(record, true, authentication_requirements);
-    entry = pairing_handler_map_.find(packet.GetBdAddr());
+    entry = pairing_handler_map_.find(bd_addr);
   }
   entry->second->OnReceive(packet);
 }
@@ -226,15 +233,16 @@
       break;
 
     case hci::EventCode::ENCRYPTION_CHANGE: {
-      EncryptionChangeView enc_chg_packet = EncryptionChangeView::Create(event);
-      if (!enc_chg_packet.IsValid()) {
+      EncryptionChangeView encryption_change_view = EncryptionChangeView::Create(event);
+      if (!encryption_change_view.IsValid()) {
         LOG_ERROR("Invalid EncryptionChange packet received");
         return;
       }
-      if (enc_chg_packet.GetConnectionHandle() == pending_le_pairing_.connection_handle_) {
+      if (encryption_change_view.GetConnectionHandle() == pending_le_pairing_.connection_handle_) {
         pending_le_pairing_.handler_->OnHciEvent(event);
         return;
       }
+      NotifyEncryptionStateChanged(encryption_change_view);
       break;
     }
 
diff --git a/gd/security/internal/security_manager_impl.h b/gd/security/internal/security_manager_impl.h
index efd69f7..df5af32 100644
--- a/gd/security/internal/security_manager_impl.h
+++ b/gd/security/internal/security_manager_impl.h
@@ -19,6 +19,7 @@
 #include <unordered_map>
 #include <utility>
 
+#include "hci/acl_manager.h"
 #include "hci/classic_device.h"
 #include "l2cap/le/l2cap_le_module.h"
 #include "os/handler.h"
@@ -148,6 +149,7 @@
   void NotifyDeviceBonded(hci::AddressWithType device);
   void NotifyDeviceBondFailed(hci::AddressWithType device, PairingResultOrFailure status);
   void NotifyDeviceUnbonded(hci::AddressWithType device);
+  void NotifyEncryptionStateChanged(hci::EncryptionChangeView encryption_change_view);
 
  private:
   template <class T>
diff --git a/gd/security/pairing/classic_pairing_handler.cc b/gd/security/pairing/classic_pairing_handler.cc
index 27bdb17..c5ef608 100644
--- a/gd/security/pairing/classic_pairing_handler.cc
+++ b/gd/security/pairing/classic_pairing_handler.cc
@@ -86,6 +86,8 @@
 }
 
 void ClassicPairingHandler::Cancel() {
+  if (is_cancelled_) return;
+  is_cancelled_ = true;
   PairingResultOrFailure result = PairingResult();
   if (last_status_ != hci::ErrorCode::SUCCESS) {
     result = PairingFailure(hci::ErrorCodeText(last_status_));
@@ -129,8 +131,6 @@
   LOG_INFO("Received: %s", hci::EventCodeText(packet.GetEventCode()).c_str());
   ASSERT_LOG(GetRecord()->GetPseudoAddress().GetAddress() == packet.GetBdAddr(), "Address mismatch");
   GetRecord()->SetLinkKey(packet.GetLinkKey(), packet.GetKeyType());
-  // We are done with the pairing flow
-  Cancel();
 }
 
 void ClassicPairingHandler::OnReceive(hci::IoCapabilityRequestView packet) {
@@ -162,8 +162,8 @@
   if (last_status_ != hci::ErrorCode::SUCCESS) {
     LOG_INFO("Failed SimplePairingComplete: %s", hci::ErrorCodeText(last_status_).c_str());
     // Cancel here since we won't get LinkKeyNotification
-    Cancel();
   }
+  Cancel();
 }
 
 void ClassicPairingHandler::OnReceive(hci::ReturnLinkKeysView packet) {
diff --git a/gd/security/pairing/classic_pairing_handler.h b/gd/security/pairing/classic_pairing_handler.h
index 518e738..c6493f6 100644
--- a/gd/security/pairing/classic_pairing_handler.h
+++ b/gd/security/pairing/classic_pairing_handler.h
@@ -98,6 +98,7 @@
   UI* user_interface_;
   os::Handler* user_interface_handler_;
   std::string device_name_;
+  bool is_cancelled_ = false;
 
   hci::ErrorCode last_status_ = hci::ErrorCode::UNKNOWN_HCI_COMMAND;
   bool locally_initiated_ = false;
diff --git a/gd/security/pairing/classic_pairing_handler_unittest.cc b/gd/security/pairing/classic_pairing_handler_unittest.cc
index c300e5b..efa77f7 100644
--- a/gd/security/pairing/classic_pairing_handler_unittest.cc
+++ b/gd/security/pairing/classic_pairing_handler_unittest.cc
@@ -182,8 +182,8 @@
 //  <- IoCapabilityResponse
 //  <- UserConfirmationRequest
 //  -> UserConfirmationRequestReply (auto)
-//  <- SimplePairingComplete
 //  <- LinkKeyNotification
+//  <- SimplePairingComplete
 //  <- AuthenticationComplete
 //  -> SetConnectionEncryption
 //  <- EncryptionChange
diff --git a/gd/security/pairing_handler_le_unittest.cc b/gd/security/pairing_handler_le_unittest.cc
index 1f585fe..94c08ee 100644
--- a/gd/security/pairing_handler_le_unittest.cc
+++ b/gd/security/pairing_handler_le_unittest.cc
@@ -45,18 +45,6 @@
   return CommandView::Create(temp_cmd_view);
 }
 
-std::condition_variable outgoing_l2cap_blocker_;
-std::optional<bluetooth::security::CommandView> outgoing_l2cap_packet_;
-
-bool WaitForOutgoingL2capPacket() {
-  std::mutex mutex;
-  std::unique_lock<std::mutex> lock(mutex);
-  if (outgoing_l2cap_blocker_.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) {
-    return false;
-  }
-  return true;
-}
-
 class PairingResultHandlerMock {
  public:
   MOCK_CONST_METHOD1(OnPairingFinished, void(PairingResultOrFailure));
@@ -111,11 +99,34 @@
     outgoing_l2cap_blocker_.notify_one();
   }
 
+  std::optional<bluetooth::security::CommandView> WaitForOutgoingL2capPacket() {
+    std::mutex mutex;
+    std::unique_lock<std::mutex> lock(mutex);
+
+    // It is possible that we lost wakeup from condition_variable, check if data is already waiting to be processed
+    if (outgoing_l2cap_packet_ != std::nullopt) {
+      std::optional<bluetooth::security::CommandView> tmp = std::nullopt;
+      outgoing_l2cap_packet_.swap(tmp);
+      return tmp;
+    }
+
+    // Data not ready yet, wait for it.
+    if (outgoing_l2cap_blocker_.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) {
+      return std::nullopt;
+    }
+
+    std::optional<bluetooth::security::CommandView> tmp = std::nullopt;
+    outgoing_l2cap_packet_.swap(tmp);
+    return tmp;
+  }
+
  public:
   os::Thread* thread_;
   os::Handler* handler_;
   std::unique_ptr<common::BidiQueue<packet::PacketView<packet::kLittleEndian>, packet::BasePacketBuilder>> bidi_queue_;
   std::unique_ptr<os::EnqueueBuffer<packet::BasePacketBuilder>> up_buffer_;
+  std::condition_variable outgoing_l2cap_blocker_;
+  std::optional<bluetooth::security::CommandView> outgoing_l2cap_packet_ = std::nullopt;
 };
 
 InitialInformations initial_informations{
@@ -144,8 +155,9 @@
   std::unique_ptr<PairingHandlerLe> pairing_handler =
       std::make_unique<PairingHandlerLe>(PairingHandlerLe::PHASE1, initial_informations);
 
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(outgoing_l2cap_packet_->GetCode(), Code::PAIRING_REQUEST);
+  std::optional<bluetooth::security::CommandView> pairing_request = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(pairing_request.has_value());
+  EXPECT_EQ(pairing_request->GetCode(), Code::PAIRING_REQUEST);
 
   EXPECT_CALL(*pairingResult, OnPairingFinished(VariantWith<PairingFailure>(_))).Times(1);
 
@@ -154,8 +166,9 @@
   bad_pairing_response.IsValid();
   pairing_handler->OnCommandView(bad_pairing_response);
 
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(outgoing_l2cap_packet_->GetCode(), Code::PAIRING_FAILED);
+  std::optional<bluetooth::security::CommandView> pairing_failure = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(pairing_failure.has_value());
+  EXPECT_EQ(pairing_failure->GetCode(), Code::PAIRING_FAILED);
 }
 
 TEST_F(PairingHandlerUnitTest, test_secure_connections_just_works) {
@@ -168,9 +181,10 @@
   std::unique_ptr<PairingHandlerLe> pairing_handler =
       std::make_unique<PairingHandlerLe>(PairingHandlerLe::PHASE1, initial_informations);
 
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(outgoing_l2cap_packet_->GetCode(), Code::PAIRING_REQUEST);
-  CommandView pairing_request = outgoing_l2cap_packet_.value();
+  std::optional<bluetooth::security::CommandView> pairing_request_pkt = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(pairing_request_pkt.has_value());
+  EXPECT_EQ(pairing_request_pkt->GetCode(), Code::PAIRING_REQUEST);
+  CommandView pairing_request = pairing_request_pkt.value();
 
   auto pairing_response = BuilderToView(
       PairingResponseBuilder::Create(IoCapability::KEYBOARD_DISPLAY, OobDataFlag::NOT_PRESENT,
@@ -179,10 +193,11 @@
   // Phase 1 finished.
 
   // pairing public key
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(Code::PAIRING_PUBLIC_KEY, outgoing_l2cap_packet_->GetCode());
+  std::optional<bluetooth::security::CommandView> public_key_pkt = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(public_key_pkt.has_value());
+  EXPECT_EQ(Code::PAIRING_PUBLIC_KEY, public_key_pkt->GetCode());
   EcdhPublicKey my_public_key;
-  auto ppkv = PairingPublicKeyView::Create(outgoing_l2cap_packet_.value());
+  auto ppkv = PairingPublicKeyView::Create(public_key_pkt.value());
   ppkv.IsValid();
   my_public_key.x = ppkv.GetPublicKeyX();
   my_public_key.y = ppkv.GetPublicKeyY();
@@ -205,9 +220,10 @@
   pairing_handler->OnCommandView(BuilderToView(PairingConfirmBuilder::Create(Cb)));
 
   // random
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(Code::PAIRING_RANDOM, outgoing_l2cap_packet_->GetCode());
-  auto prv = PairingRandomView::Create(outgoing_l2cap_packet_.value());
+  std::optional<bluetooth::security::CommandView> random_pkt = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(random_pkt.has_value());
+  EXPECT_EQ(Code::PAIRING_RANDOM, random_pkt->GetCode());
+  auto prv = PairingRandomView::Create(random_pkt.value());
   prv.IsValid();
   Octet16 Na = prv.GetRandomValue();
 
@@ -237,9 +253,10 @@
   Octet16 Ea = crypto_toolbox::f6(mac_key, Na, Nb, rb, iocapA.data(), a, b);
   Octet16 Eb = crypto_toolbox::f6(mac_key, Nb, Na, ra, iocapB.data(), b, a);
 
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(Code::PAIRING_DH_KEY_CHECK, outgoing_l2cap_packet_->GetCode());
-  auto pdhkcv = PairingDhKeyCheckView::Create(outgoing_l2cap_packet_.value());
+  std::optional<bluetooth::security::CommandView> dh_key_pkt = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(dh_key_pkt.has_value());
+  EXPECT_EQ(Code::PAIRING_DH_KEY_CHECK, dh_key_pkt->GetCode());
+  auto pdhkcv = PairingDhKeyCheckView::Create(dh_key_pkt.value());
   pdhkcv.IsValid();
   EXPECT_EQ(pdhkcv.GetDhKeyCheck(), Ea);
 
@@ -280,8 +297,9 @@
   // Simulate user accepting the pairing in UI
   pairing_handler->OnUiAction(PairingEvent::PAIRING_ACCEPTED, 0x01 /* Non-zero value means success */);
 
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(Code::PAIRING_REQUEST, outgoing_l2cap_packet_->GetCode());
+  std::optional<bluetooth::security::CommandView> pairing_request_pkt = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(pairing_request_pkt.has_value());
+  EXPECT_EQ(Code::PAIRING_REQUEST, pairing_request_pkt->GetCode());
 
   // We don't care for the rest of the flow, let it die.
   pairing_handler.reset();
@@ -322,8 +340,9 @@
   // Simulate user accepting the pairing in UI
   pairing_handler->OnUiAction(PairingEvent::PAIRING_ACCEPTED, 0x01 /* Non-zero value means success */);
 
-  EXPECT_TRUE(WaitForOutgoingL2capPacket());
-  EXPECT_EQ(Code::PAIRING_RESPONSE, outgoing_l2cap_packet_->GetCode());
+  std::optional<bluetooth::security::CommandView> pairing_response_pkt = WaitForOutgoingL2capPacket();
+  EXPECT_TRUE(pairing_response_pkt.has_value());
+  EXPECT_EQ(Code::PAIRING_RESPONSE, pairing_response_pkt->GetCode());
   // Phase 1 finished.
 
   // We don't care for the rest of the flow, it's handled in in other tests. let it die.
diff --git a/gd/security/security_manager_listener.h b/gd/security/security_manager_listener.h
index edc4e6b..9cad4aa 100644
--- a/gd/security/security_manager_listener.h
+++ b/gd/security/security_manager_listener.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "common/callback.h"
+#include "hci/acl_manager.h"
 
 namespace bluetooth {
 namespace security {
@@ -50,6 +51,13 @@
    * @param address of the device that failed to bond
    */
   virtual void OnDeviceBondFailed(bluetooth::hci::AddressWithType device) = 0;
+
+  /**
+   * Called as a result of a failure during the bonding process.
+   *
+   * @param address of the device that failed to bond
+   */
+  virtual void OnEncryptionStateChanged(hci::EncryptionChangeView encryption_change_view) = 0;
 };
 
 }  // namespace security
diff --git a/gd/security/security_module.cc b/gd/security/security_module.cc
index 426aea4..caf9dee 100644
--- a/gd/security/security_module.cc
+++ b/gd/security/security_module.cc
@@ -21,6 +21,7 @@
 #include "os/handler.h"
 #include "os/log.h"
 
+#include "hci/acl_manager.h"
 #include "hci/hci_layer.h"
 #include "l2cap/le/l2cap_le_module.h"
 #include "security/channel/security_manager_channel.h"
@@ -34,7 +35,7 @@
 
 struct SecurityModule::impl {
   impl(os::Handler* security_handler, l2cap::le::L2capLeModule* l2cap_le_module,
-       l2cap::classic::L2capClassicModule* l2cap_classic_module, hci::HciLayer* hci_layer)
+       l2cap::classic::L2capClassicModule* l2cap_classic_module, hci::HciLayer* hci_layer, hci::AclManager* acl_manager)
       : security_handler_(security_handler), l2cap_le_module_(l2cap_le_module),
         security_manager_channel_(new channel::SecurityManagerChannel(security_handler_, hci_layer,
                                                                       l2cap_classic_module->GetFixedChannelManager())),
@@ -44,6 +45,7 @@
   l2cap::le::L2capLeModule* l2cap_le_module_;
   channel::SecurityManagerChannel* security_manager_channel_;
   hci::HciLayer* hci_layer_;
+
   internal::SecurityManagerImpl security_manager_impl{security_handler_, l2cap_le_module_, security_manager_channel_,
                                                       hci_layer_};
   ~impl() {
@@ -55,11 +57,15 @@
   list->add<l2cap::le::L2capLeModule>();
   list->add<l2cap::classic::L2capClassicModule>();
   list->add<hci::HciLayer>();
+  list->add<hci::AclManager>();
 }
 
 void SecurityModule::Start() {
   pimpl_ = std::make_unique<impl>(GetHandler(), GetDependency<l2cap::le::L2capLeModule>(),
-                                  GetDependency<l2cap::classic::L2capClassicModule>(), GetDependency<hci::HciLayer>());
+                                  GetDependency<l2cap::classic::L2capClassicModule>(), GetDependency<hci::HciLayer>(),
+                                  GetDependency<hci::AclManager>());
+
+  GetDependency<hci::AclManager>()->SetSecurityModule(this);
 }
 
 void SecurityModule::Stop() {
diff --git a/gd/setup.py b/gd/setup.py
new file mode 100644
index 0000000..f6b5ba3
--- /dev/null
+++ b/gd/setup.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - 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.
+
+from distutils import log
+import os
+from setuptools import setup, find_packages
+from setuptools.command.install import install
+from setuptools.command.develop import develop
+import stat
+import subprocess
+import sys
+
+install_requires = [
+    'grpcio',
+]
+
+host_executables = [
+    'root-canal',
+    'bluetooth_stack_with_facade',
+]
+
+
+def setup_acts_for_cmd_or_die(cmd_str):
+    acts_framework_dir = os.path.abspath('acts_framework')
+    acts_setup_bin = os.path.join(acts_framework_dir, 'setup.py')
+    cmd = [sys.executable, acts_setup_bin, cmd_str]
+    subprocess.check_call(cmd, cwd=acts_framework_dir)
+
+
+def set_permssions_for_host_executables(outputs):
+    for file in outputs:
+        if os.path.basename(file) in host_executables:
+            current_mode = os.stat(file).st_mode
+            new_mode = current_mode | stat.S_IEXEC
+            os.chmod(file, new_mode)
+            log.log(
+                log.INFO, "Changed file mode of %s from %s to %s" %
+                (file, oct(current_mode), oct(new_mode)))
+
+
+class InstallLocalPackagesForInstallation(install):
+
+    def run(self):
+        self.announce('Installing ACTS for installation', log.INFO)
+        setup_acts_for_cmd_or_die("install")
+        self.announce('ACTS installed for installation.', log.INFO)
+        install.run(self)
+        set_permssions_for_host_executables(self.get_outputs())
+
+
+class InstallLocalPackagesForDevelopment(develop):
+
+    def run(self):
+        log.log(log.INFO, 'Installing ACTS for development')
+        setup_acts_for_cmd_or_die("develop")
+        log.log(log.INFO, 'ACTS installed for development')
+        develop.run(self)
+        set_permssions_for_host_executables(self.get_outputs())
+
+
+def main():
+    # Relative path from calling directory to this file
+    our_dir = os.path.dirname(__file__)
+    # Must cd into this dir for package resolution to work
+    # This won't affect the calling shell
+    os.chdir(our_dir)
+    setup(
+        name='bluetooth_cert_tests',
+        version='1.0',
+        author='Android Open Source Project',
+        license='Apache2.0',
+        description="""Bluetooth Cert Tests Package""",
+        # Include root package so that bluetooth_packets_python3.so can be
+        # included as well
+        packages=[''] + find_packages(exclude='acts_framework'),
+        install_requires=install_requires,
+        package_data={
+            '': host_executables + ['*.so', 'lib64/*.so', 'target/*'],
+            'cert': ['all_test_cases'],
+        },
+        cmdclass={
+            'install': InstallLocalPackagesForInstallation,
+            'develop': InstallLocalPackagesForDevelopment,
+        })
+
+
+if __name__ == '__main__':
+    main()
diff --git a/gd/shim/cert/stack_test.py b/gd/shim/cert/stack_test.py
index 2c9315a..3daecc0 100644
--- a/gd/shim/cert/stack_test.py
+++ b/gd/shim/cert/stack_test.py
@@ -17,10 +17,10 @@
 import os
 import sys
 
-from cert.gd_base_test_facade_only import GdFacadeOnlyBaseTestClass
+from cert.gd_base_test import GdBaseTestClass
 
 
-class StackTest(GdFacadeOnlyBaseTestClass):
+class StackTest(GdBaseTestClass):
 
     def setup_class(self):
         super().setup_class(dut_module='SHIM', cert_module='SHIM')
diff --git a/gd/shim/l2cap.cc b/gd/shim/l2cap.cc
index 60ec5c8..106945a 100644
--- a/gd/shim/l2cap.cc
+++ b/gd/shim/l2cap.cc
@@ -81,7 +81,7 @@
   ConnectionInterface(ConnectionInterfaceDescriptor cid, std::unique_ptr<l2cap::classic::DynamicChannel> channel,
                       os::Handler* handler, ConnectionClosed on_closed)
       : cid_(cid), channel_(std::move(channel)), handler_(handler), on_data_ready_callback_(nullptr),
-        on_connection_closed_callback_(nullptr), address_(channel_->GetDevice()), on_closed_(on_closed) {
+        on_connection_closed_callback_(nullptr), address_(channel_->GetDevice().GetAddress()), on_closed_(on_closed) {
     channel_->RegisterOnCloseCallback(
         handler_, common::BindOnce(&ConnectionInterface::OnConnectionClosed, common::Unretained(this)));
     channel_->GetQueueUpEnd()->RegisterDequeue(
@@ -315,7 +315,8 @@
 
   void OnConnectionOpen(std::unique_ptr<l2cap::classic::DynamicChannel> channel) {
     LOG_DEBUG("Local initiated connection is open to device:%s for psm:%hd", address_.ToString().c_str(), psm_);
-    ASSERT_LOG(address_ == channel->GetDevice(), " Expected remote device does not match actual remote device");
+    ASSERT_LOG(address_ == channel->GetDevice().GetAddress(),
+               " Expected remote device does not match actual remote device");
     pending_open_(std::move(channel));
   }
 
diff --git a/include/hardware/bluetooth.h b/include/hardware/bluetooth.h
index 063abf9..1e3a391 100644
--- a/include/hardware/bluetooth.h
+++ b/include/hardware/bluetooth.h
@@ -625,6 +625,14 @@
    * @return a string of uint8_t that is unique to this MAC address
    */
   std::string (*obfuscate_address)(const RawAddress& address);
+
+  /**
+   * Get an incremental id for as primary key for Bluetooth metric and log
+   *
+   * @param address Bluetooth MAC address of Bluetooth device
+   * @return int incremental Bluetooth id
+   */
+  int (*get_metric_id)(const RawAddress& address);
 } bt_interface_t;
 
 #define BLUETOOTH_INTERFACE_STRING "bluetoothInterface"
diff --git a/main/shim/btif_dm.cc b/main/shim/btif_dm.cc
index e112f36..adf128c 100644
--- a/main/shim/btif_dm.cc
+++ b/main/shim/btif_dm.cc
@@ -45,26 +45,33 @@
                            std::string name, uint32_t numeric_value) {
     bt_bdname_t legacy_name{0};
     memcpy(legacy_name.name, name.data(), name.length());
-    callback_(RawAddress(address.GetAddress().address), legacy_name, ((0x1F) << 8) /* COD_UNCLASSIFIED*/ , BT_SSP_VARIANT_PASSKEY_CONFIRMATION, numeric_value);
+    callback_(ToRawAddress(address.GetAddress()), legacy_name,
+              ((0x1F) << 8) /* COD_UNCLASSIFIED*/,
+              BT_SSP_VARIANT_PASSKEY_CONFIRMATION, numeric_value);
   }
 
   void DisplayYesNoDialog(const bluetooth::hci::AddressWithType& address,
                           std::string name) {
     bt_bdname_t legacy_name{0};
     memcpy(legacy_name.name, name.data(), name.length());
-    callback_(RawAddress(address.GetAddress().address), legacy_name, ((0x1F) << 8) /* COD_UNCLASSIFIED*/ , BT_SSP_VARIANT_CONSENT, 0);
+    callback_(ToRawAddress(address.GetAddress()), legacy_name,
+              ((0x1F) << 8) /* COD_UNCLASSIFIED*/, BT_SSP_VARIANT_CONSENT, 0);
   }
 
   void DisplayEnterPasskeyDialog(const bluetooth::hci::AddressWithType& address, std::string name) {
     bt_bdname_t legacy_name{0};
     memcpy(legacy_name.name, name.data(), name.length());
-    callback_(RawAddress(address.GetAddress().address), legacy_name, ((0x1F) << 8) /* COD_UNCLASSIFIED*/ , BT_SSP_VARIANT_PASSKEY_ENTRY, 0);
+    callback_(ToRawAddress(address.GetAddress()), legacy_name,
+              ((0x1F) << 8) /* COD_UNCLASSIFIED*/, BT_SSP_VARIANT_PASSKEY_ENTRY,
+              0);
   }
 
   void DisplayPasskey(const bluetooth::hci::AddressWithType& address, std::string name, uint32_t passkey) {
     bt_bdname_t legacy_name{0};
     memcpy(legacy_name.name, name.data(), name.length());
-    callback_(RawAddress(address.GetAddress().address), legacy_name, ((0x1F) << 8) /* COD_UNCLASSIFIED*/ , BT_SSP_VARIANT_PASSKEY_NOTIFICATION, passkey);
+    callback_(ToRawAddress(address.GetAddress()), legacy_name,
+              ((0x1F) << 8) /* COD_UNCLASSIFIED*/,
+              BT_SSP_VARIANT_PASSKEY_NOTIFICATION, passkey);
   }
 
   void SetLegacyCallback(std::function<void(RawAddress, bt_bdname_t, uint32_t, bt_ssp_variant_t, uint32_t)> callback) {
@@ -98,18 +105,22 @@
     bond_state_bonded_cb_ = bond_state_bonded_cb;
     bond_state_none_cb_ = bond_state_none_cb;
   }
-  void OnDeviceBonded(bluetooth::hci::AddressWithType device) {
+
+  void OnDeviceBonded(bluetooth::hci::AddressWithType device) override {
     bond_state_bonded_cb_(RawAddress(device.GetAddress().address));
   }
 
-  void OnDeviceUnbonded(bluetooth::hci::AddressWithType device) {
+  void OnDeviceUnbonded(bluetooth::hci::AddressWithType device) override {
     bond_state_none_cb_(RawAddress(device.GetAddress().address));
   }
 
-  void OnDeviceBondFailed(bluetooth::hci::AddressWithType device) {
+  void OnDeviceBondFailed(bluetooth::hci::AddressWithType device) override {
     bond_state_none_cb_(RawAddress(device.GetAddress().address));
   }
 
+  void OnEncryptionStateChanged(
+      EncryptionChangeView encryption_change_view) override {}
+
   std::function<void(RawAddress)> bond_state_bonding_cb_;
   std::function<void(RawAddress)> bond_state_bonded_cb_;
   std::function<void(RawAddress)> bond_state_none_cb_;
diff --git a/main/shim/btm.cc b/main/shim/btm.cc
index 6ed636b..8eb884d 100644
--- a/main/shim/btm.cc
+++ b/main/shim/btm.cc
@@ -107,7 +107,7 @@
     bluetooth::hci::InquiryResultView view) {
   for (auto& response : view.GetInquiryResults()) {
     btm_api_process_inquiry_result(
-        RawAddress(response.bd_addr_.address),
+        ToRawAddress(response.bd_addr_),
         static_cast<uint8_t>(response.page_scan_repetition_mode_),
         response.class_of_device_.cod, response.clock_offset_);
   }
@@ -117,7 +117,7 @@
     bluetooth::hci::InquiryResultWithRssiView view) {
   for (auto& response : view.GetInquiryResults()) {
     btm_api_process_inquiry_result_with_rssi(
-        RawAddress(response.address_.address),
+        ToRawAddress(response.address_),
         static_cast<uint8_t>(response.page_scan_repetition_mode_),
         response.class_of_device_.cod, response.clock_offset_, response.rssi_);
   }
@@ -144,7 +144,7 @@
   }
 
   btm_api_process_extended_inquiry_result(
-      RawAddress(view.GetAddress().address),
+      ToRawAddress(view.GetAddress()),
       static_cast<uint8_t>(view.GetPageScanRepetitionMode()),
       view.GetClassOfDevice().cod, view.GetClockOffset(), view.GetRssi(), data,
       data_len);
@@ -486,14 +486,14 @@
   LOG_DEBUG("%s Start read name from address:%s", __func__,
             raw_address.ToString().c_str());
   bluetooth::shim::GetName()->ReadRemoteNameRequest(
-      hci::Address(raw_address.address), hci::PageScanRepetitionMode::R1,
+      ToGdAddress(raw_address), hci::PageScanRepetitionMode::R1,
       0 /* clock_offset */, hci::ClockOffsetValid::INVALID,
 
       base::Bind(
           [](tBTM_CMPL_CB* callback, ReadRemoteName* classic_read_remote_name_,
              hci::ErrorCode status, hci::Address address,
              std::array<uint8_t, kRemoteDeviceNameLength> remote_name) {
-            RawAddress raw_address(address.address);
+            RawAddress raw_address = ToRawAddress(address);
 
             BtmRemoteDeviceName name{
                 .status = (static_cast<uint8_t>(status) == 0)
@@ -719,7 +719,7 @@
               return;
           }
 
-          RawAddress raw_address(le_report->address_.address);
+          RawAddress raw_address = ToRawAddress(le_report->address_);
 
           btm_ble_process_adv_addr(raw_address, &address_type);
           btm_ble_process_adv_pkt_cont(
@@ -746,7 +746,7 @@
                .legacy = false,
                .continuing = !extended_le_report->complete_,
                .truncated = extended_le_report->truncated_});
-          RawAddress raw_address(le_report->address_.address);
+          RawAddress raw_address = ToRawAddress(le_report->address_);
           if (address_type != BLE_ADDR_ANONYMOUS) {
             btm_ble_process_adv_addr(raw_address, &address_type);
           }
@@ -794,8 +794,7 @@
       bluetooth::shim::GetSecurityModule()->GetSecurityManager();
   switch (transport) {
     case BT_TRANSPORT_BR_EDR:
-      security_manager->CreateBond(
-          ToAddressWithType(bd_addr.address, BLE_ADDR_PUBLIC));
+      security_manager->CreateBond(ToAddressWithType(bd_addr, BLE_ADDR_PUBLIC));
       break;
     case BT_TRANSPORT_LE:
       security_manager->CreateBondLe(ToAddressWithType(bd_addr, addr_type));
diff --git a/main/shim/helpers.h b/main/shim/helpers.h
index e6fb3b7..a2fa33f 100644
--- a/main/shim/helpers.h
+++ b/main/shim/helpers.h
@@ -21,10 +21,31 @@
 
 namespace bluetooth {
 
+inline RawAddress ToRawAddress(const hci::Address& address) {
+  RawAddress ret;
+  ret.address[0] = address.address[5];
+  ret.address[1] = address.address[4];
+  ret.address[2] = address.address[3];
+  ret.address[3] = address.address[2];
+  ret.address[4] = address.address[1];
+  ret.address[5] = address.address[0];
+  return ret;
+}
+
+inline hci::Address ToGdAddress(const RawAddress& address) {
+  hci::Address ret;
+  ret.address[0] = address.address[5];
+  ret.address[1] = address.address[4];
+  ret.address[2] = address.address[3];
+  ret.address[3] = address.address[2];
+  ret.address[4] = address.address[1];
+  ret.address[5] = address.address[0];
+  return ret;
+}
+
 inline hci::AddressWithType ToAddressWithType(const RawAddress& legacy_address,
                                        tBLE_ADDR_TYPE legacy_type) {
-  // Address and RawAddress are binary equivalent;
-  hci::Address address(legacy_address.address);
+  hci::Address address = ToGdAddress(legacy_address);
 
   hci::AddressType type;
   if (legacy_type == BLE_ADDR_PUBLIC)
@@ -43,4 +64,4 @@
 
   return hci::AddressWithType{address, type};
 }
-}  // namespace bluetooth
\ No newline at end of file
+}  // namespace bluetooth
diff --git a/profile/avrcp/connection_handler.cc b/profile/avrcp/connection_handler.cc
index 7b2765a..af8fb57 100644
--- a/profile/avrcp/connection_handler.cc
+++ b/profile/avrcp/connection_handler.cc
@@ -132,7 +132,7 @@
     return;
   };
 
-  return SdpLookup(bdaddr, base::Bind(connection_lambda, this, bdaddr));
+  return SdpLookup(bdaddr, base::Bind(connection_lambda, this, bdaddr), false);
 }
 
 bool ConnectionHandler::DisconnectDevice(const RawAddress& bdaddr) {
@@ -155,7 +155,8 @@
   return list;
 }
 
-bool ConnectionHandler::SdpLookup(const RawAddress& bdaddr, SdpCallback cb) {
+bool ConnectionHandler::SdpLookup(const RawAddress& bdaddr, SdpCallback cb,
+                                  bool retry) {
   LOG(INFO) << __PRETTY_FUNCTION__;
 
   tAVRC_SDP_DB_PARAMS db_params;
@@ -172,11 +173,11 @@
   db_params.p_db = disc_db;
   db_params.p_attrs = attr_list;
 
-  return avrc_->FindService(
-             UUID_SERVCLASS_AV_REMOTE_CONTROL, bdaddr, &db_params,
-             base::Bind(&ConnectionHandler::SdpCb,
-                        weak_ptr_factory_.GetWeakPtr(), bdaddr, cb, disc_db)) ==
-         AVRC_SUCCESS;
+  return avrc_->FindService(UUID_SERVCLASS_AV_REMOTE_CONTROL, bdaddr,
+                            &db_params,
+                            base::Bind(&ConnectionHandler::SdpCb,
+                                       weak_ptr_factory_.GetWeakPtr(), bdaddr,
+                                       cb, disc_db, retry)) == AVRC_SUCCESS;
 }
 
 bool ConnectionHandler::AvrcpConnect(bool initiator, const RawAddress& bdaddr) {
@@ -342,7 +343,7 @@
         }
       };
 
-      SdpLookup(*peer_addr, base::Bind(sdp_lambda, this, handle));
+      SdpLookup(*peer_addr, base::Bind(sdp_lambda, this, handle), false);
 
       avrc_->OpenBrowse(handle, AVCT_ACP);
       AvrcpConnect(false, RawAddress::kAny);
@@ -406,10 +407,15 @@
 }
 
 void ConnectionHandler::SdpCb(const RawAddress& bdaddr, SdpCallback cb,
-                              tSDP_DISCOVERY_DB* disc_db, uint16_t status) {
+                              tSDP_DISCOVERY_DB* disc_db, bool retry,
+                              uint16_t status) {
   LOG(INFO) << __PRETTY_FUNCTION__ << ": SDP lookup callback received";
 
-  if (status != AVRC_SUCCESS) {
+  if (status == SDP_CONN_FAILED and !retry) {
+    LOG(WARNING) << __PRETTY_FUNCTION__ << ": SDP Failure retry again";
+    SdpLookup(bdaddr, cb, true);
+    return;
+  } else if (status != AVRC_SUCCESS) {
     LOG(ERROR) << __PRETTY_FUNCTION__
                << ": SDP Failure: status = " << (unsigned int)status;
     cb.Run(status, 0, 0);
diff --git a/profile/avrcp/connection_handler.h b/profile/avrcp/connection_handler.h
index e22cb6a..d5f0a27 100644
--- a/profile/avrcp/connection_handler.h
+++ b/profile/avrcp/connection_handler.h
@@ -135,9 +135,9 @@
 
   using SdpCallback = base::Callback<void(uint16_t status, uint16_t version,
                                           uint16_t features)>;
-  virtual bool SdpLookup(const RawAddress& bdaddr, SdpCallback cb);
+  virtual bool SdpLookup(const RawAddress& bdaddr, SdpCallback cb, bool retry);
   void SdpCb(const RawAddress& bdaddr, SdpCallback cb,
-             tSDP_DISCOVERY_DB* disc_db, uint16_t status);
+             tSDP_DISCOVERY_DB* disc_db, bool retry, uint16_t status);
 
   virtual bool AvrcpConnect(bool initiator, const RawAddress& bdaddr);
 
diff --git a/service/hal/fake_bluetooth_interface.cc b/service/hal/fake_bluetooth_interface.cc
index f1d03ad..4af8bc6 100644
--- a/service/hal/fake_bluetooth_interface.cc
+++ b/service/hal/fake_bluetooth_interface.cc
@@ -75,6 +75,7 @@
     nullptr, /* interop_database_add */
     nullptr, /* get_avrcp_service */
     nullptr, /* obfuscate_address */
+    nullptr, /* get_metric_id */
 };
 
 }  // namespace
diff --git a/stack/btm/btm_ble_addr.cc b/stack/btm/btm_ble_addr.cc
index 000066e..678dd76 100644
--- a/stack/btm/btm_ble_addr.cc
+++ b/stack/btm/btm_ble_addr.cc
@@ -72,7 +72,7 @@
   p_cb->own_addr_type = BLE_ADDR_RANDOM;
 
   /* start a periodical timer to refresh random addr */
-  uint64_t interval_ms = BTM_BLE_PRIVATE_ADDR_INT_MS;
+  uint64_t interval_ms = btm_get_next_private_addrress_interval_ms();
 #if (BTM_BLE_CONFORMANCE_TESTING == TRUE)
   interval_ms = btm_cb.ble_ctr_cb.rpa_tout * 1000;
 #endif
@@ -93,6 +93,14 @@
       std::move(cb)));
 }
 
+uint64_t btm_get_next_private_addrress_interval_ms() {
+  /* 7 minutes minimum, 15 minutes maximum for random address refreshing */
+  const uint64_t interval_min_ms = (7 * 60 * 1000);
+  const uint64_t interval_random_part_max_ms = (8 * 60 * 1000);
+
+  return interval_min_ms + std::rand() % interval_random_part_max_ms;
+}
+
 /*******************************************************************************
  *
  * Function         btm_gen_non_resolve_paddr_cmpl
diff --git a/stack/btm/btm_ble_gap.cc b/stack/btm/btm_ble_gap.cc
index 64a8b2a..e208c6c 100644
--- a/stack/btm/btm_ble_gap.cc
+++ b/stack/btm/btm_ble_gap.cc
@@ -88,6 +88,14 @@
     return items.front().data;
   }
 
+  bool Exist(uint8_t addr_type, const RawAddress& addr) {
+    auto it = Find(addr_type, addr);
+    if (it != items.end()) {
+        return true;
+    }
+    return false;
+  }
+
   /* Append |data| for device |addr_type, addr| */
   const std::vector<uint8_t>& Append(uint8_t addr_type, const RawAddress& addr,
                                      std::vector<uint8_t> data) {
@@ -113,6 +121,10 @@
     }
   }
 
+  void ClearAll() {
+    items.clear();
+  }
+
  private:
   struct Item {
     uint8_t addr_type;
@@ -448,6 +460,7 @@
     /* scan is not started */
     if (!BTM_BLE_IS_SCAN_ACTIVE(btm_cb.ble_ctr_cb.scan_activity)) {
       /* allow config of scan type */
+      cache.ClearAll();
       p_inq->scan_type = (p_inq->scan_type == BTM_BLE_SCAN_MODE_NONE)
                              ? BTM_BLE_SCAN_MODE_ACTI
                              : p_inq->scan_type;
@@ -1298,6 +1311,7 @@
   }
 
   if (!BTM_BLE_IS_SCAN_ACTIVE(p_ble_cb->scan_activity)) {
+    cache.ClearAll();
     btm_send_hci_set_scan_params(
         BTM_BLE_SCAN_MODE_ACTI, BTM_BLE_LOW_LATENCY_SCAN_INT,
         BTM_BLE_LOW_LATENCY_SCAN_WIN,
@@ -1944,11 +1958,19 @@
 
   bool is_scannable = ble_evt_type_is_scannable(evt_type);
   bool is_scan_resp = ble_evt_type_is_scan_resp(evt_type);
+  bool is_legacy = ble_evt_type_is_legacy(evt_type);
 
-  bool is_start =
-      ble_evt_type_is_legacy(evt_type) && is_scannable && !is_scan_resp;
+  // We might receive a legacy scan response without receving a ADV_IND
+  // or ADV_SCAN_IND before. Only parsing the scan response data which
+  // has no ad flag, the device will be set to DUMO mode. The createbond
+  // procedure will use the wrong device mode.
+  // In such case no necessary to report scan response
+  if(is_legacy && is_scan_resp && !cache.Exist(addr_type, bda))
+    return;
 
-  if (ble_evt_type_is_legacy(evt_type))
+  bool is_start = is_legacy && is_scannable && !is_scan_resp;
+
+  if (is_legacy)
     AdvertiseDataParser::RemoveTrailingZeros(tmp);
 
   // We might have send scan request to this device before, but didn't get the
@@ -1993,6 +2015,7 @@
       update = false;
     } else {
       /* if yes, skip it */
+      cache.Clear(addr_type, bda);
       return; /* assumption: one result per event */
     }
   }
diff --git a/stack/btm/btm_ble_int.h b/stack/btm/btm_ble_int.h
index fc1bfcf..b2702fe 100644
--- a/stack/btm/btm_ble_int.h
+++ b/stack/btm/btm_ble_int.h
@@ -139,6 +139,7 @@
 extern tBTM_SEC_DEV_REC* btm_ble_resolve_random_addr(
     const RawAddress& random_bda);
 extern void btm_gen_resolve_paddr_low(const RawAddress& address);
+extern uint64_t btm_get_next_private_addrress_interval_ms();
 
 /*  privacy function */
 #if (BLE_PRIVACY_SPT == TRUE)
diff --git a/stack/btm/btm_ble_int_types.h b/stack/btm/btm_ble_int_types.h
index d2fa554..375c2b3 100644
--- a/stack/btm/btm_ble_int_types.h
+++ b/stack/btm/btm_ble_int_types.h
@@ -119,9 +119,6 @@
 #define BTM_BLE_ISVALID_PARAM(x, min, max) \
   (((x) >= (min) && (x) <= (max)) || ((x) == BTM_BLE_CONN_PARAM_UNDEF))
 
-/* 15 minutes minimum for random address refreshing */
-#define BTM_BLE_PRIVATE_ADDR_INT_MS (15 * 60 * 1000)
-
 typedef struct {
   uint16_t discoverable_mode;
   uint16_t connectable_mode;
diff --git a/stack/btm/btm_ble_multi_adv.cc b/stack/btm/btm_ble_multi_adv.cc
index 120b384..6db7594 100644
--- a/stack/btm/btm_ble_multi_adv.cc
+++ b/stack/btm/btm_ble_multi_adv.cc
@@ -24,6 +24,7 @@
 #include "ble_advertiser.h"
 #include "ble_advertiser_hci_interface.h"
 #include "btm_int_types.h"
+#include "stack/btm/btm_ble_int.h"
 
 #include <string.h>
 #include <queue>
@@ -257,7 +258,7 @@
               p_inst->own_address = bda;
 
               alarm_set_on_mloop(p_inst->adv_raddr_timer,
-                                 BTM_BLE_PRIVATE_ADDR_INT_MS,
+                                 btm_get_next_private_addrress_interval_ms(),
                                  btm_ble_adv_raddr_timer_timeout, p_inst);
               cb.Run(p_inst->inst_id, BTM_BLE_MULTI_ADV_SUCCESS);
             },
@@ -429,7 +430,8 @@
             c->self->adv_inst[c->inst_id].tx_power = tx_power;
 
             if (c->self->adv_inst[c->inst_id].own_address_type == BLE_ADDR_PUBLIC) {
-              c->self->StartAdvertisingSetAfterAddressPart(std::move(c));
+              auto self = c->self;
+              self->StartAdvertisingSetAfterAddressPart(std::move(c));
               return;
             }
 
@@ -449,7 +451,8 @@
                   return;
                 }
 
-                c->self->StartAdvertisingSetAfterAddressPart(std::move(c));
+                auto self = c->self;
+                self->StartAdvertisingSetAfterAddressPart(std::move(c));
           }, base::Passed(&c)));
         }, base::Passed(&c)));
     }, base::Passed(&c)));
@@ -492,11 +495,11 @@
                           return;
                         }
 
+                        auto self = c->self;
                         if (c->periodic_params.enable) {
-                          c->self->StartAdvertisingSetPeriodicPart(
-                              std::move(c));
+                          self->StartAdvertisingSetPeriodicPart(std::move(c));
                         } else {
-                          c->self->StartAdvertisingSetFinish(std::move(c));
+                          self->StartAdvertisingSetFinish(std::move(c));
                         }
                       },
                       base::Passed(&c)));
@@ -550,7 +553,8 @@
                   return;
                 }
 
-                c->self->StartAdvertisingSetFinish(std::move(c));
+                auto self = c->self;
+                self->StartAdvertisingSetFinish(std::move(c));
 
               }, base::Passed(&c)));
         }, base::Passed(&c)));
@@ -791,8 +795,9 @@
     int length = moreThanOnePacket ? ADV_DATA_LEN_MAX : dataSize - offset;
     int newOffset = offset + length;
 
+    auto dataData = data.data();
     sender.Run(
-        inst_id, operation, length, data.data() + offset,
+        inst_id, operation, length, dataData + offset,
         Bind(&BleAdvertisingManagerImpl::DivideAndSendDataRecursively, false,
              inst_id, std::move(data), newOffset, std::move(done_cb), sender));
   }
diff --git a/stack/btm/btm_devctl.cc b/stack/btm/btm_devctl.cc
index 2b717b9..52b66b4 100644
--- a/stack/btm/btm_devctl.cc
+++ b/stack/btm/btm_devctl.cc
@@ -199,14 +199,17 @@
 
   l2c_link_processs_num_bufs(controller->get_acl_buffer_count_classic());
 
+  // setup the random number generator
+  std::srand(std::time(nullptr));
+
 #if (BLE_PRIVACY_SPT == TRUE)
   /* Set up the BLE privacy settings */
   if (controller->supports_ble() && controller->supports_ble_privacy() &&
       controller->get_ble_resolving_list_max_size() > 0) {
     btm_ble_resolving_list_init(controller->get_ble_resolving_list_max_size());
     /* set the default random private address timeout */
-    btsnd_hcic_ble_set_rand_priv_addr_timeout(BTM_BLE_PRIVATE_ADDR_INT_MS /
-                                              1000);
+    btsnd_hcic_ble_set_rand_priv_addr_timeout(
+        btm_get_next_private_addrress_interval_ms() / 1000);
   }
 #endif
 
diff --git a/stack/btm/btm_inq.cc b/stack/btm/btm_inq.cc
index 80cf186..e00f7ae 100644
--- a/stack/btm/btm_inq.cc
+++ b/stack/btm/btm_inq.cc
@@ -842,10 +842,9 @@
   /* Make sure there is not already one in progress */
   if (p_inq->remname_active) {
     if (BTM_UseLeLink(p_inq->remname_bda)) {
-      if (btm_ble_cancel_remote_name(p_inq->remname_bda))
-        return (BTM_CMD_STARTED);
-      else
-        return (BTM_UNKNOWN_ADDR);
+      /* Cancel remote name request for LE device, and process remote name
+       * callback. */
+      btm_inq_rmt_name_failed_cancelled();
     } else
       btsnd_hcic_rmt_name_req_cancel(p_inq->remname_bda);
     return (BTM_CMD_STARTED);
@@ -1984,22 +1983,22 @@
 }
 
 void btm_inq_remote_name_timer_timeout(UNUSED_ATTR void* data) {
-  btm_inq_rmt_name_failed();
+  btm_inq_rmt_name_failed_cancelled();
 }
 
 /*******************************************************************************
  *
- * Function         btm_inq_rmt_name_failed
+ * Function         btm_inq_rmt_name_failed_cancelled
  *
- * Description      This function is if timeout expires while getting remote
- *                  name.  This is done for devices that incorrectly do not
- *                  report operation failure
+ * Description      This function is if timeout expires or request is cancelled
+ *                  while getting remote name.  This is done for devices that
+ *                  incorrectly do not report operation failure
  *
  * Returns          void
  *
  ******************************************************************************/
-void btm_inq_rmt_name_failed(void) {
-  BTM_TRACE_ERROR("btm_inq_rmt_name_failed()  remname_active=%d",
+void btm_inq_rmt_name_failed_cancelled(void) {
+  BTM_TRACE_ERROR("btm_inq_rmt_name_failed_cancelled()  remname_active=%d",
                   btm_cb.btm_inq_vars.remname_active);
 
   if (btm_cb.btm_inq_vars.remname_active)
diff --git a/stack/btm/btm_int.h b/stack/btm/btm_int.h
index 88cb724..05180db 100644
--- a/stack/btm/btm_int.h
+++ b/stack/btm/btm_int.h
@@ -59,7 +59,7 @@
 
 extern void btm_process_remote_name(const RawAddress* bda, BD_NAME name,
                                     uint16_t evt_len, uint8_t hci_status);
-extern void btm_inq_rmt_name_failed(void);
+extern void btm_inq_rmt_name_failed_cancelled(void);
 extern void btm_inq_remote_name_timer_timeout(void* data);
 
 /* Inquiry related functions */
diff --git a/stack/smp/smp_keys.cc b/stack/smp/smp_keys.cc
index a4df7952..a372ba4 100644
--- a/stack/smp/smp_keys.cc
+++ b/stack/smp/smp_keys.cc
@@ -987,7 +987,8 @@
 
   link_key_type += BTM_LTK_DERIVED_LKEY_OFFSET;
 
-  Octet16 notif_link_key = link_key;
+  Octet16 notif_link_key;
+  std::reverse_copy(link_key.begin(), link_key.end(), notif_link_key.begin());
   btm_sec_link_key_notification(bda_for_lk, notif_link_key, link_key_type);
 
   SMP_TRACE_EVENT("%s is completed", __func__);
diff --git a/stack/test/ble_advertiser_test.cc b/stack/test/ble_advertiser_test.cc
index 952aa41..98142dd 100644
--- a/stack/test/ble_advertiser_test.cc
+++ b/stack/test/ble_advertiser_test.cc
@@ -68,6 +68,8 @@
 void alarm_free(alarm_t* alarm) {}
 const controller_t* controller_get_interface() { return nullptr; }
 
+uint64_t btm_get_next_private_addrress_interval_ms() { return 15 * 60 * 1000; }
+
 namespace {
 void DoNothing(uint8_t) {}
 
diff --git a/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc b/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc
index e9feca4..561ddbb 100644
--- a/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc
+++ b/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc
@@ -439,7 +439,7 @@
 void DualModeController::ReadRemoteVersionInformation(
     CommandPacketView command) {
   auto command_view = gd_hci::ReadRemoteVersionInformationView::Create(
-      gd_hci::DiscoveryCommandView::Create(command));
+      gd_hci::ConnectionManagementCommandView::Create(command));
   ASSERT(command_view.IsValid());
 
   auto status = link_layer_controller_.SendCommandToRemoteByHandle(
@@ -515,7 +515,7 @@
 
 void DualModeController::ReadRemoteExtendedFeatures(CommandPacketView command) {
   auto command_view = gd_hci::ReadRemoteExtendedFeaturesView::Create(
-      gd_hci::DiscoveryCommandView::Create(command));
+      gd_hci::ConnectionManagementCommandView::Create(command));
   ASSERT(command_view.IsValid());
 
   auto status = link_layer_controller_.SendCommandToRemoteByHandle(
@@ -543,7 +543,7 @@
 void DualModeController::ReadRemoteSupportedFeatures(
     CommandPacketView command) {
   auto command_view = gd_hci::ReadRemoteSupportedFeaturesView::Create(
-      gd_hci::DiscoveryCommandView::Create(command));
+      gd_hci::ConnectionManagementCommandView::Create(command));
   ASSERT(command_view.IsValid());
 
   auto status = link_layer_controller_.SendCommandToRemoteByHandle(