Separate LE scanner and GATT client BTIF interfaces (3/3)

In order to properly separate low level structures associated with LE
scanner and GATT client, their interfaces must be separated first.

Test: sl4a BleScanApiTest
Bug: 30622771
Change-Id: If804632c8e4b08fd15d5b847c7838822cf98d79f
diff --git a/btif/Android.mk b/btif/Android.mk
index d5deb20..46696bf 100644
--- a/btif/Android.mk
+++ b/btif/Android.mk
@@ -36,6 +36,7 @@
   src/btif_av.cc \
   src/btif_avrcp_audio_track.cc \
   src/btif_ble_advertiser.cc \
+  src/btif_ble_scanner.cc \
   src/btif_config.cc \
   src/btif_config_transcode.cc \
   src/btif_core.cc \
diff --git a/btif/BUILD.gn b/btif/BUILD.gn
index 972fc15..b3d9504 100644
--- a/btif/BUILD.gn
+++ b/btif/BUILD.gn
@@ -26,6 +26,7 @@
     #TODO(jpawlowski): heavily depends on Android,
     #   "src/btif_avrcp_audio_track.cc",
     "src/btif_ble_advertiser.cc",
+    "src/btif_ble_scanner.cc",
     "src/btif_config.cc",
     "src/btif_config_transcode.cc",
     "src/btif_core.cc",
diff --git a/btif/include/btif_gatt.h b/btif/include/btif_gatt.h
index 3ddff43..c5d56b0 100644
--- a/btif/include/btif_gatt.h
+++ b/btif/include/btif_gatt.h
@@ -29,6 +29,7 @@
 
 extern const btgatt_client_interface_t btgattClientInterface;
 extern const btgatt_server_interface_t btgattServerInterface;
+extern const btgatt_scanner_interface_t btgattScannerInterface;
 
 BleAdvertiserInterface* get_ble_advertiser_instance();
 #endif
diff --git a/btif/src/btif_ble_scanner.cc b/btif/src/btif_ble_scanner.cc
new file mode 100644
index 0000000..eac3906
--- /dev/null
+++ b/btif/src/btif_ble_scanner.cc
@@ -0,0 +1,668 @@
+/******************************************************************************
+ *
+ *  Copyright (C) 2016 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at:
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ ******************************************************************************/
+
+#define LOG_TAG "bt_btif_scanner"
+
+#include <base/bind.h>
+#include <base/threading/thread.h>
+#include <errno.h>
+#include <hardware/bluetooth.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unordered_set>
+#include "device/include/controller.h"
+
+#include "btcore/include/bdaddr.h"
+#include "btif_common.h"
+#include "btif_util.h"
+
+#if (BLE_INCLUDED == TRUE)
+
+#include <hardware/bt_gatt.h>
+
+#include "bta_api.h"
+#include "bta_gatt_api.h"
+#include "btif_config.h"
+#include "btif_dm.h"
+#include "btif_gatt.h"
+#include "btif_gatt_util.h"
+#include "btif_storage.h"
+#include "osi/include/log.h"
+#include "vendor_api.h"
+
+using base::Bind;
+using base::Owned;
+using std::vector;
+
+extern bt_status_t do_in_jni_thread(const base::Closure& task);
+extern const btgatt_callbacks_t* bt_gatt_callbacks;
+
+#define  SCAN_CBACK_IN_JNI(P_CBACK, ...)                                       \
+  do {                                                                         \
+    if (bt_gatt_callbacks && bt_gatt_callbacks->scanner->P_CBACK) {            \
+      BTIF_TRACE_API("HAL bt_gatt_callbacks->client->%s", #P_CBACK);           \
+      do_in_jni_thread(Bind(bt_gatt_callbacks->scanner->P_CBACK, __VA_ARGS__)); \
+    } else {                                                                   \
+      ASSERTC(0, "Callback is NULL", 0);                                       \
+    }                                                                          \
+  } while (0)
+
+#define CHECK_BTGATT_INIT()                                      \
+  do {                                                           \
+    if (bt_gatt_callbacks == NULL) {                             \
+      LOG_WARN(LOG_TAG, "%s: BTGATT not initialized", __func__); \
+      return BT_STATUS_NOT_READY;                                \
+    } else {                                                     \
+      LOG_VERBOSE(LOG_TAG, "%s", __func__);                      \
+    }                                                            \
+  } while (0)
+
+namespace std {
+template <>
+struct hash<bt_bdaddr_t> {
+  size_t operator()(const bt_bdaddr_t& f) const {
+    return f.address[0] + f.address[1] + f.address[2] + f.address[3] +
+           f.address[4] + f.address[5];
+  }
+};
+
+template <>
+struct equal_to<bt_bdaddr_t> {
+  size_t operator()(const bt_bdaddr_t& x, const bt_bdaddr_t& y) const {
+    return memcmp(x.address, y.address, BD_ADDR_LEN);
+  }
+};
+}
+
+namespace {
+
+std::unordered_set<bt_bdaddr_t> p_dev_cb;
+
+void btif_gattc_add_remote_bdaddr(BD_ADDR p_bda, uint8_t addr_type) {
+  bt_bdaddr_t bd_addr;
+  memcpy(bd_addr.address, p_bda, BD_ADDR_LEN);
+  p_dev_cb.insert(bd_addr);
+}
+
+bool btif_gattc_find_bdaddr(BD_ADDR p_bda) {
+  bt_bdaddr_t bd_addr;
+  memcpy(bd_addr.address, p_bda, BD_ADDR_LEN);
+  return (p_dev_cb.count(bd_addr) != 0);
+}
+
+void btif_gattc_init_dev_cb(void) { p_dev_cb.clear(); }
+
+btgattc_error_t btif_gattc_translate_btm_status(tBTM_STATUS status) {
+  switch (status) {
+    case BTM_SUCCESS:
+    case BTM_SUCCESS_NO_SECURITY:
+      return BT_GATTC_COMMAND_SUCCESS;
+
+    case BTM_CMD_STARTED:
+      return BT_GATTC_COMMAND_STARTED;
+
+    case BTM_BUSY:
+      return BT_GATTC_COMMAND_BUSY;
+
+    case BTM_CMD_STORED:
+      return BT_GATTC_COMMAND_STORED;
+
+    case BTM_NO_RESOURCES:
+      return BT_GATTC_NO_RESOURCES;
+
+    case BTM_MODE_UNSUPPORTED:
+    case BTM_WRONG_MODE:
+    case BTM_MODE4_LEVEL4_NOT_SUPPORTED:
+      return BT_GATTC_MODE_UNSUPPORTED;
+
+    case BTM_ILLEGAL_VALUE:
+    case BTM_SCO_BAD_LENGTH:
+      return BT_GATTC_ILLEGAL_VALUE;
+
+    case BTM_UNKNOWN_ADDR:
+      return BT_GATTC_UNKNOWN_ADDR;
+
+    case BTM_DEVICE_TIMEOUT:
+      return BT_GATTC_DEVICE_TIMEOUT;
+
+    case BTM_FAILED_ON_SECURITY:
+    case BTM_REPEATED_ATTEMPTS:
+    case BTM_NOT_AUTHORIZED:
+      return BT_GATTC_SECURITY_ERROR;
+
+    case BTM_DEV_RESET:
+    case BTM_ILLEGAL_ACTION:
+      return BT_GATTC_INCORRECT_STATE;
+
+    case BTM_BAD_VALUE_RET:
+      return BT_GATTC_INVALID_CONTROLLER_OUTPUT;
+
+    case BTM_DELAY_CHECK:
+      return BT_GATTC_DELAYED_ENCRYPTION_CHECK;
+
+    case BTM_ERR_PROCESSING:
+    default:
+      return BT_GATTC_ERR_PROCESSING;
+  }
+}
+
+void btif_gatts_upstreams_evt(uint16_t event, char* p_param) {
+  LOG_VERBOSE(LOG_TAG, "%s: Event %d", __func__, event);
+
+  tBTA_GATTC* p_data = (tBTA_GATTC*)p_param;
+  switch (event) {
+    case BTA_GATTC_REG_EVT: {
+      bt_uuid_t app_uuid;
+      bta_to_btif_uuid(&app_uuid, &p_data->reg_oper.app_uuid);
+      HAL_CBACK(bt_gatt_callbacks, scanner->register_scanner_cb,
+                p_data->reg_oper.status, p_data->reg_oper.client_if, &app_uuid);
+      break;
+    }
+
+    case BTA_GATTC_DEREG_EVT:
+      break;
+
+    case BTA_GATTC_SEARCH_CMPL_EVT: {
+      HAL_CBACK(bt_gatt_callbacks, client->search_complete_cb,
+                p_data->search_cmpl.conn_id, p_data->search_cmpl.status);
+      break;
+    }
+
+    default:
+      LOG_ERROR(LOG_TAG, "%s: Unhandled event (%d)!", __func__, event);
+      break;
+  }
+}
+
+void bta_gatts_cback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
+  bt_status_t status =
+      btif_transfer_context(btif_gatts_upstreams_evt, (uint16_t)event,
+                            (char*)p_data, sizeof(tBTA_GATTC), NULL);
+  ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
+}
+
+void bta_scan_param_setup_cb(tGATT_IF client_if, tBTM_STATUS status) {
+  SCAN_CBACK_IN_JNI(scan_parameter_setup_completed_cb, client_if,
+                   btif_gattc_translate_btm_status(status));
+}
+
+void bta_scan_filt_cfg_cb(tBTA_DM_BLE_PF_ACTION action,
+                          tBTA_DM_BLE_SCAN_COND_OP cfg_op,
+                          tBTA_DM_BLE_PF_AVBL_SPACE avbl_space,
+                          tBTA_STATUS status, tBTA_DM_BLE_REF_VALUE ref_value) {
+  SCAN_CBACK_IN_JNI(scan_filter_cfg_cb, action, ref_value, status, cfg_op,
+                   avbl_space);
+}
+
+void bta_scan_filt_param_setup_cb(uint8_t action_type,
+                                  tBTA_DM_BLE_PF_AVBL_SPACE avbl_space,
+                                  tBTA_DM_BLE_REF_VALUE ref_value,
+                                  tBTA_STATUS status) {
+  SCAN_CBACK_IN_JNI(scan_filter_param_cb, action_type, ref_value, status,
+                   avbl_space);
+}
+
+void bta_scan_filt_status_cb(uint8_t action, tBTA_STATUS status,
+                             tBTA_DM_BLE_REF_VALUE ref_value) {
+  SCAN_CBACK_IN_JNI(scan_filter_status_cb, action, ref_value, status);
+}
+
+void bta_batch_scan_setup_cb(tBTA_BLE_BATCH_SCAN_EVT evt,
+                             tBTA_DM_BLE_REF_VALUE ref_value,
+                             tBTA_STATUS status) {
+  BTIF_TRACE_DEBUG("bta_batch_scan_setup_cb-Status:%x, client_if:%d, evt=%d",
+                   status, ref_value, evt);
+
+  switch (evt) {
+    case BTA_BLE_BATCH_SCAN_ENB_EVT: {
+      SCAN_CBACK_IN_JNI(batchscan_enb_disable_cb, 1, ref_value, status);
+      return;
+    }
+
+    case BTA_BLE_BATCH_SCAN_DIS_EVT: {
+      SCAN_CBACK_IN_JNI(batchscan_enb_disable_cb, 0, ref_value, status);
+      return;
+    }
+
+    case BTA_BLE_BATCH_SCAN_CFG_STRG_EVT: {
+      SCAN_CBACK_IN_JNI(batchscan_cfg_storage_cb, ref_value, status);
+      return;
+    }
+
+    case BTA_BLE_BATCH_SCAN_DATA_EVT: {
+      SCAN_CBACK_IN_JNI(batchscan_reports_cb, ref_value, status, 0, 0,
+                       vector<uint8_t>());
+      return;
+    }
+
+    case BTA_BLE_BATCH_SCAN_THRES_EVT: {
+      SCAN_CBACK_IN_JNI(batchscan_threshold_cb, ref_value);
+      return;
+    }
+
+    default:
+      return;
+  }
+}
+
+void bta_batch_scan_threshold_cb(tBTA_DM_BLE_REF_VALUE ref_value) {
+  SCAN_CBACK_IN_JNI(batchscan_threshold_cb, ref_value);
+}
+
+void bta_batch_scan_reports_cb(tBTA_DM_BLE_REF_VALUE ref_value,
+                               uint8_t report_format, uint8_t num_records,
+                               uint16_t data_len, uint8_t* p_rep_data,
+                               tBTA_STATUS status) {
+  BTIF_TRACE_DEBUG("%s - client_if:%d, %d, %d, %d", __func__, ref_value, status,
+                   num_records, data_len);
+
+  if (data_len > 0) {
+    vector<uint8_t> data(p_rep_data, p_rep_data + data_len);
+    osi_free(p_rep_data);
+
+    SCAN_CBACK_IN_JNI(batchscan_reports_cb, ref_value, status, report_format,
+                     num_records, std::move(data));
+  } else {
+    SCAN_CBACK_IN_JNI(batchscan_reports_cb, ref_value, status, report_format,
+                     num_records, vector<uint8_t>());
+  }
+}
+
+void bta_scan_results_cb_impl(bt_bdaddr_t bd_addr, tBT_DEVICE_TYPE device_type,
+                              int8_t rssi, uint8_t addr_type,
+                              vector<uint8_t> value) {
+  uint8_t remote_name_len;
+  const uint8_t* p_eir_remote_name = NULL;
+  bt_device_type_t dev_type;
+  bt_property_t properties;
+
+  p_eir_remote_name = BTM_CheckEirData(
+      value.data(), BTM_EIR_COMPLETE_LOCAL_NAME_TYPE, &remote_name_len);
+
+  if (p_eir_remote_name == NULL) {
+    p_eir_remote_name = BTM_CheckEirData(
+        value.data(), BT_EIR_SHORTENED_LOCAL_NAME_TYPE, &remote_name_len);
+  }
+
+  if ((addr_type != BLE_ADDR_RANDOM) || (p_eir_remote_name)) {
+    if (!btif_gattc_find_bdaddr(bd_addr.address)) {
+      btif_gattc_add_remote_bdaddr(bd_addr.address, addr_type);
+
+      if (p_eir_remote_name) {
+        bt_bdname_t bdname;
+        memcpy(bdname.name, p_eir_remote_name, remote_name_len);
+        bdname.name[remote_name_len] = '\0';
+
+        LOG_VERBOSE(LOG_TAG, "%s BLE device name=%s len=%d dev_type=%d",
+                    __func__, bdname.name, remote_name_len, device_type);
+        btif_dm_update_ble_remote_properties(bd_addr.address, bdname.name,
+                                             device_type);
+      }
+    }
+  }
+
+  dev_type = (bt_device_type_t)device_type;
+  BTIF_STORAGE_FILL_PROPERTY(&properties, BT_PROPERTY_TYPE_OF_DEVICE,
+                             sizeof(dev_type), &dev_type);
+  btif_storage_set_remote_device_property(&(bd_addr), &properties);
+
+  btif_storage_set_remote_addr_type(&bd_addr, addr_type);
+
+  HAL_CBACK(bt_gatt_callbacks, scanner->scan_result_cb, &bd_addr, rssi,
+            std::move(value));
+}
+
+void bta_scan_results_cb(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* p_data) {
+  uint8_t len;
+
+  if (event == BTA_DM_INQ_CMPL_EVT) {
+    BTIF_TRACE_DEBUG("%s  BLE observe complete. Num Resp %d", __func__,
+                     p_data->inq_cmpl.num_resps);
+    return;
+  }
+
+  if (event != BTA_DM_INQ_RES_EVT) {
+    BTIF_TRACE_WARNING("%s : Unknown event 0x%x", __func__, event);
+    return;
+  }
+
+  vector<uint8_t> value(BTGATT_MAX_ATTR_LEN);
+  if (p_data->inq_res.p_eir) {
+    value.insert(value.begin(), p_data->inq_res.p_eir,
+                 p_data->inq_res.p_eir + 62);
+
+    if (BTM_CheckEirData(p_data->inq_res.p_eir,
+                         BTM_EIR_COMPLETE_LOCAL_NAME_TYPE, &len)) {
+      p_data->inq_res.remt_name_not_required = true;
+    }
+  }
+
+  bt_bdaddr_t bdaddr;
+  bdcpy(bdaddr.address, p_data->inq_res.bd_addr);
+  do_in_jni_thread(Bind(bta_scan_results_cb_impl, bdaddr,
+                        p_data->inq_res.device_type, p_data->inq_res.rssi,
+                        p_data->inq_res.ble_addr_type, std::move(value)));
+}
+
+void bta_track_adv_event_cb(tBTA_DM_BLE_TRACK_ADV_DATA* p_track_adv_data) {
+  btgatt_track_adv_info_t* btif_scan_track_cb = new btgatt_track_adv_info_t;
+
+  BTIF_TRACE_DEBUG("%s", __func__);
+  btif_gatt_move_track_adv_data(btif_scan_track_cb,
+                                (btgatt_track_adv_info_t*)p_track_adv_data);
+
+  SCAN_CBACK_IN_JNI(track_adv_event_cb, Owned(btif_scan_track_cb));
+}
+
+
+void btif_gattc_register_scanner_impl(tBT_UUID uuid) {
+  BTA_GATTC_AppRegister(&uuid, bta_gatts_cback);
+}
+
+bt_status_t btif_gattc_register_scanner(bt_uuid_t* uuid) {
+  CHECK_BTGATT_INIT();
+
+  tBT_UUID bt_uuid;
+  btif_to_bta_uuid(&bt_uuid, uuid);
+  return do_in_jni_thread(Bind(&btif_gattc_register_scanner_impl, bt_uuid));
+}
+
+void btif_gattc_unregister_scanner_impl(int client_if) {
+  BTA_GATTC_AppDeregister(client_if);
+}
+
+bt_status_t btif_gattc_unregister_scanner(int scanner_id) {
+  CHECK_BTGATT_INIT();
+  return do_in_jni_thread(Bind(&btif_gattc_unregister_scanner_impl, scanner_id));
+}
+
+bt_status_t btif_gattc_scan(bool start) {
+  CHECK_BTGATT_INIT();
+  if (start) {
+    btif_gattc_init_dev_cb();
+    return do_in_jni_thread(Bind(&BTA_DmBleObserve, true, 0,
+                                 (tBTA_DM_SEARCH_CBACK*)bta_scan_results_cb));
+  }
+
+  return do_in_jni_thread(Bind(&BTA_DmBleObserve, false, 0, nullptr));
+}
+
+void btif_gattc_scan_filter_param_setup_impl(
+    int client_if, uint8_t action, int filt_index,
+    tBTA_DM_BLE_PF_FILT_PARAMS* adv_filt_param) {
+  if (1 == adv_filt_param->dely_mode) {
+    BTA_DmBleTrackAdvertiser(client_if, bta_track_adv_event_cb);
+  }
+
+  BTA_DmBleScanFilterSetup(action, filt_index, adv_filt_param, NULL,
+                           bta_scan_filt_param_setup_cb, client_if);
+}
+
+bt_status_t btif_gattc_scan_filter_param_setup(
+    btgatt_filt_param_setup_t filt_param) {
+  CHECK_BTGATT_INIT();
+  BTIF_TRACE_DEBUG("%s", __func__);
+
+  tBTA_DM_BLE_PF_FILT_PARAMS* adv_filt_param = new tBTA_DM_BLE_PF_FILT_PARAMS;
+  adv_filt_param->feat_seln = filt_param.feat_seln;
+  adv_filt_param->list_logic_type = filt_param.list_logic_type;
+  adv_filt_param->filt_logic_type = filt_param.filt_logic_type;
+  adv_filt_param->rssi_high_thres = filt_param.rssi_high_thres;
+  adv_filt_param->rssi_low_thres = filt_param.rssi_low_thres;
+  adv_filt_param->dely_mode = filt_param.dely_mode;
+  adv_filt_param->found_timeout = filt_param.found_timeout;
+  adv_filt_param->lost_timeout = filt_param.lost_timeout;
+  adv_filt_param->found_timeout_cnt = filt_param.found_timeout_cnt;
+  adv_filt_param->num_of_tracking_entries = filt_param.num_of_tracking_entries;
+
+  return do_in_jni_thread(
+      Bind(base::IgnoreResult(&btif_gattc_scan_filter_param_setup_impl),
+           filt_param.client_if, filt_param.action, filt_param.filt_index,
+           base::Owned(adv_filt_param)));
+}
+
+void btif_gattc_scan_filter_add_srvc_uuid(tBT_UUID uuid,
+                                          tBTA_DM_BLE_PF_COND_MASK* p_uuid_mask,
+                                          int action, int filt_type,
+                                          int filt_index, int client_if) {
+  tBTA_DM_BLE_PF_COND_PARAM cond;
+  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
+
+  cond.srvc_uuid.p_target_addr = NULL;
+  cond.srvc_uuid.cond_logic = BTA_DM_BLE_PF_LOGIC_AND;
+  cond.srvc_uuid.uuid = uuid;
+  cond.srvc_uuid.p_uuid_mask = p_uuid_mask;
+
+  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
+                              &bta_scan_filt_cfg_cb, client_if);
+}
+
+void btif_gattc_scan_filter_add_local_name(vector<uint8_t> data, int action,
+                                           int filt_type, int filt_index,
+                                           int client_if) {
+  tBTA_DM_BLE_PF_COND_PARAM cond;
+  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
+
+  cond.local_name.data_len = data.size();
+  cond.local_name.p_data = const_cast<uint8_t*>(data.data());
+  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
+                              &bta_scan_filt_cfg_cb, client_if);
+}
+
+void btif_gattc_scan_filter_add_manu_data(int company_id, int company_id_mask,
+                                          vector<uint8_t> pattern,
+                                          vector<uint8_t> pattern_mask,
+                                          int action, int filt_type,
+                                          int filt_index, int client_if) {
+  tBTA_DM_BLE_PF_COND_PARAM cond;
+  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
+
+  cond.manu_data.company_id = company_id;
+  cond.manu_data.company_id_mask = company_id_mask ? company_id_mask : 0xFFFF;
+  cond.manu_data.data_len = pattern.size();
+  cond.manu_data.p_pattern = const_cast<uint8_t*>(pattern.data());
+  cond.manu_data.p_pattern_mask = const_cast<uint8_t*>(pattern_mask.data());
+  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
+                              &bta_scan_filt_cfg_cb, client_if);
+}
+
+void btif_gattc_scan_filter_add_data_pattern(vector<uint8_t> pattern,
+                                             vector<uint8_t> pattern_mask,
+                                             int action, int filt_type,
+                                             int filt_index, int client_if) {
+  tBTA_DM_BLE_PF_COND_PARAM cond;
+  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
+
+  cond.srvc_data.data_len = pattern.size();
+  cond.srvc_data.p_pattern = const_cast<uint8_t*>(pattern.data());
+  cond.srvc_data.p_pattern_mask = const_cast<uint8_t*>(pattern_mask.data());
+  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
+                              &bta_scan_filt_cfg_cb, client_if);
+}
+
+bt_status_t btif_gattc_scan_filter_add_remove(
+    int client_if, int action, int filt_type, int filt_index, int company_id,
+    int company_id_mask, const bt_uuid_t* p_uuid, const bt_uuid_t* p_uuid_mask,
+    const bt_bdaddr_t* bd_addr, char addr_type, vector<uint8_t> data,
+    vector<uint8_t> mask) {
+  CHECK_BTGATT_INIT();
+  BTIF_TRACE_DEBUG("%s, %d, %d", __func__, action, filt_type);
+
+  /* If data is passed, both mask and data have to be the same length */
+  if (data.size() != mask.size() && data.size() != 0 && mask.size() != 0)
+    return BT_STATUS_PARM_INVALID;
+
+  switch (filt_type) {
+    case BTA_DM_BLE_PF_ADDR_FILTER:
+    {
+      tBTA_DM_BLE_PF_COND_PARAM* cond = new tBTA_DM_BLE_PF_COND_PARAM;
+      memset(cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
+
+      bdcpy(cond->target_addr.bda, bd_addr->address);
+      cond->target_addr.type = addr_type;
+      return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition, action,
+                                   filt_type, filt_index, base::Owned(cond),
+                                   &bta_scan_filt_cfg_cb, client_if));
+    }
+
+    case BTA_DM_BLE_PF_SRVC_DATA:
+      return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition, action,
+                                   filt_type, filt_index, nullptr,
+                                   &bta_scan_filt_cfg_cb, client_if));
+
+    case BTA_DM_BLE_PF_SRVC_UUID:
+    {
+      tBT_UUID bt_uuid;
+      btif_to_bta_uuid(&bt_uuid, p_uuid);
+
+      if (p_uuid_mask != NULL) {
+        tBTA_DM_BLE_PF_COND_MASK* uuid_mask = new tBTA_DM_BLE_PF_COND_MASK;
+        btif_to_bta_uuid_mask(uuid_mask, p_uuid_mask, p_uuid);
+        return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_srvc_uuid,
+                                     bt_uuid, base::Owned(uuid_mask), action,
+                                     filt_type, filt_index, client_if));
+      }
+
+      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_srvc_uuid,
+                                   bt_uuid, nullptr, action, filt_type,
+                                   filt_index, client_if));
+    }
+
+    case BTA_DM_BLE_PF_SRVC_SOL_UUID:
+    {
+      tBTA_DM_BLE_PF_COND_PARAM* cond = new tBTA_DM_BLE_PF_COND_PARAM;
+      memset(cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
+
+      cond->solicitate_uuid.p_target_addr = NULL;
+      cond->solicitate_uuid.cond_logic = BTA_DM_BLE_PF_LOGIC_AND;
+      btif_to_bta_uuid(&cond->solicitate_uuid.uuid, p_uuid);
+
+      return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition, action,
+                                   filt_type, filt_index, base::Owned(cond),
+                                   &bta_scan_filt_cfg_cb, client_if));
+    }
+
+    case BTA_DM_BLE_PF_LOCAL_NAME:
+    {
+      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_local_name,
+                                   std::move(data), action, filt_type,
+                                   filt_index, client_if));
+    }
+
+    case BTA_DM_BLE_PF_MANU_DATA:
+    {
+      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_manu_data,
+                                   company_id, company_id_mask, std::move(data),
+                                   std::move(mask), action, filt_type,
+                                   filt_index, client_if));
+    }
+
+    case BTA_DM_BLE_PF_SRVC_DATA_PATTERN:
+    {
+      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_data_pattern,
+                                   std::move(data), std::move(mask), action,
+                                   filt_type, filt_index, client_if));
+    }
+
+    default:
+      LOG_ERROR(LOG_TAG, "%s: Unknown filter type (%d)!", __func__, action);
+      return (bt_status_t)BTA_GATT_OK;
+  }
+}
+
+bt_status_t btif_gattc_scan_filter_clear(int client_if, int filter_index) {
+  CHECK_BTGATT_INIT();
+  BTIF_TRACE_DEBUG("%s: filter_index: %d", __func__, filter_index);
+
+  return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition,
+                               BTA_DM_BLE_SCAN_COND_CLEAR,
+                               BTA_DM_BLE_PF_TYPE_ALL, filter_index, nullptr,
+                               &bta_scan_filt_cfg_cb, client_if));
+}
+
+bt_status_t btif_gattc_scan_filter_enable(int client_if, bool enable) {
+  CHECK_BTGATT_INIT();
+  BTIF_TRACE_DEBUG("%s: enable: %d", __func__, enable);
+
+  uint8_t action = enable ? 1 : 0;
+
+  return do_in_jni_thread(Bind(&BTA_DmEnableScanFilter, action,
+                               &bta_scan_filt_status_cb, client_if));
+}
+
+bt_status_t btif_gattc_set_scan_parameters(int client_if, int scan_interval,
+                                           int scan_window) {
+  CHECK_BTGATT_INIT();
+  return do_in_jni_thread(
+      Bind(BTA_DmSetBleScanParams, client_if, scan_interval, scan_window,
+           BTM_BLE_SCAN_MODE_ACTI,
+           (tBLE_SCAN_PARAM_SETUP_CBACK)bta_scan_param_setup_cb));
+}
+
+
+bt_status_t btif_gattc_cfg_storage(int client_if, int batch_scan_full_max,
+                                   int batch_scan_trunc_max,
+                                   int batch_scan_notify_threshold) {
+  CHECK_BTGATT_INIT();
+  return do_in_jni_thread(
+      Bind(BTA_DmBleSetStorageParams, batch_scan_full_max, batch_scan_trunc_max,
+           batch_scan_notify_threshold,
+           (tBTA_BLE_SCAN_SETUP_CBACK*)bta_batch_scan_setup_cb,
+           (tBTA_BLE_SCAN_THRESHOLD_CBACK*)bta_batch_scan_threshold_cb,
+           (tBTA_BLE_SCAN_REP_CBACK*)bta_batch_scan_reports_cb,
+           (tBTA_DM_BLE_REF_VALUE)client_if));
+}
+
+bt_status_t btif_gattc_enb_batch_scan(int client_if, int scan_mode,
+                                      int scan_interval, int scan_window,
+                                      int addr_type, int discard_rule) {
+  CHECK_BTGATT_INIT();
+  return do_in_jni_thread(Bind(BTA_DmBleEnableBatchScan, scan_mode,
+                               scan_interval, scan_window, discard_rule,
+                               addr_type, client_if));
+}
+
+bt_status_t btif_gattc_dis_batch_scan(int client_if) {
+  CHECK_BTGATT_INIT();
+  return do_in_jni_thread(Bind(BTA_DmBleDisableBatchScan, client_if));
+}
+
+bt_status_t btif_gattc_read_batch_scan_reports(int client_if, int scan_mode) {
+  CHECK_BTGATT_INIT();
+  return do_in_jni_thread(Bind(BTA_DmBleReadScanReports, scan_mode, client_if));
+}
+
+} //namespace
+
+const btgatt_scanner_interface_t btgattScannerInterface = {
+    btif_gattc_register_scanner,
+    btif_gattc_unregister_scanner,
+    btif_gattc_scan,
+    btif_gattc_scan_filter_param_setup,
+    btif_gattc_scan_filter_add_remove,
+    btif_gattc_scan_filter_clear,
+    btif_gattc_scan_filter_enable,
+    btif_gattc_set_scan_parameters,
+    btif_gattc_cfg_storage,
+    btif_gattc_enb_batch_scan,
+    btif_gattc_dis_batch_scan,
+    btif_gattc_read_batch_scan_reports,
+};
+
+#endif
diff --git a/btif/src/btif_gatt.cc b/btif/src/btif_gatt.cc
index d9926e2..8465d37 100644
--- a/btif/src/btif_gatt.cc
+++ b/btif/src/btif_gatt.cc
@@ -84,6 +84,7 @@
 
     &btgattClientInterface,
     &btgattServerInterface,
+    &btgattScannerInterface,
     nullptr  // filled in btif_gatt_get_interface
 };
 
diff --git a/btif/src/btif_gatt_client.cc b/btif/src/btif_gatt_client.cc
index a83fc09..53a3b5c 100644
--- a/btif/src/btif_gatt_client.cc
+++ b/btif/src/btif_gatt_client.cc
@@ -31,10 +31,8 @@
 #include <base/threading/thread.h>
 #include <errno.h>
 #include <hardware/bluetooth.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unordered_set>
 #include "device/include/controller.h"
 
 #include "btcore/include/bdaddr.h"
@@ -95,96 +93,10 @@
 #define BTM_BLE_IS_RESOLVE_BDA(x) \
   (((x)[0] & BLE_RESOLVE_ADDR_MASK) == BLE_RESOLVE_ADDR_MSB)
 
-namespace std {
-template <>
-struct hash<bt_bdaddr_t> {
-  size_t operator()(const bt_bdaddr_t& f) const {
-    return f.address[0] + f.address[1] + f.address[2] + f.address[3] +
-           f.address[4] + f.address[5];
-  }
-};
-
-template <>
-struct equal_to<bt_bdaddr_t> {
-  size_t operator()(const bt_bdaddr_t& x, const bt_bdaddr_t& y) const {
-    return memcmp(x.address, y.address, BD_ADDR_LEN);
-  }
-};
-}
-
 namespace {
 
-std::unordered_set<bt_bdaddr_t> p_dev_cb;
 uint8_t rssi_request_client_if;
 
-btgattc_error_t btif_gattc_translate_btm_status(tBTM_STATUS status) {
-  switch (status) {
-    case BTM_SUCCESS:
-    case BTM_SUCCESS_NO_SECURITY:
-      return BT_GATTC_COMMAND_SUCCESS;
-
-    case BTM_CMD_STARTED:
-      return BT_GATTC_COMMAND_STARTED;
-
-    case BTM_BUSY:
-      return BT_GATTC_COMMAND_BUSY;
-
-    case BTM_CMD_STORED:
-      return BT_GATTC_COMMAND_STORED;
-
-    case BTM_NO_RESOURCES:
-      return BT_GATTC_NO_RESOURCES;
-
-    case BTM_MODE_UNSUPPORTED:
-    case BTM_WRONG_MODE:
-    case BTM_MODE4_LEVEL4_NOT_SUPPORTED:
-      return BT_GATTC_MODE_UNSUPPORTED;
-
-    case BTM_ILLEGAL_VALUE:
-    case BTM_SCO_BAD_LENGTH:
-      return BT_GATTC_ILLEGAL_VALUE;
-
-    case BTM_UNKNOWN_ADDR:
-      return BT_GATTC_UNKNOWN_ADDR;
-
-    case BTM_DEVICE_TIMEOUT:
-      return BT_GATTC_DEVICE_TIMEOUT;
-
-    case BTM_FAILED_ON_SECURITY:
-    case BTM_REPEATED_ATTEMPTS:
-    case BTM_NOT_AUTHORIZED:
-      return BT_GATTC_SECURITY_ERROR;
-
-    case BTM_DEV_RESET:
-    case BTM_ILLEGAL_ACTION:
-      return BT_GATTC_INCORRECT_STATE;
-
-    case BTM_BAD_VALUE_RET:
-      return BT_GATTC_INVALID_CONTROLLER_OUTPUT;
-
-    case BTM_DELAY_CHECK:
-      return BT_GATTC_DELAYED_ENCRYPTION_CHECK;
-
-    case BTM_ERR_PROCESSING:
-    default:
-      return BT_GATTC_ERR_PROCESSING;
-  }
-}
-
-void btif_gattc_init_dev_cb(void) { p_dev_cb.clear(); }
-
-void btif_gattc_add_remote_bdaddr(BD_ADDR p_bda, uint8_t addr_type) {
-  bt_bdaddr_t bd_addr;
-  memcpy(bd_addr.address, p_bda, BD_ADDR_LEN);
-  p_dev_cb.insert(bd_addr);
-}
-
-bool btif_gattc_find_bdaddr(BD_ADDR p_bda) {
-  bt_bdaddr_t bd_addr;
-  memcpy(bd_addr.address, p_bda, BD_ADDR_LEN);
-  return (p_dev_cb.count(bd_addr) != 0);
-}
-
 void btif_gattc_upstreams_evt(uint16_t event, char* p_param) {
   LOG_VERBOSE(LOG_TAG, "%s: Event %d", __func__, event);
 
@@ -297,186 +209,6 @@
   ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
 }
 
-void btif_gatts_upstreams_evt(uint16_t event, char* p_param) {
-  LOG_VERBOSE(LOG_TAG, "%s: Event %d", __func__, event);
-
-  tBTA_GATTC* p_data = (tBTA_GATTC*)p_param;
-  switch (event) {
-    case BTA_GATTC_REG_EVT: {
-      bt_uuid_t app_uuid;
-      bta_to_btif_uuid(&app_uuid, &p_data->reg_oper.app_uuid);
-      HAL_CBACK(bt_gatt_callbacks, client->register_scanner_cb,
-                p_data->reg_oper.status, p_data->reg_oper.client_if, &app_uuid);
-      break;
-    }
-
-    case BTA_GATTC_DEREG_EVT:
-      break;
-
-    case BTA_GATTC_SEARCH_CMPL_EVT: {
-      HAL_CBACK(bt_gatt_callbacks, client->search_complete_cb,
-                p_data->search_cmpl.conn_id, p_data->search_cmpl.status);
-      break;
-    }
-
-    default:
-      LOG_ERROR(LOG_TAG, "%s: Unhandled event (%d)!", __func__, event);
-      break;
-  }
-}
-
-void bta_gatts_cback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
-  bt_status_t status =
-      btif_transfer_context(btif_gatts_upstreams_evt, (uint16_t)event,
-                            (char*)p_data, sizeof(tBTA_GATTC), NULL);
-  ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
-}
-void bta_batch_scan_setup_cb(tBTA_BLE_BATCH_SCAN_EVT evt,
-                             tBTA_DM_BLE_REF_VALUE ref_value,
-                             tBTA_STATUS status) {
-  BTIF_TRACE_DEBUG("bta_batch_scan_setup_cb-Status:%x, client_if:%d, evt=%d",
-                   status, ref_value, evt);
-
-  switch (evt) {
-    case BTA_BLE_BATCH_SCAN_ENB_EVT: {
-      CLI_CBACK_IN_JNI(batchscan_enb_disable_cb, 1, ref_value, status);
-      return;
-    }
-
-    case BTA_BLE_BATCH_SCAN_DIS_EVT: {
-      CLI_CBACK_IN_JNI(batchscan_enb_disable_cb, 0, ref_value, status);
-      return;
-    }
-
-    case BTA_BLE_BATCH_SCAN_CFG_STRG_EVT: {
-      CLI_CBACK_IN_JNI(batchscan_cfg_storage_cb, ref_value, status);
-      return;
-    }
-
-    case BTA_BLE_BATCH_SCAN_DATA_EVT: {
-      CLI_CBACK_IN_JNI(batchscan_reports_cb, ref_value, status, 0, 0,
-                       vector<uint8_t>());
-      return;
-    }
-
-    case BTA_BLE_BATCH_SCAN_THRES_EVT: {
-      CLI_CBACK_IN_JNI(batchscan_threshold_cb, ref_value);
-      return;
-    }
-
-    default:
-      return;
-  }
-}
-
-void bta_batch_scan_threshold_cb(tBTA_DM_BLE_REF_VALUE ref_value) {
-  CLI_CBACK_IN_JNI(batchscan_threshold_cb, ref_value);
-}
-
-void bta_batch_scan_reports_cb(tBTA_DM_BLE_REF_VALUE ref_value,
-                               uint8_t report_format, uint8_t num_records,
-                               uint16_t data_len, uint8_t* p_rep_data,
-                               tBTA_STATUS status) {
-  BTIF_TRACE_DEBUG("%s - client_if:%d, %d, %d, %d", __func__, ref_value, status,
-                   num_records, data_len);
-
-  if (data_len > 0) {
-    vector<uint8_t> data(p_rep_data, p_rep_data + data_len);
-    osi_free(p_rep_data);
-
-    CLI_CBACK_IN_JNI(batchscan_reports_cb, ref_value, status, report_format,
-                     num_records, std::move(data));
-  } else {
-    CLI_CBACK_IN_JNI(batchscan_reports_cb, ref_value, status, report_format,
-                     num_records, vector<uint8_t>());
-  }
-}
-
-void bta_scan_results_cb_impl(bt_bdaddr_t bd_addr, tBT_DEVICE_TYPE device_type,
-                              int8_t rssi, uint8_t addr_type,
-                              vector<uint8_t> value) {
-  uint8_t remote_name_len;
-  const uint8_t* p_eir_remote_name = NULL;
-  bt_device_type_t dev_type;
-  bt_property_t properties;
-
-  p_eir_remote_name = BTM_CheckEirData(
-      value.data(), BTM_EIR_COMPLETE_LOCAL_NAME_TYPE, &remote_name_len);
-
-  if (p_eir_remote_name == NULL) {
-    p_eir_remote_name = BTM_CheckEirData(
-        value.data(), BT_EIR_SHORTENED_LOCAL_NAME_TYPE, &remote_name_len);
-  }
-
-  if ((addr_type != BLE_ADDR_RANDOM) || (p_eir_remote_name)) {
-    if (!btif_gattc_find_bdaddr(bd_addr.address)) {
-      btif_gattc_add_remote_bdaddr(bd_addr.address, addr_type);
-
-      if (p_eir_remote_name) {
-        bt_bdname_t bdname;
-        memcpy(bdname.name, p_eir_remote_name, remote_name_len);
-        bdname.name[remote_name_len] = '\0';
-
-        LOG_VERBOSE(LOG_TAG, "%s BLE device name=%s len=%d dev_type=%d",
-                    __func__, bdname.name, remote_name_len, device_type);
-        btif_dm_update_ble_remote_properties(bd_addr.address, bdname.name,
-                                             device_type);
-      }
-    }
-  }
-
-  dev_type = (bt_device_type_t)device_type;
-  BTIF_STORAGE_FILL_PROPERTY(&properties, BT_PROPERTY_TYPE_OF_DEVICE,
-                             sizeof(dev_type), &dev_type);
-  btif_storage_set_remote_device_property(&(bd_addr), &properties);
-
-  btif_storage_set_remote_addr_type(&bd_addr, addr_type);
-
-  HAL_CBACK(bt_gatt_callbacks, client->scan_result_cb, &bd_addr, rssi,
-            std::move(value));
-}
-
-void bta_scan_results_cb(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* p_data) {
-  uint8_t len;
-
-  if (event == BTA_DM_INQ_CMPL_EVT) {
-    BTIF_TRACE_DEBUG("%s  BLE observe complete. Num Resp %d", __func__,
-                     p_data->inq_cmpl.num_resps);
-    return;
-  }
-
-  if (event != BTA_DM_INQ_RES_EVT) {
-    BTIF_TRACE_WARNING("%s : Unknown event 0x%x", __func__, event);
-    return;
-  }
-
-  vector<uint8_t> value(BTGATT_MAX_ATTR_LEN);
-  if (p_data->inq_res.p_eir) {
-    value.insert(value.begin(), p_data->inq_res.p_eir,
-                 p_data->inq_res.p_eir + 62);
-
-    if (BTM_CheckEirData(p_data->inq_res.p_eir,
-                         BTM_EIR_COMPLETE_LOCAL_NAME_TYPE, &len)) {
-      p_data->inq_res.remt_name_not_required = true;
-    }
-  }
-
-  bt_bdaddr_t bdaddr;
-  bdcpy(bdaddr.address, p_data->inq_res.bd_addr);
-  do_in_jni_thread(Bind(bta_scan_results_cb_impl, bdaddr,
-                        p_data->inq_res.device_type, p_data->inq_res.rssi,
-                        p_data->inq_res.ble_addr_type, std::move(value)));
-}
-
-void bta_track_adv_event_cb(tBTA_DM_BLE_TRACK_ADV_DATA* p_track_adv_data) {
-  btgatt_track_adv_info_t* btif_scan_track_cb = new btgatt_track_adv_info_t;
-
-  BTIF_TRACE_DEBUG("%s", __func__);
-  btif_gatt_move_track_adv_data(btif_scan_track_cb,
-                                (btgatt_track_adv_info_t*)p_track_adv_data);
-
-  CLI_CBACK_IN_JNI(track_adv_event_cb, Owned(btif_scan_track_cb));
-}
 
 void btm_read_rssi_cb(tBTM_RSSI_RESULTS* p_result) {
   if (!p_result) return;
@@ -487,32 +219,6 @@
                    base::Owned(addr), p_result->rssi, p_result->status);
 }
 
-void bta_scan_param_setup_cb(tGATT_IF client_if, tBTM_STATUS status) {
-  CLI_CBACK_IN_JNI(scan_parameter_setup_completed_cb, client_if,
-                   btif_gattc_translate_btm_status(status));
-}
-
-void bta_scan_filt_cfg_cb(tBTA_DM_BLE_PF_ACTION action,
-                          tBTA_DM_BLE_SCAN_COND_OP cfg_op,
-                          tBTA_DM_BLE_PF_AVBL_SPACE avbl_space,
-                          tBTA_STATUS status, tBTA_DM_BLE_REF_VALUE ref_value) {
-  CLI_CBACK_IN_JNI(scan_filter_cfg_cb, action, ref_value, status, cfg_op,
-                   avbl_space);
-}
-
-void bta_scan_filt_param_setup_cb(uint8_t action_type,
-                                  tBTA_DM_BLE_PF_AVBL_SPACE avbl_space,
-                                  tBTA_DM_BLE_REF_VALUE ref_value,
-                                  tBTA_STATUS status) {
-  CLI_CBACK_IN_JNI(scan_filter_param_cb, action_type, ref_value, status,
-                   avbl_space);
-}
-
-void bta_scan_filt_status_cb(uint8_t action, tBTA_STATUS status,
-                             tBTA_DM_BLE_REF_VALUE ref_value) {
-  CLI_CBACK_IN_JNI(scan_filter_status_cb, action, ref_value, status);
-}
-
 /*******************************************************************************
  *  Client API Functions
  *******************************************************************************/
@@ -538,40 +244,6 @@
   return do_in_jni_thread(Bind(&btif_gattc_unregister_app_impl, client_if));
 }
 
-
-void btif_gattc_register_scanner_impl(tBT_UUID uuid) {
-  BTA_GATTC_AppRegister(&uuid, bta_gatts_cback);
-}
-
-bt_status_t btif_gattc_register_scanner(bt_uuid_t* uuid) {
-  CHECK_BTGATT_INIT();
-
-  tBT_UUID bt_uuid;
-  btif_to_bta_uuid(&bt_uuid, uuid);
-  return do_in_jni_thread(Bind(&btif_gattc_register_scanner_impl, bt_uuid));
-}
-
-void btif_gattc_unregister_scanner_impl(int client_if) {
-  BTA_GATTC_AppDeregister(client_if);
-}
-
-bt_status_t btif_gattc_unregister_scanner(int scanner_id) {
-  CHECK_BTGATT_INIT();
-  return do_in_jni_thread(Bind(&btif_gattc_unregister_scanner_impl, scanner_id));
-}
-
-
-bt_status_t btif_gattc_scan(bool start) {
-  CHECK_BTGATT_INIT();
-  if (start) {
-    btif_gattc_init_dev_cb();
-    return do_in_jni_thread(Bind(&BTA_DmBleObserve, true, 0,
-                                 (tBTA_DM_SEARCH_CBACK*)bta_scan_results_cb));
-  } else {
-    return do_in_jni_thread(Bind(&BTA_DmBleObserve, false, 0, nullptr));
-  }
-}
-
 void btif_gattc_open_impl(int client_if, BD_ADDR address, bool is_direct,
                           int transport_p) {
   // Ensure device is in inquiry database
@@ -869,216 +541,6 @@
            min_interval, max_interval, latency, timeout));
 }
 
-void btif_gattc_scan_filter_param_setup_impl(
-    int client_if, uint8_t action, int filt_index,
-    tBTA_DM_BLE_PF_FILT_PARAMS* adv_filt_param) {
-  if (1 == adv_filt_param->dely_mode)
-    BTA_DmBleTrackAdvertiser(client_if, bta_track_adv_event_cb);
-  BTA_DmBleScanFilterSetup(action, filt_index, adv_filt_param, NULL,
-                           bta_scan_filt_param_setup_cb, client_if);
-}
-
-bt_status_t btif_gattc_scan_filter_param_setup(
-    btgatt_filt_param_setup_t filt_param) {
-  CHECK_BTGATT_INIT();
-  BTIF_TRACE_DEBUG("%s", __func__);
-
-  tBTA_DM_BLE_PF_FILT_PARAMS* adv_filt_param = new tBTA_DM_BLE_PF_FILT_PARAMS;
-  adv_filt_param->feat_seln = filt_param.feat_seln;
-  adv_filt_param->list_logic_type = filt_param.list_logic_type;
-  adv_filt_param->filt_logic_type = filt_param.filt_logic_type;
-  adv_filt_param->rssi_high_thres = filt_param.rssi_high_thres;
-  adv_filt_param->rssi_low_thres = filt_param.rssi_low_thres;
-  adv_filt_param->dely_mode = filt_param.dely_mode;
-  adv_filt_param->found_timeout = filt_param.found_timeout;
-  adv_filt_param->lost_timeout = filt_param.lost_timeout;
-  adv_filt_param->found_timeout_cnt = filt_param.found_timeout_cnt;
-  adv_filt_param->num_of_tracking_entries = filt_param.num_of_tracking_entries;
-
-  return do_in_jni_thread(
-      Bind(base::IgnoreResult(&btif_gattc_scan_filter_param_setup_impl),
-           filt_param.client_if, filt_param.action, filt_param.filt_index,
-           base::Owned(adv_filt_param)));
-}
-
-void btif_gattc_scan_filter_add_srvc_uuid(tBT_UUID uuid,
-                                          tBTA_DM_BLE_PF_COND_MASK* p_uuid_mask,
-                                          int action, int filt_type,
-                                          int filt_index, int client_if) {
-  tBTA_DM_BLE_PF_COND_PARAM cond;
-  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
-
-  cond.srvc_uuid.p_target_addr = NULL;
-  cond.srvc_uuid.cond_logic = BTA_DM_BLE_PF_LOGIC_AND;
-  cond.srvc_uuid.uuid = uuid;
-  cond.srvc_uuid.p_uuid_mask = p_uuid_mask;
-
-  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
-                              &bta_scan_filt_cfg_cb, client_if);
-}
-
-void btif_gattc_scan_filter_add_local_name(vector<uint8_t> data, int action,
-                                           int filt_type, int filt_index,
-                                           int client_if) {
-  tBTA_DM_BLE_PF_COND_PARAM cond;
-  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
-
-  cond.local_name.data_len = data.size();
-  cond.local_name.p_data = const_cast<uint8_t*>(data.data());
-  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
-                              &bta_scan_filt_cfg_cb, client_if);
-}
-
-void btif_gattc_scan_filter_add_manu_data(int company_id, int company_id_mask,
-                                          vector<uint8_t> pattern,
-                                          vector<uint8_t> pattern_mask,
-                                          int action, int filt_type,
-                                          int filt_index, int client_if) {
-  tBTA_DM_BLE_PF_COND_PARAM cond;
-  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
-
-  cond.manu_data.company_id = company_id;
-  cond.manu_data.company_id_mask = company_id_mask ? company_id_mask : 0xFFFF;
-  cond.manu_data.data_len = pattern.size();
-  cond.manu_data.p_pattern = const_cast<uint8_t*>(pattern.data());
-  cond.manu_data.p_pattern_mask = const_cast<uint8_t*>(pattern_mask.data());
-  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
-                              &bta_scan_filt_cfg_cb, client_if);
-}
-
-void btif_gattc_scan_filter_add_data_pattern(vector<uint8_t> pattern,
-                                             vector<uint8_t> pattern_mask,
-                                             int action, int filt_type,
-                                             int filt_index, int client_if) {
-  tBTA_DM_BLE_PF_COND_PARAM cond;
-  memset(&cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
-
-  cond.srvc_data.data_len = pattern.size();
-  cond.srvc_data.p_pattern = const_cast<uint8_t*>(pattern.data());
-  cond.srvc_data.p_pattern_mask = const_cast<uint8_t*>(pattern_mask.data());
-  BTA_DmBleCfgFilterCondition(action, filt_type, filt_index, &cond,
-                              &bta_scan_filt_cfg_cb, client_if);
-}
-
-bt_status_t btif_gattc_scan_filter_add_remove(
-    int client_if, int action, int filt_type, int filt_index, int company_id,
-    int company_id_mask, const bt_uuid_t* p_uuid, const bt_uuid_t* p_uuid_mask,
-    const bt_bdaddr_t* bd_addr, char addr_type, vector<uint8_t> data,
-    vector<uint8_t> mask) {
-  CHECK_BTGATT_INIT();
-  BTIF_TRACE_DEBUG("%s, %d, %d", __func__, action, filt_type);
-
-  /* If data is passed, both mask and data have to be the same length */
-  if (data.size() != mask.size() && data.size() != 0 && mask.size() != 0)
-    return BT_STATUS_PARM_INVALID;
-
-  switch (filt_type) {
-    case BTA_DM_BLE_PF_ADDR_FILTER:  // 0
-    {
-      tBTA_DM_BLE_PF_COND_PARAM* cond = new tBTA_DM_BLE_PF_COND_PARAM;
-      memset(cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
-
-      bdcpy(cond->target_addr.bda, bd_addr->address);
-      cond->target_addr.type = addr_type;
-      return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition, action,
-                                   filt_type, filt_index, base::Owned(cond),
-                                   &bta_scan_filt_cfg_cb, client_if));
-    }
-
-    case BTA_DM_BLE_PF_SRVC_DATA:  // 1
-      return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition, action,
-                                   filt_type, filt_index, nullptr,
-                                   &bta_scan_filt_cfg_cb, client_if));
-
-    case BTA_DM_BLE_PF_SRVC_UUID:  // 2
-    {
-      tBT_UUID bt_uuid;
-      btif_to_bta_uuid(&bt_uuid, p_uuid);
-
-      if (p_uuid_mask != NULL) {
-        tBTA_DM_BLE_PF_COND_MASK* uuid_mask = new tBTA_DM_BLE_PF_COND_MASK;
-        btif_to_bta_uuid_mask(uuid_mask, p_uuid_mask, p_uuid);
-        return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_srvc_uuid,
-                                     bt_uuid, base::Owned(uuid_mask), action,
-                                     filt_type, filt_index, client_if));
-      }
-
-      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_srvc_uuid,
-                                   bt_uuid, nullptr, action, filt_type,
-                                   filt_index, client_if));
-    }
-
-    case BTA_DM_BLE_PF_SRVC_SOL_UUID:  // 3
-    {
-      tBTA_DM_BLE_PF_COND_PARAM* cond = new tBTA_DM_BLE_PF_COND_PARAM;
-      memset(cond, 0, sizeof(tBTA_DM_BLE_PF_COND_PARAM));
-
-      cond->solicitate_uuid.p_target_addr = NULL;
-      cond->solicitate_uuid.cond_logic = BTA_DM_BLE_PF_LOGIC_AND;
-      btif_to_bta_uuid(&cond->solicitate_uuid.uuid, p_uuid);
-
-      return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition, action,
-                                   filt_type, filt_index, base::Owned(cond),
-                                   &bta_scan_filt_cfg_cb, client_if));
-    }
-
-    case BTA_DM_BLE_PF_LOCAL_NAME:  // 4
-    {
-      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_local_name,
-                                   std::move(data), action, filt_type,
-                                   filt_index, client_if));
-    }
-
-    case BTA_DM_BLE_PF_MANU_DATA:  // 5
-    {
-      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_manu_data,
-                                   company_id, company_id_mask, std::move(data),
-                                   std::move(mask), action, filt_type,
-                                   filt_index, client_if));
-    }
-
-    case BTA_DM_BLE_PF_SRVC_DATA_PATTERN:  // 6
-    {
-      return do_in_jni_thread(Bind(&btif_gattc_scan_filter_add_data_pattern,
-                                   std::move(data), std::move(mask), action,
-                                   filt_type, filt_index, client_if));
-    }
-
-    default:
-      LOG_ERROR(LOG_TAG, "%s: Unknown filter type (%d)!", __func__, action);
-      return (bt_status_t)BTA_GATT_OK;
-  }
-}
-
-bt_status_t btif_gattc_scan_filter_clear(int client_if, int filter_index) {
-  CHECK_BTGATT_INIT();
-  BTIF_TRACE_DEBUG("%s: filter_index: %d", __func__, filter_index);
-
-  return do_in_jni_thread(Bind(&BTA_DmBleCfgFilterCondition,
-                               BTA_DM_BLE_SCAN_COND_CLEAR,
-                               BTA_DM_BLE_PF_TYPE_ALL, filter_index, nullptr,
-                               &bta_scan_filt_cfg_cb, client_if));
-}
-
-bt_status_t btif_gattc_scan_filter_enable(int client_if, bool enable) {
-  CHECK_BTGATT_INIT();
-  BTIF_TRACE_DEBUG("%s: enable: %d", __func__, enable);
-
-  uint8_t action = enable ? 1 : 0;
-
-  return do_in_jni_thread(Bind(&BTA_DmEnableScanFilter, action,
-                               &bta_scan_filt_status_cb, client_if));
-}
-
-bt_status_t btif_gattc_set_scan_parameters(int client_if, int scan_interval,
-                                           int scan_window) {
-  CHECK_BTGATT_INIT();
-  return do_in_jni_thread(
-      Bind(BTA_DmSetBleScanParams, client_if, scan_interval, scan_window,
-           BTM_BLE_SCAN_MODE_ACTI,
-           (tBLE_SCAN_PARAM_SETUP_CBACK)bta_scan_param_setup_cb));
-}
-
 int btif_gattc_get_device_type(const bt_bdaddr_t* bd_addr) {
   int device_type = 0;
   char bd_addr_str[18] = {0};
@@ -1089,38 +551,6 @@
   return 0;
 }
 
-bt_status_t btif_gattc_cfg_storage(int client_if, int batch_scan_full_max,
-                                   int batch_scan_trunc_max,
-                                   int batch_scan_notify_threshold) {
-  CHECK_BTGATT_INIT();
-  return do_in_jni_thread(
-      Bind(BTA_DmBleSetStorageParams, batch_scan_full_max, batch_scan_trunc_max,
-           batch_scan_notify_threshold,
-           (tBTA_BLE_SCAN_SETUP_CBACK*)bta_batch_scan_setup_cb,
-           (tBTA_BLE_SCAN_THRESHOLD_CBACK*)bta_batch_scan_threshold_cb,
-           (tBTA_BLE_SCAN_REP_CBACK*)bta_batch_scan_reports_cb,
-           (tBTA_DM_BLE_REF_VALUE)client_if));
-}
-
-bt_status_t btif_gattc_enb_batch_scan(int client_if, int scan_mode,
-                                      int scan_interval, int scan_window,
-                                      int addr_type, int discard_rule) {
-  CHECK_BTGATT_INIT();
-  return do_in_jni_thread(Bind(BTA_DmBleEnableBatchScan, scan_mode,
-                               scan_interval, scan_window, discard_rule,
-                               addr_type, client_if));
-}
-
-bt_status_t btif_gattc_dis_batch_scan(int client_if) {
-  CHECK_BTGATT_INIT();
-  return do_in_jni_thread(Bind(BTA_DmBleDisableBatchScan, client_if));
-}
-
-bt_status_t btif_gattc_read_batch_scan_reports(int client_if, int scan_mode) {
-  CHECK_BTGATT_INIT();
-  return do_in_jni_thread(Bind(BTA_DmBleReadScanReports, scan_mode, client_if));
-}
-
 bt_status_t btif_gattc_test_command(int command, btgatt_test_params_t* params) {
   return btif_gattc_test_command_impl(command, params);
 }
@@ -1130,9 +560,6 @@
 const btgatt_client_interface_t btgattClientInterface = {
     btif_gattc_register_app,
     btif_gattc_unregister_app,
-    btif_gattc_register_scanner,
-    btif_gattc_unregister_scanner,
-    btif_gattc_scan,
     btif_gattc_open,
     btif_gattc_close,
     btif_gattc_listen,
@@ -1146,18 +573,9 @@
     btif_gattc_reg_for_notification,
     btif_gattc_dereg_for_notification,
     btif_gattc_read_remote_rssi,
-    btif_gattc_scan_filter_param_setup,
-    btif_gattc_scan_filter_add_remove,
-    btif_gattc_scan_filter_clear,
-    btif_gattc_scan_filter_enable,
     btif_gattc_get_device_type,
     btif_gattc_configure_mtu,
     btif_gattc_conn_parameter_update,
-    btif_gattc_set_scan_parameters,
-    btif_gattc_cfg_storage,
-    btif_gattc_enb_batch_scan,
-    btif_gattc_dis_batch_scan,
-    btif_gattc_read_batch_scan_reports,
     btif_gattc_test_command,
     btif_gattc_get_gatt_db};
 
diff --git a/service/Android.mk b/service/Android.mk
index 5bcd54e..51e5671 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -61,6 +61,8 @@
 	common/android/bluetooth/IBluetoothGattServerCallback.aidl \
 	common/android/bluetooth/IBluetoothLeAdvertiser.aidl \
 	common/android/bluetooth/IBluetoothLeAdvertiserCallback.aidl \
+	common/android/bluetooth/IBluetoothLeScanner.aidl \
+	common/android/bluetooth/IBluetoothLeScannerCallback.aidl \
 	common/android/bluetooth/IBluetoothLowEnergy.aidl \
 	common/android/bluetooth/IBluetoothLowEnergyCallback.aidl \
 	common/android/bluetooth/advertise_data.cc \
@@ -90,6 +92,7 @@
 	ipc/ipc_manager.cc \
 	logging_helpers.cc \
 	low_energy_advertiser.cc \
+	low_energy_scanner.cc \
 	low_energy_client.cc \
 	settings.cc
 
@@ -102,6 +105,7 @@
 	ipc/binder/bluetooth_gatt_client_binder_server.cc \
 	ipc/binder/bluetooth_gatt_server_binder_server.cc \
 	ipc/binder/bluetooth_le_advertiser_binder_server.cc \
+	ipc/binder/bluetooth_le_scanner_binder_server.cc \
 	ipc/binder/bluetooth_low_energy_binder_server.cc \
 	ipc/binder/interface_with_instances_base.cc \
 	ipc/binder/ipc_handler_binder.cc \
@@ -126,6 +130,7 @@
 	test/gatt_server_unittest.cc \
 	test/low_energy_advertiser_unittest.cc \
 	test/low_energy_client_unittest.cc \
+	test/low_energy_scanner_unittest.cc \
 	test/settings_unittest.cc \
 	test/util_unittest.cc \
 	test/uuid_unittest.cc
diff --git a/service/BUILD.gn b/service/BUILD.gn
index 50b01ff..19134c1 100644
--- a/service/BUILD.gn
+++ b/service/BUILD.gn
@@ -42,6 +42,7 @@
     "ipc/linux_ipc_host.cc",
     "logging_helpers.cc",
     "low_energy_advertiser.cc",
+    "low_energy_scanner.cc",
     "low_energy_client.cc",
     "settings.cc",
   ]
diff --git a/service/adapter.cc b/service/adapter.cc
index ede0ab2..e97b595 100644
--- a/service/adapter.cc
+++ b/service/adapter.cc
@@ -30,6 +30,7 @@
 #include "service/hal/bluetooth_interface.h"
 #include "service/logging_helpers.h"
 #include "service/low_energy_advertiser.h"
+#include "service/low_energy_scanner.h"
 #include "service/low_energy_client.h"
 
 using std::lock_guard;
@@ -80,6 +81,7 @@
     hal::BluetoothInterface::Get()->AddObserver(this);
     ble_client_factory_.reset(new LowEnergyClientFactory(*this));
     ble_advertiser_factory_.reset(new LowEnergyAdvertiserFactory());
+    ble_scanner_factory_.reset(new LowEnergyScannerFactory(*this));
     gatt_client_factory_.reset(new GattClientFactory());
     gatt_server_factory_.reset(new GattServerFactory());
     hal::BluetoothInterface::Get()->GetHALInterface()->get_adapter_properties();
@@ -223,6 +225,10 @@
     return ble_advertiser_factory_.get();
   }
 
+  LowEnergyScannerFactory* GetLeScannerFactory() const override {
+    return ble_scanner_factory_.get();
+  }
+
   GattClientFactory* GetGattClientFactory() const override {
     return gatt_client_factory_.get();
   }
@@ -395,6 +401,9 @@
   // Factory used to create per-app LeAdvertiser instances.
   std::unique_ptr<LowEnergyAdvertiserFactory> ble_advertiser_factory_;
 
+  // Factory used to create per-app LeScanner instances.
+  std::unique_ptr<LowEnergyScannerFactory> ble_scanner_factory_;
+
   // Factory used to create per-app GattClient instances.
   std::unique_ptr<GattClientFactory> gatt_client_factory_;
 
diff --git a/service/adapter.h b/service/adapter.h
index 9c2be86..75a5491 100644
--- a/service/adapter.h
+++ b/service/adapter.h
@@ -27,6 +27,7 @@
 class GattClientFactory;
 class GattServerFactory;
 class LowEnergyAdvertiserFactory;
+class LowEnergyScannerFactory;
 class LowEnergyClientFactory;
 
 // Represents the local Bluetooth adapter.
@@ -126,6 +127,10 @@
   // operations.
   virtual LowEnergyClientFactory* GetLowEnergyClientFactory() const = 0;
 
+  // Returns a pointer to the LowEnergyScannerFactory. This can be used to
+  // register per-application LowEnergyScanner instances to perform scanning.
+  virtual LowEnergyScannerFactory* GetLeScannerFactory() const = 0;
+
   // Returns a pointer to the LowEnergyAdvertiserFactory. This can be used to
   // register per-application LowEnergyAdvertiser instances to perform advertising.
   virtual LowEnergyAdvertiserFactory* GetLeAdvertiserFactory() const = 0;
diff --git a/service/client/main.cc b/service/client/main.cc
index 8c1dd5e..442b751 100644
--- a/service/client/main.cc
+++ b/service/client/main.cc
@@ -37,9 +37,11 @@
 #include <android/bluetooth/BnBluetoothGattClientCallback.h>
 #include <android/bluetooth/BnBluetoothLowEnergyCallback.h>
 #include <android/bluetooth/BnBluetoothLeAdvertiserCallback.h>
+#include <android/bluetooth/BnBluetoothLeScannerCallback.h>
 #include <android/bluetooth/IBluetooth.h>
 #include <android/bluetooth/IBluetoothGattClient.h>
 #include <android/bluetooth/IBluetoothLeAdvertiser.h>
+#include <android/bluetooth/IBluetoothLeScanner.h>
 #include <android/bluetooth/IBluetoothLowEnergy.h>
 #include <bluetooth/low_energy_constants.h>
 #include <bluetooth/scan_filter.h>
@@ -58,6 +60,7 @@
 using android::bluetooth::IBluetooth;
 using android::bluetooth::IBluetoothGattClient;
 using android::bluetooth::IBluetoothLeAdvertiser;
+using android::bluetooth::IBluetoothLeScanner;
 using android::bluetooth::IBluetoothLowEnergy;
 
 namespace {
@@ -100,6 +103,11 @@
 std::atomic_bool ble_advertiser_registering(false);
 std::atomic_int ble_advertiser_id(0);
 
+// The registered IBluetoothLeScanner handle. If |ble_scanner_registering| is
+// true then an operation to register the scanner is in progress.
+std::atomic_bool ble_scanner_registering(false);
+std::atomic_int ble_scanner_id(0);
+
 // The registered IBluetoothGattClient client handle. If |gatt_registering| is
 // true then an operation to register the client is in progress.
 std::atomic_bool gatt_registering(false);
@@ -202,22 +210,6 @@
     return Status::ok();
   }
 
-  Status OnScanResult(
-      const android::bluetooth::ScanResult& scan_result) override {
-    BeginAsyncOut();
-    cout << COLOR_BOLDWHITE "Scan result: " << COLOR_BOLDYELLOW "["
-         << scan_result.device_address() << "] "
-         << COLOR_BOLDWHITE "- RSSI: " << scan_result.rssi() << COLOR_OFF;
-
-    if (dump_scan_record) {
-      cout << " - Record: "
-           << base::HexEncode(scan_result.scan_record().data(),
-                              scan_result.scan_record().size());
-    }
-    EndAsyncOut();
-    return Status::ok();
-  }
-
  private:
   DISALLOW_COPY_AND_ASSIGN(CLIBluetoothLowEnergyCallback);
 };
@@ -259,6 +251,48 @@
   DISALLOW_COPY_AND_ASSIGN(CLIBluetoothLeAdvertiserCallback);
 };
 
+class CLIBluetoothLeScannerCallback
+    : public android::bluetooth::BnBluetoothLeScannerCallback {
+ public:
+  CLIBluetoothLeScannerCallback() = default;
+  ~CLIBluetoothLeScannerCallback() override = default;
+
+  // IBluetoothLowEnergyCallback overrides:
+  Status OnScannerRegistered(int status, int scanner_id) override {
+    BeginAsyncOut();
+    if (status != bluetooth::BLE_STATUS_SUCCESS) {
+      PrintError("Failed to register BLE client");
+    } else {
+      ble_scanner_id = scanner_id;
+      cout << COLOR_BOLDWHITE "Registered BLE client with ID: " COLOR_OFF
+           << COLOR_GREEN << scanner_id << COLOR_OFF;
+    }
+    EndAsyncOut();
+
+    ble_scanner_registering = false;
+    return Status::ok();
+  }
+
+  Status OnScanResult(
+      const android::bluetooth::ScanResult& scan_result) override {
+    BeginAsyncOut();
+    cout << COLOR_BOLDWHITE "Scan result: " << COLOR_BOLDYELLOW "["
+         << scan_result.device_address() << "] "
+         << COLOR_BOLDWHITE "- RSSI: " << scan_result.rssi() << COLOR_OFF;
+
+    if (dump_scan_record) {
+      cout << " - Record: "
+           << base::HexEncode(scan_result.scan_record().data(),
+                              scan_result.scan_record().size());
+    }
+    EndAsyncOut();
+    return Status::ok();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CLIBluetoothLeScannerCallback);
+};
+
 class CLIGattClientCallback
     : public android::bluetooth::BnBluetoothGattClientCallback {
  public:
@@ -787,6 +821,53 @@
   PrintCommandStatus(status);
 }
 
+
+void HandleRegisterBLEScanner(IBluetooth* bt_iface, const vector<string>& args) {
+  CHECK_NO_ARGS(args);
+
+  if (ble_scanner_registering.load()) {
+    PrintError("In progress");
+    return;
+  }
+
+  if (ble_scanner_id.load()) {
+    PrintError("Already registered");
+    return;
+  }
+
+  sp<IBluetoothLeScanner> ble_scanner_iface;
+  bt_iface->GetLeScannerInterface(&ble_scanner_iface);
+  if (!ble_scanner_iface.get()) {
+    PrintError("Failed to obtain handle to Bluetooth LE Scanner interface");
+    return;
+  }
+
+  bool status;
+  ble_scanner_iface->RegisterScanner(new CLIBluetoothLeScannerCallback(), &status);
+  ble_scanner_registering = status;
+  PrintCommandStatus(status);
+}
+
+void HandleUnregisterBLEScanner(IBluetooth* bt_iface, const vector<string>& args) {
+  CHECK_NO_ARGS(args);
+
+  if (!ble_scanner_id.load()) {
+    PrintError("Not registered");
+    return;
+  }
+
+  sp<IBluetoothLeScanner> ble_scanner_iface;
+  bt_iface->GetLeScannerInterface(&ble_scanner_iface);
+  if (!ble_scanner_iface.get()) {
+    PrintError("Failed to obtain handle to Bluetooth LE scanner interface");
+    return;
+  }
+
+  ble_scanner_iface->UnregisterScanner(ble_scanner_id.load());
+  ble_scanner_id = 0;
+  PrintCommandStatus(true);
+}
+
 void HandleStartLeScan(IBluetooth* bt_iface, const vector<string>& args) {
   if (!ble_client_id.load()) {
     PrintError("BLE not registered");
@@ -808,10 +889,10 @@
     }
   }
 
-  sp<IBluetoothLowEnergy> ble_iface;
-  bt_iface->GetLowEnergyInterface(&ble_iface);
-  if (!ble_iface.get()) {
-    PrintError("Failed to obtain handle to Bluetooth Low Energy interface");
+  sp<IBluetoothLeScanner> ble_scanner_iface;
+  bt_iface->GetLeScannerInterface(&ble_scanner_iface);
+  if (!ble_scanner_iface.get()) {
+    PrintError("Failed to obtain handle to Bluetooth LE scanner interface");
     return;
   }
 
@@ -819,7 +900,7 @@
   std::vector<android::bluetooth::ScanFilter> filters;
 
   bool status;
-  ble_iface->StartScan(ble_client_id.load(), settings, filters, &status);
+  ble_scanner_iface->StartScan(ble_scanner_id.load(), settings, filters, &status);
   PrintCommandStatus(status);
 }
 
@@ -829,15 +910,15 @@
     return;
   }
 
-  sp<IBluetoothLowEnergy> ble_iface;
-  bt_iface->GetLowEnergyInterface(&ble_iface);
-  if (!ble_iface.get()) {
-    PrintError("Failed to obtain handle to Bluetooth Low Energy interface");
+  sp<IBluetoothLeScanner> ble_scanner_iface;
+  bt_iface->GetLeScannerInterface(&ble_scanner_iface);
+  if (!ble_scanner_iface.get()) {
+    PrintError("Failed to obtain handle to Bluetooth LE scanner interface");
     return;
   }
 
   bool status;
-  ble_iface->StopScan(ble_client_id.load(), &status);
+  ble_scanner_iface->StopScan(ble_scanner_id.load(), &status);
   PrintCommandStatus(status);
 }
 
@@ -880,6 +961,10 @@
     {"set-mtu", HandleSetMtu, "\t\tSet MTU (-h for options)"},
     {"start-adv", HandleStartAdv, "\t\tStart advertising (-h for options)"},
     {"stop-adv", HandleStopAdv, "\t\tStop advertising"},
+    {"register-le-scanner", HandleRegisterBLEScanner,
+     "\t\tRegister with the Bluetooth Low Energy scanner interface"},
+    {"unregister-le-scanner", HandleUnregisterBLEScanner,
+     "\t\tUnregister from the Bluetooth LE scanner interface"},
     {"start-le-scan", HandleStartLeScan,
      "\t\tStart LE device scan (-h for options)"},
     {"stop-le-scan", HandleStopLeScan, "\t\tStop LE device scan"},
diff --git a/service/common/android/bluetooth/IBluetooth.aidl b/service/common/android/bluetooth/IBluetooth.aidl
index 58d3e04..1e96952 100644
--- a/service/common/android/bluetooth/IBluetooth.aidl
+++ b/service/common/android/bluetooth/IBluetooth.aidl
@@ -19,6 +19,7 @@
 import android.bluetooth.IBluetoothCallback;
 import android.bluetooth.IBluetoothLowEnergy;
 import android.bluetooth.IBluetoothLeAdvertiser;
+import android.bluetooth.IBluetoothLeScanner;
 import android.bluetooth.IBluetoothGattClient;
 import android.bluetooth.IBluetoothGattServer;
 
@@ -43,6 +44,7 @@
 
   IBluetoothLowEnergy GetLowEnergyInterface();
   IBluetoothLeAdvertiser GetLeAdvertiserInterface();
+  IBluetoothLeScanner GetLeScannerInterface();
   IBluetoothGattClient GetGattClientInterface();
   IBluetoothGattServer GetGattServerInterface();
 }
diff --git a/service/common/android/bluetooth/IBluetoothLeScanner.aidl b/service/common/android/bluetooth/IBluetoothLeScanner.aidl
new file mode 100644
index 0000000..2f9506d
--- /dev/null
+++ b/service/common/android/bluetooth/IBluetoothLeScanner.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.IBluetoothLeScannerCallback;
+import android.bluetooth.ScanFilter;
+import android.bluetooth.ScanSettings;
+
+interface IBluetoothLeScanner {
+  boolean RegisterScanner(in IBluetoothLeScannerCallback callback);
+  void UnregisterScanner(int scanner_id);
+  void UnregisterAll();
+
+  boolean StartScan(int client_id,
+      in ScanSettings settings,
+      in ScanFilter[] filters);
+  boolean StopScan(int client_id);
+}
diff --git a/service/common/android/bluetooth/IBluetoothLeScannerCallback.aidl b/service/common/android/bluetooth/IBluetoothLeScannerCallback.aidl
new file mode 100644
index 0000000..4ee6be3
--- /dev/null
+++ b/service/common/android/bluetooth/IBluetoothLeScannerCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.ScanResult;
+
+oneway interface IBluetoothLeScannerCallback {
+  void OnScannerRegistered(int status, int client_id);
+  void OnScanResult(in ScanResult scan_result);
+}
\ No newline at end of file
diff --git a/service/common/android/bluetooth/IBluetoothLowEnergy.aidl b/service/common/android/bluetooth/IBluetoothLowEnergy.aidl
index cdc8c88..4d49430 100644
--- a/service/common/android/bluetooth/IBluetoothLowEnergy.aidl
+++ b/service/common/android/bluetooth/IBluetoothLowEnergy.aidl
@@ -20,8 +20,6 @@
 
 import android.bluetooth.AdvertiseData;
 import android.bluetooth.AdvertiseSettings;
-import android.bluetooth.ScanFilter;
-import android.bluetooth.ScanSettings;
 
 interface IBluetoothLowEnergy {
   boolean RegisterClient(in IBluetoothLowEnergyCallback callback);
@@ -32,9 +30,4 @@
   boolean Disconnect(int client_id, String address);
 
   boolean SetMtu(int client_id, String address, int mtu);
-
-  boolean StartScan(int client_id,
-      in ScanSettings settings,
-      in ScanFilter[] filters);
-  boolean StopScan(int client_id);
 }
\ No newline at end of file
diff --git a/service/common/android/bluetooth/IBluetoothLowEnergyCallback.aidl b/service/common/android/bluetooth/IBluetoothLowEnergyCallback.aidl
index 45c5dc8..858506a 100644
--- a/service/common/android/bluetooth/IBluetoothLowEnergyCallback.aidl
+++ b/service/common/android/bluetooth/IBluetoothLowEnergyCallback.aidl
@@ -23,5 +23,4 @@
   void OnClientRegistered(int status, int client_id);
   void OnConnectionState(int status, int client_id, String address, boolean connected);
   void OnMtuChanged(int status, String address, int mtu);
-  void OnScanResult(in ScanResult scan_result);
 }
\ No newline at end of file
diff --git a/service/gatt_server_old.cc b/service/gatt_server_old.cc
index 9622a00..22a4a62 100644
--- a/service/gatt_server_old.cc
+++ b/service/gatt_server_old.cc
@@ -424,8 +424,6 @@
 // to refer to the client interface.
 const btgatt_client_callbacks_t gatt_client_callbacks = {
     RegisterClientCallback,
-    RegisterScannerCallback,
-    ScanResultCallback,
     ClientConnectCallback,
     ClientDisconnectCallback,
     nullptr, /* search_complete_cb; */
@@ -439,19 +437,25 @@
     nullptr, /* read_remote_rssi_cb; */
     ListenCallback,
     nullptr, /* configure_mtu_cb; */
-    nullptr, /* scan_filter_cfg_cb; */
-    nullptr, /* scan_filter_param_cb; */
-    nullptr, /* scan_filter_status_cb; */
     nullptr, /* congestion_cb; */
+    nullptr, /* get_gatt_db_cb; */
+    nullptr, /* services_removed_cb */
+    nullptr, /* services_added_cb */
+};
+
+const btgatt_scanner_callbacks_t gatt_scanner_callbacks = {
+    RegisterScannerCallback,
+    ScanResultCallback,
     nullptr, /* batchscan_cfg_storage_cb; */
     nullptr, /* batchscan_enb_disable_cb; */
     nullptr, /* batchscan_reports_cb; */
     nullptr, /* batchscan_threshold_cb; */
     nullptr, /* track_adv_event_cb; */
     nullptr, /* scan_parameter_setup_completed_cb; */
-    nullptr, /* get_gatt_db_cb; */
-    nullptr, /* services_removed_cb */
-    nullptr, /* services_added_cb */
+    nullptr, /* scan_filter_cfg_cb; */
+    nullptr, /* scan_filter_param_cb; */
+    nullptr, /* scan_filter_status_cb; */
+
 };
 
 const btgatt_callbacks_t gatt_callbacks = {
@@ -463,6 +467,9 @@
 
     /** GATT Server callbacks */
     &gatt_server_callbacks,
+
+    /** GATT Server callbacks */
+    &gatt_scanner_callbacks,
 };
 
 }  // namespace
@@ -687,7 +694,7 @@
 }
 
 bool Server::ScanEnable() {
-  bt_status_t btstat = internal_->gatt->client->scan(true);
+  bt_status_t btstat = internal_->gatt->scanner->scan(true);
   if (btstat) {
     LOG_ERROR(LOG_TAG, "Enable scan failed: %d", btstat);
     return false;
@@ -696,7 +703,7 @@
 }
 
 bool Server::ScanDisable() {
-  bt_status_t btstat = internal_->gatt->client->scan(false);
+  bt_status_t btstat = internal_->gatt->scanner->scan(false);
   if (btstat) {
     LOG_ERROR(LOG_TAG, "Disable scan failed: %d", btstat);
     return false;
diff --git a/service/hal/bluetooth_gatt_interface.cc b/service/hal/bluetooth_gatt_interface.cc
index dfc85b5..9dc2151 100644
--- a/service/hal/bluetooth_gatt_interface.cc
+++ b/service/hal/bluetooth_gatt_interface.cc
@@ -49,11 +49,17 @@
 
 // Helper for obtaining the observer lists. This is forward declared here
 // and defined below since it depends on BluetoothInterfaceImpl.
+base::ObserverList<BluetoothGattInterface::ScannerObserver>*
+    GetScannerObservers();
 base::ObserverList<BluetoothGattInterface::ClientObserver>*
     GetClientObservers();
 base::ObserverList<BluetoothGattInterface::ServerObserver>*
     GetServerObservers();
 
+#define FOR_EACH_SCANNER_OBSERVER(func) \
+  FOR_EACH_OBSERVER(BluetoothGattInterface::ScannerObserver, \
+                    *GetScannerObservers(), func)
+
 #define FOR_EACH_CLIENT_OBSERVER(func) \
   FOR_EACH_OBSERVER(BluetoothGattInterface::ClientObserver, \
                     *GetClientObservers(), func)
@@ -86,7 +92,7 @@
   VERIFY_INTERFACE_OR_RETURN();
   CHECK(app_uuid);
 
-  FOR_EACH_CLIENT_OBSERVER(
+  FOR_EACH_SCANNER_OBSERVER(
       RegisterScannerCallback(g_interface, status, scanner_id, *app_uuid));
 }
 
@@ -97,7 +103,7 @@
 
   VLOG(2) << __func__ << " - BD_ADDR: " << BtAddrString(bda)
           << " RSSI: " << rssi;
-  FOR_EACH_CLIENT_OBSERVER(
+  FOR_EACH_SCANNER_OBSERVER(
     ScanResultCallback(g_interface, *bda, rssi, adv_data));
 }
 
@@ -395,10 +401,23 @@
 
 // The HAL Bluetooth GATT client interface callbacks. These signal a mixture of
 // GATT client-role and GAP events.
-const btgatt_client_callbacks_t gatt_client_callbacks = {
-    RegisterClientCallback,
+
+const btgatt_scanner_callbacks_t gatt_scanner_callbacks = {
     RegisterScannerCallback,
     ScanResultCallback,
+    nullptr,  // batchscan_cfg_storage_cb
+    nullptr,  // batchscan_enb_disable_cb
+    nullptr,  // batchscan_reports_cb
+    nullptr,  // batchscan_threshold_cb
+    nullptr,  // track_adv_event_cb
+    nullptr,  // scan_parameter_setup_completed_cb
+    nullptr,  // scan_filter_cfg_cb
+    nullptr,  // scan_filter_param_cb
+    nullptr,  // scan_filter_status_cb
+};
+
+const btgatt_client_callbacks_t gatt_client_callbacks = {
+    RegisterClientCallback,
     ConnectCallback,
     DisconnectCallback,
     SearchCompleteCallback,
@@ -412,16 +431,7 @@
     nullptr,  // read_remote_rssi_cb
     ListenCallback,
     MtuChangedCallback,
-    nullptr,  // scan_filter_cfg_cb
-    nullptr,  // scan_filter_param_cb
-    nullptr,  // scan_filter_status_cb
     nullptr,  // congestion_cb
-    nullptr,  // batchscan_cfg_storage_cb
-    nullptr,  // batchscan_enb_disable_cb
-    nullptr,  // batchscan_reports_cb
-    nullptr,  // batchscan_threshold_cb
-    nullptr,  // track_adv_event_cb
-    nullptr,  // scan_parameter_setup_completed_cb
     GetGattDbCallback,
     ServicesRemovedCallback,
     ServicesAddedCallback,
@@ -448,6 +458,7 @@
   sizeof(btgatt_callbacks_t),
   &gatt_client_callbacks,
   &gatt_server_callbacks,
+  &gatt_scanner_callbacks,
 };
 
 }  // namespace
@@ -463,6 +474,14 @@
         hal_iface_->cleanup();
   }
 
+  void AddScannerObserver(ScannerObserver* observer) override {
+    scanner_observers_.AddObserver(observer);
+  }
+
+  void RemoveScannerObserver(ScannerObserver* observer) override {
+    scanner_observers_.RemoveObserver(observer);
+  }
+
   void AddClientObserver(ClientObserver* observer) override {
     client_observers_.AddObserver(observer);
   }
@@ -483,6 +502,10 @@
     return hal_iface_->advertiser;
   }
 
+  const btgatt_scanner_interface_t* GetScannerHALInterface() const override {
+    return hal_iface_->scanner;
+  }
+
   const btgatt_client_interface_t* GetClientHALInterface() const override {
     return hal_iface_->client;
   }
@@ -516,6 +539,10 @@
     return true;
   }
 
+  base::ObserverList<ScannerObserver>* scanner_observers() {
+    return &scanner_observers_;
+  }
+
   base::ObserverList<ClientObserver>* client_observers() {
     return &client_observers_;
   }
@@ -529,6 +556,7 @@
   // We're not using a base::ObserverListThreadSafe, which it posts observer
   // events automatically on the origin threads, as we want to avoid that
   // overhead and simply forward the events to the upper layer.
+  base::ObserverList<ScannerObserver> scanner_observers_;
   base::ObserverList<ClientObserver> client_observers_;
   base::ObserverList<ServerObserver> server_observers_;
 
@@ -541,6 +569,13 @@
 
 namespace {
 
+base::ObserverList<BluetoothGattInterface::ScannerObserver>*
+GetScannerObservers() {
+  CHECK(g_interface);
+  return static_cast<BluetoothGattInterfaceImpl*>(
+      g_interface)->scanner_observers();
+}
+
 base::ObserverList<BluetoothGattInterface::ClientObserver>*
 GetClientObservers() {
   CHECK(g_interface);
@@ -559,15 +594,8 @@
 
 // Default observer implementations. These are provided so that the methods
 // themselves are optional.
-void BluetoothGattInterface::ClientObserver::RegisterClientCallback(
-    BluetoothGattInterface* /* gatt_iface */,
-    int /* status */,
-    int /* client_if */,
-    const bt_uuid_t& /* app_uuid */) {
-  // Do nothing.
-}
 
-void BluetoothGattInterface::ClientObserver::RegisterScannerCallback(
+void BluetoothGattInterface::ScannerObserver::RegisterScannerCallback(
     BluetoothGattInterface* /* gatt_iface */,
     int /* status */,
     int /* scanner_id */,
@@ -575,7 +603,7 @@
   // Do nothing.
 }
 
-void BluetoothGattInterface::ClientObserver::ScanResultCallback(
+void BluetoothGattInterface::ScannerObserver::ScanResultCallback(
     BluetoothGattInterface* /* gatt_iface */,
     const bt_bdaddr_t& /* bda */,
     int /* rssi */,
@@ -583,6 +611,14 @@
   // Do Nothing.
 }
 
+void BluetoothGattInterface::ClientObserver::RegisterClientCallback(
+    BluetoothGattInterface* /* gatt_iface */,
+    int /* status */,
+    int /* client_if */,
+    const bt_uuid_t& /* app_uuid */) {
+  // Do nothing.
+}
+
 void BluetoothGattInterface::ClientObserver::ConnectCallback(
     BluetoothGattInterface* /* gatt_iface */,
     int /* conn_id */,
@@ -861,7 +897,7 @@
   // If this is the first scan client, then make a call into the stack. We
   // only do this when the reference count changes to or from 0.
   if (scan_client_set_.empty()) {
-    bt_status_t status = GetClientHALInterface()->scan(true);
+    bt_status_t status = GetScannerHALInterface()->scan(true);
     if (status != BT_STATUS_SUCCESS) {
       LOG(ERROR) << "HAL call to scan failed";
       return status;
@@ -885,7 +921,7 @@
   }
 
   if (scan_client_set_.size() == 1) {
-    bt_status_t status = GetClientHALInterface()->scan(false);
+    bt_status_t status = GetScannerHALInterface()->scan(false);
     if (status != BT_STATUS_SUCCESS) {
       LOG(ERROR) << "HAL call to stop scan failed";
       return status;
diff --git a/service/hal/bluetooth_gatt_interface.h b/service/hal/bluetooth_gatt_interface.h
index 5a0e989..6cb4e34 100644
--- a/service/hal/bluetooth_gatt_interface.h
+++ b/service/hal/bluetooth_gatt_interface.h
@@ -40,22 +40,19 @@
 class BluetoothGattInterface {
  public:
 
-  // The standard BT-GATT client callback interface. The HAL interface doesn't
-  // allow registering "user data" that carries context beyond the callback
-  // parameters, forcing implementations to deal with global variables. The
-  // Observer interface is to redirect these events to interested parties in an
-  // object-oriented manner.
-  class ClientObserver {
+  // The HAL interface doesn't allow registering "user data" that carries
+  // context beyond the callback parameters, forcing implementations to deal
+  // with global variables. The *Observer interface is to redirect these events
+  // to interested parties in an object-oriented manner.
+
+  // The standard LE scanner callback interface.
+  class ScannerObserver {
    public:
-    virtual ~ClientObserver() = default;
+
+    virtual ~ScannerObserver() = default;
 
     // All of the events below correspond to callbacks defined in
-    // "bt_gatt_client_callbacks_t" in the HAL API definitions.
-
-    virtual void RegisterClientCallback(
-        BluetoothGattInterface* gatt_iface,
-        int status, int client_if,
-        const bt_uuid_t& app_uuid);
+    // "btgatt_scanner_callbacks_t" in the HAL API definitions.
 
     virtual void RegisterScannerCallback(
         BluetoothGattInterface* gatt_iface,
@@ -66,6 +63,21 @@
         BluetoothGattInterface* gatt_iface,
         const bt_bdaddr_t& bda, int rssi,
         vector<uint8_t> adv_data);  // NOLINT(pass-by-value)
+  };
+
+  // The standard BT-GATT client callback interface.
+  class ClientObserver {
+   public:
+
+    virtual ~ClientObserver() = default;
+
+    // All of the events below correspond to callbacks defined in
+    // "bt_gatt_client_callbacks_t" in the HAL API definitions.
+
+    virtual void RegisterClientCallback(
+        BluetoothGattInterface* gatt_iface,
+        int status, int client_if,
+        const bt_uuid_t& app_uuid);
 
     virtual void ConnectCallback(
         BluetoothGattInterface* gatt_iface,
@@ -229,6 +241,11 @@
   // call this re-entrantly from an observer event as this may cause a deadlock.
   static BluetoothGattInterface* Get();
 
+  // Add or remove an observer that is interested in LE scanner interface
+  // notifications from us. Thread-safety is guaranteed by ObserverList.
+  virtual void AddScannerObserver(ScannerObserver* observer) = 0;
+  virtual void RemoveScannerObserver(ScannerObserver* observer) = 0;
+
   // Add or remove an observer that is interested in GATT client interface
   // notifications from us. Thread-safety is guaranteed by ObserverList.
   virtual void AddClientObserver(ClientObserver* observer) = 0;
@@ -247,6 +264,14 @@
   // structure.
   virtual BleAdvertiserInterface* GetAdvertiserHALInterface() const = 0;
 
+  // The HAL module pointer that represents the standard BT LE scanner
+  // interface. This is implemented in and provided by the shared Bluetooth
+  // library, so this isn't owned by us.
+  //
+  // Upper layers can make ble_scanner_interface_t API calls through this
+  // structure.
+  virtual const btgatt_scanner_interface_t* GetScannerHALInterface() const = 0;
+
   // The HAL module pointer that represents the standard BT-GATT client
   // interface. This is implemented in and provided by the shared Bluetooth
   // library, so this isn't owned by us.
diff --git a/service/hal/fake_bluetooth_gatt_interface.cc b/service/hal/fake_bluetooth_gatt_interface.cc
index b19a216..56b7514 100644
--- a/service/hal/fake_bluetooth_gatt_interface.cc
+++ b/service/hal/fake_bluetooth_gatt_interface.cc
@@ -24,6 +24,7 @@
 // interface methods all have to be global and their signatures don't allow us
 // to pass in user_data.
 std::shared_ptr<BleAdvertiserInterface> g_advertiser_handler;
+std::shared_ptr<FakeBluetoothGattInterface::TestScannerHandler> g_scanner_handler;
 std::shared_ptr<FakeBluetoothGattInterface::TestClientHandler> g_client_handler;
 std::shared_ptr<FakeBluetoothGattInterface::TestServerHandler> g_server_handler;
 
@@ -42,22 +43,22 @@
 }
 
 bt_status_t FakeRegisterScanner(bt_uuid_t* app_uuid) {
-  if (g_client_handler)
-    return g_client_handler->RegisterClient(app_uuid);
+  if (g_scanner_handler)
+    return g_scanner_handler->RegisterScanner(app_uuid);
 
   return BT_STATUS_FAIL;
 }
 
 bt_status_t FakeUnregisterScanner(int client_if) {
-  if (g_client_handler)
-    return g_client_handler->UnregisterClient(client_if);
+  if (g_scanner_handler)
+    return g_scanner_handler->UnregisterScanner(client_if);
 
   return BT_STATUS_FAIL;
 }
 
 bt_status_t FakeScan(bool start) {
-  if (g_client_handler)
-    return g_client_handler->Scan(start);
+  if (g_scanner_handler)
+    return g_scanner_handler->Scan(start);
 
   return BT_STATUS_FAIL;
 }
@@ -125,12 +126,24 @@
   return BT_STATUS_FAIL;
 }
 
-btgatt_client_interface_t fake_btgattc_iface = {
-  FakeRegisterClient,
-  FakeUnregisterClient,
+btgatt_scanner_interface_t fake_scanner_iface = {
   FakeRegisterScanner,
   FakeUnregisterScanner,
   FakeScan,
+  nullptr,  // scan_filter_param_setup
+  nullptr,  // scan_filter_add_remove
+  nullptr,  // scan_filter_clear
+  nullptr,  // scan_filter_enable
+  nullptr,  // set_scan_parameters
+  nullptr,  // batchscan_cfg_storate
+  nullptr,  // batchscan_enb_batch_scan
+  nullptr,  // batchscan_dis_batch_scan
+  nullptr,  // batchscan_read_reports
+};
+
+btgatt_client_interface_t fake_btgattc_iface = {
+  FakeRegisterClient,
+  FakeUnregisterClient,
   FakeConnect,
   FakeDisconnect,
   nullptr,  // listen
@@ -144,18 +157,9 @@
   nullptr,  // register_for_notification
   nullptr,  // deregister_for_notification
   nullptr,  // read_remote_rssi
-  nullptr,  // scan_filter_param_setup
-  nullptr,  // scan_filter_add_remove
-  nullptr,  // scan_filter_clear
-  nullptr,  // scan_filter_enable
   nullptr,  // get_device_type
   nullptr,  // configure_mtu
   nullptr,  // conn_parameter_update
-  nullptr,  // set_scan_parameters
-  nullptr,  // batchscan_cfg_storate
-  nullptr,  // batchscan_enb_batch_scan
-  nullptr,  // batchscan_dis_batch_scan
-  nullptr,  // batchscan_read_reports
   nullptr,  // test_command
   nullptr,  // get_gatt_db
 };
@@ -176,10 +180,12 @@
 
 FakeBluetoothGattInterface::FakeBluetoothGattInterface(
     std::shared_ptr<BleAdvertiserInterface> advertiser_handler,
+    std::shared_ptr<TestScannerHandler> scanner_handler,
     std::shared_ptr<TestClientHandler> client_handler,
     std::shared_ptr<TestServerHandler> server_handler)
     : client_handler_(client_handler) {
   CHECK(!g_advertiser_handler);
+  CHECK(!g_scanner_handler);
   CHECK(!g_client_handler);
   CHECK(!g_server_handler);
 
@@ -187,6 +193,9 @@
   if (advertiser_handler)
     g_advertiser_handler = advertiser_handler;
 
+  if (scanner_handler)
+    g_scanner_handler = scanner_handler;
+
   if (client_handler)
     g_client_handler = client_handler;
 
@@ -198,6 +207,9 @@
   if (g_advertiser_handler)
     g_advertiser_handler = nullptr;
 
+  if (g_scanner_handler)
+    g_scanner_handler = nullptr;
+
   if (g_client_handler)
     g_client_handler = nullptr;
 
@@ -207,6 +219,19 @@
 
 // The methods below can be used to notify observers with certain events and
 // given parameters.
+void FakeBluetoothGattInterface::NotifyRegisterScannerCallback(
+    int status, int client_if,
+    const bt_uuid_t& app_uuid) {
+  FOR_EACH_OBSERVER(ScannerObserver, scanner_observers_,
+                    RegisterScannerCallback(this, status, client_if, app_uuid));
+}
+
+void FakeBluetoothGattInterface::NotifyScanResultCallback(
+    const bt_bdaddr_t& bda, int rssi, vector<uint8_t> adv_data) {
+  FOR_EACH_OBSERVER(ScannerObserver, scanner_observers_,
+                    ScanResultCallback(this, bda, rssi, adv_data));
+}
+
 void FakeBluetoothGattInterface::NotifyRegisterClientCallback(
     int status, int client_if,
     const bt_uuid_t& app_uuid) {
@@ -226,12 +251,6 @@
                     DisconnectCallback(this, conn_id, status, client_if, bda));
 }
 
-void FakeBluetoothGattInterface::NotifyScanResultCallback(
-    const bt_bdaddr_t& bda, int rssi, vector<uint8_t> adv_data) {
-  FOR_EACH_OBSERVER(ClientObserver, client_observers_,
-                    ScanResultCallback(this, bda, rssi, adv_data));
-}
-
 void FakeBluetoothGattInterface::NotifyRegisterServerCallback(
     int status, int server_if,
     const bt_uuid_t& app_uuid) {
@@ -307,6 +326,17 @@
                     IndicationSentCallback(this, conn_id, status));
 }
 
+void FakeBluetoothGattInterface::AddScannerObserver(ScannerObserver* observer) {
+  CHECK(observer);
+  scanner_observers_.AddObserver(observer);
+}
+
+void FakeBluetoothGattInterface::RemoveScannerObserver(
+    ScannerObserver* observer) {
+  CHECK(observer);
+  scanner_observers_.RemoveObserver(observer);
+}
+
 void FakeBluetoothGattInterface::AddClientObserver(ClientObserver* observer) {
   CHECK(observer);
   client_observers_.AddObserver(observer);
@@ -334,6 +364,12 @@
   return g_advertiser_handler.get();
 }
 
+const btgatt_scanner_interface_t*
+FakeBluetoothGattInterface::GetScannerHALInterface() const {
+  return &fake_scanner_iface;
+}
+
+
 const btgatt_client_interface_t*
 FakeBluetoothGattInterface::GetClientHALInterface() const {
   return &fake_btgattc_iface;
diff --git a/service/hal/fake_bluetooth_gatt_interface.h b/service/hal/fake_bluetooth_gatt_interface.h
index 06c9109..363e053 100644
--- a/service/hal/fake_bluetooth_gatt_interface.h
+++ b/service/hal/fake_bluetooth_gatt_interface.h
@@ -27,6 +27,19 @@
 class FakeBluetoothGattInterface : public BluetoothGattInterface {
  public:
 
+  // Handles HAL LE scanner API calls for testing. Test code can
+  // provide a fake or mock implementation of this and all calls will be routed
+  // to it.
+  class TestScannerHandler {
+   public:
+    virtual ~TestScannerHandler() = default;
+
+    virtual bt_status_t RegisterScanner(bt_uuid_t* app_uuid) = 0;
+    virtual bt_status_t UnregisterScanner(int client_if) = 0;
+
+    virtual bt_status_t Scan(bool start) = 0;
+  };
+
   // Handles HAL Bluetooth GATT client API calls for testing. Test code can
   // provide a fake or mock implementation of this and all calls will be routed
   // to it.
@@ -37,7 +50,6 @@
     virtual bt_status_t RegisterClient(bt_uuid_t* app_uuid) = 0;
     virtual bt_status_t UnregisterClient(int client_if) = 0;
 
-    virtual bt_status_t Scan(bool start) = 0;
     virtual bt_status_t Connect(int client_if, const bt_bdaddr_t *bd_addr,
                                 bool is_direct, int transport) = 0;
     virtual bt_status_t Disconnect(int client_if, const bt_bdaddr_t *bd_addr,
@@ -67,6 +79,7 @@
   // provide their own handlers or simply pass "nullptr" for the default
   // behavior in which BT_STATUS_FAIL will be returned from all calls.
   FakeBluetoothGattInterface(std::shared_ptr<BleAdvertiserInterface> advertiser_handler,
+                             std::shared_ptr<TestScannerHandler> scanner_handler,
                              std::shared_ptr<TestClientHandler> client_handler,
                              std::shared_ptr<TestServerHandler> server_handler);
   ~FakeBluetoothGattInterface();
@@ -74,6 +87,11 @@
   // The methods below can be used to notify observers with certain events and
   // given parameters.
 
+  void NotifyRegisterScannerCallback(int status, int client_if,
+                                    const bt_uuid_t& app_uuid);
+  void NotifyScanResultCallback(const bt_bdaddr_t& bda, int rssi,
+                                vector<uint8_t> adv_data);
+
   // Client callbacks:
   void NotifyRegisterClientCallback(int status, int client_if,
                                     const bt_uuid_t& app_uuid);
@@ -81,8 +99,6 @@
                              const bt_bdaddr_t& bda);
   void NotifyDisconnectCallback(int conn_id, int status, int client_if,
                                 const bt_bdaddr_t& bda);
-  void NotifyScanResultCallback(const bt_bdaddr_t& bda, int rssi,
-                                vector<uint8_t> adv_data);
 
   // Server callbacks:
   void NotifyRegisterServerCallback(int status, int server_if,
@@ -118,17 +134,22 @@
   void NotifyIndicationSentCallback(int conn_id, int status);
 
   // BluetoothGattInterface overrides:
+  void AddScannerObserver(ScannerObserver* observer) override;
+  void RemoveScannerObserver(ScannerObserver* observer) override;
   void AddClientObserver(ClientObserver* observer) override;
   void RemoveClientObserver(ClientObserver* observer) override;
   void AddServerObserver(ServerObserver* observer) override;
   void RemoveServerObserver(ServerObserver* observer) override;
   BleAdvertiserInterface* GetAdvertiserHALInterface() const override;
+  const btgatt_scanner_interface_t* GetScannerHALInterface() const override;
   const btgatt_client_interface_t* GetClientHALInterface() const override;
   const btgatt_server_interface_t* GetServerHALInterface() const override;
 
  private:
+  base::ObserverList<ScannerObserver> scanner_observers_;
   base::ObserverList<ClientObserver> client_observers_;
   base::ObserverList<ServerObserver> server_observers_;
+  std::shared_ptr<TestScannerHandler> scanner_handler_;
   std::shared_ptr<TestClientHandler> client_handler_;
   std::shared_ptr<TestServerHandler> server_handler_;
 
diff --git a/service/ipc/binder/bluetooth_binder_server.cc b/service/ipc/binder/bluetooth_binder_server.cc
index 9d1db8b..953f974 100644
--- a/service/ipc/binder/bluetooth_binder_server.cc
+++ b/service/ipc/binder/bluetooth_binder_server.cc
@@ -22,6 +22,7 @@
 #include "service/ipc/binder/bluetooth_gatt_server_binder_server.h"
 #include "service/ipc/binder/bluetooth_low_energy_binder_server.h"
 #include "service/ipc/binder/bluetooth_le_advertiser_binder_server.h"
+#include "service/ipc/binder/bluetooth_le_scanner_binder_server.h"
 
 #include "service/hal/bluetooth_interface.h"
 
@@ -168,6 +169,23 @@
   return Status::ok();
 }
 
+Status BluetoothBinderServer::GetLeScannerInterface(
+    ::android::sp<IBluetoothLeScanner>* _aidl_return) {
+  VLOG(2) << __func__;
+
+  if (!adapter_->IsEnabled()) {
+    LOG(ERROR) << "Cannot obtain IBluetoothLeScanner interface while disabled";
+    *_aidl_return = NULL;
+    return Status::ok();
+  }
+
+  if (!le_scanner_interface_.get())
+    le_scanner_interface_ = new BluetoothLeScannerBinderServer(adapter_);
+
+  *_aidl_return = le_scanner_interface_;
+  return Status::ok();
+}
+
 Status BluetoothBinderServer::GetGattClientInterface(
     ::android::sp<IBluetoothGattClient>* _aidl_return) {
   VLOG(2) << __func__;
diff --git a/service/ipc/binder/bluetooth_binder_server.h b/service/ipc/binder/bluetooth_binder_server.h
index 3e9ed41..5bb794e 100644
--- a/service/ipc/binder/bluetooth_binder_server.h
+++ b/service/ipc/binder/bluetooth_binder_server.h
@@ -29,6 +29,7 @@
 #include <android/bluetooth/IBluetoothGattServer.h>
 #include <android/bluetooth/IBluetoothLowEnergy.h>
 #include <android/bluetooth/IBluetoothLeAdvertiser.h>
+#include <android/bluetooth/IBluetoothLeScanner.h>
 
 #include "service/adapter.h"
 #include "service/common/bluetooth/uuid.h"
@@ -43,6 +44,7 @@
 using android::bluetooth::IBluetoothGattServer;
 using android::bluetooth::IBluetoothLowEnergy;
 using android::bluetooth::IBluetoothLeAdvertiser;
+using android::bluetooth::IBluetoothLeScanner;
 
 namespace ipc {
 namespace binder {
@@ -76,6 +78,8 @@
       ::android::sp<IBluetoothLowEnergy>* _aidl_return) override;
   Status GetLeAdvertiserInterface(
       ::android::sp<IBluetoothLeAdvertiser>* _aidl_return) override;
+  Status GetLeScannerInterface(
+      ::android::sp<IBluetoothLeScanner>* _aidl_return) override;
   Status GetGattClientInterface(
       ::android::sp<IBluetoothGattClient>* _aidl_return) override;
   Status GetGattServerInterface(
@@ -101,6 +105,10 @@
   // first call to GetLeAdvertiserInterface().
   android::sp<IBluetoothLeAdvertiser> le_advertiser_interface_;
 
+  // The IBluetoothLeScanner interface handle. This is lazily initialized on the
+  // first call to GetLeScannerInterface().
+  android::sp<IBluetoothLeScanner> le_scanner_interface_;
+
   // The IBluetoothGattClient interface handle. This is lazily initialized on
   // the first call to GetGattClientInterface().
   android::sp<IBluetoothGattClient> gatt_client_interface_;
diff --git a/service/ipc/binder/bluetooth_le_scanner_binder_server.cc b/service/ipc/binder/bluetooth_le_scanner_binder_server.cc
new file mode 100644
index 0000000..e65ed06
--- /dev/null
+++ b/service/ipc/binder/bluetooth_le_scanner_binder_server.cc
@@ -0,0 +1,148 @@
+//
+//  Copyright (C) 2016 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+#include "service/ipc/binder/bluetooth_le_scanner_binder_server.h"
+
+#include <base/logging.h>
+
+#include "service/adapter.h"
+
+using android::String8;
+using android::String16;
+using android::binder::Status;
+
+namespace ipc {
+namespace binder {
+
+namespace {
+const int kInvalidInstanceId = -1;
+}  // namespace
+
+BluetoothLeScannerBinderServer::BluetoothLeScannerBinderServer(
+    bluetooth::Adapter* adapter)
+    : adapter_(adapter) {
+  CHECK(adapter_);
+}
+
+BluetoothLeScannerBinderServer::~BluetoothLeScannerBinderServer() {}
+
+Status BluetoothLeScannerBinderServer::RegisterScanner(
+    const android::sp<IBluetoothLeScannerCallback>& callback,
+    bool* _aidl_return) {
+  VLOG(2) << __func__;
+  bluetooth::LowEnergyScannerFactory* ble_factory =
+      adapter_->GetLeScannerFactory();
+
+  *_aidl_return = RegisterInstanceBase(callback, ble_factory);
+  return Status::ok();
+}
+
+Status BluetoothLeScannerBinderServer::UnregisterScanner(int scanner_id) {
+  VLOG(2) << __func__;
+  UnregisterInstanceBase(scanner_id);
+  return Status::ok();
+}
+
+Status BluetoothLeScannerBinderServer::UnregisterAll() {
+  VLOG(2) << __func__;
+  UnregisterAllBase();
+  return Status::ok();
+}
+
+Status BluetoothLeScannerBinderServer::StartScan(
+    int scanner_id, const android::bluetooth::ScanSettings& settings,
+    const std::vector<android::bluetooth::ScanFilter>& filters,
+    bool* _aidl_return) {
+  VLOG(2) << __func__ << " scanner_id: " << scanner_id;
+  std::lock_guard<std::mutex> lock(*maps_lock());
+
+  auto scanner = GetLEScanner(scanner_id);
+  if (!scanner) {
+    LOG(ERROR) << "Unknown scanner_id: " << scanner_id;
+    *_aidl_return = false;
+    return Status::ok();
+  }
+
+  std::vector<bluetooth::ScanFilter> flt;
+  for (const auto& filter : filters) {
+    flt.push_back(filter);
+  }
+
+  *_aidl_return = scanner->StartScan(settings, flt);
+  return Status::ok();
+}
+
+Status BluetoothLeScannerBinderServer::StopScan(int scanner_id,
+                                                bool* _aidl_return) {
+  VLOG(2) << __func__ << " scanner_id: " << scanner_id;
+  std::lock_guard<std::mutex> lock(*maps_lock());
+
+  auto scanner = GetLEScanner(scanner_id);
+  if (!scanner) {
+    LOG(ERROR) << "Unknown scanner_id: " << scanner_id;
+    *_aidl_return = false;
+    return Status::ok();
+  }
+
+  *_aidl_return = scanner->StopScan();
+  return Status::ok();
+}
+
+void BluetoothLeScannerBinderServer::OnScanResult(
+    bluetooth::LowEnergyScanner* scanner, const bluetooth::ScanResult& result) {
+  VLOG(2) << __func__;
+  std::lock_guard<std::mutex> lock(*maps_lock());
+
+  int scanner_id = scanner->GetInstanceId();
+  auto cb = GetLECallback(scanner->GetInstanceId());
+  if (!cb.get()) {
+    VLOG(2) << "Scanner was unregistered - scanner_id: " << scanner_id;
+    return;
+  }
+
+  cb->OnScanResult(result);
+}
+
+android::sp<IBluetoothLeScannerCallback>
+BluetoothLeScannerBinderServer::GetLECallback(int scanner_id) {
+  auto cb = GetCallback(scanner_id);
+  return android::sp<IBluetoothLeScannerCallback>(
+      static_cast<IBluetoothLeScannerCallback*>(cb.get()));
+}
+
+std::shared_ptr<bluetooth::LowEnergyScanner>
+BluetoothLeScannerBinderServer::GetLEScanner(int scanner_id) {
+  return std::static_pointer_cast<bluetooth::LowEnergyScanner>(
+      GetInstance(scanner_id));
+}
+
+void BluetoothLeScannerBinderServer::OnRegisterInstanceImpl(
+    bluetooth::BLEStatus status, android::sp<IInterface> callback,
+    bluetooth::BluetoothInstance* instance) {
+  VLOG(1) << __func__ << " status: " << status;
+  bluetooth::LowEnergyScanner* le_scanner =
+      static_cast<bluetooth::LowEnergyScanner*>(instance);
+  le_scanner->SetDelegate(this);
+
+  android::sp<IBluetoothLeScannerCallback> cb(
+      static_cast<IBluetoothLeScannerCallback*>(callback.get()));
+  cb->OnScannerRegistered(status, (status == bluetooth::BLE_STATUS_SUCCESS)
+                                     ? instance->GetInstanceId()
+                                     : kInvalidInstanceId);
+}
+
+}  // namespace binder
+}  // namespace ipc
diff --git a/service/ipc/binder/bluetooth_le_scanner_binder_server.h b/service/ipc/binder/bluetooth_le_scanner_binder_server.h
new file mode 100644
index 0000000..655c91b
--- /dev/null
+++ b/service/ipc/binder/bluetooth_le_scanner_binder_server.h
@@ -0,0 +1,86 @@
+//
+//  Copyright (C) 2016 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+#pragma once
+
+#include <memory>
+
+#include <base/macros.h>
+
+#include <android/bluetooth/IBluetoothLeScannerCallback.h>
+#include "android/bluetooth/BnBluetoothLeScanner.h"
+
+#include "service/common/bluetooth/low_energy_constants.h"
+#include "service/ipc/binder/interface_with_instances_base.h"
+#include "service/low_energy_scanner.h"
+
+using android::binder::Status;
+using android::String16;
+
+using android::bluetooth::BnBluetoothLeScanner;
+using android::bluetooth::IBluetoothLeScannerCallback;
+
+namespace bluetooth {
+class Adapter;
+}  // namespace bluetooth
+
+namespace ipc {
+namespace binder {
+
+// Implements the server side of the IBluetoothLowEnergy interface.
+class BluetoothLeScannerBinderServer
+    : public BnBluetoothLeScanner,
+      public InterfaceWithInstancesBase,
+      public bluetooth::LowEnergyScanner::Delegate {
+ public:
+  explicit BluetoothLeScannerBinderServer(bluetooth::Adapter* adapter);
+  ~BluetoothLeScannerBinderServer() override;
+
+  // IBluetoothLowEnergy overrides:
+  Status RegisterScanner(
+      const android::sp<IBluetoothLeScannerCallback>& callback,
+      bool* _aidl_return) override;
+  Status UnregisterScanner(int scanner_id) override;
+  Status UnregisterAll() override;
+  Status StartScan(int scanner_id,
+                   const android::bluetooth::ScanSettings& settings,
+                   const std::vector<android::bluetooth::ScanFilter>& filters,
+                   bool* _aidl_return) override;
+  Status StopScan(int scanner_id, bool* _aidl_return) override;
+
+  void OnScanResult(bluetooth::LowEnergyScanner* scanner,
+                    const bluetooth::ScanResult& result) override;
+ private:
+  // Returns a pointer to the IBluetoothLowEnergyCallback instance associated
+  // with |scanner_id|. Returns NULL if such a callback cannot be found.
+  android::sp<IBluetoothLeScannerCallback> GetLECallback(int scanner_id);
+
+  // Returns a pointer to the LowEnergyScanner instance associated with
+  // |scanner_id|. Returns NULL if such a scanner cannot be found.
+  std::shared_ptr<bluetooth::LowEnergyScanner> GetLEScanner(int scanner_id);
+
+  // InterfaceWithInstancesBase override:
+  void OnRegisterInstanceImpl(bluetooth::BLEStatus status,
+                              android::sp<IInterface> callback,
+                              bluetooth::BluetoothInstance* instance) override;
+
+  bluetooth::Adapter* adapter_;  // weak
+
+  DISALLOW_COPY_AND_ASSIGN(BluetoothLeScannerBinderServer);
+};
+
+}  // namespace binder
+}  // namespace ipc
diff --git a/service/ipc/binder/bluetooth_low_energy_binder_server.cc b/service/ipc/binder/bluetooth_low_energy_binder_server.cc
index d86072a..9b8a863 100644
--- a/service/ipc/binder/bluetooth_low_energy_binder_server.cc
+++ b/service/ipc/binder/bluetooth_low_energy_binder_server.cc
@@ -117,45 +117,6 @@
   return Status::ok();
 }
 
-Status BluetoothLowEnergyBinderServer::StartScan(
-    int client_id, const android::bluetooth::ScanSettings& settings,
-    const std::vector<android::bluetooth::ScanFilter>& filters,
-    bool* _aidl_return) {
-  VLOG(2) << __func__ << " client_id: " << client_id;
-  std::lock_guard<std::mutex> lock(*maps_lock());
-
-  auto client = GetLEClient(client_id);
-  if (!client) {
-    LOG(ERROR) << "Unknown client_id: " << client_id;
-    *_aidl_return = false;
-    return Status::ok();
-  }
-
-  std::vector<bluetooth::ScanFilter> flt;
-  for (const auto& filter : filters) {
-    flt.push_back(filter);
-  }
-
-  *_aidl_return = client->StartScan(settings, flt);
-  return Status::ok();
-}
-
-Status BluetoothLowEnergyBinderServer::StopScan(int client_id,
-                                                bool* _aidl_return) {
-  VLOG(2) << __func__ << " client_id: " << client_id;
-  std::lock_guard<std::mutex> lock(*maps_lock());
-
-  auto client = GetLEClient(client_id);
-  if (!client) {
-    LOG(ERROR) << "Unknown client_id: " << client_id;
-    *_aidl_return = false;
-    return Status::ok();
-  }
-
-  *_aidl_return = client->StopScan();
-  return Status::ok();
-}
-
 void BluetoothLowEnergyBinderServer::OnConnectionState(
     bluetooth::LowEnergyClient* client, int status, const char* address,
     bool connected) {
@@ -188,21 +149,6 @@
   cb->OnMtuChanged(status, String16(address, std::strlen(address)), mtu);
 }
 
-void BluetoothLowEnergyBinderServer::OnScanResult(
-    bluetooth::LowEnergyClient* client, const bluetooth::ScanResult& result) {
-  VLOG(2) << __func__;
-  std::lock_guard<std::mutex> lock(*maps_lock());
-
-  int client_id = client->GetInstanceId();
-  auto cb = GetLECallback(client->GetInstanceId());
-  if (!cb.get()) {
-    VLOG(2) << "Client was unregistered - client_id: " << client_id;
-    return;
-  }
-
-  cb->OnScanResult(result);
-}
-
 android::sp<IBluetoothLowEnergyCallback>
 BluetoothLowEnergyBinderServer::GetLECallback(int client_id) {
   auto cb = GetCallback(client_id);
diff --git a/service/ipc/binder/bluetooth_low_energy_binder_server.h b/service/ipc/binder/bluetooth_low_energy_binder_server.h
index 8b53a71..d393c7d 100644
--- a/service/ipc/binder/bluetooth_low_energy_binder_server.h
+++ b/service/ipc/binder/bluetooth_low_energy_binder_server.h
@@ -61,19 +61,12 @@
                     bool* _aidl_return) override;
   Status SetMtu(int client_id, const String16& address, int mtu,
                 bool* _aidl_return) override;
-  Status StartScan(int client_id,
-                   const android::bluetooth::ScanSettings& settings,
-                   const std::vector<android::bluetooth::ScanFilter>& filters,
-                   bool* _aidl_return) override;
-  Status StopScan(int client_id, bool* _aidl_return) override;
 
   // bluetooth::LowEnergyClient::Delegate overrides:
   void OnConnectionState(bluetooth::LowEnergyClient* client, int status,
                          const char* address, bool connected) override;
   void OnMtuChanged(bluetooth::LowEnergyClient* client, int status,
                     const char* address, int mtu) override;
-  void OnScanResult(bluetooth::LowEnergyClient* client,
-                    const bluetooth::ScanResult& result) override;
 
  private:
   // Returns a pointer to the IBluetoothLowEnergyCallback instance associated
diff --git a/service/low_energy_client.cc b/service/low_energy_client.cc
index fe7ea76..3fce035 100644
--- a/service/low_energy_client.cc
+++ b/service/low_energy_client.cc
@@ -29,40 +29,6 @@
 
 namespace bluetooth {
 
-namespace {
-
-// 31 + 31 for advertising data and scan response. This is the maximum length
-// TODO(armansito): Fix the HAL to return a concatenated blob that contains the
-// true length of each field and also provide a length parameter so that we
-// can support advertising length extensions in the future.
-const size_t kScanRecordLength = 62;
-
-// Returns the length of the given scan record array. We have to calculate this
-// based on the maximum possible data length and the TLV data. See TODO above
-// |kScanRecordLength|.
-size_t GetScanRecordLength(vector<uint8_t> bytes) {
-  for (size_t i = 0, field_len = 0; i < kScanRecordLength;
-       i += (field_len + 1)) {
-    field_len = bytes[i];
-
-    // Assert here that the data returned from the stack is correctly formatted
-    // in TLV form and that the length of the current field won't exceed the
-    // total data length.
-    CHECK(i + field_len < kScanRecordLength);
-
-    // If the field length is zero and we haven't reached the maximum length,
-    // then we have found the length, as the stack will pad the data with zeros
-    // accordingly.
-    if (field_len == 0)
-      return i;
-  }
-
-  // We have reached the end.
-  return kScanRecordLength;
-}
-
-}  // namespace
-
 // LowEnergyClient implementation
 // ========================================================
 
@@ -71,7 +37,6 @@
     : adapter_(adapter),
       app_identifier_(uuid),
       client_id_(client_id),
-      scan_started_(false),
       delegate_(nullptr) {
 }
 
@@ -84,10 +49,6 @@
 
   hal::BluetoothGattInterface::Get()->
       GetClientHALInterface()->unregister_client(client_id_);
-
-  // Stop any scans started by this client.
-  if (scan_started_.load())
-    StopScan();
 }
 
 bool LowEnergyClient::Connect(const std::string& address, bool is_direct) {
@@ -165,45 +126,6 @@
   delegate_ = delegate;
 }
 
-bool LowEnergyClient::StartScan(const ScanSettings& settings,
-                                const std::vector<ScanFilter>& filters) {
-  VLOG(2) << __func__;
-
-  // Cannot start a scan if the adapter is not enabled.
-  if (!adapter_.IsEnabled()) {
-    LOG(ERROR) << "Cannot scan while Bluetooth is disabled";
-    return false;
-  }
-
-  // TODO(jpawlowski): Push settings and filtering logic below the HAL.
-  bt_status_t status = hal::BluetoothGattInterface::Get()->
-      StartScan(client_id_);
-  if (status != BT_STATUS_SUCCESS) {
-    LOG(ERROR) << "Failed to initiate scanning for client: " << client_id_;
-    return false;
-  }
-
-  scan_started_ = true;
-  return true;
-}
-
-bool LowEnergyClient::StopScan() {
-  VLOG(2) << __func__;
-
-  // TODO(armansito): We don't support batch scanning yet so call
-  // StopRegularScanForClient directly. In the future we will need to
-  // conditionally call a batch scan API here.
-  bt_status_t status = hal::BluetoothGattInterface::Get()->
-      StopScan(client_id_);
-  if (status != BT_STATUS_SUCCESS) {
-    LOG(ERROR) << "Failed to stop scan for client: " << client_id_;
-    return false;
-  }
-
-  scan_started_ = false;
-  return true;
-}
-
 const UUID& LowEnergyClient::GetAppIdentifier() const {
   return app_identifier_;
 }
@@ -212,27 +134,6 @@
   return client_id_;
 }
 
-void LowEnergyClient::ScanResultCallback(
-    hal::BluetoothGattInterface* gatt_iface,
-    const bt_bdaddr_t& bda, int rssi, vector<uint8_t> adv_data) {
-  // Ignore scan results if this client didn't start a scan.
-  if (!scan_started_.load())
-    return;
-
-  lock_guard<mutex> lock(delegate_mutex_);
-  if (!delegate_)
-    return;
-
-  // TODO(armansito): Apply software filters here.
-
-  size_t record_len = GetScanRecordLength(adv_data);
-  std::vector<uint8_t> scan_record(adv_data.begin(), adv_data.begin() + record_len);
-
-  ScanResult result(BtAddrString(&bda), scan_record, rssi);
-
-  delegate_->OnScanResult(this, result);
-}
-
 void LowEnergyClient::ConnectCallback(
       hal::BluetoothGattInterface* gatt_iface, int conn_id, int status,
       int client_id, const bt_bdaddr_t& bda) {
diff --git a/service/low_energy_client.h b/service/low_energy_client.h
index 5afc0f2..b8e4fec 100644
--- a/service/low_energy_client.h
+++ b/service/low_energy_client.h
@@ -54,11 +54,6 @@
     Delegate() = default;
     virtual ~Delegate() = default;
 
-    // Called asynchronously to notify the delegate of nearby BLE advertisers
-    // found during a device scan.
-    virtual void OnScanResult(LowEnergyClient* client,
-                              const ScanResult& scan_result) = 0;
-
     // Called asynchronously to notify the delegate of connection state change
     virtual void OnConnectionState(LowEnergyClient* client, int status,
                                    const char* address, bool connected) = 0;
@@ -95,19 +90,6 @@
   // Return true on success, false otherwise.
   bool SetMtu(const std::string& address, int mtu);
 
-  // Initiates a BLE device scan for this client using the given |settings| and
-  // |filters|. See the documentation for ScanSettings and ScanFilter for how
-  // these parameters can be configured. Return true on success, false
-  // otherwise. Please see logs for details in case of error.
-  bool StartScan(const ScanSettings& settings,
-                 const std::vector<ScanFilter>& filters);
-
-  // Stops an ongoing BLE device scan for this client.
-  bool StopScan();
-
-  // Returns the current scan settings.
-  const ScanSettings& scan_settings() const { return scan_settings_; }
-
   // BluetoothClientInstace overrides:
   const UUID& GetAppIdentifier() const override;
   int GetInstanceId() const override;
@@ -120,11 +102,6 @@
   LowEnergyClient(Adapter& adapter, const UUID& uuid, int client_id);
 
   // BluetoothGattInterface::ClientObserver overrides:
-  void ScanResultCallback(
-      hal::BluetoothGattInterface* gatt_iface,
-      const bt_bdaddr_t& bda, int rssi,
-      vector<uint8_t> adv_data) override;
-
   void ConnectCallback(
       hal::BluetoothGattInterface* gatt_iface, int conn_id, int status,
       int client_id, const bt_bdaddr_t& bda) override;
@@ -146,15 +123,6 @@
   UUID app_identifier_;
   int client_id_;
 
-  // Protects device scan related members below.
-  std::mutex scan_fields_lock_;
-
-  // Current scan settings.
-  ScanSettings scan_settings_;
-
-  // If true, then this client have a BLE device scan in progress.
-  std::atomic_bool scan_started_;
-
   // Raw handle to the Delegate, which must outlive this LowEnergyClient
   // instance.
   std::mutex delegate_mutex_;
diff --git a/service/low_energy_scanner.cc b/service/low_energy_scanner.cc
new file mode 100644
index 0000000..c865e9b
--- /dev/null
+++ b/service/low_energy_scanner.cc
@@ -0,0 +1,234 @@
+//
+//  Copyright (C) 2016 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+#include "service/low_energy_scanner.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+
+#include "service/adapter.h"
+#include "service/common/bluetooth/util/address_helper.h"
+#include "service/logging_helpers.h"
+#include "stack/include/bt_types.h"
+#include "stack/include/hcidefs.h"
+
+using std::lock_guard;
+using std::mutex;
+
+namespace bluetooth {
+
+namespace {
+
+// 31 + 31 for advertising data and scan response. This is the maximum length
+// TODO(armansito): Fix the HAL to return a concatenated blob that contains the
+// true length of each field and also provide a length parameter so that we
+// can support advertising length extensions in the future.
+const size_t kScanRecordLength = 62;
+
+// Returns the length of the given scan record array. We have to calculate this
+// based on the maximum possible data length and the TLV data. See TODO above
+// |kScanRecordLength|.
+size_t GetScanRecordLength(vector<uint8_t> bytes) {
+  for (size_t i = 0, field_len = 0; i < kScanRecordLength;
+       i += (field_len + 1)) {
+    field_len = bytes[i];
+
+    // Assert here that the data returned from the stack is correctly formatted
+    // in TLV form and that the length of the current field won't exceed the
+    // total data length.
+    CHECK(i + field_len < kScanRecordLength);
+
+    // If the field length is zero and we haven't reached the maximum length,
+    // then we have found the length, as the stack will pad the data with zeros
+    // accordingly.
+    if (field_len == 0)
+      return i;
+  }
+
+  // We have reached the end.
+  return kScanRecordLength;
+}
+
+}  // namespace
+
+// LowEnergyScanner implementation
+// ========================================================
+
+LowEnergyScanner::LowEnergyScanner(Adapter& adapter, const UUID& uuid,
+                                   int scanner_id)
+    : adapter_(adapter),
+      app_identifier_(uuid),
+      scanner_id_(scanner_id),
+      scan_started_(false),
+      delegate_(nullptr) {}
+
+LowEnergyScanner::~LowEnergyScanner() {
+  // Automatically unregister the scanner.
+  VLOG(1) << "LowEnergyScanner unregistering scanner: " << scanner_id_;
+
+  // Unregister as observer so we no longer receive any callbacks.
+  hal::BluetoothGattInterface::Get()->RemoveScannerObserver(this);
+
+  hal::BluetoothGattInterface::Get()->
+      GetScannerHALInterface()->unregister_scanner(scanner_id_);
+
+  // Stop any scans started by this client.
+  if (scan_started_.load())
+    StopScan();
+}
+
+void LowEnergyScanner::SetDelegate(Delegate* delegate) {
+  lock_guard<mutex> lock(delegate_mutex_);
+  delegate_ = delegate;
+}
+
+bool LowEnergyScanner::StartScan(const ScanSettings& settings,
+                                const std::vector<ScanFilter>& filters) {
+  VLOG(2) << __func__;
+
+  // Cannot start a scan if the adapter is not enabled.
+  if (!adapter_.IsEnabled()) {
+    LOG(ERROR) << "Cannot scan while Bluetooth is disabled";
+    return false;
+  }
+
+  // TODO(jpawlowski): Push settings and filtering logic below the HAL.
+  bt_status_t status = hal::BluetoothGattInterface::Get()->
+      StartScan(scanner_id_);
+  if (status != BT_STATUS_SUCCESS) {
+    LOG(ERROR) << "Failed to initiate scanning for client: " << scanner_id_;
+    return false;
+  }
+
+  scan_started_ = true;
+  return true;
+}
+
+bool LowEnergyScanner::StopScan() {
+  VLOG(2) << __func__;
+
+  // TODO(armansito): We don't support batch scanning yet so call
+  // StopRegularScanForClient directly. In the future we will need to
+  // conditionally call a batch scan API here.
+  bt_status_t status = hal::BluetoothGattInterface::Get()->
+      StopScan(scanner_id_);
+  if (status != BT_STATUS_SUCCESS) {
+    LOG(ERROR) << "Failed to stop scan for client: " << scanner_id_;
+    return false;
+  }
+
+  scan_started_ = false;
+  return true;
+}
+
+const UUID& LowEnergyScanner::GetAppIdentifier() const {
+  return app_identifier_;
+}
+
+int LowEnergyScanner::GetInstanceId() const {
+  return scanner_id_;
+}
+
+void LowEnergyScanner::ScanResultCallback(
+    hal::BluetoothGattInterface* gatt_iface,
+    const bt_bdaddr_t& bda, int rssi, vector<uint8_t> adv_data) {
+  // Ignore scan results if this client didn't start a scan.
+  if (!scan_started_.load())
+    return;
+
+  lock_guard<mutex> lock(delegate_mutex_);
+  if (!delegate_)
+    return;
+
+  // TODO(armansito): Apply software filters here.
+
+  size_t record_len = GetScanRecordLength(adv_data);
+  std::vector<uint8_t> scan_record(adv_data.begin(), adv_data.begin() + record_len);
+
+  ScanResult result(BtAddrString(&bda), scan_record, rssi);
+
+  delegate_->OnScanResult(this, result);
+}
+
+// LowEnergyScannerFactory implementation
+// ========================================================
+
+LowEnergyScannerFactory::LowEnergyScannerFactory(Adapter& adapter) : adapter_(adapter) {
+  hal::BluetoothGattInterface::Get()->AddScannerObserver(this);
+}
+
+LowEnergyScannerFactory::~LowEnergyScannerFactory() {
+  hal::BluetoothGattInterface::Get()->RemoveScannerObserver(this);
+}
+
+bool LowEnergyScannerFactory::RegisterInstance(
+    const UUID& uuid,
+    const RegisterCallback& callback) {
+  VLOG(1) << __func__ << " - UUID: " << uuid.ToString();
+  lock_guard<mutex> lock(pending_calls_lock_);
+
+  if (pending_calls_.find(uuid) != pending_calls_.end()) {
+    LOG(ERROR) << "Low-Energy scanner with given UUID already registered - "
+               << "UUID: " << uuid.ToString();
+    return false;
+  }
+
+  const btgatt_scanner_interface_t* hal_iface =
+      hal::BluetoothGattInterface::Get()->GetScannerHALInterface();
+  bt_uuid_t app_uuid = uuid.GetBlueDroid();
+
+  if (hal_iface->register_scanner(&app_uuid) != BT_STATUS_SUCCESS)
+    return false;
+
+  pending_calls_[uuid] = callback;
+
+  return true;
+}
+
+void LowEnergyScannerFactory::RegisterScannerCallback(
+    hal::BluetoothGattInterface* gatt_iface,
+    int status, int scanner_id,
+    const bt_uuid_t& app_uuid) {
+  UUID uuid(app_uuid);
+
+  VLOG(1) << __func__ << " - UUID: " << uuid.ToString();
+  lock_guard<mutex> lock(pending_calls_lock_);
+
+  auto iter = pending_calls_.find(uuid);
+  if (iter == pending_calls_.end()) {
+    VLOG(1) << "Ignoring callback for unknown app_id: " << uuid.ToString();
+    return;
+  }
+
+  // No need to construct a scanner if the call wasn't successful.
+  std::unique_ptr<LowEnergyScanner> scanner;
+  BLEStatus result = BLE_STATUS_FAILURE;
+  if (status == BT_STATUS_SUCCESS) {
+    scanner.reset(new LowEnergyScanner(adapter_, uuid, scanner_id));
+
+    gatt_iface->AddScannerObserver(scanner.get());
+
+    result = BLE_STATUS_SUCCESS;
+  }
+
+  // Notify the result via the result callback.
+  iter->second(result, uuid, std::move(scanner));
+
+  pending_calls_.erase(iter);
+}
+
+
+}  // namespace bluetooth
diff --git a/service/low_energy_scanner.h b/service/low_energy_scanner.h
new file mode 100644
index 0000000..cae1f58
--- /dev/null
+++ b/service/low_energy_scanner.h
@@ -0,0 +1,160 @@
+//
+//  Copyright (C) 2016 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+#pragma once
+
+#include <atomic>
+#include <functional>
+#include <map>
+#include <mutex>
+
+#include <base/macros.h>
+
+#include "service/bluetooth_instance.h"
+#include "service/common/bluetooth/advertise_data.h"
+#include "service/common/bluetooth/advertise_settings.h"
+#include "service/common/bluetooth/low_energy_constants.h"
+#include "service/common/bluetooth/scan_filter.h"
+#include "service/common/bluetooth/scan_result.h"
+#include "service/common/bluetooth/scan_settings.h"
+#include "service/common/bluetooth/uuid.h"
+#include "service/hal/bluetooth_gatt_interface.h"
+
+namespace bluetooth {
+
+class Adapter;
+
+// A LowEnergyScanner represents an application's handle to perform various
+// Bluetooth Low Energy GAP operations. Instances cannot be created directly and
+// should be obtained through the factory.
+class LowEnergyScanner : private hal::BluetoothGattInterface::ScannerObserver,
+                         public BluetoothInstance {
+ public:
+  // The Delegate interface is used to notify asynchronous events related to LE
+  // scan.
+  class Delegate {
+   public:
+    Delegate() = default;
+    virtual ~Delegate() = default;
+
+    // Called asynchronously to notify the delegate of nearby BLE advertisers
+    // found during a device scan.
+    virtual void OnScanResult(LowEnergyScanner* client,
+                              const ScanResult& scan_result) = 0;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Delegate);
+  };
+
+  // The destructor automatically unregisters this client instance from the
+  // stack.
+  ~LowEnergyScanner() override;
+
+  // Assigns a delegate to this instance. |delegate| must out-live this
+  // LowEnergyClient instance.
+  void SetDelegate(Delegate* delegate);
+
+  // Initiates a BLE device scan for this client using the given |settings| and
+  // |filters|. See the documentation for ScanSettings and ScanFilter for how
+  // these parameters can be configured. Return true on success, false
+  // otherwise. Please see logs for details in case of error.
+  bool StartScan(const ScanSettings& settings,
+                 const std::vector<ScanFilter>& filters);
+
+  // Stops an ongoing BLE device scan for this client.
+  bool StopScan();
+
+  // Returns the current scan settings.
+  const ScanSettings& scan_settings() const { return scan_settings_; }
+
+  // BluetoothInstace overrides:
+  const UUID& GetAppIdentifier() const override;
+  int GetInstanceId() const override;
+
+  void ScanResultCallback(
+      hal::BluetoothGattInterface* gatt_iface,
+      const bt_bdaddr_t& bda, int rssi,
+      vector<uint8_t> adv_data) override;
+
+ private:
+  friend class LowEnergyScannerFactory;
+
+  // Constructor shouldn't be called directly as instances are meant to be
+  // obtained from the factory.
+  LowEnergyScanner(Adapter& adapter, const UUID& uuid, int scanner_id);
+
+  // Calls and clears the pending callbacks.
+  void InvokeAndClearStartCallback(BLEStatus status);
+  void InvokeAndClearStopCallback(BLEStatus status);
+
+  // Raw pointer to the Bluetooth Adapter.
+  Adapter& adapter_;
+
+  // See getters above for documentation.
+  UUID app_identifier_;
+  int scanner_id_;
+
+  // Protects device scan related members below.
+  std::mutex scan_fields_lock_;
+
+  // Current scan settings.
+  ScanSettings scan_settings_;
+
+  // If true, then this client have a BLE device scan in progress.
+  std::atomic_bool scan_started_;
+
+  // Raw handle to the Delegate, which must outlive this LowEnergyScanner
+  // instance.
+  std::mutex delegate_mutex_;
+  Delegate* delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(LowEnergyScanner);
+};
+
+// LowEnergyScannerFactory is used to register and obtain a per-application
+// LowEnergyScanner instance. Users should call RegisterInstance to obtain their
+// own unique LowEnergyScanner instance that has been registered with the
+// Bluetooth stack.
+class LowEnergyScannerFactory
+    : private hal::BluetoothGattInterface::ScannerObserver,
+      public BluetoothInstanceFactory {
+ public:
+  // Don't construct/destruct directly except in tests. Instead, obtain a handle
+  // from an Adapter instance.
+  explicit LowEnergyScannerFactory(Adapter& adapter);
+  ~LowEnergyScannerFactory() override;
+
+  // BluetoothInstanceFactory override:
+  bool RegisterInstance(const UUID& app_uuid, const RegisterCallback& callback) override;
+
+ private:
+  friend class LowEnergyScanner;
+
+  // BluetoothGattInterface::ScannerObserver overrides:
+  void RegisterScannerCallback(hal::BluetoothGattInterface* gatt_iface, int status,
+                               int scanner_id, const bt_uuid_t& app_uuid);
+
+  // Map of pending calls to register.
+  std::mutex pending_calls_lock_;
+  std::map<UUID, RegisterCallback> pending_calls_;
+
+  // Raw pointer to the Adapter that owns this factory.
+  Adapter& adapter_;
+
+  DISALLOW_COPY_AND_ASSIGN(LowEnergyScannerFactory);
+};
+
+}  // namespace bluetooth
diff --git a/service/test/adapter_unittest.cc b/service/test/adapter_unittest.cc
index 706d4b9..ca64618 100644
--- a/service/test/adapter_unittest.cc
+++ b/service/test/adapter_unittest.cc
@@ -37,7 +37,7 @@
 
     // Initialize GATT interface with default handlers.
     hal::BluetoothGattInterface::InitializeForTesting(
-        new hal::FakeBluetoothGattInterface(nullptr, nullptr, nullptr));
+        new hal::FakeBluetoothGattInterface(nullptr, nullptr, nullptr, nullptr));
 
     adapter_ = Adapter::Create();
   }
diff --git a/service/test/gatt_client_unittest.cc b/service/test/gatt_client_unittest.cc
index e4623bc..1628a7b 100644
--- a/service/test/gatt_client_unittest.cc
+++ b/service/test/gatt_client_unittest.cc
@@ -55,6 +55,7 @@
 
     fake_hal_gatt_iface_ = new hal::FakeBluetoothGattInterface(
         nullptr,
+        nullptr,
         std::static_pointer_cast<
             hal::FakeBluetoothGattInterface::TestClientHandler>(mock_handler_),
         nullptr);
@@ -160,23 +161,5 @@
   EXPECT_EQ(uuid1, cb_uuid);
 }
 
-TEST_F(GattClientTest, StartStopScan) {
-  EXPECT_CALL(*mock_handler_, Scan(true))
-      .Times(1)
-      .WillOnce(Return(BT_STATUS_SUCCESS));
-
-  EXPECT_CALL(*mock_handler_, Scan(false))
-      .Times(1)
-      .WillOnce(Return(BT_STATUS_SUCCESS));
-
-  for (int i = 0; i < 5; i++)
-    hal::BluetoothGattInterface::Get()->StartScan(i);
-
-  for (int i = 0; i < 5; i++)
-    hal::BluetoothGattInterface::Get()->StopScan(i);
-
-  testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
-}
-
 }  // namespace
 }  // namespace bluetooth
diff --git a/service/test/gatt_server_unittest.cc b/service/test/gatt_server_unittest.cc
index d73e186..ca4c2fe 100644
--- a/service/test/gatt_server_unittest.cc
+++ b/service/test/gatt_server_unittest.cc
@@ -175,6 +175,7 @@
     fake_hal_gatt_iface_ = new hal::FakeBluetoothGattInterface(
         nullptr,
         nullptr,
+        nullptr,
         std::static_pointer_cast<
             hal::FakeBluetoothGattInterface::TestServerHandler>(mock_handler_));
 
diff --git a/service/test/ipc_linux_unittest.cc b/service/test/ipc_linux_unittest.cc
index b36fe75..efd50ac 100644
--- a/service/test/ipc_linux_unittest.cc
+++ b/service/test/ipc_linux_unittest.cc
@@ -59,7 +59,7 @@
     bluetooth::hal::BluetoothInterface::InitializeForTesting(
         new bluetooth::hal::FakeBluetoothInterface());
     bluetooth::hal::BluetoothGattInterface::InitializeForTesting(
-        new bluetooth::hal::FakeBluetoothGattInterface(nullptr, nullptr, nullptr));
+        new bluetooth::hal::FakeBluetoothGattInterface(nullptr, nullptr, nullptr, nullptr));
 
     adapter_ = bluetooth::Adapter::Create();
     ipc_manager_.reset(new ipc::IPCManager(adapter_.get()));
diff --git a/service/test/low_energy_advertiser_unittest.cc b/service/test/low_energy_advertiser_unittest.cc
index 43189b3..339c8c9 100644
--- a/service/test/low_energy_advertiser_unittest.cc
+++ b/service/test/low_energy_advertiser_unittest.cc
@@ -77,7 +77,7 @@
     hal::BluetoothGattInterface::InitializeForTesting(
         new hal::FakeBluetoothGattInterface(
             std::static_pointer_cast<BleAdvertiserInterface>(mock_handler_),
-            nullptr, nullptr));
+            nullptr, nullptr, nullptr));
     ble_advertiser_factory_.reset(new LowEnergyAdvertiserFactory());
   }
 
diff --git a/service/test/low_energy_client_unittest.cc b/service/test/low_energy_client_unittest.cc
index 31bbeed..1fc2393 100644
--- a/service/test/low_energy_client_unittest.cc
+++ b/service/test/low_energy_client_unittest.cc
@@ -37,15 +37,11 @@
 class MockGattHandler
     : public hal::FakeBluetoothGattInterface::TestClientHandler {
  public:
-  MockGattHandler() {
-    ON_CALL(*this, Scan(false))
-        .WillByDefault(Return(BT_STATUS_SUCCESS));
-  }
+  MockGattHandler() {};
   ~MockGattHandler() override = default;
 
   MOCK_METHOD1(RegisterClient, bt_status_t(bt_uuid_t*));
   MOCK_METHOD1(UnregisterClient, bt_status_t(int));
-  MOCK_METHOD1(Scan, bt_status_t(bool));
   MOCK_METHOD4(Connect, bt_status_t(int , const bt_bdaddr_t *, bool, int));
   MOCK_METHOD3(Disconnect, bt_status_t(int , const bt_bdaddr_t *, int));
 
@@ -55,15 +51,11 @@
 
 class TestDelegate : public LowEnergyClient::Delegate {
  public:
-  TestDelegate() : scan_result_count_(0), connection_state_count_(0),
-                   last_mtu_(0) {
+  TestDelegate() : connection_state_count_(0), last_mtu_(0) {
   }
 
   ~TestDelegate() override = default;
 
-  int scan_result_count() const { return scan_result_count_; }
-  const ScanResult& last_scan_result() const { return last_scan_result_; }
-
   int connection_state_count() const { return connection_state_count_; }
 
   void OnConnectionState(LowEnergyClient* client, int status,
@@ -78,16 +70,7 @@
     last_mtu_ = mtu;
   }
 
-  void OnScanResult(LowEnergyClient* client, const ScanResult& scan_result) {
-    ASSERT_TRUE(client);
-    scan_result_count_++;
-    last_scan_result_ = scan_result;
-  }
-
  private:
-  int scan_result_count_;
-  ScanResult last_scan_result_;
-
   int connection_state_count_;
 
   int last_mtu_;
@@ -106,6 +89,7 @@
         mock_handler_.reset(new MockGattHandler());
     fake_hal_gatt_iface_ = new hal::FakeBluetoothGattInterface(
         nullptr,
+        nullptr,
         std::static_pointer_cast<
             hal::FakeBluetoothGattInterface::TestClientHandler>(mock_handler_),
         nullptr);
@@ -269,98 +253,6 @@
   EXPECT_EQ(uuid1, cb_uuid);
 }
 
-TEST_F(LowEnergyClientPostRegisterTest, ScanSettings) {
-  EXPECT_CALL(mock_adapter_, IsEnabled())
-      .WillOnce(Return(false))
-      .WillRepeatedly(Return(true));
-
-  ScanSettings settings;
-  std::vector<ScanFilter> filters;
-
-  // Adapter is not enabled.
-  EXPECT_FALSE(le_client_->StartScan(settings, filters));
-
-  // TODO(jpawlowski): add tests checking settings and filter parsing when
-  // implemented
-
-  // These should succeed and result in a HAL call
-  EXPECT_CALL(*mock_handler_, Scan(true))
-      .Times(1)
-      .WillOnce(Return(BT_STATUS_SUCCESS));
-  EXPECT_TRUE(le_client_->StartScan(settings, filters));
-
-  // These should succeed and result in a HAL call
-  EXPECT_CALL(*mock_handler_, Scan(false))
-      .Times(1)
-      .WillOnce(Return(BT_STATUS_SUCCESS));
-  EXPECT_TRUE(le_client_->StopScan());
-
-  ::testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
-}
-
-TEST_F(LowEnergyClientPostRegisterTest, ScanRecord) {
-  TestDelegate delegate;
-  le_client_->SetDelegate(&delegate);
-
-  EXPECT_EQ(0, delegate.scan_result_count());
-
-  vector<uint8_t> kTestRecord0({ 0x02, 0x01, 0x00, 0x00 });
-  vector<uint8_t> kTestRecord1({ 0x00 });
-  vector<uint8_t> kTestRecord2({
-    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
-    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
-    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
-    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
-    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
-    0x01, 0x00
-  });
-  const bt_bdaddr_t kTestAddress = {
-    { 0x01, 0x02, 0x03, 0x0A, 0x0B, 0x0C }
-  };
-  const char kTestAddressStr[] = "01:02:03:0A:0B:0C";
-  const int kTestRssi = 64;
-
-  // Scan wasn't started. Result should be ignored.
-  fake_hal_gatt_iface_->NotifyScanResultCallback(
-      kTestAddress, kTestRssi, kTestRecord0);
-  EXPECT_EQ(0, delegate.scan_result_count());
-
-  // Start a scan session for |le_client_|.
-  EXPECT_CALL(mock_adapter_, IsEnabled())
-      .Times(1)
-      .WillOnce(Return(true));
-  EXPECT_CALL(*mock_handler_, Scan(_))
-      .Times(2)
-      .WillOnce(Return(BT_STATUS_SUCCESS))
-      .WillOnce(Return(BT_STATUS_SUCCESS));
-  ScanSettings settings;
-  std::vector<ScanFilter> filters;
-  ASSERT_TRUE(le_client_->StartScan(settings, filters));
-
-  fake_hal_gatt_iface_->NotifyScanResultCallback(
-      kTestAddress, kTestRssi, kTestRecord0);
-  EXPECT_EQ(1, delegate.scan_result_count());
-  EXPECT_EQ(kTestAddressStr, delegate.last_scan_result().device_address());
-  EXPECT_EQ(kTestRssi, delegate.last_scan_result().rssi());
-  EXPECT_EQ(3U, delegate.last_scan_result().scan_record().size());
-
-  fake_hal_gatt_iface_->NotifyScanResultCallback(
-      kTestAddress, kTestRssi, kTestRecord1);
-  EXPECT_EQ(2, delegate.scan_result_count());
-  EXPECT_EQ(kTestAddressStr, delegate.last_scan_result().device_address());
-  EXPECT_EQ(kTestRssi, delegate.last_scan_result().rssi());
-  EXPECT_TRUE(delegate.last_scan_result().scan_record().empty());
-
-  fake_hal_gatt_iface_->NotifyScanResultCallback(
-      kTestAddress, kTestRssi, kTestRecord2);
-  EXPECT_EQ(3, delegate.scan_result_count());
-  EXPECT_EQ(kTestAddressStr, delegate.last_scan_result().device_address());
-  EXPECT_EQ(kTestRssi, delegate.last_scan_result().rssi());
-  EXPECT_EQ(62U, delegate.last_scan_result().scan_record().size());
-
-  le_client_->SetDelegate(nullptr);
-}
-
 MATCHER_P(BitEq, x, std::string(negation ? "isn't" : "is") +
                         " bitwise equal to " + ::testing::PrintToString(x)) {
   static_assert(sizeof(x) == sizeof(arg), "Size mismatch");
diff --git a/service/test/low_energy_scanner_unittest.cc b/service/test/low_energy_scanner_unittest.cc
new file mode 100644
index 0000000..f966a14
--- /dev/null
+++ b/service/test/low_energy_scanner_unittest.cc
@@ -0,0 +1,346 @@
+//
+//  Copyright (C) 2016 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "service/adapter.h"
+#include "service/hal/fake_bluetooth_gatt_interface.h"
+#include "service/low_energy_scanner.h"
+#include "stack/include/bt_types.h"
+#include "stack/include/hcidefs.h"
+#include "test/mock_adapter.h"
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::Pointee;
+using ::testing::DoAll;
+using ::testing::Invoke;
+
+namespace bluetooth {
+namespace {
+
+class MockGattHandler
+    : public hal::FakeBluetoothGattInterface::TestScannerHandler {
+ public:
+  MockGattHandler() {
+    ON_CALL(*this, Scan(false))
+        .WillByDefault(Return(BT_STATUS_SUCCESS));
+  }
+  ~MockGattHandler() override = default;
+
+  MOCK_METHOD1(RegisterScanner, bt_status_t(bt_uuid_t*));
+  MOCK_METHOD1(UnregisterScanner, bt_status_t(int));
+  MOCK_METHOD1(Scan, bt_status_t(bool));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockGattHandler);
+};
+
+class TestDelegate : public LowEnergyScanner::Delegate {
+ public:
+  TestDelegate() : scan_result_count_(0) {
+  }
+
+  ~TestDelegate() override = default;
+
+  int scan_result_count() const { return scan_result_count_; }
+  const ScanResult& last_scan_result() const { return last_scan_result_; }
+
+  void OnScanResult(LowEnergyScanner* scanner, const ScanResult& scan_result) {
+    ASSERT_TRUE(scanner);
+    scan_result_count_++;
+    last_scan_result_ = scan_result;
+  }
+
+ private:
+  int scan_result_count_;
+  ScanResult last_scan_result_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestDelegate);
+};
+
+class LowEnergyScannerTest : public ::testing::Test {
+ public:
+  LowEnergyScannerTest() = default;
+  ~LowEnergyScannerTest() override = default;
+
+  void SetUp() override {
+    // Only set |mock_handler_| if a test hasn't set it.
+    if (!mock_handler_)
+        mock_handler_.reset(new MockGattHandler());
+    fake_hal_gatt_iface_ = new hal::FakeBluetoothGattInterface(
+        nullptr,
+        std::static_pointer_cast<
+            hal::FakeBluetoothGattInterface::TestScannerHandler>(mock_handler_),
+        nullptr,
+        nullptr);
+    hal::BluetoothGattInterface::InitializeForTesting(fake_hal_gatt_iface_);
+    ble_factory_.reset(new LowEnergyScannerFactory(mock_adapter_));
+  }
+
+  void TearDown() override {
+    ble_factory_.reset();
+    hal::BluetoothGattInterface::CleanUp();
+  }
+
+ protected:
+  hal::FakeBluetoothGattInterface* fake_hal_gatt_iface_;
+  testing::MockAdapter mock_adapter_;
+  std::shared_ptr<MockGattHandler> mock_handler_;
+  std::unique_ptr<LowEnergyScannerFactory> ble_factory_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(LowEnergyScannerTest);
+};
+
+// Used for tests that operate on a pre-registered scanner.
+class LowEnergyScannerPostRegisterTest : public LowEnergyScannerTest {
+ public:
+  LowEnergyScannerPostRegisterTest() : next_scanner_id_(0) {
+  }
+  ~LowEnergyScannerPostRegisterTest() override = default;
+
+  void SetUp() override {
+    LowEnergyScannerTest::SetUp();
+    auto callback = [&](std::unique_ptr<LowEnergyScanner> scanner) {
+      le_scanner_ = std::move(scanner);
+    };
+    RegisterTestScanner(callback);
+  }
+
+  void TearDown() override {
+    EXPECT_CALL(*mock_handler_, UnregisterScanner(_))
+        .Times(1)
+        .WillOnce(Return(BT_STATUS_SUCCESS));
+    le_scanner_.reset();
+    LowEnergyScannerTest::TearDown();
+  }
+
+  void RegisterTestScanner(
+      const std::function<void(std::unique_ptr<LowEnergyScanner> scanner)>
+          callback) {
+    UUID uuid = UUID::GetRandom();
+    auto api_callback = [&](BLEStatus status, const UUID& in_uuid,
+                        std::unique_ptr<BluetoothInstance> in_scanner) {
+      CHECK(in_uuid == uuid);
+      CHECK(in_scanner.get());
+      CHECK(status == BLE_STATUS_SUCCESS);
+
+      callback(std::unique_ptr<LowEnergyScanner>(
+          static_cast<LowEnergyScanner*>(in_scanner.release())));
+    };
+
+    EXPECT_CALL(*mock_handler_, RegisterScanner(_))
+        .Times(1)
+        .WillOnce(Return(BT_STATUS_SUCCESS));
+
+    ble_factory_->RegisterInstance(uuid, api_callback);
+
+    bt_uuid_t hal_uuid = uuid.GetBlueDroid();
+    fake_hal_gatt_iface_->NotifyRegisterScannerCallback(
+        0, next_scanner_id_++, hal_uuid);
+    ::testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
+  }
+
+ protected:
+  std::unique_ptr<LowEnergyScanner> le_scanner_;
+
+ private:
+  int next_scanner_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(LowEnergyScannerPostRegisterTest);
+};
+
+TEST_F(LowEnergyScannerTest, RegisterInstance) {
+  EXPECT_CALL(*mock_handler_, RegisterScanner(_))
+      .Times(2)
+      .WillOnce(Return(BT_STATUS_FAIL))
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+
+  // These will be asynchronously populated with a result when the callback
+  // executes.
+  BLEStatus status = BLE_STATUS_SUCCESS;
+  UUID cb_uuid;
+  std::unique_ptr<LowEnergyScanner> scanner;
+  int callback_count = 0;
+
+  auto callback = [&](BLEStatus in_status, const UUID& uuid,
+                      std::unique_ptr<BluetoothInstance> in_scanner) {
+        status = in_status;
+        cb_uuid = uuid;
+        scanner = std::unique_ptr<LowEnergyScanner>(
+            static_cast<LowEnergyScanner*>(in_scanner.release()));
+        callback_count++;
+      };
+
+  UUID uuid0 = UUID::GetRandom();
+
+  // HAL returns failure.
+  EXPECT_FALSE(ble_factory_->RegisterInstance(uuid0, callback));
+  EXPECT_EQ(0, callback_count);
+
+  // HAL returns success.
+  EXPECT_TRUE(ble_factory_->RegisterInstance(uuid0, callback));
+  EXPECT_EQ(0, callback_count);
+
+  // Calling twice with the same UUID should fail with no additional call into
+  // the stack.
+  EXPECT_FALSE(ble_factory_->RegisterInstance(uuid0, callback));
+
+  ::testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
+
+  // Call with a different UUID while one is pending.
+  UUID uuid1 = UUID::GetRandom();
+  EXPECT_CALL(*mock_handler_, RegisterScanner(_))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_TRUE(ble_factory_->RegisterInstance(uuid1, callback));
+
+  // Trigger callback with an unknown UUID. This should get ignored.
+  UUID uuid2 = UUID::GetRandom();
+  bt_uuid_t hal_uuid = uuid2.GetBlueDroid();
+  fake_hal_gatt_iface_->NotifyRegisterScannerCallback(0, 0, hal_uuid);
+  EXPECT_EQ(0, callback_count);
+
+  // |uuid0| succeeds.
+  int scanner_if0 = 2;  // Pick something that's not 0.
+  hal_uuid = uuid0.GetBlueDroid();
+  fake_hal_gatt_iface_->NotifyRegisterScannerCallback(
+      BT_STATUS_SUCCESS, scanner_if0, hal_uuid);
+
+  EXPECT_EQ(1, callback_count);
+  ASSERT_TRUE(scanner.get() != nullptr);  // Assert to terminate in case of error
+  EXPECT_EQ(BLE_STATUS_SUCCESS, status);
+  EXPECT_EQ(scanner_if0, scanner->GetInstanceId());
+  EXPECT_EQ(uuid0, scanner->GetAppIdentifier());
+  EXPECT_EQ(uuid0, cb_uuid);
+
+  // The scanner should unregister itself when deleted.
+  EXPECT_CALL(*mock_handler_, UnregisterScanner(scanner_if0))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  scanner.reset();
+  ::testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
+
+  // |uuid1| fails.
+  int scanner_if1 = 3;
+  hal_uuid = uuid1.GetBlueDroid();
+  fake_hal_gatt_iface_->NotifyRegisterScannerCallback(
+      BT_STATUS_FAIL, scanner_if1, hal_uuid);
+
+  EXPECT_EQ(2, callback_count);
+  ASSERT_TRUE(scanner.get() == nullptr);  // Assert to terminate in case of error
+  EXPECT_EQ(BLE_STATUS_FAILURE, status);
+  EXPECT_EQ(uuid1, cb_uuid);
+}
+
+TEST_F(LowEnergyScannerPostRegisterTest, ScanSettings) {
+  EXPECT_CALL(mock_adapter_, IsEnabled())
+      .WillOnce(Return(false))
+      .WillRepeatedly(Return(true));
+
+  ScanSettings settings;
+  std::vector<ScanFilter> filters;
+
+  // Adapter is not enabled.
+  EXPECT_FALSE(le_scanner_->StartScan(settings, filters));
+
+  // TODO(jpawlowski): add tests checking settings and filter parsing when
+  // implemented
+
+  // These should succeed and result in a HAL call
+  EXPECT_CALL(*mock_handler_, Scan(true))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_TRUE(le_scanner_->StartScan(settings, filters));
+
+  // These should succeed and result in a HAL call
+  EXPECT_CALL(*mock_handler_, Scan(false))
+      .Times(1)
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  EXPECT_TRUE(le_scanner_->StopScan());
+
+  ::testing::Mock::VerifyAndClearExpectations(mock_handler_.get());
+}
+
+TEST_F(LowEnergyScannerPostRegisterTest, ScanRecord) {
+  TestDelegate delegate;
+  le_scanner_->SetDelegate(&delegate);
+
+  EXPECT_EQ(0, delegate.scan_result_count());
+
+  vector<uint8_t> kTestRecord0({ 0x02, 0x01, 0x00, 0x00 });
+  vector<uint8_t> kTestRecord1({ 0x00 });
+  vector<uint8_t> kTestRecord2({
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00
+  });
+  const bt_bdaddr_t kTestAddress = {
+    { 0x01, 0x02, 0x03, 0x0A, 0x0B, 0x0C }
+  };
+  const char kTestAddressStr[] = "01:02:03:0A:0B:0C";
+  const int kTestRssi = 64;
+
+  // Scan wasn't started. Result should be ignored.
+  fake_hal_gatt_iface_->NotifyScanResultCallback(
+      kTestAddress, kTestRssi, kTestRecord0);
+  EXPECT_EQ(0, delegate.scan_result_count());
+
+  // Start a scan session for |le_scanner_|.
+  EXPECT_CALL(mock_adapter_, IsEnabled())
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*mock_handler_, Scan(_))
+      .Times(2)
+      .WillOnce(Return(BT_STATUS_SUCCESS))
+      .WillOnce(Return(BT_STATUS_SUCCESS));
+  ScanSettings settings;
+  std::vector<ScanFilter> filters;
+  ASSERT_TRUE(le_scanner_->StartScan(settings, filters));
+
+  fake_hal_gatt_iface_->NotifyScanResultCallback(
+      kTestAddress, kTestRssi, kTestRecord0);
+  EXPECT_EQ(1, delegate.scan_result_count());
+  EXPECT_EQ(kTestAddressStr, delegate.last_scan_result().device_address());
+  EXPECT_EQ(kTestRssi, delegate.last_scan_result().rssi());
+  EXPECT_EQ(3U, delegate.last_scan_result().scan_record().size());
+
+  fake_hal_gatt_iface_->NotifyScanResultCallback(
+      kTestAddress, kTestRssi, kTestRecord1);
+  EXPECT_EQ(2, delegate.scan_result_count());
+  EXPECT_EQ(kTestAddressStr, delegate.last_scan_result().device_address());
+  EXPECT_EQ(kTestRssi, delegate.last_scan_result().rssi());
+  EXPECT_TRUE(delegate.last_scan_result().scan_record().empty());
+
+  fake_hal_gatt_iface_->NotifyScanResultCallback(
+      kTestAddress, kTestRssi, kTestRecord2);
+  EXPECT_EQ(3, delegate.scan_result_count());
+  EXPECT_EQ(kTestAddressStr, delegate.last_scan_result().device_address());
+  EXPECT_EQ(kTestRssi, delegate.last_scan_result().rssi());
+  EXPECT_EQ(62U, delegate.last_scan_result().scan_record().size());
+
+  le_scanner_->SetDelegate(nullptr);
+}
+
+
+}  // namespace
+}  // namespace bluetooth
diff --git a/service/test/mock_adapter.h b/service/test/mock_adapter.h
index f90e50f..1a762fb 100644
--- a/service/test/mock_adapter.h
+++ b/service/test/mock_adapter.h
@@ -44,6 +44,7 @@
   MOCK_METHOD0(IsOffloadedScanBatchingSupported, bool());
   MOCK_CONST_METHOD0(GetLowEnergyClientFactory, LowEnergyClientFactory*());
   MOCK_CONST_METHOD0(GetLeAdvertiserFactory, LowEnergyAdvertiserFactory*());
+  MOCK_CONST_METHOD0(GetLeScannerFactory, LowEnergyScannerFactory*());
   MOCK_CONST_METHOD0(GetGattClientFactory, GattClientFactory*());
   MOCK_CONST_METHOD0(GetGattServerFactory, GattServerFactory*());
 
diff --git a/test/suite/gatt/gatt_test.cc b/test/suite/gatt/gatt_test.cc
index f4812ab..16f53ed 100644
--- a/test/suite/gatt/gatt_test.cc
+++ b/test/suite/gatt/gatt_test.cc
@@ -80,6 +80,10 @@
   BluetoothTest::TearDown();
 }
 
+const btgatt_scanner_interface_t* GattTest::gatt_scanner_interface() {
+  return gatt_scanner_interface_;
+}
+
 const btgatt_client_interface_t* GattTest::gatt_client_interface() {
   return gatt_client_interface_;
 }
diff --git a/test/suite/gatt/gatt_test.h b/test/suite/gatt/gatt_test.h
index d4cc664..57e54f0 100644
--- a/test/suite/gatt/gatt_test.h
+++ b/test/suite/gatt/gatt_test.h
@@ -25,11 +25,15 @@
 // helpers and callbacks for GUnit to use for testing gatt.
 class GattTest : public BluetoothTest,
                  public bluetooth::hal::BluetoothGattInterface::ClientObserver,
+                 public bluetooth::hal::BluetoothGattInterface::ScannerObserver,
                  public bluetooth::hal::BluetoothGattInterface::ServerObserver {
  protected:
   GattTest() = default;
   virtual ~GattTest() = default;
 
+  // Gets the gatt_scanner_interface
+  const btgatt_scanner_interface_t* gatt_scanner_interface();
+
   // Gets the gatt_client_interface
   const btgatt_client_interface_t* gatt_client_interface();
 
@@ -92,6 +96,9 @@
   semaphore_t* service_deleted_callback_sem_;
 
  private:
+  // The btgatt_scanner_interface_t that all the tests use to interact with the HAL
+  const btgatt_scanner_interface_t* gatt_scanner_interface_;
+
   // The gatt_client_interface that all the tests use to interact with the HAL
   const btgatt_client_interface_t* gatt_client_interface_;
 
diff --git a/test/suite/gatt/gatt_unittest.cc b/test/suite/gatt/gatt_unittest.cc
index 9fc4f02..2f66fff 100644
--- a/test/suite/gatt/gatt_unittest.cc
+++ b/test/suite/gatt/gatt_unittest.cc
@@ -50,15 +50,6 @@
   gatt_client_interface()->unregister_client(client_interface_id());
 }
 
-TEST_F(GattTest, GattClientScanRemoteDevice) {
-  // Starts BLE scan. NB: This test assumes there is a BLE beacon advertising nearby.
-  gatt_client_interface()->scan(true);
-  semaphore_wait(scan_result_callback_sem_);
-
-  // Ends BLE scan. No callback is expected.
-  gatt_client_interface()->scan(false);
-}
-
 TEST_F(GattTest, GattClientAdvertise) {
   // Registers a new client app.
   bt_uuid_t gatt_client_uuid;