summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorthestig <thestig@chromium.org>2014-08-29 16:41:06 -0700
committerCommit bot <commit-bot@chromium.org>2014-08-29 23:49:43 +0000
commit8146daf0a5a91e4bedec34cfe0b942803c36aa90 (patch)
treea693b4354861b7a9640f764f2b3972de77582243 /extensions
parent386f9301889efde34b202a4600496b1e10aa06c8 (diff)
downloadchromium_src-8146daf0a5a91e4bedec34cfe0b942803c36aa90.zip
chromium_src-8146daf0a5a91e4bedec34cfe0b942803c36aa90.tar.gz
chromium_src-8146daf0a5a91e4bedec34cfe0b942803c36aa90.tar.bz2
Extensions: Move bluetooth APIs to extensions/.
BUG=395240 TBR=keybuk@chromium.org Review URL: https://codereview.chromium.org/420663003 Cr-Commit-Position: refs/heads/master@{#292719}
Diffstat (limited to 'extensions')
-rw-r--r--extensions/DEPS1
-rw-r--r--extensions/browser/BUILD.gn29
-rw-r--r--extensions/browser/DEPS1
-rw-r--r--extensions/browser/api/api_resource_manager.h9
-rw-r--r--extensions/browser/api/bluetooth/OWNERS3
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_api.cc203
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_api.h137
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc111
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h52
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_api_utils.cc147
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_api_utils.h30
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_apitest.cc459
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_event_router.cc367
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_event_router.h164
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc101
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_extension_function.cc69
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_extension_function.h47
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_private_api.cc290
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_private_api.h97
-rw-r--r--extensions/browser/api/bluetooth/bluetooth_private_apitest.cc186
-rw-r--r--extensions/browser/api/bluetooth_low_energy/OWNERS2
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc773
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h337
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc1283
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.cc41
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h52
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc1450
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h418
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.cc41
-rw-r--r--extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h54
-rw-r--r--extensions/browser/api/bluetooth_low_energy/utils.cc56
-rw-r--r--extensions/browser/api/bluetooth_low_energy/utils.h34
-rw-r--r--extensions/browser/api/bluetooth_socket/OWNERS2
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc197
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h161
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc678
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h352
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc219
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc370
-rw-r--r--extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h119
-rw-r--r--extensions/common/BUILD.gn13
-rw-r--r--extensions/common/DEPS1
-rw-r--r--extensions/common/api/bluetooth.idl156
-rw-r--r--extensions/common/api/bluetooth/bluetooth_manifest_data.cc67
-rw-r--r--extensions/common/api/bluetooth/bluetooth_manifest_data.h61
-rw-r--r--extensions/common/api/bluetooth/bluetooth_manifest_handler.cc47
-rw-r--r--extensions/common/api/bluetooth/bluetooth_manifest_handler.h41
-rw-r--r--extensions/common/api/bluetooth/bluetooth_manifest_permission.cc202
-rw-r--r--extensions/common/api/bluetooth/bluetooth_manifest_permission.h69
-rw-r--r--extensions/common/api/bluetooth_low_energy.idl299
-rw-r--r--extensions/common/api/bluetooth_private.json190
-rw-r--r--extensions/common/api/bluetooth_socket.idl316
-rw-r--r--extensions/common/api/extensions_manifest_types.json26
-rw-r--r--extensions/common/api/schemas.gypi4
-rw-r--r--extensions/extensions.gyp43
-rw-r--r--extensions/extensions_strings.grd12
56 files changed, 10681 insertions, 8 deletions
diff --git a/extensions/DEPS b/extensions/DEPS
index 67c2fd1..91be1a7 100644
--- a/extensions/DEPS
+++ b/extensions/DEPS
@@ -31,6 +31,7 @@ specific_include_rules = {
"+chrome/browser/extensions/extension_function_test_utils.h",
"+chrome/browser/extensions/extension_service.h",
"+chrome/browser/extensions/extension_service_test_base.h",
+ "+chrome/browser/extensions/extension_test_message_listener.h",
"+chrome/browser/extensions/test_extension_dir.h",
"+chrome/browser/extensions/test_extension_prefs.h",
"+chrome/browser/extensions/test_extension_system.h",
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index b1667d2..03278c0 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -43,6 +43,34 @@ source_set("browser") {
"api/app_view/app_view_internal_api.h",
"api/async_api_function.cc",
"api/async_api_function.h",
+ "api/bluetooth/bluetooth_api.cc",
+ "api/bluetooth/bluetooth_api.h",
+ "api/bluetooth/bluetooth_api_pairing_delegate.cc",
+ "api/bluetooth/bluetooth_api_pairing_delegate.h",
+ "api/bluetooth/bluetooth_api_utils.cc",
+ "api/bluetooth/bluetooth_api_utils.h",
+ "api/bluetooth/bluetooth_event_router.cc",
+ "api/bluetooth/bluetooth_event_router.h",
+ "api/bluetooth/bluetooth_extension_function.cc",
+ "api/bluetooth/bluetooth_extension_function.h",
+ "api/bluetooth/bluetooth_private_api.cc",
+ "api/bluetooth/bluetooth_private_api.h",
+ "api/bluetooth_low_energy/bluetooth_low_energy_api.cc",
+ "api/bluetooth_low_energy/bluetooth_low_energy_api.h",
+ "api/bluetooth_low_energy/bluetooth_low_energy_connection.cc",
+ "api/bluetooth_low_energy/bluetooth_low_energy_connection.h",
+ "api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc",
+ "api/bluetooth_low_energy/bluetooth_low_energy_event_router.h",
+ "api/bluetooth_low_energy/bluetooth_low_energy_notify_session.cc",
+ "api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h",
+ "api/bluetooth_low_energy/utils.cc",
+ "api/bluetooth_low_energy/utils.h",
+ "api/bluetooth_socket/bluetooth_api_socket.cc",
+ "api/bluetooth_socket/bluetooth_api_socket.h",
+ "api/bluetooth_socket/bluetooth_socket_api.cc",
+ "api/bluetooth_socket/bluetooth_socket_api.h",
+ "api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc",
+ "api/bluetooth_socket/bluetooth_socket_event_dispatcher.h",
"api/cast_channel/cast_auth_util.cc",
"api/cast_channel/cast_auth_util.h",
"api/cast_channel/cast_channel_api.cc",
@@ -304,6 +332,7 @@ source_set("browser") {
deps += [
"//components/usb_service",
"//crypto:platform",
+ "//device/bluetooth",
"//device/hid",
"//device/serial",
"//extensions/browser/api/cast_channel:cast_channel_proto",
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS
index d6800f3..dfc1e78 100644
--- a/extensions/browser/DEPS
+++ b/extensions/browser/DEPS
@@ -4,6 +4,7 @@ include_rules = [
"+components/sessions",
"+components/web_modal",
"+content/public/browser",
+ "+device/bluetooth",
"+grit/extensions_strings.h",
"+net",
"+skia/ext/image_operations.h",
diff --git a/extensions/browser/api/api_resource_manager.h b/extensions/browser/api/api_resource_manager.h
index 0eaf36d..e8b5d33 100644
--- a/extensions/browser/api/api_resource_manager.h
+++ b/extensions/browser/api/api_resource_manager.h
@@ -27,12 +27,9 @@
namespace extensions {
-namespace api {
+namespace core_api {
class BluetoothSocketApiFunction;
class BluetoothSocketEventDispatcher;
-}
-
-namespace core_api {
class SerialEventDispatcher;
class TCPServerSocketEventDispatcher;
class TCPSocketEventDispatcher;
@@ -204,8 +201,8 @@ class ApiResourceManager : public BrowserContextKeyedAPI,
// TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and
// we could avoid maintaining a friends list here.
friend class BluetoothAPI;
- friend class api::BluetoothSocketApiFunction;
- friend class api::BluetoothSocketEventDispatcher;
+ friend class core_api::BluetoothSocketApiFunction;
+ friend class core_api::BluetoothSocketEventDispatcher;
friend class core_api::SerialEventDispatcher;
friend class core_api::TCPServerSocketEventDispatcher;
friend class core_api::TCPSocketEventDispatcher;
diff --git a/extensions/browser/api/bluetooth/OWNERS b/extensions/browser/api/bluetooth/OWNERS
new file mode 100644
index 0000000..beccf04
--- /dev/null
+++ b/extensions/browser/api/bluetooth/OWNERS
@@ -0,0 +1,3 @@
+ikarienator@chromium.org
+rpaquay@chromium.org
+keybuk@chromium.org
diff --git a/extensions/browser/api/bluetooth/bluetooth_api.cc b/extensions/browser/api/bluetooth/bluetooth_api.cc
new file mode 100644
index 0000000..2e1e4bc
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_api.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+
+#include <string>
+
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+using device::BluetoothAdapter;
+using device::BluetoothDevice;
+
+namespace bluetooth = extensions::core_api::bluetooth;
+namespace GetDevice = extensions::core_api::bluetooth::GetDevice;
+namespace GetDevices = extensions::core_api::bluetooth::GetDevices;
+
+namespace {
+
+const char kInvalidDevice[] = "Invalid device";
+const char kStartDiscoveryFailed[] = "Starting discovery failed";
+const char kStopDiscoveryFailed[] = "Failed to stop discovery";
+
+extensions::BluetoothEventRouter* GetEventRouter(BrowserContext* context) {
+ // Note: |context| is valid on UI thread only.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return extensions::BluetoothAPI::Get(context)->event_router();
+}
+
+} // namespace
+
+namespace extensions {
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<BluetoothAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothAPI>*
+BluetoothAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+BluetoothAPI* BluetoothAPI::Get(BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return GetFactoryInstance()->Get(context);
+}
+
+BluetoothAPI::BluetoothAPI(content::BrowserContext* context)
+ : browser_context_(context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ event_router->RegisterObserver(this,
+ bluetooth::OnAdapterStateChanged::kEventName);
+ event_router->RegisterObserver(this, bluetooth::OnDeviceAdded::kEventName);
+ event_router->RegisterObserver(this, bluetooth::OnDeviceChanged::kEventName);
+ event_router->RegisterObserver(this, bluetooth::OnDeviceRemoved::kEventName);
+}
+
+BluetoothAPI::~BluetoothAPI() {}
+
+BluetoothEventRouter* BluetoothAPI::event_router() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!event_router_) {
+ event_router_.reset(new BluetoothEventRouter(browser_context_));
+ }
+ return event_router_.get();
+}
+
+void BluetoothAPI::Shutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+void BluetoothAPI::OnListenerAdded(const EventListenerInfo& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (event_router()->IsBluetoothSupported())
+ event_router()->OnListenerAdded();
+}
+
+void BluetoothAPI::OnListenerRemoved(const EventListenerInfo& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (event_router()->IsBluetoothSupported())
+ event_router()->OnListenerRemoved();
+}
+
+namespace core_api {
+
+BluetoothGetAdapterStateFunction::~BluetoothGetAdapterStateFunction() {}
+
+bool BluetoothGetAdapterStateFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ bluetooth::AdapterState state;
+ PopulateAdapterState(*adapter.get(), &state);
+ results_ = bluetooth::GetAdapterState::Results::Create(state);
+ SendResponse(true);
+ return true;
+}
+
+BluetoothGetDevicesFunction::~BluetoothGetDevicesFunction() {}
+
+bool BluetoothGetDevicesFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ base::ListValue* device_list = new base::ListValue;
+ SetResult(device_list);
+
+ BluetoothAdapter::DeviceList devices = adapter->GetDevices();
+ for (BluetoothAdapter::DeviceList::const_iterator iter = devices.begin();
+ iter != devices.end();
+ ++iter) {
+ const BluetoothDevice* device = *iter;
+ DCHECK(device);
+
+ bluetooth::Device extension_device;
+ bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);
+
+ device_list->Append(extension_device.ToValue().release());
+ }
+
+ SendResponse(true);
+
+ return true;
+}
+
+BluetoothGetDeviceFunction::~BluetoothGetDeviceFunction() {}
+
+bool BluetoothGetDeviceFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ scoped_ptr<GetDevice::Params> params(GetDevice::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ BluetoothDevice* device = adapter->GetDevice(params->device_address);
+ if (device) {
+ bluetooth::Device extension_device;
+ bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);
+ SetResult(extension_device.ToValue().release());
+ SendResponse(true);
+ } else {
+ SetError(kInvalidDevice);
+ SendResponse(false);
+ }
+
+ return false;
+}
+
+void BluetoothStartDiscoveryFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothStartDiscoveryFunction::OnErrorCallback() {
+ SetError(kStartDiscoveryFailed);
+ SendResponse(false);
+}
+
+bool BluetoothStartDiscoveryFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ GetEventRouter(browser_context())->StartDiscoverySession(
+ adapter.get(),
+ extension_id(),
+ base::Bind(&BluetoothStartDiscoveryFunction::OnSuccessCallback, this),
+ base::Bind(&BluetoothStartDiscoveryFunction::OnErrorCallback, this));
+
+ return true;
+}
+
+void BluetoothStopDiscoveryFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothStopDiscoveryFunction::OnErrorCallback() {
+ SetError(kStopDiscoveryFailed);
+ SendResponse(false);
+}
+
+bool BluetoothStopDiscoveryFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ GetEventRouter(browser_context())->StopDiscoverySession(
+ adapter.get(),
+ extension_id(),
+ base::Bind(&BluetoothStopDiscoveryFunction::OnSuccessCallback, this),
+ base::Bind(&BluetoothStopDiscoveryFunction::OnErrorCallback, this));
+
+ return true;
+}
+
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_api.h b/extensions/browser/api/bluetooth/bluetooth_api.h
new file mode 100644
index 0000000..b395eb5
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_api.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_extension_function.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/bluetooth.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+class BluetoothAdapter;
+}
+
+namespace extensions {
+
+class BluetoothEventRouter;
+
+// The profile-keyed service that manages the bluetooth extension API.
+// All methods of this class must be called on the UI thread.
+// TODO(rpaquay): Rename this and move to separate file.
+class BluetoothAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ // Convenience method to get the BluetoothAPI for a browser context.
+ static BluetoothAPI* Get(content::BrowserContext* context);
+
+ static BrowserContextKeyedAPIFactory<BluetoothAPI>* GetFactoryInstance();
+
+ explicit BluetoothAPI(content::BrowserContext* context);
+ virtual ~BluetoothAPI();
+
+ BluetoothEventRouter* event_router();
+
+ // KeyedService implementation.
+ virtual void Shutdown() OVERRIDE;
+
+ // EventRouter::Observer implementation.
+ virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE;
+ virtual void OnListenerRemoved(const EventListenerInfo& details) OVERRIDE;
+
+ private:
+ // BrowserContextKeyedAPI implementation.
+ friend class BrowserContextKeyedAPIFactory<BluetoothAPI>;
+ static const char* service_name() { return "BluetoothAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ content::BrowserContext* browser_context_;
+
+ // Created lazily on first access.
+ scoped_ptr<BluetoothEventRouter> event_router_;
+};
+
+namespace core_api {
+
+class BluetoothGetAdapterStateFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.getAdapterState",
+ BLUETOOTH_GETADAPTERSTATE)
+
+ protected:
+ virtual ~BluetoothGetAdapterStateFunction();
+
+ // BluetoothExtensionFunction:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+};
+
+class BluetoothGetDevicesFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.getDevices", BLUETOOTH_GETDEVICES)
+
+ protected:
+ virtual ~BluetoothGetDevicesFunction();
+
+ // BluetoothExtensionFunction:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+};
+
+class BluetoothGetDeviceFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.getDevice", BLUETOOTH_GETDEVICE)
+
+ // BluetoothExtensionFunction:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+
+ protected:
+ virtual ~BluetoothGetDeviceFunction();
+};
+
+class BluetoothStartDiscoveryFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.startDiscovery",
+ BLUETOOTH_STARTDISCOVERY)
+
+ protected:
+ virtual ~BluetoothStartDiscoveryFunction() {}
+
+ // BluetoothExtensionFunction:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+
+ private:
+ void OnSuccessCallback();
+ void OnErrorCallback();
+};
+
+class BluetoothStopDiscoveryFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.stopDiscovery", BLUETOOTH_STOPDISCOVERY)
+
+ protected:
+ virtual ~BluetoothStopDiscoveryFunction() {}
+
+ // BluetoothExtensionFunction:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+
+ private:
+ void OnSuccessCallback();
+ void OnErrorCallback();
+};
+
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_H_
diff --git a/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc b/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc
new file mode 100644
index 0000000..7ffbe63
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/browser_context.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace extensions {
+
+namespace bt_private = core_api::bluetooth_private;
+
+namespace {
+
+void PopulatePairingEvent(const device::BluetoothDevice* device,
+ bt_private::PairingEventType type,
+ bt_private::PairingEvent* out) {
+ core_api::bluetooth::BluetoothDeviceToApiDevice(*device, &out->device);
+ out->pairing = type;
+}
+
+} // namespace
+
+BluetoothApiPairingDelegate::BluetoothApiPairingDelegate(
+ const std::string& extension_id,
+ content::BrowserContext* browser_context)
+ : extension_id_(extension_id), browser_context_(browser_context) {}
+
+BluetoothApiPairingDelegate::~BluetoothApiPairingDelegate() {}
+
+void BluetoothApiPairingDelegate::RequestPinCode(
+ device::BluetoothDevice* device) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_REQUESTPINCODE, &event);
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::RequestPasskey(
+ device::BluetoothDevice* device) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_REQUESTPASSKEY, &event);
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::DisplayPinCode(
+ device::BluetoothDevice* device,
+ const std::string& pincode) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_DISPLAYPINCODE, &event);
+ event.pincode.reset(new std::string(pincode));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::DisplayPasskey(
+ device::BluetoothDevice* device,
+ uint32 passkey) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_DISPLAYPASSKEY, &event);
+ event.passkey.reset(new int(passkey));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::KeysEntered(device::BluetoothDevice* device,
+ uint32 entered) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_KEYSENTERED, &event);
+ event.entered_key.reset(new int(entered));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::ConfirmPasskey(
+ device::BluetoothDevice* device,
+ uint32 passkey) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_CONFIRMPASSKEY, &event);
+ event.passkey.reset(new int(passkey));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::AuthorizePairing(
+ device::BluetoothDevice* device) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_REQUESTAUTHORIZATION, &event);
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::DispatchPairingEvent(
+ const bt_private::PairingEvent& pairing_event) {
+ scoped_ptr<base::ListValue> args =
+ bt_private::OnPairing::Create(pairing_event);
+ scoped_ptr<Event> event(
+ new Event(bt_private::OnPairing::kEventName, args.Pass()));
+ EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id_, event.Pass());
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h b/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h
new file mode 100644
index 0000000..e999264
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_PAIRING_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_PAIRING_DELEGATE_H_
+
+#include <string>
+
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// A pairing delegate to dispatch incoming Bluetooth pairing events to the API
+// event router.
+class BluetoothApiPairingDelegate
+ : public device::BluetoothDevice::PairingDelegate {
+ public:
+ BluetoothApiPairingDelegate(const std::string& extension_id,
+ content::BrowserContext* browser_context);
+ virtual ~BluetoothApiPairingDelegate();
+
+ // device::PairingDelegate overrides:
+ virtual void RequestPinCode(device::BluetoothDevice* device) OVERRIDE;
+ virtual void RequestPasskey(device::BluetoothDevice* device) OVERRIDE;
+ virtual void DisplayPinCode(device::BluetoothDevice* device,
+ const std::string& pincode) OVERRIDE;
+ virtual void DisplayPasskey(device::BluetoothDevice* device,
+ uint32 passkey) OVERRIDE;
+ virtual void KeysEntered(device::BluetoothDevice* device,
+ uint32 entered) OVERRIDE;
+ virtual void ConfirmPasskey(device::BluetoothDevice* device,
+ uint32 passkey) OVERRIDE;
+ virtual void AuthorizePairing(device::BluetoothDevice* device) OVERRIDE;
+
+ private:
+ // Dispatches a pairing event to the extension.
+ void DispatchPairingEvent(
+ const core_api::bluetooth_private::PairingEvent& pairing_event);
+
+ std::string extension_id_;
+ content::BrowserContext* browser_context_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_PAIRING_DELEGATE_H_
diff --git a/extensions/browser/api/bluetooth/bluetooth_api_utils.cc b/extensions/browser/api/bluetooth/bluetooth_api_utils.cc
new file mode 100644
index 0000000..58a5d0a
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_api_utils.cc
@@ -0,0 +1,147 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/common/api/bluetooth.h"
+
+namespace bluetooth = extensions::core_api::bluetooth;
+
+using device::BluetoothDevice;
+using bluetooth::VendorIdSource;
+
+namespace {
+
+bool ConvertVendorIDSourceToApi(const BluetoothDevice::VendorIDSource& input,
+ bluetooth::VendorIdSource* output) {
+ switch (input) {
+ case BluetoothDevice::VENDOR_ID_UNKNOWN:
+ *output = bluetooth::VENDOR_ID_SOURCE_NONE;
+ return true;
+ case BluetoothDevice::VENDOR_ID_BLUETOOTH:
+ *output = bluetooth::VENDOR_ID_SOURCE_BLUETOOTH;
+ return true;
+ case BluetoothDevice::VENDOR_ID_USB:
+ *output = bluetooth::VENDOR_ID_SOURCE_USB;
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool ConvertDeviceTypeToApi(const BluetoothDevice::DeviceType& input,
+ bluetooth::DeviceType* output) {
+ switch (input) {
+ case BluetoothDevice::DEVICE_UNKNOWN:
+ *output = bluetooth::DEVICE_TYPE_NONE;
+ return true;
+ case BluetoothDevice::DEVICE_COMPUTER:
+ *output = bluetooth::DEVICE_TYPE_COMPUTER;
+ return true;
+ case BluetoothDevice::DEVICE_PHONE:
+ *output = bluetooth::DEVICE_TYPE_PHONE;
+ return true;
+ case BluetoothDevice::DEVICE_MODEM:
+ *output = bluetooth::DEVICE_TYPE_MODEM;
+ return true;
+ case BluetoothDevice::DEVICE_AUDIO:
+ *output = bluetooth::DEVICE_TYPE_AUDIO;
+ return true;
+ case BluetoothDevice::DEVICE_CAR_AUDIO:
+ *output = bluetooth::DEVICE_TYPE_CARAUDIO;
+ return true;
+ case BluetoothDevice::DEVICE_VIDEO:
+ *output = bluetooth::DEVICE_TYPE_VIDEO;
+ return true;
+ case BluetoothDevice::DEVICE_PERIPHERAL:
+ *output = bluetooth::DEVICE_TYPE_PERIPHERAL;
+ return true;
+ case BluetoothDevice::DEVICE_JOYSTICK:
+ *output = bluetooth::DEVICE_TYPE_JOYSTICK;
+ return true;
+ case BluetoothDevice::DEVICE_GAMEPAD:
+ *output = bluetooth::DEVICE_TYPE_GAMEPAD;
+ return true;
+ case BluetoothDevice::DEVICE_KEYBOARD:
+ *output = bluetooth::DEVICE_TYPE_KEYBOARD;
+ return true;
+ case BluetoothDevice::DEVICE_MOUSE:
+ *output = bluetooth::DEVICE_TYPE_MOUSE;
+ return true;
+ case BluetoothDevice::DEVICE_TABLET:
+ *output = bluetooth::DEVICE_TYPE_TABLET;
+ return true;
+ case BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO:
+ *output = bluetooth::DEVICE_TYPE_KEYBOARDMOUSECOMBO;
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace core_api {
+namespace bluetooth {
+
+void BluetoothDeviceToApiDevice(const device::BluetoothDevice& device,
+ Device* out) {
+ out->address = device.GetAddress();
+ out->name.reset(new std::string(base::UTF16ToUTF8(device.GetName())));
+ out->device_class.reset(new int(device.GetBluetoothClass()));
+
+ // Only include the Device ID members when one exists for the device, and
+ // always include all or none.
+ if (ConvertVendorIDSourceToApi(device.GetVendorIDSource(),
+ &(out->vendor_id_source)) &&
+ out->vendor_id_source != VENDOR_ID_SOURCE_NONE) {
+ out->vendor_id.reset(new int(device.GetVendorID()));
+ out->product_id.reset(new int(device.GetProductID()));
+ out->device_id.reset(new int(device.GetDeviceID()));
+ }
+
+ ConvertDeviceTypeToApi(device.GetDeviceType(), &(out->type));
+
+ out->paired.reset(new bool(device.IsPaired()));
+ out->connected.reset(new bool(device.IsConnected()));
+
+ int rssi = device.GetRSSI();
+ if (rssi != BluetoothDevice::kUnknownPower)
+ out->rssi.reset(new int(rssi));
+
+ if (*out->connected) {
+ int current_transmit_power = device.GetCurrentHostTransmitPower();
+ if (current_transmit_power != BluetoothDevice::kUnknownPower)
+ out->current_host_transmit_power.reset(new int(current_transmit_power));
+ int maximum_transmit_power = device.GetMaximumHostTransmitPower();
+ if (maximum_transmit_power != BluetoothDevice::kUnknownPower)
+ out->maximum_host_transmit_power.reset(new int(maximum_transmit_power));
+ }
+
+ std::vector<std::string>* string_uuids = new std::vector<std::string>();
+ const device::BluetoothDevice::UUIDList& uuids = device.GetUUIDs();
+ for (device::BluetoothDevice::UUIDList::const_iterator iter = uuids.begin();
+ iter != uuids.end(); ++iter)
+ string_uuids->push_back(iter->canonical_value());
+ out->uuids.reset(string_uuids);
+}
+
+void PopulateAdapterState(const device::BluetoothAdapter& adapter,
+ AdapterState* out) {
+ out->discovering = adapter.IsDiscovering();
+ out->available = adapter.IsPresent();
+ out->powered = adapter.IsPowered();
+ out->name = adapter.GetName();
+ out->address = adapter.GetAddress();
+}
+
+} // namespace bluetooth
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_api_utils.h b/extensions/browser/api/bluetooth/bluetooth_api_utils.h
new file mode 100644
index 0000000..082244f9
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_api_utils.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_UTILS_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_UTILS_H_
+
+#include "base/values.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/common/api/bluetooth.h"
+
+namespace extensions {
+namespace core_api {
+namespace bluetooth {
+
+// Fill in a Device object from a BluetoothDevice.
+void BluetoothDeviceToApiDevice(
+ const device::BluetoothDevice& device,
+ Device* out);
+
+// Fill in an AdapterState object from a BluetoothAdapter.
+void PopulateAdapterState(const device::BluetoothAdapter& adapter,
+ AdapterState* out);
+
+} // namespace bluetooth
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_UTILS_H_
diff --git a/extensions/browser/api/bluetooth/bluetooth_apitest.cc b/extensions/browser/api/bluetooth/bluetooth_apitest.cc
new file mode 100644
index 0000000..b62982f
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_apitest.cc
@@ -0,0 +1,459 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_discovery_session.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::BluetoothAdapter;
+using device::BluetoothDevice;
+using device::BluetoothDiscoverySession;
+using device::BluetoothUUID;
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using device::MockBluetoothDiscoverySession;
+using extensions::Extension;
+
+namespace utils = extension_function_test_utils;
+namespace api = extensions::core_api;
+
+namespace {
+
+static const char* kAdapterAddress = "A1:A2:A3:A4:A5:A6";
+static const char* kName = "whatsinaname";
+
+class BluetoothApiTest : public ExtensionApiTest {
+ public:
+ BluetoothApiTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionApiTest::SetUpOnMainThread();
+ empty_extension_ = utils::CreateEmptyExtension();
+ SetUpMockAdapter();
+ }
+
+ virtual void TearDownOnMainThread() OVERRIDE {
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
+ }
+
+ void SetUpMockAdapter() {
+ // The browser will clean this up when it is torn down
+ mock_adapter_ = new testing::StrictMock<MockBluetoothAdapter>();
+ event_router()->SetAdapterForTest(mock_adapter_);
+
+ device1_.reset(new testing::NiceMock<MockBluetoothDevice>(
+ mock_adapter_, 0, "d1", "11:12:13:14:15:16",
+ true /* paired */, true /* connected */));
+ device2_.reset(new testing::NiceMock<MockBluetoothDevice>(
+ mock_adapter_, 0, "d2", "21:22:23:24:25:26",
+ false /* paired */, false /* connected */));
+ device3_.reset(new testing::NiceMock<MockBluetoothDevice>(
+ mock_adapter_, 0, "d3", "31:32:33:34:35:36",
+ false /* paired */, false /* connected */));
+ }
+
+ void DiscoverySessionCallback(
+ const BluetoothAdapter::DiscoverySessionCallback& callback,
+ const BluetoothAdapter::ErrorCallback& error_callback) {
+ if (mock_session_.get()) {
+ callback.Run(
+ scoped_ptr<BluetoothDiscoverySession>(mock_session_.release()));
+ return;
+ }
+ error_callback.Run();
+ }
+
+ template <class T>
+ T* setupFunction(T* function) {
+ function->set_extension(empty_extension_.get());
+ function->set_has_callback(true);
+ return function;
+ }
+
+ protected:
+ testing::StrictMock<MockBluetoothAdapter>* mock_adapter_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDiscoverySession> > mock_session_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device2_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device3_;
+
+ extensions::BluetoothEventRouter* event_router() {
+ return bluetooth_api()->event_router();
+ }
+
+ extensions::BluetoothAPI* bluetooth_api() {
+ return extensions::BluetoothAPI::Get(browser()->profile());
+ }
+
+ private:
+ scoped_refptr<Extension> empty_extension_;
+};
+
+static void StopDiscoverySessionCallback(const base::Closure& callback,
+ const base::Closure& error_callback) {
+ callback.Run();
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetAdapterState) {
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(false));
+
+ scoped_refptr<api::BluetoothGetAdapterStateFunction> get_adapter_state;
+ get_adapter_state = setupFunction(new api::BluetoothGetAdapterStateFunction);
+
+ scoped_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult(
+ get_adapter_state.get(), "[]", browser()));
+ ASSERT_TRUE(result.get() != NULL);
+ api::bluetooth::AdapterState state;
+ ASSERT_TRUE(api::bluetooth::AdapterState::Populate(*result, &state));
+
+ EXPECT_FALSE(state.available);
+ EXPECT_TRUE(state.powered);
+ EXPECT_FALSE(state.discovering);
+ EXPECT_EQ(kName, state.name);
+ EXPECT_EQ(kAdapterAddress, state.address);
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DeviceEvents) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/device_events")));
+
+ ExtensionTestMessageListener events_received("ready", true);
+ event_router()->DeviceAdded(mock_adapter_, device1_.get());
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+
+ EXPECT_CALL(*device2_.get(), GetDeviceName())
+ .WillRepeatedly(testing::Return("the real d2"));
+ EXPECT_CALL(*device2_.get(), GetName())
+ .WillRepeatedly(testing::Return(base::UTF8ToUTF16("the real d2")));
+ event_router()->DeviceChanged(mock_adapter_, device2_.get());
+
+ event_router()->DeviceAdded(mock_adapter_, device3_.get());
+ event_router()->DeviceRemoved(mock_adapter_, device1_.get());
+ EXPECT_TRUE(events_received.WaitUntilSatisfied());
+ events_received.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, Discovery) {
+ // Try with a failure to start. This will return an error as we haven't
+ // initialied a session object.
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+
+ // StartDiscovery failure will not reference the adapter.
+ scoped_refptr<api::BluetoothStartDiscoveryFunction> start_function;
+ start_function = setupFunction(new api::BluetoothStartDiscoveryFunction);
+ std::string error(
+ utils::RunFunctionAndReturnError(start_function.get(), "[]", browser()));
+ ASSERT_FALSE(error.empty());
+
+ // Reset the adapter and initiate a discovery session. The ownership of the
+ // mock session will be passed to the event router.
+ ASSERT_FALSE(mock_session_.get());
+ SetUpMockAdapter();
+
+ // Create a mock session to be returned as a result. Get a handle to it as
+ // its ownership will be passed and |mock_session_| will be reset.
+ mock_session_.reset(new testing::NiceMock<MockBluetoothDiscoverySession>());
+ MockBluetoothDiscoverySession* session = mock_session_.get();
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+ start_function = setupFunction(new api::BluetoothStartDiscoveryFunction);
+ (void)
+ utils::RunFunctionAndReturnError(start_function.get(), "[]", browser());
+
+ // End the discovery session. The StopDiscovery function should succeed.
+ testing::Mock::VerifyAndClearExpectations(mock_adapter_);
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*session, Stop(testing::_, testing::_))
+ .WillOnce(testing::Invoke(StopDiscoverySessionCallback));
+
+ // StopDiscovery success will remove the session object, unreferencing the
+ // adapter.
+ scoped_refptr<api::BluetoothStopDiscoveryFunction> stop_function;
+ stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction);
+ (void) utils::RunFunctionAndReturnSingleResult(
+ stop_function.get(), "[]", browser());
+
+ // Reset the adapter. Simulate failure for stop discovery. The event router
+ // still owns the session. Make it appear inactive.
+ SetUpMockAdapter();
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(false));
+ stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction);
+ error =
+ utils::RunFunctionAndReturnError(stop_function.get(), "[]", browser());
+ ASSERT_FALSE(error.empty());
+ SetUpMockAdapter();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DiscoveryCallback) {
+ mock_session_.reset(new testing::NiceMock<MockBluetoothDiscoverySession>());
+ MockBluetoothDiscoverySession* session = mock_session_.get();
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*session, Stop(testing::_, testing::_))
+ .WillOnce(testing::Invoke(StopDiscoverySessionCallback));
+
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ ExtensionTestMessageListener discovery_started("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/discovery_callback")));
+ EXPECT_TRUE(discovery_started.WaitUntilSatisfied());
+
+ event_router()->DeviceAdded(mock_adapter_, device1_.get());
+
+ discovery_started.Reply("go");
+ ExtensionTestMessageListener discovery_stopped("ready", true);
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
+ EXPECT_TRUE(discovery_stopped.WaitUntilSatisfied());
+
+ SetUpMockAdapter();
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+ discovery_stopped.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DiscoveryInProgress) {
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+
+ // Fake that the adapter is discovering
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(true));
+ event_router()->AdapterDiscoveringChanged(mock_adapter_, true);
+
+ // Cache a device before the extension starts discovering
+ event_router()->DeviceAdded(mock_adapter_, device1_.get());
+
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ mock_session_.reset(new testing::NiceMock<MockBluetoothDiscoverySession>());
+ MockBluetoothDiscoverySession* session = mock_session_.get();
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*session, Stop(testing::_, testing::_))
+ .WillOnce(testing::Invoke(StopDiscoverySessionCallback));
+
+ ExtensionTestMessageListener discovery_started("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/discovery_in_progress")));
+ EXPECT_TRUE(discovery_started.WaitUntilSatisfied());
+
+ // Only this should be received. No additional notification should be sent for
+ // devices discovered before the discovery session started.
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+
+ discovery_started.Reply("go");
+ ExtensionTestMessageListener discovery_stopped("ready", true);
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
+ EXPECT_TRUE(discovery_stopped.WaitUntilSatisfied());
+
+ SetUpMockAdapter();
+ // This should never be received.
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+ discovery_stopped.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, OnAdapterStateChanged) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/on_adapter_state_changed")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(false));
+ event_router()->AdapterPoweredChanged(mock_adapter_, false);
+
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(true));
+ event_router()->AdapterPresentChanged(mock_adapter_, true);
+
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(true));
+ event_router()->AdapterDiscoveringChanged(mock_adapter_, true);
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetDevices) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ BluetoothAdapter::ConstDeviceList devices;
+ devices.push_back(device1_.get());
+ devices.push_back(device2_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevices())
+ .Times(1)
+ .WillRepeatedly(testing::Return(devices));
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth/get_devices")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetDevice) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(device1_->GetAddress()))
+ .WillOnce(testing::Return(device1_.get()));
+ EXPECT_CALL(*mock_adapter_, GetDevice(device2_->GetAddress()))
+ .Times(1)
+ .WillRepeatedly(testing::Return(static_cast<BluetoothDevice*>(NULL)));
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth/get_device")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DeviceInfo) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Set up the first device object to reflect a real-world device.
+ BluetoothAdapter::ConstDeviceList devices;
+
+ EXPECT_CALL(*device1_.get(), GetAddress())
+ .WillRepeatedly(testing::Return("A4:17:31:00:00:00"));
+ EXPECT_CALL(*device1_.get(), GetDeviceName())
+ .WillRepeatedly(testing::Return("Chromebook Pixel"));
+ EXPECT_CALL(*device1_.get(), GetName())
+ .WillRepeatedly(testing::Return(base::UTF8ToUTF16("Chromebook Pixel")));
+ EXPECT_CALL(*device1_.get(), GetBluetoothClass())
+ .WillRepeatedly(testing::Return(0x080104));
+ EXPECT_CALL(*device1_.get(), GetDeviceType())
+ .WillRepeatedly(testing::Return(BluetoothDevice::DEVICE_COMPUTER));
+ EXPECT_CALL(*device1_.get(), GetVendorIDSource())
+ .WillRepeatedly(testing::Return(BluetoothDevice::VENDOR_ID_BLUETOOTH));
+ EXPECT_CALL(*device1_.get(), GetVendorID())
+ .WillRepeatedly(testing::Return(0x00E0));
+ EXPECT_CALL(*device1_.get(), GetProductID())
+ .WillRepeatedly(testing::Return(0x240A));
+ EXPECT_CALL(*device1_.get(), GetDeviceID())
+ .WillRepeatedly(testing::Return(0x0400));
+ EXPECT_CALL(*device1_, GetRSSI()).WillRepeatedly(testing::Return(-42));
+ EXPECT_CALL(*device1_, GetCurrentHostTransmitPower())
+ .WillRepeatedly(testing::Return(-16));
+ EXPECT_CALL(*device1_, GetMaximumHostTransmitPower())
+ .WillRepeatedly(testing::Return(10));
+
+ BluetoothDevice::UUIDList uuids;
+ uuids.push_back(BluetoothUUID("1105"));
+ uuids.push_back(BluetoothUUID("1106"));
+
+ EXPECT_CALL(*device1_.get(), GetUUIDs())
+ .WillOnce(testing::Return(uuids));
+
+ devices.push_back(device1_.get());
+
+ // Leave the second largely empty so we can check a device without
+ // available information.
+ devices.push_back(device2_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevices())
+ .Times(1)
+ .WillRepeatedly(testing::Return(devices));
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth/device_info")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/extensions/browser/api/bluetooth/bluetooth_event_router.cc b/extensions/browser/api/bluetooth/bluetooth_event_router.cc
new file mode 100644
index 0000000..1064d0b
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_event_router.cc
@@ -0,0 +1,367 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_discovery_session.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+#include "extensions/browser/api/bluetooth/bluetooth_private_api.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/api/bluetooth.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace extensions {
+
+namespace bluetooth = core_api::bluetooth;
+namespace bt_private = core_api::bluetooth_private;
+
+BluetoothEventRouter::BluetoothEventRouter(content::BrowserContext* context)
+ : browser_context_(context),
+ adapter_(NULL),
+ num_event_listeners_(0),
+ extension_registry_observer_(this),
+ weak_ptr_factory_(this) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK(browser_context_);
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<content::BrowserContext>(browser_context_));
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+}
+
+BluetoothEventRouter::~BluetoothEventRouter() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter_.get()) {
+ adapter_->RemoveObserver(this);
+ adapter_ = NULL;
+ }
+ CleanUpAllExtensions();
+}
+
+bool BluetoothEventRouter::IsBluetoothSupported() const {
+ return adapter_.get() ||
+ device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable();
+}
+
+void BluetoothEventRouter::GetAdapter(
+ const device::BluetoothAdapterFactory::AdapterCallback& callback) {
+ if (adapter_.get()) {
+ callback.Run(scoped_refptr<device::BluetoothAdapter>(adapter_));
+ return;
+ }
+
+ device::BluetoothAdapterFactory::GetAdapter(callback);
+}
+
+void BluetoothEventRouter::StartDiscoverySession(
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback) {
+ if (adapter != adapter_.get()) {
+ error_callback.Run();
+ return;
+ }
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter != discovery_session_map_.end() && iter->second->IsActive()) {
+ DVLOG(1) << "An active discovery session exists for extension.";
+ error_callback.Run();
+ return;
+ }
+ adapter->StartDiscoverySession(
+ base::Bind(&BluetoothEventRouter::OnStartDiscoverySession,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ callback),
+ error_callback);
+}
+
+void BluetoothEventRouter::StopDiscoverySession(
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback) {
+ if (adapter != adapter_.get()) {
+ error_callback.Run();
+ return;
+ }
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter == discovery_session_map_.end() || !iter->second->IsActive()) {
+ DVLOG(1) << "No active discovery session exists for extension.";
+ error_callback.Run();
+ return;
+ }
+ device::BluetoothDiscoverySession* session = iter->second;
+ session->Stop(callback, error_callback);
+}
+
+BluetoothApiPairingDelegate* BluetoothEventRouter::GetPairingDelegate(
+ const std::string& extension_id) {
+ return ContainsKey(pairing_delegate_map_, extension_id)
+ ? pairing_delegate_map_[extension_id]
+ : NULL;
+}
+
+void BluetoothEventRouter::OnAdapterInitialized(
+ const base::Closure& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ if (!adapter_.get()) {
+ adapter_ = adapter;
+ adapter_->AddObserver(this);
+ }
+
+ callback.Run();
+}
+
+void BluetoothEventRouter::MaybeReleaseAdapter() {
+ if (adapter_.get() && num_event_listeners_ == 0 &&
+ pairing_delegate_map_.empty()) {
+ adapter_->RemoveObserver(this);
+ adapter_ = NULL;
+ }
+}
+
+void BluetoothEventRouter::AddPairingDelegate(const std::string& extension_id) {
+ if (!adapter_.get()) {
+ base::Closure self_callback =
+ base::Bind(&BluetoothEventRouter::AddPairingDelegate,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id);
+ GetAdapter(base::Bind(&BluetoothEventRouter::OnAdapterInitialized,
+ weak_ptr_factory_.GetWeakPtr(),
+ self_callback));
+ return;
+ }
+
+ if (!ContainsKey(pairing_delegate_map_, extension_id)) {
+ BluetoothApiPairingDelegate* delegate =
+ new BluetoothApiPairingDelegate(extension_id, browser_context_);
+ DCHECK(adapter_.get());
+ adapter_->AddPairingDelegate(
+ delegate, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH);
+ pairing_delegate_map_[extension_id] = delegate;
+ } else {
+ LOG(ERROR) << "Pairing delegate already exists for extension. "
+ << "There should be at most one onPairing listener.";
+ NOTREACHED();
+ }
+}
+
+void BluetoothEventRouter::RemovePairingDelegate(
+ const std::string& extension_id) {
+ if (ContainsKey(pairing_delegate_map_, extension_id)) {
+ BluetoothApiPairingDelegate* delegate = pairing_delegate_map_[extension_id];
+ if (adapter_.get())
+ adapter_->RemovePairingDelegate(delegate);
+ pairing_delegate_map_.erase(extension_id);
+ delete delegate;
+ MaybeReleaseAdapter();
+ }
+}
+
+void BluetoothEventRouter::AdapterPresentChanged(
+ device::BluetoothAdapter* adapter,
+ bool present) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+ DispatchAdapterStateEvent();
+}
+
+void BluetoothEventRouter::AdapterPoweredChanged(
+ device::BluetoothAdapter* adapter,
+ bool has_power) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+ DispatchAdapterStateEvent();
+}
+
+void BluetoothEventRouter::AdapterDiscoveringChanged(
+ device::BluetoothAdapter* adapter,
+ bool discovering) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ if (!discovering) {
+ // If any discovery sessions are inactive, clean them up.
+ DiscoverySessionMap active_session_map;
+ for (DiscoverySessionMap::iterator iter = discovery_session_map_.begin();
+ iter != discovery_session_map_.end();
+ ++iter) {
+ device::BluetoothDiscoverySession* session = iter->second;
+ if (session->IsActive()) {
+ active_session_map[iter->first] = session;
+ continue;
+ }
+ delete session;
+ }
+ discovery_session_map_.swap(active_session_map);
+ MaybeReleaseAdapter();
+ }
+
+ DispatchAdapterStateEvent();
+}
+
+void BluetoothEventRouter::DeviceAdded(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ DispatchDeviceEvent(bluetooth::OnDeviceAdded::kEventName, device);
+}
+
+void BluetoothEventRouter::DeviceChanged(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ DispatchDeviceEvent(bluetooth::OnDeviceChanged::kEventName, device);
+}
+
+void BluetoothEventRouter::DeviceRemoved(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ DispatchDeviceEvent(bluetooth::OnDeviceRemoved::kEventName, device);
+}
+
+void BluetoothEventRouter::OnListenerAdded() {
+ num_event_listeners_++;
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (!adapter_.get()) {
+ GetAdapter(base::Bind(&BluetoothEventRouter::OnAdapterInitialized,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Bind(&base::DoNothing)));
+ }
+}
+
+void BluetoothEventRouter::OnListenerRemoved() {
+ if (num_event_listeners_ > 0)
+ num_event_listeners_--;
+ MaybeReleaseAdapter();
+}
+
+void BluetoothEventRouter::DispatchAdapterStateEvent() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ core_api::bluetooth::AdapterState state;
+ PopulateAdapterState(*adapter_.get(), &state);
+
+ scoped_ptr<base::ListValue> args =
+ bluetooth::OnAdapterStateChanged::Create(state);
+ scoped_ptr<Event> event(new Event(
+ bluetooth::OnAdapterStateChanged::kEventName,
+ args.Pass()));
+ EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
+}
+
+void BluetoothEventRouter::DispatchDeviceEvent(
+ const std::string& event_name,
+ device::BluetoothDevice* device) {
+ bluetooth::Device extension_device;
+ bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);
+
+ scoped_ptr<base::ListValue> args =
+ bluetooth::OnDeviceAdded::Create(extension_device);
+ scoped_ptr<Event> event(new Event(event_name, args.Pass()));
+ EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
+}
+
+void BluetoothEventRouter::CleanUpForExtension(
+ const std::string& extension_id) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ RemovePairingDelegate(extension_id);
+
+ // Remove any discovery session initiated by the extension.
+ DiscoverySessionMap::iterator session_iter =
+ discovery_session_map_.find(extension_id);
+ if (session_iter == discovery_session_map_.end())
+ return;
+ delete session_iter->second;
+ discovery_session_map_.erase(session_iter);
+}
+
+void BluetoothEventRouter::CleanUpAllExtensions() {
+ for (DiscoverySessionMap::iterator it = discovery_session_map_.begin();
+ it != discovery_session_map_.end();
+ ++it) {
+ delete it->second;
+ }
+ discovery_session_map_.clear();
+
+ PairingDelegateMap::iterator pairing_iter = pairing_delegate_map_.begin();
+ while (pairing_iter != pairing_delegate_map_.end())
+ RemovePairingDelegate(pairing_iter++->first);
+}
+
+void BluetoothEventRouter::OnStartDiscoverySession(
+ const std::string& extension_id,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothDiscoverySession> discovery_session) {
+ // Clean up any existing session instance for the extension.
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter != discovery_session_map_.end())
+ delete iter->second;
+ discovery_session_map_[extension_id] = discovery_session.release();
+ callback.Run();
+}
+
+void BluetoothEventRouter::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, type);
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ CleanUpForExtension(host->extension_id());
+}
+
+void BluetoothEventRouter::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ CleanUpForExtension(extension->id());
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_event_router.h b/extensions/browser/api/bluetooth/bluetooth_event_router.h
new file mode 100644
index 0000000..e0fdf9a
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_event_router.h
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EVENT_ROUTER_H_
+
+#include <map>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/bluetooth.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+
+class BluetoothDevice;
+class BluetoothDiscoverySession;
+
+} // namespace device
+
+namespace extensions {
+class BluetoothApiPairingDelegate;
+class ExtensionRegistry;
+
+class BluetoothEventRouter : public device::BluetoothAdapter::Observer,
+ public content::NotificationObserver,
+ public ExtensionRegistryObserver {
+ public:
+ explicit BluetoothEventRouter(content::BrowserContext* context);
+ virtual ~BluetoothEventRouter();
+
+ // Returns true if adapter_ has been initialized for testing or bluetooth
+ // adapter is available for the current platform.
+ bool IsBluetoothSupported() const;
+
+ void GetAdapter(
+ const device::BluetoothAdapterFactory::AdapterCallback& callback);
+
+ // Requests that a new device discovery session be initiated for extension
+ // with id |extension_id|. |callback| is called, if a session has been
+ // initiated. |error_callback| is called, if the adapter failed to initiate
+ // the session or if an active session already exists for the extension.
+ void StartDiscoverySession(device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback);
+
+ // Requests that the active discovery session that belongs to the extension
+ // with id |extension_id| be terminated. |callback| is called, if the session
+ // successfully ended. |error_callback| is called, if the adapter failed to
+ // terminate the session or if no active discovery session exists for the
+ // extension.
+ void StopDiscoverySession(device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback);
+
+ // Called when a bluetooth event listener is added.
+ void OnListenerAdded();
+
+ // Called when a bluetooth event listener is removed.
+ void OnListenerRemoved();
+
+ // Adds a pairing delegate for an extension.
+ void AddPairingDelegate(const std::string& extension_id);
+
+ // Removes the pairing delegate for an extension.
+ void RemovePairingDelegate(const std::string& extension_id);
+
+ // Returns the pairing delegate for an extension or NULL if it doesn't have a
+ // pairing delegate.
+ BluetoothApiPairingDelegate* GetPairingDelegate(
+ const std::string& extension_id);
+
+ // Exposed for testing.
+ void SetAdapterForTest(device::BluetoothAdapter* adapter) {
+ adapter_ = adapter;
+ }
+
+ // Override from device::BluetoothAdapter::Observer.
+ virtual void AdapterPresentChanged(device::BluetoothAdapter* adapter,
+ bool present) OVERRIDE;
+ virtual void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
+ bool has_power) OVERRIDE;
+ virtual void AdapterDiscoveringChanged(device::BluetoothAdapter* adapter,
+ bool discovering) OVERRIDE;
+ virtual void DeviceAdded(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) OVERRIDE;
+ virtual void DeviceChanged(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) OVERRIDE;
+ virtual void DeviceRemoved(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) OVERRIDE;
+
+ // Overridden from content::NotificationObserver.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Overridden from ExtensionRegistryObserver.
+ virtual void OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) OVERRIDE;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothEventRouter"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ void OnAdapterInitialized(const base::Closure& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter);
+ void MaybeReleaseAdapter();
+ void DispatchAdapterStateEvent();
+ void DispatchDeviceEvent(const std::string& event_name,
+ device::BluetoothDevice* device);
+ void CleanUpForExtension(const std::string& extension_id);
+ void CleanUpAllExtensions();
+ void OnStartDiscoverySession(
+ const std::string& extension_id,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothDiscoverySession> discovery_session);
+
+ content::BrowserContext* browser_context_;
+ scoped_refptr<device::BluetoothAdapter> adapter_;
+
+ int num_event_listeners_;
+
+ // A map that maps extension ids to BluetoothDiscoverySession pointers.
+ typedef std::map<std::string, device::BluetoothDiscoverySession*>
+ DiscoverySessionMap;
+ DiscoverySessionMap discovery_session_map_;
+
+ // Maps an extension id to its pairing delegate.
+ typedef std::map<std::string, BluetoothApiPairingDelegate*>
+ PairingDelegateMap;
+ PairingDelegateMap pairing_delegate_map_;
+
+ content::NotificationRegistrar registrar_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ base::WeakPtrFactory<BluetoothEventRouter> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothEventRouter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EVENT_ROUTER_H_
diff --git a/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc b/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc
new file mode 100644
index 0000000..4d336b7
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/bluetooth.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestExtensionId[] = "test extension id";
+const device::BluetoothUUID kAudioProfileUuid("1234");
+const device::BluetoothUUID kHealthProfileUuid("4321");
+
+} // namespace
+
+namespace extensions {
+
+namespace bluetooth = core_api::bluetooth;
+
+class BluetoothEventRouterTest : public testing::Test {
+ public:
+ BluetoothEventRouterTest()
+ : ui_thread_(content::BrowserThread::UI, &message_loop_),
+ mock_adapter_(new testing::StrictMock<device::MockBluetoothAdapter>()),
+ test_profile_(new TestingProfile()),
+ router_(new BluetoothEventRouter(test_profile_.get())) {
+ router_->SetAdapterForTest(mock_adapter_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Some profile-dependent services rely on UI thread to clean up. We make
+ // sure they are properly cleaned up by running the UI message loop until
+ // idle.
+ // It's important to destroy the router before the |test_profile_| so it
+ // removes itself as an observer.
+ router_.reset(NULL);
+ test_profile_.reset(NULL);
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoopForUI message_loop_;
+ // Note: |ui_thread_| must be declared before |router_|.
+ content::TestBrowserThread ui_thread_;
+ testing::StrictMock<device::MockBluetoothAdapter>* mock_adapter_;
+ scoped_ptr<TestingProfile> test_profile_;
+ scoped_ptr<BluetoothEventRouter> router_;
+};
+
+TEST_F(BluetoothEventRouterTest, BluetoothEventListener) {
+ router_->OnListenerAdded();
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+ router_->OnListenerRemoved();
+}
+
+TEST_F(BluetoothEventRouterTest, MultipleBluetoothEventListeners) {
+ router_->OnListenerAdded();
+ router_->OnListenerAdded();
+ router_->OnListenerAdded();
+ router_->OnListenerRemoved();
+ router_->OnListenerRemoved();
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+ router_->OnListenerRemoved();
+}
+
+TEST_F(BluetoothEventRouterTest, UnloadExtension) {
+ scoped_refptr<const extensions::Extension> extension =
+ extensions::ExtensionBuilder()
+ .SetManifest(extensions::DictionaryBuilder()
+ .Set("name", "BT event router test")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2))
+ .SetID(kTestExtensionId)
+ .Build();
+
+ ExtensionRegistry::Get(test_profile_.get())->TriggerOnUnloaded(
+ extension.get(), UnloadedExtensionInfo::REASON_DISABLE);
+
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_extension_function.cc b/extensions/browser/api/bluetooth/bluetooth_extension_function.cc
new file mode 100644
index 0000000..7a5eefe
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_extension_function.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_extension_function.h"
+
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+
+using content::BrowserThread;
+
+namespace {
+
+const char kPlatformNotSupported[] =
+ "This operation is not supported on your platform";
+
+extensions::BluetoothEventRouter* GetEventRouter(
+ content::BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return extensions::BluetoothAPI::Get(context)->event_router();
+}
+
+bool IsBluetoothSupported(content::BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return GetEventRouter(context)->IsBluetoothSupported();
+}
+
+void GetAdapter(const device::BluetoothAdapterFactory::AdapterCallback callback,
+ content::BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ GetEventRouter(context)->GetAdapter(callback);
+}
+
+} // namespace
+
+namespace extensions {
+namespace core_api {
+
+BluetoothExtensionFunction::BluetoothExtensionFunction() {
+}
+
+BluetoothExtensionFunction::~BluetoothExtensionFunction() {
+}
+
+bool BluetoothExtensionFunction::RunAsync() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!IsBluetoothSupported(browser_context())) {
+ SetError(kPlatformNotSupported);
+ return false;
+ }
+ GetAdapter(base::Bind(&BluetoothExtensionFunction::RunOnAdapterReady, this),
+ browser_context());
+
+ return true;
+}
+
+void BluetoothExtensionFunction::RunOnAdapterReady(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DoWork(adapter);
+}
+
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_extension_function.h b/extensions/browser/api/bluetooth/bluetooth_extension_function.h
new file mode 100644
index 0000000..9a1cc73
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_extension_function.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EXTENSION_FUNCTION_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EXTENSION_FUNCTION_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/extension_function.h"
+
+namespace device {
+
+class BluetoothAdapter;
+
+} // namespace device
+
+namespace extensions {
+namespace core_api {
+
+// Base class for bluetooth extension functions. This class initializes
+// bluetooth adapter and calls (on the UI thread) DoWork() implemented by
+// individual bluetooth extension functions.
+class BluetoothExtensionFunction : public AsyncExtensionFunction {
+ public:
+ BluetoothExtensionFunction();
+
+ protected:
+ virtual ~BluetoothExtensionFunction();
+
+ // ExtensionFunction:
+ virtual bool RunAsync() OVERRIDE;
+
+ private:
+ void RunOnAdapterReady(scoped_refptr<device::BluetoothAdapter> adapter);
+
+ // Implemented by individual bluetooth extension functions, called
+ // automatically on the UI thread once |adapter| has been initialized.
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothExtensionFunction);
+};
+
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EXTENSION_FUNCTION_H_
diff --git a/extensions/browser/api/bluetooth/bluetooth_private_api.cc b/extensions/browser/api/bluetooth/bluetooth_private_api.cc
new file mode 100644
index 0000000..8729f73
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_private_api.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_private_api.h"
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/strings/string_util.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace bt_private = extensions::core_api::bluetooth_private;
+
+namespace extensions {
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<BluetoothPrivateAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>*
+BluetoothPrivateAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+BluetoothPrivateAPI::BluetoothPrivateAPI(content::BrowserContext* context)
+ : browser_context_(context) {
+ EventRouter::Get(browser_context_)
+ ->RegisterObserver(this, bt_private::OnPairing::kEventName);
+}
+
+BluetoothPrivateAPI::~BluetoothPrivateAPI() {}
+
+void BluetoothPrivateAPI::Shutdown() {
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+void BluetoothPrivateAPI::OnListenerAdded(const EventListenerInfo& details) {
+ // This function can be called multiple times for the same JS listener, for
+ // example, once for the addListener call and again if it is a lazy listener.
+ if (!details.browser_context)
+ return;
+
+ BluetoothAPI::Get(browser_context_)->event_router()->AddPairingDelegate(
+ details.extension_id);
+}
+
+void BluetoothPrivateAPI::OnListenerRemoved(const EventListenerInfo& details) {
+ // This function can be called multiple times for the same JS listener, for
+ // example, once for the addListener call and again if it is a lazy listener.
+ if (!details.browser_context)
+ return;
+
+ BluetoothAPI::Get(browser_context_)->event_router()->RemovePairingDelegate(
+ details.extension_id);
+}
+
+namespace core_api {
+
+namespace {
+
+const char kNameProperty[] = "name";
+const char kPoweredProperty[] = "powered";
+const char kDiscoverableProperty[] = "discoverable";
+
+const char kSetAdapterPropertyError[] = "Error setting adapter properties: $1";
+
+const char kDeviceNotFoundError[] =
+ "Given address is not a valid Bluetooth device.";
+
+const char kPairingNotEnabled[] =
+ "Pairing must be enabled to set a pairing response.";
+
+const char kInvalidPairingResponseOptions[] =
+ "Invalid pairing response options";
+
+const char kAdapterNotPresent[] =
+ "Could not find a Bluetooth adapter.";
+
+// Returns true if the pairing response options passed into the
+// setPairingResponse function are valid.
+bool ValidatePairingResponseOptions(
+ const device::BluetoothDevice* device,
+ const bt_private::SetPairingResponseOptions& options) {
+ bool response = options.response != bt_private::PAIRING_RESPONSE_NONE;
+ bool pincode = options.pincode.get() != NULL;
+ bool passkey = options.passkey.get() != NULL;
+
+ if (!response && !pincode && !passkey)
+ return false;
+ if (pincode && passkey)
+ return false;
+ if (options.response != bt_private::PAIRING_RESPONSE_CONFIRM &&
+ (pincode || passkey))
+ return false;
+
+ // Check the BluetoothDevice is in expecting the correct response.
+ if (!device->ExpectingConfirmation() && !device->ExpectingPinCode() &&
+ !device->ExpectingPasskey())
+ return false;
+ if (pincode && !device->ExpectingPinCode())
+ return false;
+ if (passkey && !device->ExpectingPasskey())
+ return false;
+ if (options.response == bt_private::PAIRING_RESPONSE_CONFIRM && !pincode &&
+ !passkey && !device->ExpectingConfirmation())
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+BluetoothPrivateSetAdapterStateFunction::
+ BluetoothPrivateSetAdapterStateFunction() {}
+
+BluetoothPrivateSetAdapterStateFunction::
+ ~BluetoothPrivateSetAdapterStateFunction() {}
+
+bool BluetoothPrivateSetAdapterStateFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::SetAdapterState::Params> params(
+ bt_private::SetAdapterState::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (!adapter->IsPresent()) {
+ SetError(kAdapterNotPresent);
+ SendResponse(false);
+ return true;
+ }
+
+ const bt_private::NewAdapterState& new_state = params->adapter_state;
+
+ // These properties are not owned.
+ std::string* name = new_state.name.get();
+ bool* powered = new_state.powered.get();
+ bool* discoverable = new_state.discoverable.get();
+
+ if (name && adapter->GetName() != *name) {
+ pending_properties_.insert(kNameProperty);
+ adapter->SetName(*name,
+ CreatePropertySetCallback(kNameProperty),
+ CreatePropertyErrorCallback(kNameProperty));
+ }
+
+ if (powered && adapter->IsPowered() != *powered) {
+ pending_properties_.insert(kPoweredProperty);
+ adapter->SetPowered(*powered,
+ CreatePropertySetCallback(kPoweredProperty),
+ CreatePropertyErrorCallback(kPoweredProperty));
+ }
+
+ if (discoverable && adapter->IsDiscoverable() != *discoverable) {
+ pending_properties_.insert(kDiscoverableProperty);
+ adapter->SetDiscoverable(
+ *discoverable,
+ CreatePropertySetCallback(kDiscoverableProperty),
+ CreatePropertyErrorCallback(kDiscoverableProperty));
+ }
+
+ if (pending_properties_.empty())
+ SendResponse(true);
+ return true;
+}
+
+base::Closure
+BluetoothPrivateSetAdapterStateFunction::CreatePropertySetCallback(
+ const std::string& property_name) {
+ return base::Bind(
+ &BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertySet,
+ this,
+ property_name);
+}
+
+base::Closure
+BluetoothPrivateSetAdapterStateFunction::CreatePropertyErrorCallback(
+ const std::string& property_name) {
+ return base::Bind(
+ &BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertyError,
+ this,
+ property_name);
+}
+
+void BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertySet(
+ const std::string& property) {
+ DCHECK(pending_properties_.find(property) != pending_properties_.end());
+ DCHECK(failed_properties_.find(property) == failed_properties_.end());
+
+ pending_properties_.erase(property);
+ if (pending_properties_.empty()) {
+ if (failed_properties_.empty())
+ SendResponse(true);
+ else
+ SendError();
+ }
+}
+
+void BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertyError(
+ const std::string& property) {
+ DCHECK(pending_properties_.find(property) != pending_properties_.end());
+ DCHECK(failed_properties_.find(property) == failed_properties_.end());
+
+ pending_properties_.erase(property);
+ failed_properties_.insert(property);
+ if (pending_properties_.empty())
+ SendError();
+}
+
+void BluetoothPrivateSetAdapterStateFunction::SendError() {
+ DCHECK(pending_properties_.empty());
+ DCHECK(!failed_properties_.empty());
+
+ std::vector<std::string> failed_vector;
+ std::copy(failed_properties_.begin(),
+ failed_properties_.end(),
+ std::back_inserter(failed_vector));
+
+ std::vector<std::string> replacements(1);
+ replacements[0] = JoinString(failed_vector, ", ");
+ std::string error =
+ ReplaceStringPlaceholders(kSetAdapterPropertyError, replacements, NULL);
+ SetError(error);
+ SendResponse(false);
+}
+
+BluetoothPrivateSetPairingResponseFunction::
+ BluetoothPrivateSetPairingResponseFunction() {}
+
+BluetoothPrivateSetPairingResponseFunction::
+ ~BluetoothPrivateSetPairingResponseFunction() {}
+
+bool BluetoothPrivateSetPairingResponseFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::SetPairingResponse::Params> params(
+ bt_private::SetPairingResponse::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const bt_private::SetPairingResponseOptions& options = params->options;
+
+ BluetoothEventRouter* router =
+ BluetoothAPI::Get(browser_context())->event_router();
+ if (!router->GetPairingDelegate(extension_id())) {
+ SetError(kPairingNotEnabled);
+ SendResponse(false);
+ return true;
+ }
+
+ const std::string& device_address = options.device.address;
+ device::BluetoothDevice* device = adapter->GetDevice(device_address);
+ if (!device) {
+ SetError(kDeviceNotFoundError);
+ SendResponse(false);
+ return true;
+ }
+
+ if (!ValidatePairingResponseOptions(device, options)) {
+ SetError(kInvalidPairingResponseOptions);
+ SendResponse(false);
+ return true;
+ }
+
+ if (options.pincode.get()) {
+ device->SetPinCode(*options.pincode.get());
+ } else if (options.passkey.get()) {
+ device->SetPasskey(*options.passkey.get());
+ } else {
+ switch (options.response) {
+ case bt_private::PAIRING_RESPONSE_CONFIRM:
+ device->ConfirmPairing();
+ break;
+ case bt_private::PAIRING_RESPONSE_REJECT:
+ device->RejectPairing();
+ break;
+ case bt_private::PAIRING_RESPONSE_CANCEL:
+ device->CancelPairing();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ SendResponse(true);
+ return true;
+}
+
+} // namespace core_api
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_private_api.h b/extensions/browser/api/bluetooth/bluetooth_private_api.h
new file mode 100644
index 0000000..0520861
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_private_api.h
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_PRIVATE_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_PRIVATE_API_H_
+
+#include "base/callback_forward.h"
+#include "extensions/browser/api/bluetooth/bluetooth_extension_function.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+
+namespace device {
+class BluetoothAdapter;
+}
+
+namespace extensions {
+
+class BluetoothApiPairingDelegate;
+
+// The profile-keyed service that manages the bluetoothPrivate extension API.
+class BluetoothPrivateAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ static BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>*
+ GetFactoryInstance();
+
+ explicit BluetoothPrivateAPI(content::BrowserContext* context);
+ virtual ~BluetoothPrivateAPI();
+
+ // KeyedService implementation.
+ virtual void Shutdown() OVERRIDE;
+
+ // EventRouter::Observer implementation.
+ virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE;
+ virtual void OnListenerRemoved(const EventListenerInfo& details) OVERRIDE;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothPrivateAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>;
+
+ content::BrowserContext* browser_context_;
+};
+
+namespace core_api {
+
+class BluetoothPrivateSetAdapterStateFunction
+ : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.setAdapterState",
+ BLUETOOTHPRIVATE_SETADAPTERSTATE)
+ BluetoothPrivateSetAdapterStateFunction();
+
+ private:
+ virtual ~BluetoothPrivateSetAdapterStateFunction();
+
+ base::Closure CreatePropertySetCallback(const std::string& property_name);
+ base::Closure CreatePropertyErrorCallback(const std::string& property_name);
+ void OnAdapterPropertySet(const std::string& property);
+ void OnAdapterPropertyError(const std::string& property);
+ void SendError();
+
+ // BluetoothExtensionFunction overrides:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+
+ // Set of expected adapter properties to be changed.
+ std::set<std::string> pending_properties_;
+
+ // Set of adapter properties that were not set successfully.
+ std::set<std::string> failed_properties_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateSetAdapterStateFunction);
+};
+
+class BluetoothPrivateSetPairingResponseFunction
+ : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.setPairingResponse",
+ BLUETOOTHPRIVATE_SETPAIRINGRESPONSE)
+ BluetoothPrivateSetPairingResponseFunction();
+ // BluetoothExtensionFunction overrides:
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) OVERRIDE;
+
+ private:
+ virtual ~BluetoothPrivateSetPairingResponseFunction();
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateSetPairingResponseFunction);
+};
+
+} // namespace core_api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_PRIVATE_API_H_
diff --git a/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc b/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc
new file mode 100644
index 0000000..186462f
--- /dev/null
+++ b/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth_private.h"
+#include "extensions/common/switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using testing::_;
+using testing::InSequence;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::WithArgs;
+using testing::WithoutArgs;
+
+namespace bt = extensions::core_api::bluetooth;
+namespace bt_private = extensions::core_api::bluetooth_private;
+
+namespace extensions {
+
+namespace {
+
+const char kTestExtensionId[] = "jofgjdphhceggjecimellaapdjjadibj";
+const char kAdapterName[] = "Helix";
+const char kDeviceName[] = "Red";
+}
+
+class BluetoothPrivateApiTest : public ExtensionApiTest {
+ public:
+ BluetoothPrivateApiTest()
+ : adapter_name_(kAdapterName),
+ adapter_powered_(false),
+ adapter_discoverable_(false) {}
+
+ virtual ~BluetoothPrivateApiTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kWhitelistedExtensionID, kTestExtensionId);
+ mock_adapter_ = new NiceMock<MockBluetoothAdapter>();
+ event_router()->SetAdapterForTest(mock_adapter_.get());
+ mock_device_.reset(new NiceMock<MockBluetoothDevice>(mock_adapter_.get(),
+ 0,
+ kDeviceName,
+ "11:12:13:14:15:16",
+ false,
+ false));
+ ON_CALL(*mock_adapter_.get(), GetDevice(mock_device_->GetAddress()))
+ .WillByDefault(Return(mock_device_.get()));
+ ON_CALL(*mock_adapter_.get(), IsPresent()).WillByDefault(Return(true));
+ }
+
+ virtual void TearDownOnMainThread() OVERRIDE {}
+
+ BluetoothEventRouter* event_router() {
+ return BluetoothAPI::Get(browser()->profile())->event_router();
+ }
+
+ void SetName(const std::string& name, const base::Closure& callback) {
+ adapter_name_ = name;
+ callback.Run();
+ }
+
+ void SetPowered(bool powered, const base::Closure& callback) {
+ adapter_powered_ = powered;
+ callback.Run();
+ }
+
+ void SetDiscoverable(bool discoverable, const base::Closure& callback) {
+ adapter_discoverable_ = discoverable;
+ callback.Run();
+ }
+
+ void DispatchPairingEvent(bt_private::PairingEventType pairing_event_type) {
+ bt_private::PairingEvent pairing_event;
+ pairing_event.pairing = pairing_event_type;
+ pairing_event.device.name.reset(new std::string(kDeviceName));
+ pairing_event.device.address = mock_device_->GetAddress();
+ pairing_event.device.vendor_id_source = bt::VENDOR_ID_SOURCE_USB;
+ pairing_event.device.type = bt::DEVICE_TYPE_PHONE;
+
+ scoped_ptr<base::ListValue> args =
+ bt_private::OnPairing::Create(pairing_event);
+ scoped_ptr<Event> event(
+ new Event(bt_private::OnPairing::kEventName, args.Pass()));
+ EventRouter::Get(browser()->profile())->DispatchEventToExtension(
+ kTestExtensionId, event.Pass());
+ }
+
+ void DispatchAuthorizePairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_REQUESTAUTHORIZATION);
+ }
+
+ void DispatchPincodePairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_REQUESTPINCODE);
+ }
+
+ void DispatchPasskeyPairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_REQUESTPASSKEY);
+ }
+
+ protected:
+ std::string adapter_name_;
+ bool adapter_powered_;
+ bool adapter_discoverable_;
+
+ scoped_refptr<NiceMock<MockBluetoothAdapter> > mock_adapter_;
+ scoped_ptr<NiceMock<MockBluetoothDevice> > mock_device_;
+};
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, SetAdapterState) {
+ ON_CALL(*mock_adapter_.get(), GetName())
+ .WillByDefault(ReturnPointee(&adapter_name_));
+ ON_CALL(*mock_adapter_.get(), IsPowered())
+ .WillByDefault(ReturnPointee(&adapter_powered_));
+ ON_CALL(*mock_adapter_.get(), IsDiscoverable())
+ .WillByDefault(ReturnPointee(&adapter_discoverable_));
+
+ EXPECT_CALL(*mock_adapter_.get(), SetName("Dome", _, _)).WillOnce(
+ WithArgs<0, 1>(Invoke(this, &BluetoothPrivateApiTest::SetName)));
+ EXPECT_CALL(*mock_adapter_.get(), SetPowered(true, _, _)).WillOnce(
+ WithArgs<0, 1>(Invoke(this, &BluetoothPrivateApiTest::SetPowered)));
+ EXPECT_CALL(*mock_adapter_.get(), SetDiscoverable(true, _, _)).WillOnce(
+ WithArgs<0, 1>(Invoke(this, &BluetoothPrivateApiTest::SetDiscoverable)));
+
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/adapter_state"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, NoBluetoothAdapter) {
+ ON_CALL(*mock_adapter_.get(), IsPresent()).WillByDefault(Return(false));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/no_adapter"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, CancelPairing) {
+ InSequence s;
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH))
+ .WillOnce(WithoutArgs(Invoke(
+ this, &BluetoothPrivateApiTest::DispatchAuthorizePairingEvent)));
+ EXPECT_CALL(*mock_device_, ExpectingConfirmation())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, CancelPairing());
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/cancel_pairing"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, PincodePairing) {
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH))
+ .WillOnce(WithoutArgs(
+ Invoke(this, &BluetoothPrivateApiTest::DispatchPincodePairingEvent)));
+ EXPECT_CALL(*mock_device_, ExpectingPinCode()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, SetPinCode("abbbbbbk"));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/pincode_pairing"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, PasskeyPairing) {
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH))
+ .WillOnce(WithoutArgs(
+ Invoke(this, &BluetoothPrivateApiTest::DispatchPasskeyPairingEvent)));
+ EXPECT_CALL(*mock_device_, ExpectingPasskey()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, SetPasskey(900531));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/passkey_pairing"))
+ << message_;
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_low_energy/OWNERS b/extensions/browser/api/bluetooth_low_energy/OWNERS
new file mode 100644
index 0000000..7bf8537
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/OWNERS
@@ -0,0 +1,2 @@
+keybuk@chromium.org
+armansito@chromium.org
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
new file mode 100644
index 0000000..981a8a0
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
@@ -0,0 +1,773 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/bluetooth_low_energy/utils.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/api/bluetooth_low_energy.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace apibtle = extensions::core_api::bluetooth_low_energy;
+
+namespace extensions {
+
+namespace {
+
+const char kErrorAdapterNotInitialized[] =
+ "Could not initialize Bluetooth adapter";
+const char kErrorAlreadyConnected[] = "Already connected";
+const char kErrorAlreadyNotifying[] = "Already notifying";
+const char kErrorInProgress[] = "In progress";
+const char kErrorNotConnected[] = "Not connected";
+const char kErrorNotNotifying[] = "Not notifying";
+const char kErrorNotFound[] = "Instance not found";
+const char kErrorOperationFailed[] = "Operation failed";
+const char kErrorPermissionDenied[] = "Permission denied";
+const char kErrorPlatformNotSupported[] =
+ "This operation is not supported on the current platform";
+
+// Returns the correct error string based on error status |status|. This is used
+// to set the value of |chrome.runtime.lastError.message| and should not be
+// passed |BluetoothLowEnergyEventRouter::kStatusSuccess|.
+std::string StatusToString(BluetoothLowEnergyEventRouter::Status status) {
+ switch (status) {
+ case BluetoothLowEnergyEventRouter::kStatusErrorPermissionDenied:
+ return kErrorPermissionDenied;
+ case BluetoothLowEnergyEventRouter::kStatusErrorNotFound:
+ return kErrorNotFound;
+ case BluetoothLowEnergyEventRouter::kStatusErrorAlreadyConnected:
+ return kErrorAlreadyConnected;
+ case BluetoothLowEnergyEventRouter::kStatusErrorAlreadyNotifying:
+ return kErrorAlreadyNotifying;
+ case BluetoothLowEnergyEventRouter::kStatusErrorNotConnected:
+ return kErrorNotConnected;
+ case BluetoothLowEnergyEventRouter::kStatusErrorNotNotifying:
+ return kErrorNotNotifying;
+ case BluetoothLowEnergyEventRouter::kStatusErrorInProgress:
+ return kErrorInProgress;
+ case BluetoothLowEnergyEventRouter::kStatusSuccess:
+ NOTREACHED();
+ break;
+ default:
+ return kErrorOperationFailed;
+ }
+ return "";
+}
+
+extensions::BluetoothLowEnergyEventRouter* GetEventRouter(
+ BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return extensions::BluetoothLowEnergyAPI::Get(context)->event_router();
+}
+
+void DoWorkCallback(const base::Callback<bool()>& callback) {
+ DCHECK(!callback.is_null());
+ callback.Run();
+}
+
+} // namespace
+
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<BluetoothLowEnergyAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothLowEnergyAPI>*
+BluetoothLowEnergyAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+BluetoothLowEnergyAPI* BluetoothLowEnergyAPI::Get(BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return GetFactoryInstance()->Get(context);
+}
+
+BluetoothLowEnergyAPI::BluetoothLowEnergyAPI(BrowserContext* context)
+ : event_router_(new BluetoothLowEnergyEventRouter(context)),
+ browser_context_(context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+BluetoothLowEnergyAPI::~BluetoothLowEnergyAPI() {
+}
+
+void BluetoothLowEnergyAPI::Shutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+namespace core_api {
+
+BluetoothLowEnergyExtensionFunction::BluetoothLowEnergyExtensionFunction() {
+}
+
+BluetoothLowEnergyExtensionFunction::~BluetoothLowEnergyExtensionFunction() {
+}
+
+bool BluetoothLowEnergyExtensionFunction::RunAsync() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!BluetoothManifestData::CheckLowEnergyPermitted(extension())) {
+ error_ = kErrorPermissionDenied;
+ return false;
+ }
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+ if (!event_router->IsBluetoothSupported()) {
+ SetError(kErrorPlatformNotSupported);
+ return false;
+ }
+
+ // It is safe to pass |this| here as ExtensionFunction is refcounted.
+ if (!event_router->InitializeAdapterAndInvokeCallback(base::Bind(
+ &DoWorkCallback,
+ base::Bind(&BluetoothLowEnergyExtensionFunction::DoWork, this)))) {
+ SetError(kErrorAdapterNotInitialized);
+ return false;
+ }
+
+ return true;
+}
+
+bool BluetoothLowEnergyConnectFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::Connect::Params> params(
+ apibtle::Connect::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ bool persistent = false; // Not persistent by default.
+ apibtle::ConnectProperties* properties = params.get()->properties.get();
+ if (properties)
+ persistent = properties->persistent;
+
+ event_router->Connect(
+ persistent,
+ extension(),
+ params->device_address,
+ base::Bind(&BluetoothLowEnergyConnectFunction::SuccessCallback, this),
+ base::Bind(&BluetoothLowEnergyConnectFunction::ErrorCallback, this));
+
+ return true;
+}
+
+void BluetoothLowEnergyConnectFunction::SuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyConnectFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyDisconnectFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::Disconnect::Params> params(
+ apibtle::Disconnect::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ event_router->Disconnect(
+ extension(),
+ params->device_address,
+ base::Bind(&BluetoothLowEnergyDisconnectFunction::SuccessCallback, this),
+ base::Bind(&BluetoothLowEnergyDisconnectFunction::ErrorCallback, this));
+
+ return true;
+}
+
+void BluetoothLowEnergyDisconnectFunction::SuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyDisconnectFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyGetServiceFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetService::Params> params(
+ apibtle::GetService::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ apibtle::Service service;
+ BluetoothLowEnergyEventRouter::Status status =
+ event_router->GetService(params->service_id, &service);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return false;
+ }
+
+ results_ = apibtle::GetService::Results::Create(service);
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyGetServicesFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetServices::Params> params(
+ apibtle::GetServices::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ BluetoothLowEnergyEventRouter::ServiceList service_list;
+ if (!event_router->GetServices(params->device_address, &service_list)) {
+ SetError(kErrorNotFound);
+ SendResponse(false);
+ return false;
+ }
+
+ results_ = apibtle::GetServices::Results::Create(service_list);
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyGetCharacteristicFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetCharacteristic::Params> params(
+ apibtle::GetCharacteristic::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ apibtle::Characteristic characteristic;
+ BluetoothLowEnergyEventRouter::Status status =
+ event_router->GetCharacteristic(
+ extension(), params->characteristic_id, &characteristic);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return false;
+ }
+
+ // Manually construct the result instead of using
+ // apibtle::GetCharacteristic::Result::Create as it doesn't convert lists of
+ // enums correctly.
+ SetResult(apibtle::CharacteristicToValue(&characteristic).release());
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyGetCharacteristicsFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetCharacteristics::Params> params(
+ apibtle::GetCharacteristics::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ BluetoothLowEnergyEventRouter::CharacteristicList characteristic_list;
+ BluetoothLowEnergyEventRouter::Status status =
+ event_router->GetCharacteristics(
+ extension(), params->service_id, &characteristic_list);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return false;
+ }
+
+ // Manually construct the result instead of using
+ // apibtle::GetCharacteristics::Result::Create as it doesn't convert lists of
+ // enums correctly.
+ scoped_ptr<base::ListValue> result(new base::ListValue());
+ for (BluetoothLowEnergyEventRouter::CharacteristicList::iterator iter =
+ characteristic_list.begin();
+ iter != characteristic_list.end();
+ ++iter)
+ result->Append(apibtle::CharacteristicToValue(iter->get()).release());
+
+ SetResult(result.release());
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyGetIncludedServicesFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetIncludedServices::Params> params(
+ apibtle::GetIncludedServices::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ BluetoothLowEnergyEventRouter::ServiceList service_list;
+ BluetoothLowEnergyEventRouter::Status status =
+ event_router->GetIncludedServices(params->service_id, &service_list);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return false;
+ }
+
+ results_ = apibtle::GetIncludedServices::Results::Create(service_list);
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyGetDescriptorFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetDescriptor::Params> params(
+ apibtle::GetDescriptor::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ apibtle::Descriptor descriptor;
+ BluetoothLowEnergyEventRouter::Status status = event_router->GetDescriptor(
+ extension(), params->descriptor_id, &descriptor);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return false;
+ }
+
+ // Manually construct the result instead of using
+ // apibtle::GetDescriptor::Result::Create as it doesn't convert lists of enums
+ // correctly.
+ SetResult(apibtle::DescriptorToValue(&descriptor).release());
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyGetDescriptorsFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::GetDescriptors::Params> params(
+ apibtle::GetDescriptors::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ BluetoothLowEnergyEventRouter::DescriptorList descriptor_list;
+ BluetoothLowEnergyEventRouter::Status status = event_router->GetDescriptors(
+ extension(), params->characteristic_id, &descriptor_list);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return false;
+ }
+
+ // Manually construct the result instead of using
+ // apibtle::GetDescriptors::Result::Create as it doesn't convert lists of
+ // enums correctly.
+ scoped_ptr<base::ListValue> result(new base::ListValue());
+ for (BluetoothLowEnergyEventRouter::DescriptorList::iterator iter =
+ descriptor_list.begin();
+ iter != descriptor_list.end();
+ ++iter)
+ result->Append(apibtle::DescriptorToValue(iter->get()).release());
+
+ SetResult(result.release());
+ SendResponse(true);
+
+ return true;
+}
+
+bool BluetoothLowEnergyReadCharacteristicValueFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::ReadCharacteristicValue::Params> params(
+ apibtle::ReadCharacteristicValue::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ instance_id_ = params->characteristic_id;
+ event_router->ReadCharacteristicValue(
+ extension(),
+ instance_id_,
+ base::Bind(
+ &BluetoothLowEnergyReadCharacteristicValueFunction::SuccessCallback,
+ this),
+ base::Bind(
+ &BluetoothLowEnergyReadCharacteristicValueFunction::ErrorCallback,
+ this));
+
+ return true;
+}
+
+void BluetoothLowEnergyReadCharacteristicValueFunction::SuccessCallback() {
+ // Obtain info on the characteristic and see whether or not the characteristic
+ // is still around.
+ apibtle::Characteristic characteristic;
+ BluetoothLowEnergyEventRouter::Status status =
+ GetEventRouter(browser_context())
+ ->GetCharacteristic(extension(), instance_id_, &characteristic);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return;
+ }
+
+ // Manually construct the result instead of using
+ // apibtle::GetCharacteristic::Result::Create as it doesn't convert lists of
+ // enums correctly.
+ SetResult(apibtle::CharacteristicToValue(&characteristic).release());
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyReadCharacteristicValueFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyWriteCharacteristicValueFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::WriteCharacteristicValue::Params> params(
+ apibtle::WriteCharacteristicValue::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ std::vector<uint8> value(params->value.begin(), params->value.end());
+ event_router->WriteCharacteristicValue(
+ extension(),
+ params->characteristic_id,
+ value,
+ base::Bind(
+ &BluetoothLowEnergyWriteCharacteristicValueFunction::SuccessCallback,
+ this),
+ base::Bind(
+ &BluetoothLowEnergyWriteCharacteristicValueFunction::ErrorCallback,
+ this));
+
+ return true;
+}
+
+void BluetoothLowEnergyWriteCharacteristicValueFunction::SuccessCallback() {
+ results_ = apibtle::WriteCharacteristicValue::Results::Create();
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyWriteCharacteristicValueFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyStartCharacteristicNotificationsFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::StartCharacteristicNotifications::Params> params(
+ apibtle::StartCharacteristicNotifications::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ bool persistent = false; // Not persistent by default.
+ apibtle::NotificationProperties* properties = params.get()->properties.get();
+ if (properties)
+ persistent = properties->persistent;
+
+ event_router->StartCharacteristicNotifications(
+ persistent,
+ extension(),
+ params->characteristic_id,
+ base::Bind(&BluetoothLowEnergyStartCharacteristicNotificationsFunction::
+ SuccessCallback,
+ this),
+ base::Bind(&BluetoothLowEnergyStartCharacteristicNotificationsFunction::
+ ErrorCallback,
+ this));
+
+ return true;
+}
+
+void
+BluetoothLowEnergyStartCharacteristicNotificationsFunction::SuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyStartCharacteristicNotificationsFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyStopCharacteristicNotificationsFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::StopCharacteristicNotifications::Params> params(
+ apibtle::StopCharacteristicNotifications::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ event_router->StopCharacteristicNotifications(
+ extension(),
+ params->characteristic_id,
+ base::Bind(&BluetoothLowEnergyStopCharacteristicNotificationsFunction::
+ SuccessCallback,
+ this),
+ base::Bind(&BluetoothLowEnergyStopCharacteristicNotificationsFunction::
+ ErrorCallback,
+ this));
+
+ return true;
+}
+
+void
+BluetoothLowEnergyStopCharacteristicNotificationsFunction::SuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyStopCharacteristicNotificationsFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyReadDescriptorValueFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::ReadDescriptorValue::Params> params(
+ apibtle::ReadDescriptorValue::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ instance_id_ = params->descriptor_id;
+ event_router->ReadDescriptorValue(
+ extension(),
+ instance_id_,
+ base::Bind(
+ &BluetoothLowEnergyReadDescriptorValueFunction::SuccessCallback,
+ this),
+ base::Bind(&BluetoothLowEnergyReadDescriptorValueFunction::ErrorCallback,
+ this));
+
+ return true;
+}
+
+void BluetoothLowEnergyReadDescriptorValueFunction::SuccessCallback() {
+ // Obtain info on the descriptor and see whether or not the descriptor is
+ // still around.
+ apibtle::Descriptor descriptor;
+ BluetoothLowEnergyEventRouter::Status status =
+ GetEventRouter(browser_context())
+ ->GetDescriptor(extension(), instance_id_, &descriptor);
+ if (status != BluetoothLowEnergyEventRouter::kStatusSuccess) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+ return;
+ }
+
+ // Manually construct the result instead of using
+ // apibtle::GetDescriptor::Results::Create as it doesn't convert lists of
+ // enums correctly.
+ SetResult(apibtle::DescriptorToValue(&descriptor).release());
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyReadDescriptorValueFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+bool BluetoothLowEnergyWriteDescriptorValueFunction::DoWork() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BluetoothLowEnergyEventRouter* event_router =
+ GetEventRouter(browser_context());
+
+ // The adapter must be initialized at this point, but return an error instead
+ // of asserting.
+ if (!event_router->HasAdapter()) {
+ SetError(kErrorAdapterNotInitialized);
+ SendResponse(false);
+ return false;
+ }
+
+ scoped_ptr<apibtle::WriteDescriptorValue::Params> params(
+ apibtle::WriteDescriptorValue::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ std::vector<uint8> value(params->value.begin(), params->value.end());
+ event_router->WriteDescriptorValue(
+ extension(),
+ params->descriptor_id,
+ value,
+ base::Bind(
+ &BluetoothLowEnergyWriteDescriptorValueFunction::SuccessCallback,
+ this),
+ base::Bind(&BluetoothLowEnergyWriteDescriptorValueFunction::ErrorCallback,
+ this));
+
+ return true;
+}
+
+void BluetoothLowEnergyWriteDescriptorValueFunction::SuccessCallback() {
+ results_ = apibtle::WriteDescriptorValue::Results::Create();
+ SendResponse(true);
+}
+
+void BluetoothLowEnergyWriteDescriptorValueFunction::ErrorCallback(
+ BluetoothLowEnergyEventRouter::Status status) {
+ SetError(StatusToString(status));
+ SendResponse(false);
+}
+
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h
new file mode 100644
index 0000000..0d586b0
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h
@@ -0,0 +1,337 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_API_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+
+namespace extensions {
+
+class BluetoothLowEnergyEventRouter;
+
+// The profile-keyed service that manages the bluetoothLowEnergy extension API.
+class BluetoothLowEnergyAPI : public BrowserContextKeyedAPI {
+ public:
+ static BrowserContextKeyedAPIFactory<BluetoothLowEnergyAPI>*
+ GetFactoryInstance();
+
+ // Convenience method to get the BluetoothLowEnergy API for a browser context.
+ static BluetoothLowEnergyAPI* Get(content::BrowserContext* context);
+
+ explicit BluetoothLowEnergyAPI(content::BrowserContext* context);
+ virtual ~BluetoothLowEnergyAPI();
+
+ // KeyedService implementation..
+ virtual void Shutdown() OVERRIDE;
+
+ BluetoothLowEnergyEventRouter* event_router() const {
+ return event_router_.get();
+ }
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothLowEnergyAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<BluetoothLowEnergyAPI>;
+
+ scoped_ptr<BluetoothLowEnergyEventRouter> event_router_;
+
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothLowEnergyAPI);
+};
+
+namespace core_api {
+
+// Base class for bluetoothLowEnergy API functions. This class handles some of
+// the common logic involved in all API functions, such as checking for
+// platform support and returning the correct error.
+class BluetoothLowEnergyExtensionFunction : public AsyncExtensionFunction {
+ public:
+ BluetoothLowEnergyExtensionFunction();
+
+ protected:
+ virtual ~BluetoothLowEnergyExtensionFunction();
+
+ // ExtensionFunction override.
+ virtual bool RunAsync() OVERRIDE;
+
+ // Implemented by individual bluetoothLowEnergy extension functions to perform
+ // the body of the function. This invoked asynchonously after RunAsync after
+ // the BluetoothLowEnergyEventRouter has obtained a handle on the
+ // BluetoothAdapter.
+ virtual bool DoWork() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BluetoothLowEnergyExtensionFunction);
+};
+
+class BluetoothLowEnergyConnectFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.connect",
+ BLUETOOTHLOWENERGY_CONNECT);
+
+ protected:
+ virtual ~BluetoothLowEnergyConnectFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::Connect.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+};
+
+class BluetoothLowEnergyDisconnectFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.disconnect",
+ BLUETOOTHLOWENERGY_DISCONNECT);
+
+ protected:
+ virtual ~BluetoothLowEnergyDisconnectFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::Disconnect.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+};
+
+class BluetoothLowEnergyGetServiceFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getService",
+ BLUETOOTHLOWENERGY_GETSERVICE);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetServiceFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyGetServicesFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getServices",
+ BLUETOOTHLOWENERGY_GETSERVICES);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetServicesFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyGetCharacteristicFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getCharacteristic",
+ BLUETOOTHLOWENERGY_GETCHARACTERISTIC);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetCharacteristicFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyGetCharacteristicsFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getCharacteristics",
+ BLUETOOTHLOWENERGY_GETCHARACTERISTICS);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetCharacteristicsFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyGetIncludedServicesFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getIncludedServices",
+ BLUETOOTHLOWENERGY_GETINCLUDEDSERVICES);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetIncludedServicesFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyGetDescriptorFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getDescriptor",
+ BLUETOOTHLOWENERGY_GETDESCRIPTOR);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetDescriptorFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyGetDescriptorsFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.getDescriptors",
+ BLUETOOTHLOWENERGY_GETDESCRIPTORS);
+
+ protected:
+ virtual ~BluetoothLowEnergyGetDescriptorsFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+};
+
+class BluetoothLowEnergyReadCharacteristicValueFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.readCharacteristicValue",
+ BLUETOOTHLOWENERGY_READCHARACTERISTICVALUE);
+
+ protected:
+ virtual ~BluetoothLowEnergyReadCharacteristicValueFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::ReadCharacteristicValue.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+
+ // The instance ID of the requested characteristic.
+ std::string instance_id_;
+};
+
+class BluetoothLowEnergyWriteCharacteristicValueFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.writeCharacteristicValue",
+ BLUETOOTHLOWENERGY_WRITECHARACTERISTICVALUE);
+
+ protected:
+ virtual ~BluetoothLowEnergyWriteCharacteristicValueFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::WriteCharacteristicValue.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+
+ // The instance ID of the requested characteristic.
+ std::string instance_id_;
+};
+
+class BluetoothLowEnergyStartCharacteristicNotificationsFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION(
+ "bluetoothLowEnergy.startCharacteristicNotifications",
+ BLUETOOTHLOWENERGY_STARTCHARACTERISTICNOTIFICATIONS);
+
+ protected:
+ virtual ~BluetoothLowEnergyStartCharacteristicNotificationsFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::StartCharacteristicNotifications.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+};
+
+class BluetoothLowEnergyStopCharacteristicNotificationsFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION(
+ "bluetoothLowEnergy.stopCharacteristicNotifications",
+ BLUETOOTHLOWENERGY_STOPCHARACTERISTICNOTIFICATIONS);
+
+ protected:
+ virtual ~BluetoothLowEnergyStopCharacteristicNotificationsFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::StopCharacteristicNotifications.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+};
+
+class BluetoothLowEnergyReadDescriptorValueFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.readDescriptorValue",
+ BLUETOOTHLOWENERGY_READDESCRIPTORVALUE);
+
+ protected:
+ virtual ~BluetoothLowEnergyReadDescriptorValueFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::ReadDescriptorValue.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+
+ // The instance ID of the requested descriptor.
+ std::string instance_id_;
+};
+
+class BluetoothLowEnergyWriteDescriptorValueFunction
+ : public BluetoothLowEnergyExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothLowEnergy.writeDescriptorValue",
+ BLUETOOTHLOWENERGY_WRITEDESCRIPTORVALUE);
+
+ protected:
+ virtual ~BluetoothLowEnergyWriteDescriptorValueFunction() {}
+
+ // BluetoothLowEnergyExtensionFunction override.
+ virtual bool DoWork() OVERRIDE;
+
+ private:
+ // Success and error callbacks, called by
+ // BluetoothLowEnergyEventRouter::WriteDescriptorValue.
+ void SuccessCallback();
+ void ErrorCallback(BluetoothLowEnergyEventRouter::Status status);
+
+ // The instance ID of the requested descriptor.
+ std::string instance_id_;
+};
+
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_API_H_
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc
new file mode 100644
index 0000000..ac09e45
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc
@@ -0,0 +1,1283 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_descriptor.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h"
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::BluetoothUUID;
+using device::BluetoothAdapter;
+using device::BluetoothDevice;
+using device::BluetoothGattCharacteristic;
+using device::BluetoothGattConnection;
+using device::BluetoothGattDescriptor;
+using device::BluetoothGattService;
+using device::BluetoothGattNotifySession;
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using device::MockBluetoothGattCharacteristic;
+using device::MockBluetoothGattConnection;
+using device::MockBluetoothGattDescriptor;
+using device::MockBluetoothGattService;
+using device::MockBluetoothGattNotifySession;
+using extensions::BluetoothLowEnergyEventRouter;
+using testing::Invoke;
+using testing::Return;
+using testing::ReturnRef;
+using testing::ReturnRefOfCopy;
+using testing::SaveArg;
+using testing::_;
+
+namespace utils = extension_function_test_utils;
+
+namespace {
+
+// Test service constants.
+const char kTestLeDeviceAddress0[] = "11:22:33:44:55:66";
+const char kTestLeDeviceName0[] = "Test LE Device 0";
+
+const char kTestLeDeviceAddress1[] = "77:88:99:AA:BB:CC";
+const char kTestLeDeviceName1[] = "Test LE Device 1";
+
+const char kTestServiceId0[] = "service_id0";
+const char kTestServiceUuid0[] = "1234";
+
+const char kTestServiceId1[] = "service_id1";
+const char kTestServiceUuid1[] = "5678";
+
+// Test characteristic constants.
+const char kTestCharacteristicId0[] = "char_id0";
+const char kTestCharacteristicUuid0[] = "1211";
+const BluetoothGattCharacteristic::Properties kTestCharacteristicProperties0 =
+ BluetoothGattCharacteristic::kPropertyBroadcast |
+ BluetoothGattCharacteristic::kPropertyRead |
+ BluetoothGattCharacteristic::kPropertyWriteWithoutResponse |
+ BluetoothGattCharacteristic::kPropertyIndicate;
+const uint8 kTestCharacteristicDefaultValue0[] = {0x01, 0x02, 0x03, 0x04, 0x05};
+
+const char kTestCharacteristicId1[] = "char_id1";
+const char kTestCharacteristicUuid1[] = "1212";
+const BluetoothGattCharacteristic::Properties kTestCharacteristicProperties1 =
+ BluetoothGattCharacteristic::kPropertyRead |
+ BluetoothGattCharacteristic::kPropertyWrite |
+ BluetoothGattCharacteristic::kPropertyNotify;
+const uint8 kTestCharacteristicDefaultValue1[] = {0x06, 0x07, 0x08};
+
+const char kTestCharacteristicId2[] = "char_id2";
+const char kTestCharacteristicUuid2[] = "1213";
+const BluetoothGattCharacteristic::Properties kTestCharacteristicProperties2 =
+ BluetoothGattCharacteristic::kPropertyNone;
+
+// Test descriptor constants.
+const char kTestDescriptorId0[] = "desc_id0";
+const char kTestDescriptorUuid0[] = "1221";
+const uint8 kTestDescriptorDefaultValue0[] = {0x01, 0x02, 0x03};
+
+const char kTestDescriptorId1[] = "desc_id1";
+const char kTestDescriptorUuid1[] = "1222";
+const uint8 kTestDescriptorDefaultValue1[] = {0x04, 0x05};
+
+class BluetoothLowEnergyApiTest : public ExtensionApiTest {
+ public:
+ BluetoothLowEnergyApiTest() {}
+
+ virtual ~BluetoothLowEnergyApiTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionApiTest::SetUpOnMainThread();
+ empty_extension_ = utils::CreateEmptyExtension();
+ SetUpMocks();
+ }
+
+ virtual void TearDownOnMainThread() OVERRIDE {
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(_));
+ }
+
+ void SetUpMocks() {
+ mock_adapter_ = new testing::StrictMock<MockBluetoothAdapter>();
+ EXPECT_CALL(*mock_adapter_, GetDevices())
+ .WillOnce(Return(BluetoothAdapter::ConstDeviceList()));
+
+ event_router()->SetAdapterForTesting(mock_adapter_);
+
+ device0_.reset(
+ new testing::NiceMock<MockBluetoothDevice>(mock_adapter_,
+ 0,
+ kTestLeDeviceName0,
+ kTestLeDeviceAddress0,
+ false /* paired */,
+ true /* connected */));
+
+ device1_.reset(
+ new testing::NiceMock<MockBluetoothDevice>(mock_adapter_,
+ 0,
+ kTestLeDeviceName1,
+ kTestLeDeviceAddress1,
+ false /* paired */,
+ false /* connected */));
+
+ service0_.reset(new testing::NiceMock<MockBluetoothGattService>(
+ device0_.get(),
+ kTestServiceId0,
+ BluetoothUUID(kTestServiceUuid0),
+ true /* is_primary */,
+ false /* is_local */));
+
+ service1_.reset(new testing::NiceMock<MockBluetoothGattService>(
+ device0_.get(),
+ kTestServiceId1,
+ BluetoothUUID(kTestServiceUuid1),
+ false /* is_primary */,
+ false /* is_local */));
+
+ // Assign characteristics some random properties and permissions. They don't
+ // need to reflect what the characteristic is actually capable of, since
+ // the JS API just passes values through from
+ // device::BluetoothGattCharacteristic.
+ std::vector<uint8> default_value;
+ chrc0_.reset(new testing::NiceMock<MockBluetoothGattCharacteristic>(
+ service0_.get(),
+ kTestCharacteristicId0,
+ BluetoothUUID(kTestCharacteristicUuid0),
+ false /* is_local */,
+ kTestCharacteristicProperties0,
+ BluetoothGattCharacteristic::kPermissionNone));
+ default_value.assign(kTestCharacteristicDefaultValue0,
+ (kTestCharacteristicDefaultValue0 +
+ sizeof(kTestCharacteristicDefaultValue0)));
+ ON_CALL(*chrc0_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
+
+ chrc1_.reset(new testing::NiceMock<MockBluetoothGattCharacteristic>(
+ service0_.get(),
+ kTestCharacteristicId1,
+ BluetoothUUID(kTestCharacteristicUuid1),
+ false /* is_local */,
+ kTestCharacteristicProperties1,
+ BluetoothGattCharacteristic::kPermissionNone));
+ default_value.assign(kTestCharacteristicDefaultValue1,
+ (kTestCharacteristicDefaultValue1 +
+ sizeof(kTestCharacteristicDefaultValue1)));
+ ON_CALL(*chrc1_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
+
+ chrc2_.reset(new testing::NiceMock<MockBluetoothGattCharacteristic>(
+ service1_.get(),
+ kTestCharacteristicId2,
+ BluetoothUUID(kTestCharacteristicUuid2),
+ false /* is_local */,
+ kTestCharacteristicProperties2,
+ BluetoothGattCharacteristic::kPermissionNone));
+
+ desc0_.reset(new testing::NiceMock<MockBluetoothGattDescriptor>(
+ chrc0_.get(),
+ kTestDescriptorId0,
+ BluetoothUUID(kTestDescriptorUuid0),
+ false /* is_local */,
+ BluetoothGattCharacteristic::kPermissionNone));
+ default_value.assign(
+ kTestDescriptorDefaultValue0,
+ (kTestDescriptorDefaultValue0 + sizeof(kTestDescriptorDefaultValue0)));
+ ON_CALL(*desc0_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
+
+ desc1_.reset(new testing::NiceMock<MockBluetoothGattDescriptor>(
+ chrc0_.get(),
+ kTestDescriptorId1,
+ BluetoothUUID(kTestDescriptorUuid1),
+ false /* is_local */,
+ BluetoothGattCharacteristic::kPermissionNone));
+ default_value.assign(
+ kTestDescriptorDefaultValue1,
+ (kTestDescriptorDefaultValue1 + sizeof(kTestDescriptorDefaultValue1)));
+ ON_CALL(*desc1_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
+ }
+
+ protected:
+ BluetoothLowEnergyEventRouter* event_router() {
+ return extensions::BluetoothLowEnergyAPI::Get(browser()->profile())
+ ->event_router();
+ }
+
+ testing::StrictMock<MockBluetoothAdapter>* mock_adapter_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device0_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattService> > service0_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattService> > service1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattCharacteristic> > chrc0_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattCharacteristic> > chrc1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattCharacteristic> > chrc2_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattDescriptor> > desc0_;
+ scoped_ptr<testing::NiceMock<MockBluetoothGattDescriptor> > desc1_;
+
+ private:
+ scoped_refptr<extensions::Extension> empty_extension_;
+};
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_0_VALUE_PARAMS()) {
+ ::std::tr1::get<k>(args).Run();
+}
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p0)) {
+ ::std::tr1::get<k>(args).Run(p0);
+}
+
+ACTION_TEMPLATE(InvokeCallbackWithScopedPtrArg,
+ HAS_2_TEMPLATE_PARAMS(int, k, typename, T),
+ AND_1_VALUE_PARAMS(p0)) {
+ ::std::tr1::get<k>(args).Run(scoped_ptr<T>(p0));
+}
+
+BluetoothGattConnection* CreateGattConnection(
+ const std::string& device_address,
+ bool expect_disconnect) {
+ testing::NiceMock<MockBluetoothGattConnection>* conn =
+ new testing::NiceMock<MockBluetoothGattConnection>(device_address);
+
+ if (expect_disconnect) {
+ EXPECT_CALL(*conn, Disconnect(_))
+ .Times(1)
+ .WillOnce(InvokeCallbackArgument<0>());
+ } else {
+ EXPECT_CALL(*conn, Disconnect(_)).Times(0);
+ }
+
+ return conn;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetServices) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ std::vector<BluetoothGattService*> services;
+ services.push_back(service0_.get());
+ services.push_back(service1_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillOnce(Return(static_cast<BluetoothDevice*>(NULL)))
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattServices())
+ .Times(2)
+ .WillOnce(Return(std::vector<BluetoothGattService*>()))
+ .WillOnce(Return(services));
+
+ // Load and wait for setup.
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_services")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetService) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillOnce(Return(static_cast<BluetoothDevice*>(NULL)))
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(2)
+ .WillOnce(Return(static_cast<BluetoothGattService*>(NULL)))
+ .WillOnce(Return(service0_.get()));
+
+ // Load and wait for setup.
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_service")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, ServiceEvents) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Load the extension and let it set up.
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/service_events")));
+
+ // These will create the identifier mappings.
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service1_.get());
+
+ // These will send the onServiceAdded event to apps.
+ event_router()->GattDiscoveryCompleteForService(mock_adapter_,
+ service0_.get());
+ event_router()->GattDiscoveryCompleteForService(mock_adapter_,
+ service1_.get());
+
+ // This will send the onServiceChanged event to apps.
+ event_router()->GattServiceChanged(mock_adapter_, service1_.get());
+
+ // This will send the onServiceRemoved event to apps.
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service1_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetRemovedService) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Load the extension and let it set up.
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_removed_service")));
+
+ // 1. getService success.
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(1)
+ .WillOnce(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(1)
+ .WillOnce(Return(service0_.get()));
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattDiscoveryCompleteForService(mock_adapter_,
+ service0_.get());
+
+ ExtensionTestMessageListener get_service_success_listener("getServiceSuccess",
+ true);
+ EXPECT_TRUE(get_service_success_listener.WaitUntilSatisfied());
+ testing::Mock::VerifyAndClearExpectations(mock_adapter_);
+ testing::Mock::VerifyAndClearExpectations(device0_.get());
+
+ // 2. getService fail.
+ EXPECT_CALL(*mock_adapter_, GetDevice(_)).Times(0);
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0)).Times(0);
+
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+
+ ExtensionTestMessageListener get_service_fail_listener("getServiceFail",
+ true);
+ EXPECT_TRUE(get_service_fail_listener.WaitUntilSatisfied());
+ testing::Mock::VerifyAndClearExpectations(mock_adapter_);
+ testing::Mock::VerifyAndClearExpectations(device0_.get());
+
+ get_service_fail_listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetIncludedServices) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/get_included_services")));
+
+ // Wait for initial call to end with failure as there is no mapping.
+ ExtensionTestMessageListener listener("ready", true);
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ // Set up for the rest of the calls before replying. Included services can be
+ // returned even if there is no instance ID mapping for them yet, so no need
+ // to call GattServiceAdded for |service1_| here.
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+
+ std::vector<BluetoothGattService*> includes;
+ includes.push_back(service1_.get());
+ EXPECT_CALL(*mock_adapter_, GetDevice(kTestLeDeviceAddress0))
+ .Times(2)
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(2)
+ .WillRepeatedly(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetIncludedServices())
+ .Times(2)
+ .WillOnce(Return(std::vector<BluetoothGattService*>()))
+ .WillOnce(Return(includes));
+
+ listener.Reply("go");
+ listener.Reset();
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetCharacteristics) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ std::vector<BluetoothGattCharacteristic*> characteristics;
+ characteristics.push_back(chrc0_.get());
+ characteristics.push_back(chrc1_.get());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_)).Times(3).WillRepeatedly(
+ Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillOnce(Return(static_cast<BluetoothGattService*>(NULL)))
+ .WillRepeatedly(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristics())
+ .Times(2)
+ .WillOnce(Return(std::vector<BluetoothGattCharacteristic*>()))
+ .WillOnce(Return(characteristics));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_characteristics")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetCharacteristic) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(4)
+ .WillOnce(Return(static_cast<BluetoothDevice*>(NULL)))
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillOnce(Return(static_cast<BluetoothGattService*>(NULL)))
+ .WillRepeatedly(Return(service0_.get()));
+
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(2)
+ .WillOnce(Return(static_cast<BluetoothGattCharacteristic*>(NULL)))
+ .WillOnce(Return(chrc0_.get()));
+
+ // Load the extension and wait for first test.
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_characteristic")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, CharacteristicProperties) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(12)
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(12)
+ .WillRepeatedly(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(12)
+ .WillRepeatedly(Return(chrc0_.get()));
+ EXPECT_CALL(*chrc0_, GetProperties())
+ .Times(12)
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyNone))
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyBroadcast))
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyRead))
+ .WillOnce(
+ Return(BluetoothGattCharacteristic::kPropertyWriteWithoutResponse))
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyWrite))
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyNotify))
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyIndicate))
+ .WillOnce(Return(
+ BluetoothGattCharacteristic::kPropertyAuthenticatedSignedWrites))
+ .WillOnce(
+ Return(BluetoothGattCharacteristic::kPropertyExtendedProperties))
+ .WillOnce(Return(BluetoothGattCharacteristic::kPropertyReliableWrite))
+ .WillOnce(
+ Return(BluetoothGattCharacteristic::kPropertyWritableAuxiliaries))
+ .WillOnce(Return(
+ BluetoothGattCharacteristic::kPropertyBroadcast |
+ BluetoothGattCharacteristic::kPropertyRead |
+ BluetoothGattCharacteristic::kPropertyWriteWithoutResponse |
+ BluetoothGattCharacteristic::kPropertyWrite |
+ BluetoothGattCharacteristic::kPropertyNotify |
+ BluetoothGattCharacteristic::kPropertyIndicate |
+ BluetoothGattCharacteristic::kPropertyAuthenticatedSignedWrites |
+ BluetoothGattCharacteristic::kPropertyExtendedProperties |
+ BluetoothGattCharacteristic::kPropertyReliableWrite |
+ BluetoothGattCharacteristic::kPropertyWritableAuxiliaries));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/characteristic_properties")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetRemovedCharacteristic) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(1)
+ .WillOnce(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(1)
+ .WillOnce(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(1)
+ .WillOnce(Return(chrc0_.get()));
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/get_removed_characteristic")));
+
+ ExtensionTestMessageListener listener("ready", true);
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ testing::Mock::VerifyAndClearExpectations(mock_adapter_);
+ testing::Mock::VerifyAndClearExpectations(device0_.get());
+ testing::Mock::VerifyAndClearExpectations(service0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_)).Times(0);
+ EXPECT_CALL(*device0_, GetGattService(_)).Times(0);
+ EXPECT_CALL(*service0_, GetCharacteristic(_)).Times(0);
+
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+
+ listener.Reply("go");
+ listener.Reset();
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, CharacteristicValueChanged) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Cause events to be sent to the extension.
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service1_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc2_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(2)
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(1)
+ .WillOnce(Return(service0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId1))
+ .Times(1)
+ .WillOnce(Return(service1_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(1)
+ .WillOnce(Return(chrc0_.get()));
+ EXPECT_CALL(*service1_, GetCharacteristic(kTestCharacteristicId2))
+ .Times(1)
+ .WillOnce(Return(chrc2_.get()));
+
+ BluetoothGattNotifySession* session0 =
+ new testing::NiceMock<MockBluetoothGattNotifySession>(
+ kTestCharacteristicId0);
+ BluetoothGattNotifySession* session1 =
+ new testing::NiceMock<MockBluetoothGattNotifySession>(
+ kTestCharacteristicId2);
+
+ EXPECT_CALL(*chrc0_, StartNotifySession(_, _))
+ .Times(1)
+ .WillOnce(
+ InvokeCallbackWithScopedPtrArg<0, BluetoothGattNotifySession>(
+ session0));
+ EXPECT_CALL(*chrc2_, StartNotifySession(_, _))
+ .Times(1)
+ .WillOnce(
+ InvokeCallbackWithScopedPtrArg<0, BluetoothGattNotifySession>(
+ session1));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/characteristic_value_changed")));
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ std::vector<uint8> value;
+ event_router()->GattCharacteristicValueChanged(
+ mock_adapter_, chrc0_.get(), value);
+ event_router()->GattCharacteristicValueChanged(
+ mock_adapter_, chrc2_.get(), value);
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc2_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service1_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, ReadCharacteristicValue) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillRepeatedly(Return(service0_.get()));
+
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(3)
+ .WillRepeatedly(Return(chrc0_.get()));
+
+ std::vector<uint8> value;
+ EXPECT_CALL(*chrc0_, ReadRemoteCharacteristic(_, _))
+ .Times(2)
+ .WillOnce(InvokeCallbackArgument<1>())
+ .WillOnce(InvokeCallbackArgument<0>(value));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/read_characteristic_value")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, WriteCharacteristicValue) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillRepeatedly(Return(service0_.get()));
+
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(3)
+ .WillRepeatedly(Return(chrc0_.get()));
+
+ std::vector<uint8> write_value;
+ EXPECT_CALL(*chrc0_, WriteRemoteCharacteristic(_, _, _))
+ .Times(2)
+ .WillOnce(InvokeCallbackArgument<2>())
+ .WillOnce(DoAll(SaveArg<0>(&write_value), InvokeCallbackArgument<1>()));
+
+ EXPECT_CALL(*chrc0_, GetValue()).Times(1).WillOnce(ReturnRef(write_value));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/write_characteristic_value")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetDescriptors) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ std::vector<BluetoothGattDescriptor*> descriptors;
+ descriptors.push_back(desc0_.get());
+ descriptors.push_back(desc1_.get());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillRepeatedly(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(3)
+ .WillOnce(Return(static_cast<BluetoothGattCharacteristic*>(NULL)))
+ .WillRepeatedly(Return(chrc0_.get()));
+ EXPECT_CALL(*chrc0_, GetDescriptors())
+ .Times(2)
+ .WillOnce(Return(std::vector<BluetoothGattDescriptor*>()))
+ .WillOnce(Return(descriptors));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_descriptors")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetDescriptor) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(5)
+ .WillOnce(Return(static_cast<BluetoothDevice*>(NULL)))
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(4)
+ .WillOnce(Return(static_cast<BluetoothGattService*>(NULL)))
+ .WillRepeatedly(Return(service0_.get()));
+
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(3)
+ .WillOnce(Return(static_cast<BluetoothGattCharacteristic*>(NULL)))
+ .WillRepeatedly(Return(chrc0_.get()));
+
+ EXPECT_CALL(*chrc0_, GetDescriptor(kTestDescriptorId0))
+ .Times(2)
+ .WillOnce(Return(static_cast<BluetoothGattDescriptor*>(NULL)))
+ .WillOnce(Return(desc0_.get()));
+
+ // Load the extension and wait for first test.
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/get_descriptor")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GetRemovedDescriptor) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(1)
+ .WillOnce(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(1)
+ .WillOnce(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(1)
+ .WillOnce(Return(chrc0_.get()));
+ EXPECT_CALL(*chrc0_, GetDescriptor(kTestDescriptorId0))
+ .Times(1)
+ .WillOnce(Return(desc0_.get()));
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/get_removed_descriptor")));
+
+ ExtensionTestMessageListener listener("ready", true);
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ testing::Mock::VerifyAndClearExpectations(mock_adapter_);
+ testing::Mock::VerifyAndClearExpectations(device0_.get());
+ testing::Mock::VerifyAndClearExpectations(service0_.get());
+ testing::Mock::VerifyAndClearExpectations(chrc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_)).Times(0);
+ EXPECT_CALL(*device0_, GetGattService(_)).Times(0);
+ EXPECT_CALL(*service0_, GetCharacteristic(_)).Times(0);
+ EXPECT_CALL(*chrc0_, GetDescriptor(_)).Times(0);
+
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+
+ listener.Reply("go");
+ listener.Reset();
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, DescriptorValueChanged) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc1_.get());
+
+ // Load the extension and let it set up.
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/descriptor_value_changed")));
+
+ // Cause events to be sent to the extension.
+ std::vector<uint8> value;
+ event_router()->GattDescriptorValueChanged(
+ mock_adapter_, desc0_.get(), value);
+ event_router()->GattDescriptorValueChanged(
+ mock_adapter_, desc1_.get(), value);
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc1_.get());
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, ReadDescriptorValue) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillRepeatedly(Return(service0_.get()));
+
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(3)
+ .WillRepeatedly(Return(chrc0_.get()));
+
+ EXPECT_CALL(*chrc0_, GetDescriptor(kTestDescriptorId0))
+ .Times(3)
+ .WillRepeatedly(Return(desc0_.get()));
+
+ std::vector<uint8> value;
+ EXPECT_CALL(*desc0_, ReadRemoteDescriptor(_, _))
+ .Times(2)
+ .WillOnce(InvokeCallbackArgument<1>())
+ .WillOnce(InvokeCallbackArgument<0>(value));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/read_descriptor_value")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, WriteDescriptorValue) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .Times(3)
+ .WillRepeatedly(Return(device0_.get()));
+
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .Times(3)
+ .WillRepeatedly(Return(service0_.get()));
+
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(3)
+ .WillRepeatedly(Return(chrc0_.get()));
+
+ EXPECT_CALL(*chrc0_, GetDescriptor(kTestDescriptorId0))
+ .Times(3)
+ .WillRepeatedly(Return(desc0_.get()));
+
+ std::vector<uint8> write_value;
+ EXPECT_CALL(*desc0_, WriteRemoteDescriptor(_, _, _))
+ .Times(2)
+ .WillOnce(InvokeCallbackArgument<2>())
+ .WillOnce(DoAll(SaveArg<0>(&write_value), InvokeCallbackArgument<1>()));
+
+ EXPECT_CALL(*desc0_, GetValue()).Times(1).WillOnce(ReturnRef(write_value));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/write_descriptor_value")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, PermissionDenied) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/permission_denied")));
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, UuidPermissionMethods) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+
+ std::vector<BluetoothGattService*> services;
+ services.push_back(service0_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattServices()).WillOnce(Return(services));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .WillRepeatedly(Return(service0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .WillRepeatedly(Return(chrc0_.get()));
+ EXPECT_CALL(*chrc0_, GetDescriptor(kTestDescriptorId0))
+ .WillRepeatedly(Return(desc0_.get()));
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/uuid_permission_methods")));
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, UuidPermissionEvents) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/uuid_permission_events")));
+
+ // Cause events to be sent to the extension.
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattDescriptorAdded(mock_adapter_, desc0_.get());
+
+ std::vector<uint8> value;
+ event_router()->GattCharacteristicValueChanged(
+ mock_adapter_, chrc0_.get(), value);
+ event_router()->GattDescriptorValueChanged(
+ mock_adapter_, desc0_.get(), value);
+ event_router()->GattServiceChanged(mock_adapter_, service0_.get());
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply("go");
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ event_router()->GattDescriptorRemoved(mock_adapter_, desc0_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, GattConnection) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .WillRepeatedly(Return(static_cast<BluetoothDevice*>(NULL)));
+ EXPECT_CALL(*mock_adapter_, GetDevice(kTestLeDeviceAddress0))
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*mock_adapter_, GetDevice(kTestLeDeviceAddress1))
+ .WillRepeatedly(Return(device1_.get()));
+ EXPECT_CALL(*device0_, CreateGattConnection(_, _))
+ .Times(3)
+ .WillOnce(InvokeCallbackArgument<1>(BluetoothDevice::ERROR_FAILED))
+ .WillOnce(InvokeCallbackWithScopedPtrArg<0, BluetoothGattConnection>(
+ CreateGattConnection(kTestLeDeviceAddress0,
+ true /* expect_disconnect */)))
+ .WillOnce(InvokeCallbackWithScopedPtrArg<0, BluetoothGattConnection>(
+ CreateGattConnection(kTestLeDeviceAddress0,
+ false /* expect_disconnect */)));
+ EXPECT_CALL(*device1_, CreateGattConnection(_, _))
+ .Times(1)
+ .WillOnce(InvokeCallbackWithScopedPtrArg<0, BluetoothGattConnection>(
+ CreateGattConnection(kTestLeDeviceAddress1,
+ true /* expect_disconnect */)));
+
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth_low_energy/gatt_connection")));
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, ReconnectAfterDisconnected) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(kTestLeDeviceAddress0))
+ .WillRepeatedly(Return(device0_.get()));
+
+ MockBluetoothGattConnection* first_conn =
+ static_cast<MockBluetoothGattConnection*>(CreateGattConnection(
+ kTestLeDeviceAddress0, false /* expect_disconnect */));
+ EXPECT_CALL(*first_conn, IsConnected())
+ .Times(2)
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+
+ EXPECT_CALL(*device0_, CreateGattConnection(_, _))
+ .Times(2)
+ .WillOnce(InvokeCallbackWithScopedPtrArg<0, BluetoothGattConnection>(
+ first_conn))
+ .WillOnce(InvokeCallbackWithScopedPtrArg<0, BluetoothGattConnection>(
+ CreateGattConnection(kTestLeDeviceAddress0,
+ false /* expect_disconnect */)));
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/reconnect_after_disconnected")));
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, ConnectInProgress) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(kTestLeDeviceAddress0))
+ .WillRepeatedly(Return(device0_.get()));
+
+ BluetoothDevice::GattConnectionCallback connect_callback;
+ base::Closure disconnect_callback;
+
+ testing::NiceMock<MockBluetoothGattConnection>* conn =
+ new testing::NiceMock<MockBluetoothGattConnection>(
+ kTestLeDeviceAddress0);
+ scoped_ptr<BluetoothGattConnection> conn_ptr(conn);
+ EXPECT_CALL(*conn, Disconnect(_))
+ .Times(1)
+ .WillOnce(SaveArg<0>(&disconnect_callback));
+
+ EXPECT_CALL(*device0_, CreateGattConnection(_, _))
+ .Times(1)
+ .WillOnce(SaveArg<0>(&connect_callback));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/connect_in_progress")));
+
+ listener.WaitUntilSatisfied();
+ connect_callback.Run(conn_ptr.Pass());
+
+ listener.Reset();
+ listener.WaitUntilSatisfied();
+ disconnect_callback.Run();
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothLowEnergyApiTest, StartStopNotifications) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service0_.get());
+ event_router()->GattServiceAdded(
+ mock_adapter_, device0_.get(), service1_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc0_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc1_.get());
+ event_router()->GattCharacteristicAdded(mock_adapter_, chrc2_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(_))
+ .WillRepeatedly(Return(device0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId0))
+ .WillRepeatedly(Return(service0_.get()));
+ EXPECT_CALL(*device0_, GetGattService(kTestServiceId1))
+ .WillRepeatedly(Return(service1_.get()));
+ EXPECT_CALL(*service1_, GetCharacteristic(kTestCharacteristicId2))
+ .Times(1)
+ .WillOnce(Return(chrc2_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId0))
+ .Times(2)
+ .WillRepeatedly(Return(chrc0_.get()));
+ EXPECT_CALL(*service0_, GetCharacteristic(kTestCharacteristicId1))
+ .Times(1)
+ .WillOnce(Return(chrc1_.get()));
+
+ BluetoothGattNotifySession* session0 =
+ new testing::NiceMock<MockBluetoothGattNotifySession>(
+ kTestCharacteristicId0);
+ MockBluetoothGattNotifySession* session1 =
+ new testing::NiceMock<MockBluetoothGattNotifySession>(
+ kTestCharacteristicId1);
+
+ EXPECT_CALL(*session1, Stop(_))
+ .Times(1)
+ .WillOnce(InvokeCallbackArgument<0>());
+
+ EXPECT_CALL(*chrc0_, StartNotifySession(_, _))
+ .Times(2)
+ .WillOnce(InvokeCallbackArgument<1>())
+ .WillOnce(
+ InvokeCallbackWithScopedPtrArg<0, BluetoothGattNotifySession>(
+ session0));
+ EXPECT_CALL(*chrc1_, StartNotifySession(_, _))
+ .Times(1)
+ .WillOnce(
+ InvokeCallbackWithScopedPtrArg<0, BluetoothGattNotifySession>(
+ session1));
+
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_low_energy/start_stop_notifications")));
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ std::vector<uint8> value;
+ event_router()->GattCharacteristicValueChanged(
+ mock_adapter_, chrc0_.get(), value);
+ event_router()->GattCharacteristicValueChanged(
+ mock_adapter_, chrc1_.get(), value);
+ event_router()->GattCharacteristicValueChanged(
+ mock_adapter_, chrc2_.get(), value);
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc2_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc1_.get());
+ event_router()->GattCharacteristicRemoved(mock_adapter_, chrc0_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service1_.get());
+ event_router()->GattServiceRemoved(
+ mock_adapter_, device0_.get(), service0_.get());
+}
+
+} // namespace
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.cc
new file mode 100644
index 0000000..c52aea4
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h"
+
+namespace extensions {
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<
+ ApiResourceManager<BluetoothLowEnergyConnection> > > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+template <>
+BrowserContextKeyedAPIFactory<
+ ApiResourceManager<BluetoothLowEnergyConnection> >*
+ApiResourceManager<BluetoothLowEnergyConnection>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+BluetoothLowEnergyConnection::BluetoothLowEnergyConnection(
+ bool persistent,
+ const std::string& owner_extension_id,
+ scoped_ptr<device::BluetoothGattConnection> connection)
+ : ApiResource(owner_extension_id),
+ persistent_(persistent),
+ connection_(connection.release()) {
+}
+
+BluetoothLowEnergyConnection::~BluetoothLowEnergyConnection() {
+}
+
+device::BluetoothGattConnection*
+BluetoothLowEnergyConnection::GetConnection() const {
+ return connection_.get();
+}
+
+bool BluetoothLowEnergyConnection::IsPersistent() const {
+ return persistent_;
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h
new file mode 100644
index 0000000..f1b01fd
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_CONNECTION_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_CONNECTION_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "device/bluetooth/bluetooth_gatt_connection.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+
+namespace extensions {
+
+// An ApiResource wrapper for a device::BluetoothGattConnection.
+class BluetoothLowEnergyConnection : public ApiResource {
+ public:
+ explicit BluetoothLowEnergyConnection(
+ bool persistent,
+ const std::string& owner_extension_id,
+ scoped_ptr<device::BluetoothGattConnection> connection);
+ virtual ~BluetoothLowEnergyConnection();
+
+ // Returns a pointer to the underlying connection object.
+ device::BluetoothGattConnection* GetConnection() const;
+
+ // ApiResource override.
+ virtual bool IsPersistent() const OVERRIDE;
+
+ // This resource should be managed on the UI thread.
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ private:
+ friend class ApiResourceManager<BluetoothLowEnergyConnection>;
+ static const char* service_name() {
+ return "BluetoothLowEnergyConnectionManager";
+ }
+
+ // True, if this resource should be persistent.
+ bool persistent_;
+
+ // The connection is owned by this instance and will automatically disconnect
+ // when deleted.
+ scoped_ptr<device::BluetoothGattConnection> connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothLowEnergyConnection);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_CONNECTION_H_
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc
new file mode 100644
index 0000000..f012ae4
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc
@@ -0,0 +1,1450 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/bluetooth_gatt_connection.h"
+#include "device/bluetooth/bluetooth_gatt_descriptor.h"
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h"
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h"
+#include "extensions/browser/api/bluetooth_low_energy/utils.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+
+using content::BrowserThread;
+
+using device::BluetoothAdapter;
+using device::BluetoothAdapterFactory;
+using device::BluetoothDevice;
+using device::BluetoothGattCharacteristic;
+using device::BluetoothGattConnection;
+using device::BluetoothGattDescriptor;
+using device::BluetoothGattService;
+
+namespace apibtle = extensions::core_api::bluetooth_low_energy;
+
+namespace {
+
+void PopulateService(const BluetoothGattService* service,
+ apibtle::Service* out) {
+ DCHECK(out);
+
+ out->uuid = service->GetUUID().canonical_value();
+ out->is_primary = service->IsPrimary();
+ out->is_local = service->IsLocal();
+ out->instance_id.reset(new std::string(service->GetIdentifier()));
+
+ if (!service->GetDevice())
+ return;
+
+ out->device_address.reset(
+ new std::string(service->GetDevice()->GetAddress()));
+}
+
+void PopulateCharacteristicProperties(
+ BluetoothGattCharacteristic::Properties properties,
+ std::vector<apibtle::CharacteristicProperty>* api_properties) {
+ DCHECK(api_properties && api_properties->empty());
+
+ if (properties == BluetoothGattCharacteristic::kPropertyNone)
+ return;
+
+ if (properties & BluetoothGattCharacteristic::kPropertyBroadcast)
+ api_properties->push_back(apibtle::CHARACTERISTIC_PROPERTY_BROADCAST);
+ if (properties & BluetoothGattCharacteristic::kPropertyRead)
+ api_properties->push_back(apibtle::CHARACTERISTIC_PROPERTY_READ);
+ if (properties & BluetoothGattCharacteristic::kPropertyWriteWithoutResponse) {
+ api_properties->push_back(
+ apibtle::CHARACTERISTIC_PROPERTY_WRITEWITHOUTRESPONSE);
+ }
+ if (properties & BluetoothGattCharacteristic::kPropertyWrite)
+ api_properties->push_back(apibtle::CHARACTERISTIC_PROPERTY_WRITE);
+ if (properties & BluetoothGattCharacteristic::kPropertyNotify)
+ api_properties->push_back(apibtle::CHARACTERISTIC_PROPERTY_NOTIFY);
+ if (properties & BluetoothGattCharacteristic::kPropertyIndicate)
+ api_properties->push_back(apibtle::CHARACTERISTIC_PROPERTY_INDICATE);
+ if (properties &
+ BluetoothGattCharacteristic::kPropertyAuthenticatedSignedWrites) {
+ api_properties->push_back(
+ apibtle::CHARACTERISTIC_PROPERTY_AUTHENTICATEDSIGNEDWRITES);
+ }
+ if (properties & BluetoothGattCharacteristic::kPropertyExtendedProperties) {
+ api_properties->push_back(
+ apibtle::CHARACTERISTIC_PROPERTY_EXTENDEDPROPERTIES);
+ }
+ if (properties & BluetoothGattCharacteristic::kPropertyReliableWrite)
+ api_properties->push_back(apibtle::CHARACTERISTIC_PROPERTY_RELIABLEWRITE);
+ if (properties & BluetoothGattCharacteristic::kPropertyWritableAuxiliaries) {
+ api_properties->push_back(
+ apibtle::CHARACTERISTIC_PROPERTY_WRITABLEAUXILIARIES);
+ }
+}
+
+void PopulateCharacteristic(const BluetoothGattCharacteristic* characteristic,
+ apibtle::Characteristic* out) {
+ DCHECK(out);
+
+ out->uuid = characteristic->GetUUID().canonical_value();
+ out->is_local = characteristic->IsLocal();
+ out->instance_id.reset(new std::string(characteristic->GetIdentifier()));
+
+ PopulateService(characteristic->GetService(), &out->service);
+ PopulateCharacteristicProperties(characteristic->GetProperties(),
+ &out->properties);
+
+ const std::vector<uint8>& value = characteristic->GetValue();
+ if (value.empty())
+ return;
+
+ out->value.reset(new std::string(value.begin(), value.end()));
+}
+
+void PopulateDescriptor(const BluetoothGattDescriptor* descriptor,
+ apibtle::Descriptor* out) {
+ DCHECK(out);
+
+ out->uuid = descriptor->GetUUID().canonical_value();
+ out->is_local = descriptor->IsLocal();
+ out->instance_id.reset(new std::string(descriptor->GetIdentifier()));
+
+ PopulateCharacteristic(descriptor->GetCharacteristic(), &out->characteristic);
+
+ const std::vector<uint8>& value = descriptor->GetValue();
+ if (value.empty())
+ return;
+
+ out->value.reset(new std::string(value.begin(), value.end()));
+}
+
+typedef extensions::ApiResourceManager<extensions::BluetoothLowEnergyConnection>
+ ConnectionResourceManager;
+ConnectionResourceManager* GetConnectionResourceManager(
+ content::BrowserContext* context) {
+ ConnectionResourceManager* manager = ConnectionResourceManager::Get(context);
+ DCHECK(manager)
+ << "There is no Bluetooth low energy connection manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<BluetoothLowEnergyConnection>.";
+ return manager;
+}
+
+typedef extensions::ApiResourceManager<
+ extensions::BluetoothLowEnergyNotifySession> NotifySessionResourceManager;
+NotifySessionResourceManager* GetNotifySessionResourceManager(
+ content::BrowserContext* context) {
+ NotifySessionResourceManager* manager =
+ NotifySessionResourceManager::Get(context);
+ DCHECK(manager)
+ << "There is no Bluetooth low energy value update session manager."
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<BluetoothLowEnergyNotifySession>.";
+ return manager;
+}
+
+} // namespace
+
+namespace extensions {
+
+BluetoothLowEnergyEventRouter::BluetoothLowEnergyEventRouter(
+ content::BrowserContext* context)
+ : adapter_(NULL), browser_context_(context), weak_ptr_factory_(this) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(browser_context_);
+ VLOG(1) << "Initializing BluetoothLowEnergyEventRouter.";
+
+ if (!IsBluetoothSupported()) {
+ VLOG(1) << "Bluetooth not supported on the current platform.";
+ return;
+ }
+}
+
+BluetoothLowEnergyEventRouter::~BluetoothLowEnergyEventRouter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!adapter_.get())
+ return;
+
+ adapter_->RemoveObserver(this);
+ adapter_ = NULL;
+}
+
+bool BluetoothLowEnergyEventRouter::IsBluetoothSupported() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return adapter_.get() ||
+ BluetoothAdapterFactory::IsBluetoothAdapterAvailable();
+}
+
+bool BluetoothLowEnergyEventRouter::InitializeAdapterAndInvokeCallback(
+ const base::Closure& callback) {
+ if (!IsBluetoothSupported())
+ return false;
+
+ if (adapter_.get()) {
+ callback.Run();
+ return true;
+ }
+
+ BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnGetAdapter,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+ return true;
+}
+
+bool BluetoothLowEnergyEventRouter::HasAdapter() const {
+ return (adapter_.get() != NULL);
+}
+
+void BluetoothLowEnergyEventRouter::Connect(
+ bool persistent,
+ const Extension* extension,
+ const std::string& device_address,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ const std::string extension_id = extension->id();
+ const std::string connect_id = extension_id + device_address;
+
+ if (connecting_devices_.count(connect_id) != 0) {
+ error_callback.Run(kStatusErrorInProgress);
+ return;
+ }
+
+ BluetoothLowEnergyConnection* conn =
+ FindConnection(extension_id, device_address);
+ if (conn) {
+ if (conn->GetConnection()->IsConnected()) {
+ VLOG(1) << "Application already connected to device: " << device_address;
+ error_callback.Run(kStatusErrorAlreadyConnected);
+ return;
+ }
+
+ // There is a connection object but it's no longer active. Simply remove it.
+ RemoveConnection(extension_id, device_address);
+ }
+
+ BluetoothDevice* device = adapter_->GetDevice(device_address);
+ if (!device) {
+ VLOG(1) << "Bluetooth device not found: " << device_address;
+ error_callback.Run(kStatusErrorNotFound);
+ return;
+ }
+
+ connecting_devices_.insert(connect_id);
+ device->CreateGattConnection(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnCreateGattConnection,
+ weak_ptr_factory_.GetWeakPtr(),
+ persistent,
+ extension_id,
+ device_address,
+ callback),
+ base::Bind(&BluetoothLowEnergyEventRouter::OnConnectError,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ device_address,
+ error_callback));
+}
+
+void BluetoothLowEnergyEventRouter::Disconnect(
+ const Extension* extension,
+ const std::string& device_address,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ const std::string extension_id = extension->id();
+ const std::string disconnect_id = extension_id + device_address;
+
+ if (disconnecting_devices_.count(disconnect_id) != 0) {
+ error_callback.Run(kStatusErrorInProgress);
+ return;
+ }
+
+ BluetoothLowEnergyConnection* conn =
+ FindConnection(extension_id, device_address);
+ if (!conn || !conn->GetConnection()->IsConnected()) {
+ VLOG(1) << "Application not connected to device: " << device_address;
+ error_callback.Run(kStatusErrorNotConnected);
+ return;
+ }
+
+ disconnecting_devices_.insert(disconnect_id);
+ conn->GetConnection()->Disconnect(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnDisconnect,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ device_address,
+ callback));
+}
+
+bool BluetoothLowEnergyEventRouter::GetServices(
+ const std::string& device_address,
+ ServiceList* out_services) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(out_services);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ return false;
+ }
+
+ BluetoothDevice* device = adapter_->GetDevice(device_address);
+ if (!device) {
+ VLOG(1) << "Bluetooth device not found: " << device_address;
+ return false;
+ }
+
+ out_services->clear();
+
+ const std::vector<BluetoothGattService*>& services =
+ device->GetGattServices();
+ for (std::vector<BluetoothGattService*>::const_iterator iter =
+ services.begin();
+ iter != services.end();
+ ++iter) {
+ // Populate an API service and add it to the return value.
+ const BluetoothGattService* service = *iter;
+ linked_ptr<apibtle::Service> api_service(new apibtle::Service());
+ PopulateService(service, api_service.get());
+
+ out_services->push_back(api_service);
+ }
+
+ return true;
+}
+
+BluetoothLowEnergyEventRouter::Status BluetoothLowEnergyEventRouter::GetService(
+ const std::string& instance_id,
+ apibtle::Service* out_service) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(out_service);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ return kStatusErrorFailed;
+ }
+
+ BluetoothGattService* gatt_service = FindServiceById(instance_id);
+ if (!gatt_service) {
+ VLOG(1) << "Service not found: " << instance_id;
+ return kStatusErrorNotFound;
+ }
+
+ PopulateService(gatt_service, out_service);
+ return kStatusSuccess;
+}
+
+BluetoothLowEnergyEventRouter::Status
+BluetoothLowEnergyEventRouter::GetIncludedServices(
+ const std::string& instance_id,
+ ServiceList* out_services) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(out_services);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ return kStatusErrorFailed;
+ }
+
+ BluetoothGattService* service = FindServiceById(instance_id);
+ if (!service) {
+ VLOG(1) << "Service not found: " << instance_id;
+ return kStatusErrorNotFound;
+ }
+
+ out_services->clear();
+
+ const std::vector<BluetoothGattService*>& includes =
+ service->GetIncludedServices();
+ for (std::vector<BluetoothGattService*>::const_iterator iter =
+ includes.begin();
+ iter != includes.end();
+ ++iter) {
+ // Populate an API service and add it to the return value.
+ const BluetoothGattService* included = *iter;
+ linked_ptr<apibtle::Service> api_service(new apibtle::Service());
+ PopulateService(included, api_service.get());
+
+ out_services->push_back(api_service);
+ }
+
+ return kStatusSuccess;
+}
+
+BluetoothLowEnergyEventRouter::Status
+BluetoothLowEnergyEventRouter::GetCharacteristics(
+ const Extension* extension,
+ const std::string& instance_id,
+ CharacteristicList* out_characteristics) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ DCHECK(out_characteristics);
+ if (!adapter_.get()) {
+ VLOG(1) << "BlutoothAdapter not ready.";
+ return kStatusErrorFailed;
+ }
+
+ BluetoothGattService* service = FindServiceById(instance_id);
+ if (!service) {
+ VLOG(1) << "Service not found: " << instance_id;
+ return kStatusErrorNotFound;
+ }
+
+ BluetoothPermissionRequest request(service->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access the characteristics of this "
+ << "service: " << instance_id;
+ return kStatusErrorPermissionDenied;
+ }
+
+ out_characteristics->clear();
+
+ const std::vector<BluetoothGattCharacteristic*>& characteristics =
+ service->GetCharacteristics();
+ for (std::vector<BluetoothGattCharacteristic*>::const_iterator iter =
+ characteristics.begin();
+ iter != characteristics.end();
+ ++iter) {
+ // Populate an API characteristic and add it to the return value.
+ const BluetoothGattCharacteristic* characteristic = *iter;
+ linked_ptr<apibtle::Characteristic> api_characteristic(
+ new apibtle::Characteristic());
+ PopulateCharacteristic(characteristic, api_characteristic.get());
+
+ out_characteristics->push_back(api_characteristic);
+ }
+
+ return kStatusSuccess;
+}
+
+BluetoothLowEnergyEventRouter::Status
+BluetoothLowEnergyEventRouter::GetCharacteristic(
+ const Extension* extension,
+ const std::string& instance_id,
+ apibtle::Characteristic* out_characteristic) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ DCHECK(out_characteristic);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ return kStatusErrorFailed;
+ }
+
+ BluetoothGattCharacteristic* characteristic =
+ FindCharacteristicById(instance_id);
+ if (!characteristic) {
+ VLOG(1) << "Characteristic not found: " << instance_id;
+ return kStatusErrorNotFound;
+ }
+
+ BluetoothPermissionRequest request(
+ characteristic->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this characteristic: "
+ << instance_id;
+ return kStatusErrorPermissionDenied;
+ }
+
+ PopulateCharacteristic(characteristic, out_characteristic);
+ return kStatusSuccess;
+}
+
+BluetoothLowEnergyEventRouter::Status
+BluetoothLowEnergyEventRouter::GetDescriptors(
+ const Extension* extension,
+ const std::string& instance_id,
+ DescriptorList* out_descriptors) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ DCHECK(out_descriptors);
+ if (!adapter_.get()) {
+ VLOG(1) << "BlutoothAdapter not ready.";
+ return kStatusErrorFailed;
+ }
+
+ BluetoothGattCharacteristic* characteristic =
+ FindCharacteristicById(instance_id);
+ if (!characteristic) {
+ VLOG(1) << "Characteristic not found: " << instance_id;
+ return kStatusErrorNotFound;
+ }
+
+ BluetoothPermissionRequest request(
+ characteristic->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access the descriptors of this "
+ << "characteristic: " << instance_id;
+ return kStatusErrorPermissionDenied;
+ }
+
+ out_descriptors->clear();
+
+ const std::vector<BluetoothGattDescriptor*>& descriptors =
+ characteristic->GetDescriptors();
+ for (std::vector<BluetoothGattDescriptor*>::const_iterator iter =
+ descriptors.begin();
+ iter != descriptors.end();
+ ++iter) {
+ // Populate an API descriptor and add it to the return value.
+ const BluetoothGattDescriptor* descriptor = *iter;
+ linked_ptr<apibtle::Descriptor> api_descriptor(new apibtle::Descriptor());
+ PopulateDescriptor(descriptor, api_descriptor.get());
+
+ out_descriptors->push_back(api_descriptor);
+ }
+
+ return kStatusSuccess;
+}
+
+BluetoothLowEnergyEventRouter::Status
+BluetoothLowEnergyEventRouter::GetDescriptor(
+ const Extension* extension,
+ const std::string& instance_id,
+ core_api::bluetooth_low_energy::Descriptor* out_descriptor) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ DCHECK(out_descriptor);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ return kStatusErrorFailed;
+ }
+
+ BluetoothGattDescriptor* descriptor = FindDescriptorById(instance_id);
+ if (!descriptor) {
+ VLOG(1) << "Descriptor not found: " << instance_id;
+ return kStatusErrorNotFound;
+ }
+
+ BluetoothPermissionRequest request(
+ descriptor->GetCharacteristic()->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this descriptor: "
+ << instance_id;
+ return kStatusErrorPermissionDenied;
+ }
+
+ PopulateDescriptor(descriptor, out_descriptor);
+ return kStatusSuccess;
+}
+
+void BluetoothLowEnergyEventRouter::ReadCharacteristicValue(
+ const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ BluetoothGattCharacteristic* characteristic =
+ FindCharacteristicById(instance_id);
+ if (!characteristic) {
+ VLOG(1) << "Characteristic not found: " << instance_id;
+ error_callback.Run(kStatusErrorNotFound);
+ return;
+ }
+
+ BluetoothPermissionRequest request(
+ characteristic->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this characteristic: "
+ << instance_id;
+ error_callback.Run(kStatusErrorPermissionDenied);
+ return;
+ }
+
+ characteristic->ReadRemoteCharacteristic(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnValueSuccess,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback),
+ base::Bind(&BluetoothLowEnergyEventRouter::OnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ error_callback));
+}
+
+void BluetoothLowEnergyEventRouter::WriteCharacteristicValue(
+ const Extension* extension,
+ const std::string& instance_id,
+ const std::vector<uint8>& value,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ BluetoothGattCharacteristic* characteristic =
+ FindCharacteristicById(instance_id);
+ if (!characteristic) {
+ VLOG(1) << "Characteristic not found: " << instance_id;
+ error_callback.Run(kStatusErrorNotFound);
+ return;
+ }
+
+ BluetoothPermissionRequest request(
+ characteristic->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this characteristic: "
+ << instance_id;
+ error_callback.Run(kStatusErrorPermissionDenied);
+ return;
+ }
+
+ characteristic->WriteRemoteCharacteristic(
+ value,
+ callback,
+ base::Bind(&BluetoothLowEnergyEventRouter::OnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ error_callback));
+}
+
+void BluetoothLowEnergyEventRouter::StartCharacteristicNotifications(
+ bool persistent,
+ const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ const std::string extension_id = extension->id();
+ const std::string session_id = extension_id + instance_id;
+
+ if (pending_session_calls_.count(session_id) != 0) {
+ error_callback.Run(kStatusErrorInProgress);
+ return;
+ }
+
+ BluetoothLowEnergyNotifySession* session =
+ FindNotifySession(extension_id, instance_id);
+ if (session) {
+ if (session->GetSession()->IsActive()) {
+ VLOG(1) << "Application has already enabled notifications from "
+ << "characteristic: " << instance_id;
+ error_callback.Run(kStatusErrorAlreadyNotifying);
+ return;
+ }
+
+ RemoveNotifySession(extension_id, instance_id);
+ }
+
+ BluetoothGattCharacteristic* characteristic =
+ FindCharacteristicById(instance_id);
+ if (!characteristic) {
+ VLOG(1) << "Characteristic not found: " << instance_id;
+ error_callback.Run(kStatusErrorNotFound);
+ return;
+ }
+
+ BluetoothPermissionRequest request(
+ characteristic->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this characteristic: "
+ << instance_id;
+ error_callback.Run(kStatusErrorPermissionDenied);
+ return;
+ }
+
+ pending_session_calls_.insert(session_id);
+ characteristic->StartNotifySession(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnStartNotifySession,
+ weak_ptr_factory_.GetWeakPtr(),
+ persistent,
+ extension_id,
+ instance_id,
+ callback),
+ base::Bind(&BluetoothLowEnergyEventRouter::OnStartNotifySessionError,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ instance_id,
+ error_callback));
+}
+
+void BluetoothLowEnergyEventRouter::StopCharacteristicNotifications(
+ const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ const std::string extension_id = extension->id();
+
+ BluetoothLowEnergyNotifySession* session =
+ FindNotifySession(extension_id, instance_id);
+ if (!session || !session->GetSession()->IsActive()) {
+ VLOG(1) << "Application has not enabled notifications from "
+ << "characteristic: " << instance_id;
+ error_callback.Run(kStatusErrorNotNotifying);
+ return;
+ }
+
+ session->GetSession()->Stop(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnStopNotifySession,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ instance_id,
+ callback));
+}
+
+void BluetoothLowEnergyEventRouter::ReadDescriptorValue(
+ const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ BluetoothGattDescriptor* descriptor = FindDescriptorById(instance_id);
+ if (!descriptor) {
+ VLOG(1) << "Descriptor not found: " << instance_id;
+ error_callback.Run(kStatusErrorNotFound);
+ return;
+ }
+
+ BluetoothPermissionRequest request(
+ descriptor->GetCharacteristic()->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this descriptor: "
+ << instance_id;
+ error_callback.Run(kStatusErrorPermissionDenied);
+ return;
+ }
+
+ descriptor->ReadRemoteDescriptor(
+ base::Bind(&BluetoothLowEnergyEventRouter::OnValueSuccess,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback),
+ base::Bind(&BluetoothLowEnergyEventRouter::OnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ error_callback));
+}
+
+void BluetoothLowEnergyEventRouter::WriteDescriptorValue(
+ const Extension* extension,
+ const std::string& instance_id,
+ const std::vector<uint8>& value,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(extension);
+ if (!adapter_.get()) {
+ VLOG(1) << "BluetoothAdapter not ready.";
+ error_callback.Run(kStatusErrorFailed);
+ return;
+ }
+
+ BluetoothGattDescriptor* descriptor = FindDescriptorById(instance_id);
+ if (!descriptor) {
+ VLOG(1) << "Descriptor not found: " << instance_id;
+ error_callback.Run(kStatusErrorNotFound);
+ return;
+ }
+
+ BluetoothPermissionRequest request(
+ descriptor->GetCharacteristic()->GetService()->GetUUID().value());
+ if (!BluetoothManifestData::CheckRequest(extension, request)) {
+ VLOG(1) << "App has no permission to access this descriptor: "
+ << instance_id;
+ error_callback.Run(kStatusErrorPermissionDenied);
+ return;
+ }
+
+ descriptor->WriteRemoteDescriptor(
+ value,
+ callback,
+ base::Bind(&BluetoothLowEnergyEventRouter::OnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ error_callback));
+}
+
+void BluetoothLowEnergyEventRouter::SetAdapterForTesting(
+ device::BluetoothAdapter* adapter) {
+ adapter_ = adapter;
+ InitializeIdentifierMappings();
+}
+
+void BluetoothLowEnergyEventRouter::GattServiceAdded(
+ BluetoothAdapter* adapter,
+ BluetoothDevice* device,
+ BluetoothGattService* service) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT service added: " << service->GetIdentifier();
+
+ DCHECK(service_id_to_device_address_.find(service->GetIdentifier()) ==
+ service_id_to_device_address_.end());
+
+ service_id_to_device_address_[service->GetIdentifier()] =
+ device->GetAddress();
+}
+
+void BluetoothLowEnergyEventRouter::GattServiceRemoved(
+ BluetoothAdapter* adapter,
+ BluetoothDevice* device,
+ BluetoothGattService* service) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT service removed: " << service->GetIdentifier();
+
+ DCHECK(service_id_to_device_address_.find(service->GetIdentifier()) !=
+ service_id_to_device_address_.end());
+
+ DCHECK(device->GetAddress() ==
+ service_id_to_device_address_[service->GetIdentifier()]);
+ service_id_to_device_address_.erase(service->GetIdentifier());
+
+ // Signal API event.
+ apibtle::Service api_service;
+ PopulateService(service, &api_service);
+
+ scoped_ptr<base::ListValue> args =
+ apibtle::OnServiceRemoved::Create(api_service);
+ scoped_ptr<Event> event(
+ new Event(apibtle::OnServiceRemoved::kEventName, args.Pass()));
+ EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
+}
+
+void BluetoothLowEnergyEventRouter::GattDiscoveryCompleteForService(
+ BluetoothAdapter* adapter,
+ BluetoothGattService* service) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT service discovery complete: " << service->GetIdentifier();
+
+ DCHECK(service_id_to_device_address_.find(service->GetIdentifier()) !=
+ service_id_to_device_address_.end());
+
+ // Signal the service added event here.
+ apibtle::Service api_service;
+ PopulateService(service, &api_service);
+
+ scoped_ptr<base::ListValue> args =
+ apibtle::OnServiceAdded::Create(api_service);
+ scoped_ptr<Event> event(
+ new Event(apibtle::OnServiceAdded::kEventName, args.Pass()));
+ EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
+}
+
+void BluetoothLowEnergyEventRouter::GattServiceChanged(
+ BluetoothAdapter* adapter,
+ BluetoothGattService* service) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT service changed: " << service->GetIdentifier();
+ DCHECK(service_id_to_device_address_.find(service->GetIdentifier()) !=
+ service_id_to_device_address_.end());
+
+ // Signal API event.
+ apibtle::Service api_service;
+ PopulateService(service, &api_service);
+
+ DispatchEventToExtensionsWithPermission(
+ apibtle::OnServiceChanged::kEventName,
+ service->GetUUID(),
+ "" /* characteristic_id */,
+ apibtle::OnServiceChanged::Create(api_service));
+}
+
+void BluetoothLowEnergyEventRouter::GattCharacteristicAdded(
+ BluetoothAdapter* adapter,
+ BluetoothGattCharacteristic* characteristic) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT characteristic added: " << characteristic->GetIdentifier();
+
+ BluetoothGattService* service = characteristic->GetService();
+ DCHECK(service);
+
+ DCHECK(chrc_id_to_service_id_.find(characteristic->GetIdentifier()) ==
+ chrc_id_to_service_id_.end());
+ DCHECK(service_id_to_device_address_.find(service->GetIdentifier()) !=
+ service_id_to_device_address_.end());
+
+ chrc_id_to_service_id_[characteristic->GetIdentifier()] =
+ service->GetIdentifier();
+}
+
+void BluetoothLowEnergyEventRouter::GattCharacteristicRemoved(
+ BluetoothAdapter* adapter,
+ BluetoothGattCharacteristic* characteristic) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT characteristic removed: " << characteristic->GetIdentifier();
+
+ BluetoothGattService* service = characteristic->GetService();
+ DCHECK(service);
+
+ DCHECK(chrc_id_to_service_id_.find(characteristic->GetIdentifier()) !=
+ chrc_id_to_service_id_.end());
+ DCHECK(service->GetIdentifier() ==
+ chrc_id_to_service_id_[characteristic->GetIdentifier()]);
+
+ chrc_id_to_service_id_.erase(characteristic->GetIdentifier());
+}
+
+void BluetoothLowEnergyEventRouter::GattDescriptorAdded(
+ BluetoothAdapter* adapter,
+ BluetoothGattDescriptor* descriptor) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT descriptor added: " << descriptor->GetIdentifier();
+
+ BluetoothGattCharacteristic* characteristic = descriptor->GetCharacteristic();
+ DCHECK(characteristic);
+
+ DCHECK(desc_id_to_chrc_id_.find(descriptor->GetIdentifier()) ==
+ desc_id_to_chrc_id_.end());
+ DCHECK(chrc_id_to_service_id_.find(characteristic->GetIdentifier()) !=
+ chrc_id_to_service_id_.end());
+
+ desc_id_to_chrc_id_[descriptor->GetIdentifier()] =
+ characteristic->GetIdentifier();
+}
+
+void BluetoothLowEnergyEventRouter::GattDescriptorRemoved(
+ BluetoothAdapter* adapter,
+ BluetoothGattDescriptor* descriptor) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT descriptor removed: " << descriptor->GetIdentifier();
+
+ BluetoothGattCharacteristic* characteristic = descriptor->GetCharacteristic();
+ DCHECK(characteristic);
+
+ DCHECK(desc_id_to_chrc_id_.find(descriptor->GetIdentifier()) !=
+ desc_id_to_chrc_id_.end());
+ DCHECK(characteristic->GetIdentifier() ==
+ desc_id_to_chrc_id_[descriptor->GetIdentifier()]);
+
+ desc_id_to_chrc_id_.erase(descriptor->GetIdentifier());
+}
+
+void BluetoothLowEnergyEventRouter::GattCharacteristicValueChanged(
+ BluetoothAdapter* adapter,
+ BluetoothGattCharacteristic* characteristic,
+ const std::vector<uint8>& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT characteristic value changed: "
+ << characteristic->GetIdentifier();
+
+ BluetoothGattService* service = characteristic->GetService();
+ DCHECK(service);
+
+ DCHECK(service_id_to_device_address_.find(service->GetIdentifier()) !=
+ service_id_to_device_address_.end());
+ DCHECK(chrc_id_to_service_id_.find(characteristic->GetIdentifier()) !=
+ chrc_id_to_service_id_.end());
+ DCHECK(chrc_id_to_service_id_[characteristic->GetIdentifier()] ==
+ service->GetIdentifier());
+
+ // Send the event; manually construct the arguments, instead of using
+ // apibtle::OnCharacteristicValueChanged::Create, as it doesn't convert
+ // lists of enums correctly.
+ apibtle::Characteristic api_characteristic;
+ PopulateCharacteristic(characteristic, &api_characteristic);
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(apibtle::CharacteristicToValue(&api_characteristic).release());
+
+ DispatchEventToExtensionsWithPermission(
+ apibtle::OnCharacteristicValueChanged::kEventName,
+ service->GetUUID(),
+ characteristic->GetIdentifier(),
+ args.Pass());
+}
+
+void BluetoothLowEnergyEventRouter::GattDescriptorValueChanged(
+ BluetoothAdapter* adapter,
+ BluetoothGattDescriptor* descriptor,
+ const std::vector<uint8>& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(adapter, adapter_.get());
+ VLOG(2) << "GATT descriptor value changed: " << descriptor->GetIdentifier();
+
+ BluetoothGattCharacteristic* characteristic = descriptor->GetCharacteristic();
+ DCHECK(characteristic);
+
+ DCHECK(desc_id_to_chrc_id_.find(descriptor->GetIdentifier()) !=
+ desc_id_to_chrc_id_.end());
+ DCHECK(characteristic->GetIdentifier() ==
+ desc_id_to_chrc_id_[descriptor->GetIdentifier()]);
+
+ // Send the event; manually construct the arguments, instead of using
+ // apibtle::OnDescriptorValueChanged::Create, as it doesn't convert
+ // lists of enums correctly.
+ apibtle::Descriptor api_descriptor;
+ PopulateDescriptor(descriptor, &api_descriptor);
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(apibtle::DescriptorToValue(&api_descriptor).release());
+
+ DispatchEventToExtensionsWithPermission(
+ apibtle::OnDescriptorValueChanged::kEventName,
+ characteristic->GetService()->GetUUID(),
+ "" /* characteristic_id */,
+ args.Pass());
+}
+
+void BluetoothLowEnergyEventRouter::OnGetAdapter(
+ const base::Closure& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ adapter_ = adapter;
+
+ // Initialize instance ID mappings for all discovered GATT objects and add
+ // observers.
+ InitializeIdentifierMappings();
+ adapter_->AddObserver(this);
+
+ callback.Run();
+}
+
+void BluetoothLowEnergyEventRouter::InitializeIdentifierMappings() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(service_id_to_device_address_.empty());
+ DCHECK(chrc_id_to_service_id_.empty());
+
+ // Devices
+ BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
+ for (BluetoothAdapter::DeviceList::iterator iter = devices.begin();
+ iter != devices.end();
+ ++iter) {
+ BluetoothDevice* device = *iter;
+
+ // Services
+ std::vector<BluetoothGattService*> services = device->GetGattServices();
+ for (std::vector<BluetoothGattService*>::iterator siter = services.begin();
+ siter != services.end();
+ ++siter) {
+ BluetoothGattService* service = *siter;
+
+ const std::string& service_id = service->GetIdentifier();
+ service_id_to_device_address_[service_id] = device->GetAddress();
+
+ // Characteristics
+ const std::vector<BluetoothGattCharacteristic*>& characteristics =
+ service->GetCharacteristics();
+ for (std::vector<BluetoothGattCharacteristic*>::const_iterator citer =
+ characteristics.begin();
+ citer != characteristics.end();
+ ++citer) {
+ BluetoothGattCharacteristic* characteristic = *citer;
+
+ const std::string& chrc_id = characteristic->GetIdentifier();
+ chrc_id_to_service_id_[chrc_id] = service_id;
+
+ // Descriptors
+ const std::vector<BluetoothGattDescriptor*>& descriptors =
+ characteristic->GetDescriptors();
+ for (std::vector<BluetoothGattDescriptor*>::const_iterator diter =
+ descriptors.begin();
+ diter != descriptors.end();
+ ++diter) {
+ BluetoothGattDescriptor* descriptor = *diter;
+
+ const std::string& desc_id = descriptor->GetIdentifier();
+ desc_id_to_chrc_id_[desc_id] = chrc_id;
+ }
+ }
+ }
+ }
+}
+
+void BluetoothLowEnergyEventRouter::DispatchEventToExtensionsWithPermission(
+ const std::string& event_name,
+ const device::BluetoothUUID& uuid,
+ const std::string& characteristic_id,
+ scoped_ptr<base::ListValue> args) {
+ // Obtain the listeners of |event_name|. The list can contain multiple
+ // entries for the same extension, so we keep track of the extensions that we
+ // already sent the event to, since we want the send an event to an extension
+ // only once.
+ BluetoothPermissionRequest request(uuid.value());
+ std::set<std::string> handled_extensions;
+ const EventListenerMap::ListenerList listeners =
+ EventRouter::Get(browser_context_)->listeners().GetEventListenersByName(
+ event_name);
+
+ for (EventListenerMap::ListenerList::const_iterator iter = listeners.begin();
+ iter != listeners.end();
+ ++iter) {
+ const std::string extension_id = (*iter)->extension_id();
+ if (handled_extensions.find(extension_id) != handled_extensions.end())
+ continue;
+
+ handled_extensions.insert(extension_id);
+
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context_)
+ ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+
+ // For all API methods, the "low_energy" permission check is handled by
+ // BluetoothLowEnergyExtensionFunction but for events we have to do the
+ // check here.
+ if (!BluetoothManifestData::CheckRequest(extension, request) ||
+ !BluetoothManifestData::CheckLowEnergyPermitted(extension))
+ continue;
+
+ // If |event_name| is "onCharacteristicValueChanged", then send the
+ // event only if the extension has requested notifications from the
+ // related characteristic.
+ if (event_name == apibtle::OnCharacteristicValueChanged::kEventName &&
+ !characteristic_id.empty() &&
+ !FindNotifySession(extension_id, characteristic_id))
+ continue;
+
+ // Send the event.
+ scoped_ptr<base::ListValue> args_copy(args->DeepCopy());
+ scoped_ptr<Event> event(new Event(event_name, args_copy.Pass()));
+ EventRouter::Get(browser_context_)->DispatchEventToExtension(
+ extension_id, event.Pass());
+ }
+}
+
+BluetoothGattService* BluetoothLowEnergyEventRouter::FindServiceById(
+ const std::string& instance_id) const {
+ InstanceIdMap::const_iterator iter =
+ service_id_to_device_address_.find(instance_id);
+ if (iter == service_id_to_device_address_.end()) {
+ VLOG(1) << "GATT service identifier unknown: " << instance_id;
+ return NULL;
+ }
+
+ const std::string& address = iter->second;
+
+ BluetoothDevice* device = adapter_->GetDevice(address);
+ if (!device) {
+ VLOG(1) << "Bluetooth device not found: " << address;
+ return NULL;
+ }
+
+ BluetoothGattService* service = device->GetGattService(instance_id);
+ if (!service) {
+ VLOG(1) << "GATT service with ID \"" << instance_id
+ << "\" not found on device \"" << address << "\"";
+ return NULL;
+ }
+
+ return service;
+}
+
+BluetoothGattCharacteristic*
+BluetoothLowEnergyEventRouter::FindCharacteristicById(
+ const std::string& instance_id) const {
+ InstanceIdMap::const_iterator iter = chrc_id_to_service_id_.find(instance_id);
+ if (iter == chrc_id_to_service_id_.end()) {
+ VLOG(1) << "GATT characteristic identifier unknown: " << instance_id;
+ return NULL;
+ }
+
+ const std::string& service_id = iter->second;
+
+ BluetoothGattService* service = FindServiceById(service_id);
+ if (!service) {
+ VLOG(1) << "Failed to obtain service for characteristic: " << instance_id;
+ return NULL;
+ }
+
+ BluetoothGattCharacteristic* characteristic =
+ service->GetCharacteristic(instance_id);
+ if (!characteristic) {
+ VLOG(1) << "GATT characteristic with ID \"" << instance_id
+ << "\" not found on service \"" << service_id << "\"";
+ return NULL;
+ }
+
+ return characteristic;
+}
+
+BluetoothGattDescriptor* BluetoothLowEnergyEventRouter::FindDescriptorById(
+ const std::string& instance_id) const {
+ InstanceIdMap::const_iterator iter = desc_id_to_chrc_id_.find(instance_id);
+ if (iter == desc_id_to_chrc_id_.end()) {
+ VLOG(1) << "GATT descriptor identifier unknown: " << instance_id;
+ return NULL;
+ }
+
+ const std::string& chrc_id = iter->second;
+ BluetoothGattCharacteristic* chrc = FindCharacteristicById(chrc_id);
+ if (!chrc) {
+ VLOG(1) << "Failed to obtain characteristic for descriptor: "
+ << instance_id;
+ return NULL;
+ }
+
+ BluetoothGattDescriptor* descriptor = chrc->GetDescriptor(instance_id);
+ if (!descriptor) {
+ VLOG(1) << "GATT descriptor with ID \"" << instance_id
+ << "\" not found on characteristic \"" << chrc_id << "\"";
+ return NULL;
+ }
+
+ return descriptor;
+}
+
+void BluetoothLowEnergyEventRouter::OnValueSuccess(
+ const base::Closure& callback,
+ const std::vector<uint8>& value) {
+ VLOG(2) << "Remote characteristic/descriptor value read successful.";
+ callback.Run();
+}
+
+void BluetoothLowEnergyEventRouter::OnCreateGattConnection(
+ bool persistent,
+ const std::string& extension_id,
+ const std::string& device_address,
+ const base::Closure& callback,
+ scoped_ptr<BluetoothGattConnection> connection) {
+ VLOG(2) << "GATT connection created.";
+ DCHECK(connection.get());
+ DCHECK(!FindConnection(extension_id, device_address));
+ DCHECK_EQ(device_address, connection->GetDeviceAddress());
+
+ const std::string connect_id = extension_id + device_address;
+ DCHECK_NE(0U, connecting_devices_.count(connect_id));
+
+ BluetoothLowEnergyConnection* conn = new BluetoothLowEnergyConnection(
+ persistent, extension_id, connection.Pass());
+ ConnectionResourceManager* manager =
+ GetConnectionResourceManager(browser_context_);
+ manager->Add(conn);
+
+ connecting_devices_.erase(connect_id);
+ callback.Run();
+}
+
+void BluetoothLowEnergyEventRouter::OnDisconnect(
+ const std::string& extension_id,
+ const std::string& device_address,
+ const base::Closure& callback) {
+ VLOG(2) << "GATT connection terminated.";
+
+ const std::string disconnect_id = extension_id + device_address;
+ DCHECK_NE(0U, disconnecting_devices_.count(disconnect_id));
+
+ if (!RemoveConnection(extension_id, device_address)) {
+ VLOG(1) << "The connection was removed before disconnect completed, id: "
+ << extension_id << ", device: " << device_address;
+ }
+
+ disconnecting_devices_.erase(disconnect_id);
+ callback.Run();
+}
+
+void BluetoothLowEnergyEventRouter::OnError(
+ const ErrorCallback& error_callback) {
+ VLOG(2) << "Remote characteristic/descriptor value read/write failed.";
+ error_callback.Run(kStatusErrorFailed);
+}
+
+void BluetoothLowEnergyEventRouter::OnConnectError(
+ const std::string& extension_id,
+ const std::string& device_address,
+ const ErrorCallback& error_callback,
+ BluetoothDevice::ConnectErrorCode error_code) {
+ VLOG(2) << "Failed to create GATT connection: " << error_code;
+
+ const std::string connect_id = extension_id + device_address;
+ DCHECK_NE(0U, connecting_devices_.count(connect_id));
+
+ connecting_devices_.erase(connect_id);
+ error_callback.Run(kStatusErrorFailed);
+}
+
+void BluetoothLowEnergyEventRouter::OnStartNotifySession(
+ bool persistent,
+ const std::string& extension_id,
+ const std::string& characteristic_id,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothGattNotifySession> session) {
+ VLOG(2) << "Value update session created for characteristic: "
+ << characteristic_id;
+ DCHECK(session.get());
+ DCHECK(!FindNotifySession(extension_id, characteristic_id));
+ DCHECK_EQ(characteristic_id, session->GetCharacteristicIdentifier());
+
+ const std::string session_id = extension_id + characteristic_id;
+ DCHECK_NE(0U, pending_session_calls_.count(session_id));
+
+ BluetoothLowEnergyNotifySession* resource =
+ new BluetoothLowEnergyNotifySession(
+ persistent, extension_id, session.Pass());
+
+ NotifySessionResourceManager* manager =
+ GetNotifySessionResourceManager(browser_context_);
+ manager->Add(resource);
+
+ pending_session_calls_.erase(session_id);
+ callback.Run();
+}
+
+void BluetoothLowEnergyEventRouter::OnStartNotifySessionError(
+ const std::string& extension_id,
+ const std::string& characteristic_id,
+ const ErrorCallback& error_callback) {
+ VLOG(2) << "Failed to create value update session for characteristic: "
+ << characteristic_id;
+
+ const std::string session_id = extension_id + characteristic_id;
+ DCHECK_NE(0U, pending_session_calls_.count(session_id));
+
+ pending_session_calls_.erase(session_id);
+ error_callback.Run(kStatusErrorFailed);
+}
+
+void BluetoothLowEnergyEventRouter::OnStopNotifySession(
+ const std::string& extension_id,
+ const std::string& characteristic_id,
+ const base::Closure& callback) {
+ VLOG(2) << "Value update session terminated.";
+
+ if (!RemoveNotifySession(extension_id, characteristic_id)) {
+ VLOG(1) << "The value update session was removed before Stop completed, "
+ << "id: " << extension_id
+ << ", characteristic: " << characteristic_id;
+ }
+
+ callback.Run();
+}
+
+BluetoothLowEnergyConnection* BluetoothLowEnergyEventRouter::FindConnection(
+ const std::string& extension_id,
+ const std::string& device_address) {
+ ConnectionResourceManager* manager =
+ GetConnectionResourceManager(browser_context_);
+
+ base::hash_set<int>* connection_ids = manager->GetResourceIds(extension_id);
+ if (!connection_ids)
+ return NULL;
+
+ for (base::hash_set<int>::const_iterator iter = connection_ids->begin();
+ iter != connection_ids->end();
+ ++iter) {
+ extensions::BluetoothLowEnergyConnection* conn =
+ manager->Get(extension_id, *iter);
+ if (!conn)
+ continue;
+
+ if (conn->GetConnection()->GetDeviceAddress() == device_address)
+ return conn;
+ }
+
+ return NULL;
+}
+
+bool BluetoothLowEnergyEventRouter::RemoveConnection(
+ const std::string& extension_id,
+ const std::string& device_address) {
+ ConnectionResourceManager* manager =
+ GetConnectionResourceManager(browser_context_);
+
+ base::hash_set<int>* connection_ids = manager->GetResourceIds(extension_id);
+ if (!connection_ids)
+ return false;
+
+ for (base::hash_set<int>::const_iterator iter = connection_ids->begin();
+ iter != connection_ids->end();
+ ++iter) {
+ extensions::BluetoothLowEnergyConnection* conn =
+ manager->Get(extension_id, *iter);
+ if (!conn || conn->GetConnection()->GetDeviceAddress() != device_address)
+ continue;
+
+ manager->Remove(extension_id, *iter);
+ return true;
+ }
+
+ return false;
+}
+
+BluetoothLowEnergyNotifySession*
+BluetoothLowEnergyEventRouter::FindNotifySession(
+ const std::string& extension_id,
+ const std::string& characteristic_id) {
+ NotifySessionResourceManager* manager =
+ GetNotifySessionResourceManager(browser_context_);
+
+ base::hash_set<int>* ids = manager->GetResourceIds(extension_id);
+ if (!ids)
+ return NULL;
+
+ for (base::hash_set<int>::const_iterator iter = ids->begin();
+ iter != ids->end();
+ ++iter) {
+ BluetoothLowEnergyNotifySession* session =
+ manager->Get(extension_id, *iter);
+ if (!session)
+ continue;
+
+ if (session->GetSession()->GetCharacteristicIdentifier() ==
+ characteristic_id)
+ return session;
+ }
+
+ return NULL;
+}
+
+bool BluetoothLowEnergyEventRouter::RemoveNotifySession(
+ const std::string& extension_id,
+ const std::string& characteristic_id) {
+ NotifySessionResourceManager* manager =
+ GetNotifySessionResourceManager(browser_context_);
+
+ base::hash_set<int>* ids = manager->GetResourceIds(extension_id);
+ if (!ids)
+ return false;
+
+ for (base::hash_set<int>::const_iterator iter = ids->begin();
+ iter != ids->end();
+ ++iter) {
+ BluetoothLowEnergyNotifySession* session =
+ manager->Get(extension_id, *iter);
+ if (!session ||
+ session->GetSession()->GetCharacteristicIdentifier() !=
+ characteristic_id)
+ continue;
+
+ manager->Remove(extension_id, *iter);
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h
new file mode 100644
index 0000000..0cfab18
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h
@@ -0,0 +1,418 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_EVENT_ROUTER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/notification_observer.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_gatt_service.h"
+#include "extensions/common/api/bluetooth_low_energy.h"
+
+namespace base {
+
+class ListValue;
+
+} // namespace base
+
+namespace content {
+
+class BrowserContext;
+
+} // namespace content
+
+namespace device {
+
+class BluetoothGattNotifySession;
+
+} // namespace device
+
+namespace extensions {
+
+class BluetoothLowEnergyConnection;
+class BluetoothLowEnergyNotifySession;
+class Extension;
+
+// The BluetoothLowEnergyEventRouter is used by the bluetoothLowEnergy API to
+// interface with the internal Bluetooth API in device/bluetooth.
+class BluetoothLowEnergyEventRouter
+ : public device::BluetoothAdapter::Observer {
+ public:
+ explicit BluetoothLowEnergyEventRouter(content::BrowserContext* context);
+ virtual ~BluetoothLowEnergyEventRouter();
+
+ // Possible ways that an API method can fail or succeed.
+ enum Status {
+ kStatusSuccess = 0,
+ kStatusErrorPermissionDenied,
+ kStatusErrorNotFound,
+ kStatusErrorAlreadyConnected,
+ kStatusErrorAlreadyNotifying,
+ kStatusErrorNotConnected,
+ kStatusErrorNotNotifying,
+ kStatusErrorInProgress,
+ kStatusErrorFailed
+ };
+
+ // Error callback is used by asynchronous methods to report failures.
+ typedef base::Callback<void(Status)> ErrorCallback;
+
+ // Returns true if Bluetooth is supported on the current platform or if the
+ // internal |adapter_| instance has been initialized for testing.
+ bool IsBluetoothSupported() const;
+
+ // Obtains a handle on the BluetoothAdapter and invokes |callback|. Returns
+ // false, if Bluetooth is not supported. Otherwise, asynchronously initializes
+ // it and invokes |callback|. Until the first successful call to this method,
+ // none of the methods in this class will succeed and no device::Bluetooth*
+ // API events will be observed.
+ bool InitializeAdapterAndInvokeCallback(const base::Closure& callback);
+
+ // Returns true, if the BluetoothAdapter was initialized.
+ bool HasAdapter() const;
+
+ // Creates a GATT connection to the device with address |device_address| for
+ // extension |extension|. The connection is kept alive until the extension is
+ // unloaded, the device is removed, or is disconnect by the host subsystem.
+ // |error_callback| is called with an error status in case of failure. If
+ // |persistent| is true, then the allocated connection resource is persistent
+ // across unloads.
+ void Connect(bool persistent,
+ const Extension* extension,
+ const std::string& device_address,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Disconnects the currently open GATT connection of extension |extension| to
+ // device with address |device_address|. |error_callback| is called with an
+ // error status in case of failure, e.g. if the device is not found or the
+ // given
+ // extension does not have an open connection to the device.
+ void Disconnect(const Extension* extension,
+ const std::string& device_address,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Returns the list of core_api::bluetooth_low_energy::Service objects
+ // associated with the Bluetooth device with address |device_address| in
+ // |out_services|.
+ // Returns false, if no device with the given address is known. If the device
+ // is found but it has no GATT services, then returns true and leaves
+ // |out_services| empty. Returns true, on success. |out_services| must not
+ // be NULL. If it is non-empty, then its contents will be cleared.
+ typedef std::vector<linked_ptr<core_api::bluetooth_low_energy::Service> >
+ ServiceList;
+ bool GetServices(const std::string& device_address,
+ ServiceList* out_services) const;
+
+ // Populates |out_service| based on GATT service with instance ID
+ // |instance_id|. |out_service| must not be NULL.
+ Status GetService(const std::string& instance_id,
+ core_api::bluetooth_low_energy::Service* out_service) const;
+
+ // Populates |out_services| with the list of GATT services that are included
+ // by the GATT service with instance ID |instance_id|. Returns false, if not
+ // GATT service with the given ID is known. If the given service has no
+ // included services, then |out_service| will be empty. |out_service| must not
+ // be NULL. If it is non-empty, then its contents will be cleared.
+ Status GetIncludedServices(const std::string& instance_id,
+ ServiceList* out_services) const;
+
+ // Returns the list of core_api::bluetooth_low_energy::Characteristic objects
+ // associated with the GATT service with instance ID |instance_id| in
+ // |out_characteristics|. Returns false, if no service with the given instance
+ // ID is known. If the service is found but it has no characteristics, then
+ // returns true and leaves |out_characteristics| empty.
+ // |out_characteristics| must not be NULL and if it is non-empty,
+ // then its contents will be cleared. |extension| is the extension that made
+ // the call.
+ typedef std::vector<
+ linked_ptr<core_api::bluetooth_low_energy::Characteristic> >
+ CharacteristicList;
+ Status GetCharacteristics(const Extension* extension,
+ const std::string& instance_id,
+ CharacteristicList* out_characteristics) const;
+
+ // Populates |out_characteristic| based on GATT characteristic with instance
+ // ID |instance_id|. |out_characteristic| must not be NULL. |extension| is the
+ // extension that made the call.
+ Status GetCharacteristic(
+ const Extension* extension,
+ const std::string& instance_id,
+ core_api::bluetooth_low_energy::Characteristic* out_characteristic) const;
+
+ // Returns the list of core_api::bluetooth_low_energy::Descriptor objects
+ // associated with the GATT characteristic with instance ID |instance_id| in
+ // |out_descriptors|. If the characteristic is found but it has no
+ // descriptors, then returns true and leaves |out_descriptors| empty.
+ // |out_descriptors| must not be NULL and if it is non-empty,
+ // then its contents will be cleared. |extension| is the extension that made
+ // the call.
+ typedef std::vector<linked_ptr<core_api::bluetooth_low_energy::Descriptor> >
+ DescriptorList;
+ Status GetDescriptors(const Extension* extension,
+ const std::string& instance_id,
+ DescriptorList* out_descriptors) const;
+
+ // Populates |out_descriptor| based on GATT characteristic descriptor with
+ // instance ID |instance_id|. |out_descriptor| must not be NULL.
+ // |extension| is the extension that made the call.
+ Status GetDescriptor(
+ const Extension* extension,
+ const std::string& instance_id,
+ core_api::bluetooth_low_energy::Descriptor* out_descriptor) const;
+
+ // Sends a request to read the value of the characteristic with intance ID
+ // |instance_id|. Invokes |callback| on success and |error_callback| on
+ // failure. |extension| is the extension that made the call.
+ void ReadCharacteristicValue(const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Sends a request to write the value of the characteristic with instance ID
+ // |instance_id|. Invokes |callback| on success and |error_callback| on
+ // failure. |extension| is the extension that made the call.
+ void WriteCharacteristicValue(const Extension* extension,
+ const std::string& instance_id,
+ const std::vector<uint8>& value,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Sends a request to start characteristic notifications from characteristic
+ // with instance ID |instance_id|, for extension |extension|. Invokes
+ // |callback| on success and |error_callback| on failure. If |persistent| is
+ // true, then the allocated connection resource is persistent across unloads.
+ void StartCharacteristicNotifications(bool persistent,
+ const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Sends a request to stop characteristic notifications from characteristic
+ // with instance ID |instance_id|, for extension |extension|. Invokes
+ // |callback| on success and |error_callback| on failure.
+ void StopCharacteristicNotifications(const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Sends a request to read the value of the descriptor with instance ID
+ // |instance_id|. Invokes |callback| on success and |error_callback| on
+ // failure. |extension| is the extension that made the call.
+ void ReadDescriptorValue(const Extension* extension,
+ const std::string& instance_id,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Sends a request to write the value of the descriptor with instance ID
+ // |instance_id|. Invokes |callback| on success and |error_callback| on
+ // failure. |extension| is the extension that made the call.
+ void WriteDescriptorValue(const Extension* extension,
+ const std::string& instance_id,
+ const std::vector<uint8>& value,
+ const base::Closure& callback,
+ const ErrorCallback& error_callback);
+
+ // Initializes the adapter for testing. Used by unit tests only.
+ void SetAdapterForTesting(device::BluetoothAdapter* adapter);
+
+ // device::BluetoothAdapter::Observer overrides.
+ virtual void GattServiceAdded(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device,
+ device::BluetoothGattService* service) OVERRIDE;
+ virtual void GattServiceRemoved(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device,
+ device::BluetoothGattService* service) OVERRIDE;
+ virtual void GattDiscoveryCompleteForService(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattService* service) OVERRIDE;
+ virtual void GattServiceChanged(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattService* service) OVERRIDE;
+ virtual void GattCharacteristicAdded(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattCharacteristic* characteristic) OVERRIDE;
+ virtual void GattCharacteristicRemoved(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattCharacteristic* characteristic) OVERRIDE;
+ virtual void GattDescriptorAdded(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattDescriptor* descriptor) OVERRIDE;
+ virtual void GattDescriptorRemoved(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattDescriptor* descriptor) OVERRIDE;
+ virtual void GattCharacteristicValueChanged(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattCharacteristic* characteristic,
+ const std::vector<uint8>& value) OVERRIDE;
+ virtual void GattDescriptorValueChanged(
+ device::BluetoothAdapter* adapter,
+ device::BluetoothGattDescriptor* descriptor,
+ const std::vector<uint8>& value) OVERRIDE;
+
+ private:
+ // Called by BluetoothAdapterFactory.
+ void OnGetAdapter(const base::Closure& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter);
+
+ // Initializes the identifier for all existing GATT objects and devices.
+ // Called by OnGetAdapter and SetAdapterForTesting.
+ void InitializeIdentifierMappings();
+
+ // Sends the event named |event_name| to all listeners of that event that
+ // have the Bluetooth UUID manifest permission for UUID |uuid| and the
+ // "low_energy" manifest permission, with |args| as the argument to that
+ // event. If the event involves a characteristic, then |characteristic_id|
+ // should be the instance ID of the involved characteristic. Otherwise, an
+ // empty string should be passed.
+ void DispatchEventToExtensionsWithPermission(
+ const std::string& event_name,
+ const device::BluetoothUUID& uuid,
+ const std::string& characteristic_id,
+ scoped_ptr<base::ListValue> args);
+
+ // Returns a BluetoothGattService by its instance ID |instance_id|. Returns
+ // NULL, if the service cannot be found.
+ device::BluetoothGattService* FindServiceById(
+ const std::string& instance_id) const;
+
+ // Returns a BluetoothGattCharacteristic by its instance ID |instance_id|.
+ // Returns NULL, if the characteristic cannot be found.
+ device::BluetoothGattCharacteristic* FindCharacteristicById(
+ const std::string& instance_id) const;
+
+ // Returns a BluetoothGattDescriptor by its instance ID |instance_id|.
+ // Returns NULL, if the descriptor cannot be found.
+ device::BluetoothGattDescriptor* FindDescriptorById(
+ const std::string& instance_id) const;
+
+ // Called by BluetoothGattCharacteristic and BluetoothGattDescriptor in
+ // response to ReadRemoteCharacteristic and ReadRemoteDescriptor.
+ void OnValueSuccess(const base::Closure& callback,
+ const std::vector<uint8>& value);
+
+ // Called by BluetoothDevice in response to a call to CreateGattConnection.
+ void OnCreateGattConnection(
+ bool persistent,
+ const std::string& extension_id,
+ const std::string& device_address,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothGattConnection> connection);
+
+ // Called by BluetoothGattConnection in response to a call to Disconnect.
+ void OnDisconnect(const std::string& extension_id,
+ const std::string& device_address,
+ const base::Closure& callback);
+
+ // Called by BluetoothGattCharacteristic and BluetoothGattDescriptor in
+ // case of an error during the read/write operations.
+ void OnError(const ErrorCallback& error_callback);
+
+ // Called by BluetoothDevice in response to a call to CreateGattConnection.
+ void OnConnectError(const std::string& extension_id,
+ const std::string& device_address,
+ const ErrorCallback& error_callback,
+ device::BluetoothDevice::ConnectErrorCode error_code);
+
+ // Called by BluetoothGattCharacteristic in response to a call to
+ // StartNotifySession.
+ void OnStartNotifySession(
+ bool persistent,
+ const std::string& extension_id,
+ const std::string& characteristic_id,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothGattNotifySession> session);
+
+ // Called by BluetoothGattCharacteristic in response to a call to
+ // StartNotifySession.
+ void OnStartNotifySessionError(const std::string& extension_id,
+ const std::string& characteristic_id,
+ const ErrorCallback& error_callback);
+
+ // Called by BluetoothGattNotifySession in response to a call to Stop.
+ void OnStopNotifySession(const std::string& extension_id,
+ const std::string& characteristic_id,
+ const base::Closure& callback);
+
+ // Finds and returns a BluetoothLowEnergyConnection to device with address
+ // |device_address| from the managed API resources for extension with ID
+ // |extension_id|.
+ BluetoothLowEnergyConnection* FindConnection(
+ const std::string& extension_id,
+ const std::string& device_address);
+
+ // Removes the connection to device with address |device_address| from the
+ // managed API resources for extension with ID |extension_id|. Returns false,
+ // if the connection could not be found.
+ bool RemoveConnection(const std::string& extension_id,
+ const std::string& device_address);
+
+ // Finds and returns a BluetoothLowEnergyNotifySession associated with
+ // characteristic with instance ID |characteristic_id| from the managed API
+ // API resources for extension with ID |extension_id|.
+ BluetoothLowEnergyNotifySession* FindNotifySession(
+ const std::string& extension_id,
+ const std::string& characteristic_id);
+
+ // Removes the notify session associated with characteristic with
+ // instance ID |characteristic_id| from the managed API resources for
+ // extension with ID |extension_id|. Returns false, if the session could
+ // not be found.
+ bool RemoveNotifySession(const std::string& extension_id,
+ const std::string& characteristic_id);
+
+ // Mapping from instance ids to identifiers of owning instances. The keys are
+ // used to identify individual instances of GATT objects and are used by
+ // bluetoothLowEnergy API functions to obtain the correct GATT object to
+ // operate on. Instance IDs are string identifiers that are returned by the
+ // device/bluetooth API, by calling GetIdentifier() on the corresponding
+ // device::BluetoothGatt* instance.
+ //
+ // This mapping is necessary, as GATT object instances can only be obtained
+ // from the object that owns it, where raw pointers should not be cached. E.g.
+ // to obtain a device::BluetoothGattCharacteristic, it is necessary to obtain
+ // a pointer to the associated device::BluetoothDevice, and then to the
+ // device::BluetoothGattService that owns the characteristic.
+ typedef std::map<std::string, std::string> InstanceIdMap;
+ InstanceIdMap service_id_to_device_address_;
+ InstanceIdMap chrc_id_to_service_id_;
+ InstanceIdMap desc_id_to_chrc_id_;
+
+ // Pointer to the current BluetoothAdapter instance. This represents a local
+ // Bluetooth adapter of the system.
+ scoped_refptr<device::BluetoothAdapter> adapter_;
+
+ // Set of extension ID + device addresses to which a connect/disconnect is
+ // currently pending.
+ std::set<std::string> connecting_devices_;
+ std::set<std::string> disconnecting_devices_;
+
+ // Set of extension ID + characteristic ID to which a request to start a
+ // notify session is currently pending.
+ std::set<std::string> pending_session_calls_;
+
+ // BrowserContext passed during initialization.
+ content::BrowserContext* browser_context_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<BluetoothLowEnergyEventRouter> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothLowEnergyEventRouter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_EVENT_ROUTER_H_
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.cc
new file mode 100644
index 0000000..bb8cad3
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h"
+
+namespace extensions {
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<
+ ApiResourceManager<BluetoothLowEnergyNotifySession> > > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+template <>
+BrowserContextKeyedAPIFactory<
+ ApiResourceManager<BluetoothLowEnergyNotifySession> >*
+ApiResourceManager<BluetoothLowEnergyNotifySession>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+BluetoothLowEnergyNotifySession::BluetoothLowEnergyNotifySession(
+ bool persistent,
+ const std::string& owner_extension_id,
+ scoped_ptr<device::BluetoothGattNotifySession> session)
+ : ApiResource(owner_extension_id),
+ persistent_(persistent),
+ session_(session.release()) {
+}
+
+BluetoothLowEnergyNotifySession::~BluetoothLowEnergyNotifySession() {
+}
+
+device::BluetoothGattNotifySession*
+BluetoothLowEnergyNotifySession::GetSession() const {
+ return session_.get();
+}
+
+bool BluetoothLowEnergyNotifySession::IsPersistent() const {
+ return persistent_;
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h
new file mode 100644
index 0000000..e2699a4
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_NOTIFY_SESSION_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_NOTIFY_SESSION_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "device/bluetooth/bluetooth_gatt_notify_session.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+
+namespace extensions {
+
+// An ApiResource wrapper for a device::BluetoothGattNotifySession
+class BluetoothLowEnergyNotifySession : public ApiResource {
+ public:
+ explicit BluetoothLowEnergyNotifySession(
+ bool persistent,
+ const std::string& owner_extension_id,
+ scoped_ptr<device::BluetoothGattNotifySession> session);
+ virtual ~BluetoothLowEnergyNotifySession();
+
+ // Returns a pointer to the underlying session object.
+ device::BluetoothGattNotifySession* GetSession() const;
+
+ // ApiResource override.
+ virtual bool IsPersistent() const OVERRIDE;
+
+ // This resource should be managed on the UI thread.
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ private:
+ friend class ApiResourceManager<BluetoothLowEnergyNotifySession>;
+ static const char* service_name() {
+ return "BluetoothLowEnergyNotifySessionManager";
+ }
+
+ // True, if this resource should be persistent across suspends.
+ bool persistent_;
+
+ // The session is owned by this instance and will automatically stop when
+ // deleted.
+ scoped_ptr<device::BluetoothGattNotifySession> session_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothLowEnergyNotifySession);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_BLUETOOTH_LOW_ENERGY_NOTIFY_SESSION_H_
diff --git a/extensions/browser/api/bluetooth_low_energy/utils.cc b/extensions/browser/api/bluetooth_low_energy/utils.cc
new file mode 100644
index 0000000..33eae2f
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/utils.cc
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_low_energy/utils.h"
+
+namespace extensions {
+namespace core_api {
+namespace bluetooth_low_energy {
+
+namespace {
+
+// Converts a list of CharacteristicProperty to a base::ListValue of strings.
+scoped_ptr<base::ListValue> CharacteristicPropertiesToValue(
+ const std::vector<CharacteristicProperty> properties) {
+ scoped_ptr<base::ListValue> property_list(new base::ListValue());
+ for (std::vector<CharacteristicProperty>::const_iterator iter =
+ properties.begin();
+ iter != properties.end();
+ ++iter)
+ property_list->Append(new base::StringValue(ToString(*iter)));
+ return property_list.Pass();
+}
+
+} // namespace
+
+scoped_ptr<base::DictionaryValue> CharacteristicToValue(Characteristic* from) {
+ // Copy the properties. Use Characteristic::ToValue to generate the result
+ // dictionary without the properties, to prevent json_schema_compiler from
+ // failing.
+ std::vector<CharacteristicProperty> properties = from->properties;
+ from->properties.clear();
+ scoped_ptr<base::DictionaryValue> to = from->ToValue();
+ to->SetWithoutPathExpansion(
+ "properties", CharacteristicPropertiesToValue(properties).release());
+ return to.Pass();
+}
+
+scoped_ptr<base::DictionaryValue> DescriptorToValue(Descriptor* from) {
+ // Copy the characteristic properties and set them later manually.
+ std::vector<CharacteristicProperty> properties =
+ from->characteristic.properties;
+ from->characteristic.properties.clear();
+ scoped_ptr<base::DictionaryValue> to = from->ToValue();
+
+ base::DictionaryValue* chrc_value = NULL;
+ to->GetDictionaryWithoutPathExpansion("characteristic", &chrc_value);
+ DCHECK(chrc_value);
+ chrc_value->SetWithoutPathExpansion(
+ "properties", CharacteristicPropertiesToValue(properties).release());
+ return to.Pass();
+}
+
+} // namespace bluetooth_low_energy
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_low_energy/utils.h b/extensions/browser/api/bluetooth_low_energy/utils.h
new file mode 100644
index 0000000..35b40c6
--- /dev/null
+++ b/extensions/browser/api/bluetooth_low_energy/utils.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_UTILS_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_UTILS_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/common/api/bluetooth_low_energy.h"
+
+namespace extensions {
+namespace core_api {
+namespace bluetooth_low_energy {
+
+// TODO(armansito): Remove these functions once the described bug is fixed.
+// (See crbug.com/368368)
+
+// Converts a Characteristic to a base::Value. This function is necessary as
+// json_schema_compiler::util::AddItemToList has no template specialization for
+// user defined enums, which get treated as integers. This is because
+// Characteristic contains a list of enum CharacteristicProperty.
+scoped_ptr<base::DictionaryValue> CharacteristicToValue(Characteristic* from);
+
+// Converts a Descriptor to a base::Value. This function is necessary as a
+// Descriptor embeds a Characteristic and that needs special handling as
+// described above.
+scoped_ptr<base::DictionaryValue> DescriptorToValue(Descriptor* from);
+
+} // namespace bluetooth_low_energy
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_LOW_ENERGY_UTILS_H_
diff --git a/extensions/browser/api/bluetooth_socket/OWNERS b/extensions/browser/api/bluetooth_socket/OWNERS
new file mode 100644
index 0000000..53dafd5
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/OWNERS
@@ -0,0 +1,2 @@
+rpaquay@chromium.org
+keybuk@chromium.org
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc b/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc
new file mode 100644
index 0000000..8dbde0e
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+
+#include "device/bluetooth/bluetooth_socket.h"
+#include "net/base/io_buffer.h"
+
+namespace {
+
+const char kSocketNotConnectedError[] = "Socket not connected";
+const char kSocketNotListeningError[] = "Socket not listening";
+
+} // namespace
+
+namespace extensions {
+
+// static
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<BluetoothApiSocket> > >
+ g_server_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<BluetoothApiSocket> >*
+ApiResourceManager<BluetoothApiSocket>::GetFactoryInstance() {
+ return g_server_factory.Pointer();
+}
+
+BluetoothApiSocket::BluetoothApiSocket(const std::string& owner_extension_id)
+ : ApiResource(owner_extension_id),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(false),
+ connected_(false) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+}
+
+BluetoothApiSocket::BluetoothApiSocket(
+ const std::string& owner_extension_id,
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid)
+ : ApiResource(owner_extension_id),
+ socket_(socket),
+ device_address_(device_address),
+ uuid_(uuid),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(true),
+ connected_(true) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+}
+
+BluetoothApiSocket::~BluetoothApiSocket() {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+ if (socket_.get())
+ socket_->Close();
+}
+
+void BluetoothApiSocket::AdoptConnectedSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+
+ if (socket_.get())
+ socket_->Close();
+
+ socket_ = socket;
+ device_address_ = device_address;
+ uuid_ = uuid;
+ connected_ = true;
+}
+
+void BluetoothApiSocket::AdoptListeningSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const device::BluetoothUUID& uuid) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+
+ if (socket_.get())
+ socket_->Close();
+
+ socket_ = socket;
+ device_address_ = "";
+ uuid_ = uuid;
+ connected_ = false;
+}
+
+void BluetoothApiSocket::Disconnect(const base::Closure& callback) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+
+ if (!socket_.get()) {
+ callback.Run();
+ return;
+ }
+
+ connected_ = false;
+ socket_->Disconnect(callback);
+}
+
+bool BluetoothApiSocket::IsPersistent() const {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+ return persistent_;
+}
+
+void BluetoothApiSocket::Receive(
+ int count,
+ const ReceiveCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+
+ if (!socket_.get() || !IsConnected()) {
+ error_callback.Run(BluetoothApiSocket::kNotConnected,
+ kSocketNotConnectedError);
+ return;
+ }
+
+ socket_->Receive(count,
+ success_callback,
+ base::Bind(&OnSocketReceiveError, error_callback));
+}
+
+// static
+void BluetoothApiSocket::OnSocketReceiveError(
+ const ErrorCompletionCallback& error_callback,
+ device::BluetoothSocket::ErrorReason reason,
+ const std::string& message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+ BluetoothApiSocket::ErrorReason error_reason;
+ switch (reason) {
+ case device::BluetoothSocket::kIOPending:
+ error_reason = BluetoothApiSocket::kIOPending;
+ break;
+ case device::BluetoothSocket::kDisconnected:
+ error_reason = BluetoothApiSocket::kDisconnected;
+ break;
+ case device::BluetoothSocket::kSystemError:
+ error_reason = BluetoothApiSocket::kSystemError;
+ break;
+ default:
+ NOTREACHED();
+ }
+ error_callback.Run(error_reason, message);
+}
+
+void BluetoothApiSocket::Send(scoped_refptr<net::IOBuffer> buffer,
+ int buffer_size,
+ const SendCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+
+ if (!socket_.get() || !IsConnected()) {
+ error_callback.Run(BluetoothApiSocket::kNotConnected,
+ kSocketNotConnectedError);
+ return;
+ }
+
+ socket_->Send(buffer,
+ buffer_size,
+ success_callback,
+ base::Bind(&OnSocketSendError, error_callback));
+}
+
+// static
+void BluetoothApiSocket::OnSocketSendError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+ error_callback.Run(BluetoothApiSocket::kSystemError, message);
+}
+
+void BluetoothApiSocket::Accept(
+ const AcceptCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+
+ if (!socket_.get() || IsConnected()) {
+ error_callback.Run(BluetoothApiSocket::kNotListening,
+ kSocketNotListeningError);
+ return;
+ }
+
+ socket_->Accept(success_callback,
+ base::Bind(&OnSocketAcceptError, error_callback));
+}
+
+// static
+void BluetoothApiSocket::OnSocketAcceptError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(kThreadId));
+ error_callback.Run(BluetoothApiSocket::kSystemError, message);
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h b/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h
new file mode 100644
index 0000000..3d0efd4
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_API_SOCKET_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_API_SOCKET_H_
+
+#include <string>
+
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+
+namespace net {
+class IOBuffer;
+} // namespace net
+
+namespace extensions {
+
+// Representation of socket instances from the "bluetooth" namespace,
+// abstracting away platform differences from the underlying BluetoothSocketXxx
+// class. All methods must be called on the |kThreadId| thread.
+class BluetoothApiSocket : public ApiResource {
+ public:
+ enum ErrorReason { kSystemError, kNotConnected, kNotListening, kIOPending,
+ kDisconnected };
+
+ typedef base::Callback<void(int)> SendCompletionCallback;
+ typedef base::Callback<void(int, scoped_refptr<net::IOBuffer> io_buffer)>
+ ReceiveCompletionCallback;
+ typedef base::Callback<void(const device::BluetoothDevice* device,
+ scoped_refptr<device::BluetoothSocket>)>
+ AcceptCompletionCallback;
+ typedef base::Callback<void(ErrorReason, const std::string& error_message)>
+ ErrorCompletionCallback;
+
+ explicit BluetoothApiSocket(const std::string& owner_extension_id);
+ BluetoothApiSocket(const std::string& owner_extension_id,
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid);
+ virtual ~BluetoothApiSocket();
+
+ // Adopts a socket |socket| connected to a device with address
+ // |device_address| using the service with UUID |uuid|.
+ virtual void AdoptConnectedSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid);
+
+ // Adopts a socket |socket| listening on a service advertised with UUID
+ // |uuid|.
+ virtual void AdoptListeningSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const device::BluetoothUUID& uuid);
+
+ // Closes the underlying connection. This is a best effort, and never fails.
+ virtual void Disconnect(const base::Closure& callback);
+
+ // Receives data from the socket and calls |success_callback| when data is
+ // available. |count| is maximum amount of bytes received. If an error occurs,
+ // calls |error_callback| with a reason and a message. In particular, if a
+ // |Receive| operation is still pending, |error_callback| will be called with
+ // |kIOPending| error.
+ virtual void Receive(int count,
+ const ReceiveCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback);
+
+ // Sends |buffer| to the socket and calls |success_callback| when data has
+ // been successfully sent. |buffer_size| is the numberof bytes contained in
+ // |buffer|. If an error occurs, calls |error_callback| with a reason and a
+ // message. Calling |Send| multiple times without waiting for the callbacks to
+ // be called is a valid usage, as |buffer| instances are buffered until the
+ // underlying communication channel is available for sending data.
+ virtual void Send(scoped_refptr<net::IOBuffer> buffer,
+ int buffer_size,
+ const SendCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback);
+
+ // Accepts a client connection from the socket and calls |success_callback|
+ // when one has connected. If an error occurs, calls |error_callback| with a
+ // reason and a message.
+ virtual void Accept(const AcceptCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback);
+
+ const std::string& device_address() const { return device_address_; }
+ const device::BluetoothUUID& uuid() const { return uuid_; }
+
+ // Overriden from extensions::ApiResource.
+ virtual bool IsPersistent() const OVERRIDE;
+
+ const std::string* name() const { return name_.get(); }
+ void set_name(const std::string& name) { name_.reset(new std::string(name)); }
+
+ bool persistent() const { return persistent_; }
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+
+ int buffer_size() const { return buffer_size_; }
+ void set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; }
+
+ bool paused() const { return paused_; }
+ void set_paused(bool paused) { paused_ = paused; }
+
+ bool IsConnected() const { return connected_; }
+
+ // Platform specific implementations of |BluetoothSocket| require being called
+ // on the UI thread.
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ private:
+ friend class ApiResourceManager<BluetoothApiSocket>;
+ static const char* service_name() { return "BluetoothApiSocketManager"; }
+
+ static void OnSocketReceiveError(
+ const ErrorCompletionCallback& error_callback,
+ device::BluetoothSocket::ErrorReason reason,
+ const std::string& message);
+
+ static void OnSocketSendError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message);
+
+ static void OnSocketAcceptError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message);
+
+ // The underlying device socket instance.
+ scoped_refptr<device::BluetoothSocket> socket_;
+
+ // The address of the device this socket is connected to.
+ std::string device_address_;
+
+ // The uuid of the service this socket is connected to.
+ device::BluetoothUUID uuid_;
+
+ // Application-defined string - see bluetooth.idl.
+ scoped_ptr<std::string> name_;
+
+ // Flag indicating whether the socket is left open when the application is
+ // suspended - see bluetooth.idl.
+ bool persistent_;
+
+ // The size of the buffer used to receive data - see bluetooth.idl.
+ int buffer_size_;
+
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data - see bluetooth.idl.
+ bool paused_;
+
+ // Flag indicating whether a socket is connected.
+ bool connected_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothApiSocket);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_API_SOCKET_H_
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc b/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
new file mode 100644
index 0000000..77d19d1
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
@@ -0,0 +1,678 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h"
+
+#include <stdint.h>
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "net/base/io_buffer.h"
+
+using content::BrowserThread;
+using extensions::BluetoothApiSocket;
+using extensions::core_api::bluetooth_socket::ListenOptions;
+using extensions::core_api::bluetooth_socket::SocketInfo;
+using extensions::core_api::bluetooth_socket::SocketProperties;
+
+namespace extensions {
+namespace core_api {
+
+namespace {
+
+const char kDeviceNotFoundError[] = "Device not found";
+const char kInvalidPsmError[] = "Invalid PSM";
+const char kInvalidUuidError[] = "Invalid UUID";
+const char kPermissionDeniedError[] = "Permission denied";
+const char kSocketNotFoundError[] = "Socket not found";
+
+linked_ptr<SocketInfo> CreateSocketInfo(int socket_id,
+ BluetoothApiSocket* socket) {
+ DCHECK(BrowserThread::CurrentlyOn(BluetoothApiSocket::kThreadId));
+ linked_ptr<SocketInfo> socket_info(new SocketInfo());
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ socket_info->socket_id = socket_id;
+ if (socket->name()) {
+ socket_info->name.reset(new std::string(*socket->name()));
+ }
+ socket_info->persistent = socket->persistent();
+ if (socket->buffer_size() > 0) {
+ socket_info->buffer_size.reset(new int(socket->buffer_size()));
+ }
+ socket_info->paused = socket->paused();
+ socket_info->connected = socket->IsConnected();
+
+ if (socket->IsConnected())
+ socket_info->address.reset(new std::string(socket->device_address()));
+ socket_info->uuid.reset(new std::string(socket->uuid().canonical_value()));
+
+ return socket_info;
+}
+
+void SetSocketProperties(BluetoothApiSocket* socket,
+ SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+ if (properties->buffer_size.get()) {
+ // buffer size is validated when issuing the actual Recv operation
+ // on the socket.
+ socket->set_buffer_size(*properties->buffer_size.get());
+ }
+}
+
+BluetoothSocketEventDispatcher* GetSocketEventDispatcher(
+ content::BrowserContext* browser_context) {
+ BluetoothSocketEventDispatcher* socket_event_dispatcher =
+ BluetoothSocketEventDispatcher::Get(browser_context);
+ DCHECK(socket_event_dispatcher)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "BluetoothSocketEventDispatcher.";
+ return socket_event_dispatcher;
+}
+
+// Returns |true| if |psm| is a valid PSM.
+// Per the Bluetooth specification, the PSM field must be at least two octets in
+// length, with least significant bit of the least significant octet equal to
+// '1' and the least significant bit of the most significant octet equal to '0'.
+bool IsValidPsm(int psm) {
+ if (psm <= 0)
+ return false;
+
+ std::vector<int16_t> octets;
+ while (psm > 0) {
+ octets.push_back(psm & 0xFF);
+ psm = psm >> 8;
+ }
+
+ if (octets.size() < 2U)
+ return false;
+
+ // The least significant bit of the least significant octet must be '1'.
+ if ((octets.front() & 0x01) != 1)
+ return false;
+
+ // The least significant bit of the most significant octet must be '0'.
+ if ((octets.back() & 0x01) != 0)
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+BluetoothSocketAsyncApiFunction::BluetoothSocketAsyncApiFunction() {}
+
+BluetoothSocketAsyncApiFunction::~BluetoothSocketAsyncApiFunction() {}
+
+bool BluetoothSocketAsyncApiFunction::RunAsync() {
+ if (!PrePrepare() || !Prepare()) {
+ return false;
+ }
+ AsyncWorkStart();
+ return true;
+}
+
+bool BluetoothSocketAsyncApiFunction::PrePrepare() {
+ if (!BluetoothManifestData::CheckSocketPermitted(extension())) {
+ error_ = kPermissionDeniedError;
+ return false;
+ }
+
+ manager_ = ApiResourceManager<BluetoothApiSocket>::Get(browser_context());
+ DCHECK(manager_)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<BluetoothApiSocket>.";
+ return manager_ != NULL;
+}
+
+bool BluetoothSocketAsyncApiFunction::Respond() { return error_.empty(); }
+
+void BluetoothSocketAsyncApiFunction::AsyncWorkCompleted() {
+ SendResponse(Respond());
+}
+
+void BluetoothSocketAsyncApiFunction::Work() {}
+
+void BluetoothSocketAsyncApiFunction::AsyncWorkStart() {
+ Work();
+ AsyncWorkCompleted();
+}
+
+int BluetoothSocketAsyncApiFunction::AddSocket(BluetoothApiSocket* socket) {
+ return manager_->Add(socket);
+}
+
+content::BrowserThread::ID
+BluetoothSocketAsyncApiFunction::work_thread_id() const {
+ return BluetoothApiSocket::kThreadId;
+}
+
+BluetoothApiSocket* BluetoothSocketAsyncApiFunction::GetSocket(
+ int api_resource_id) {
+ return manager_->Get(extension_id(), api_resource_id);
+}
+
+void BluetoothSocketAsyncApiFunction::RemoveSocket(int api_resource_id) {
+ manager_->Remove(extension_id(), api_resource_id);
+}
+
+base::hash_set<int>* BluetoothSocketAsyncApiFunction::GetSocketIds() {
+ return manager_->GetResourceIds(extension_id());
+}
+
+BluetoothSocketCreateFunction::BluetoothSocketCreateFunction() {}
+
+BluetoothSocketCreateFunction::~BluetoothSocketCreateFunction() {}
+
+bool BluetoothSocketCreateFunction::Prepare() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ params_ = bluetooth_socket::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketCreateFunction::Work() {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+
+ BluetoothApiSocket* socket = new BluetoothApiSocket(extension_id());
+
+ bluetooth_socket::SocketProperties* properties =
+ params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ bluetooth_socket::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = bluetooth_socket::Create::Results::Create(create_info);
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketUpdateFunction::BluetoothSocketUpdateFunction() {}
+
+BluetoothSocketUpdateFunction::~BluetoothSocketUpdateFunction() {}
+
+bool BluetoothSocketUpdateFunction::Prepare() {
+ params_ = bluetooth_socket::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketUpdateFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = bluetooth_socket::Update::Results::Create();
+}
+
+BluetoothSocketSetPausedFunction::BluetoothSocketSetPausedFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+BluetoothSocketSetPausedFunction::~BluetoothSocketSetPausedFunction() {}
+
+bool BluetoothSocketSetPausedFunction::Prepare() {
+ params_ = bluetooth_socket::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = GetSocketEventDispatcher(browser_context());
+ return socket_event_dispatcher_ != NULL;
+}
+
+void BluetoothSocketSetPausedFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ if (socket->paused() != params_->paused) {
+ socket->set_paused(params_->paused);
+ if (!params_->paused) {
+ socket_event_dispatcher_->OnSocketResume(extension_id(),
+ params_->socket_id);
+ }
+ }
+
+ results_ = bluetooth_socket::SetPaused::Results::Create();
+}
+
+BluetoothSocketListenFunction::BluetoothSocketListenFunction() {}
+
+BluetoothSocketListenFunction::~BluetoothSocketListenFunction() {}
+
+bool BluetoothSocketListenFunction::Prepare() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!CreateParams())
+ return false;
+ socket_event_dispatcher_ = GetSocketEventDispatcher(browser_context());
+ return socket_event_dispatcher_ != NULL;
+}
+
+void BluetoothSocketListenFunction::AsyncWorkStart() {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ device::BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&BluetoothSocketListenFunction::OnGetAdapter, this));
+}
+
+void BluetoothSocketListenFunction::OnGetAdapter(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ BluetoothApiSocket* socket = GetSocket(socket_id());
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ device::BluetoothUUID bluetooth_uuid(uuid());
+ if (!bluetooth_uuid.IsValid()) {
+ error_ = kInvalidUuidError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ BluetoothPermissionRequest param(uuid());
+ if (!BluetoothManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionDeniedError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ scoped_ptr<std::string> name;
+ if (socket->name())
+ name.reset(new std::string(*socket->name()));
+
+ CreateService(
+ adapter,
+ bluetooth_uuid,
+ name.Pass(),
+ base::Bind(&BluetoothSocketListenFunction::OnCreateService, this),
+ base::Bind(&BluetoothSocketListenFunction::OnCreateServiceError, this));
+}
+
+
+void BluetoothSocketListenFunction::OnCreateService(
+ scoped_refptr<device::BluetoothSocket> socket) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+
+ // Fetch the socket again since this is not a reference-counted object, and
+ // it may have gone away in the meantime (we check earlier to avoid making
+ // a connection in the case of an obvious programming error).
+ BluetoothApiSocket* api_socket = GetSocket(socket_id());
+ if (!api_socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ api_socket->AdoptListeningSocket(socket,
+ device::BluetoothUUID(uuid()));
+ socket_event_dispatcher_->OnSocketListen(extension_id(), socket_id());
+
+ CreateResults();
+ AsyncWorkCompleted();
+}
+
+void BluetoothSocketListenFunction::OnCreateServiceError(
+ const std::string& message) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ error_ = message;
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketListenUsingRfcommFunction::
+ BluetoothSocketListenUsingRfcommFunction() {}
+
+BluetoothSocketListenUsingRfcommFunction::
+ ~BluetoothSocketListenUsingRfcommFunction() {}
+
+int BluetoothSocketListenUsingRfcommFunction::socket_id() const {
+ return params_->socket_id;
+}
+
+const std::string& BluetoothSocketListenUsingRfcommFunction::uuid() const {
+ return params_->uuid;
+}
+
+bool BluetoothSocketListenUsingRfcommFunction::CreateParams() {
+ params_ = bluetooth_socket::ListenUsingRfcomm::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketListenUsingRfcommFunction::CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) {
+ device::BluetoothAdapter::ServiceOptions service_options;
+ service_options.name = name.Pass();
+
+ ListenOptions* options = params_->options.get();
+ if (options) {
+ if (options->channel.get())
+ service_options.channel.reset(new int(*(options->channel)));
+ }
+
+ adapter->CreateRfcommService(uuid, service_options, callback, error_callback);
+}
+
+void BluetoothSocketListenUsingRfcommFunction::CreateResults() {
+ results_ = bluetooth_socket::ListenUsingRfcomm::Results::Create();
+}
+
+BluetoothSocketListenUsingL2capFunction::
+ BluetoothSocketListenUsingL2capFunction() {}
+
+BluetoothSocketListenUsingL2capFunction::
+ ~BluetoothSocketListenUsingL2capFunction() {}
+
+int BluetoothSocketListenUsingL2capFunction::socket_id() const {
+ return params_->socket_id;
+}
+
+const std::string& BluetoothSocketListenUsingL2capFunction::uuid() const {
+ return params_->uuid;
+}
+
+bool BluetoothSocketListenUsingL2capFunction::CreateParams() {
+ params_ = bluetooth_socket::ListenUsingL2cap::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketListenUsingL2capFunction::CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) {
+ device::BluetoothAdapter::ServiceOptions service_options;
+ service_options.name = name.Pass();
+
+ ListenOptions* options = params_->options.get();
+ if (options) {
+ if (options->psm) {
+ int psm = *options->psm;
+ if (!IsValidPsm(psm)) {
+ error_callback.Run(kInvalidPsmError);
+ return;
+ }
+
+ service_options.psm.reset(new int(psm));
+ }
+ }
+
+ adapter->CreateL2capService(uuid, service_options, callback, error_callback);
+}
+
+void BluetoothSocketListenUsingL2capFunction::CreateResults() {
+ results_ = bluetooth_socket::ListenUsingL2cap::Results::Create();
+}
+
+BluetoothSocketAbstractConnectFunction::
+ BluetoothSocketAbstractConnectFunction() {}
+
+BluetoothSocketAbstractConnectFunction::
+ ~BluetoothSocketAbstractConnectFunction() {}
+
+bool BluetoothSocketAbstractConnectFunction::Prepare() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ params_ = bluetooth_socket::Connect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = GetSocketEventDispatcher(browser_context());
+ return socket_event_dispatcher_ != NULL;
+}
+
+void BluetoothSocketAbstractConnectFunction::AsyncWorkStart() {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ device::BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&BluetoothSocketAbstractConnectFunction::OnGetAdapter, this));
+}
+
+void BluetoothSocketAbstractConnectFunction::OnGetAdapter(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ device::BluetoothDevice* device = adapter->GetDevice(params_->address);
+ if (!device) {
+ error_ = kDeviceNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ device::BluetoothUUID uuid(params_->uuid);
+ if (!uuid.IsValid()) {
+ error_ = kInvalidUuidError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ BluetoothPermissionRequest param(params_->uuid);
+ if (!BluetoothManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionDeniedError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ ConnectToService(device, uuid);
+}
+
+void BluetoothSocketAbstractConnectFunction::OnConnect(
+ scoped_refptr<device::BluetoothSocket> socket) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+
+ // Fetch the socket again since this is not a reference-counted object, and
+ // it may have gone away in the meantime (we check earlier to avoid making
+ // a connection in the case of an obvious programming error).
+ BluetoothApiSocket* api_socket = GetSocket(params_->socket_id);
+ if (!api_socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ api_socket->AdoptConnectedSocket(socket,
+ params_->address,
+ device::BluetoothUUID(params_->uuid));
+ socket_event_dispatcher_->OnSocketConnect(extension_id(),
+ params_->socket_id);
+
+ results_ = bluetooth_socket::Connect::Results::Create();
+ AsyncWorkCompleted();
+}
+
+void BluetoothSocketAbstractConnectFunction::OnConnectError(
+ const std::string& message) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ error_ = message;
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketConnectFunction::BluetoothSocketConnectFunction() {}
+
+BluetoothSocketConnectFunction::~BluetoothSocketConnectFunction() {}
+
+void BluetoothSocketConnectFunction::ConnectToService(
+ device::BluetoothDevice* device,
+ const device::BluetoothUUID& uuid) {
+ device->ConnectToService(
+ uuid,
+ base::Bind(&BluetoothSocketConnectFunction::OnConnect, this),
+ base::Bind(&BluetoothSocketConnectFunction::OnConnectError, this));
+}
+
+BluetoothSocketDisconnectFunction::BluetoothSocketDisconnectFunction() {}
+
+BluetoothSocketDisconnectFunction::~BluetoothSocketDisconnectFunction() {}
+
+bool BluetoothSocketDisconnectFunction::Prepare() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ params_ = bluetooth_socket::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketDisconnectFunction::AsyncWorkStart() {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->Disconnect(base::Bind(&BluetoothSocketDisconnectFunction::OnSuccess,
+ this));
+}
+
+void BluetoothSocketDisconnectFunction::OnSuccess() {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ results_ = bluetooth_socket::Disconnect::Results::Create();
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketCloseFunction::BluetoothSocketCloseFunction() {}
+
+BluetoothSocketCloseFunction::~BluetoothSocketCloseFunction() {}
+
+bool BluetoothSocketCloseFunction::Prepare() {
+ params_ = bluetooth_socket::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketCloseFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ RemoveSocket(params_->socket_id);
+ results_ = bluetooth_socket::Close::Results::Create();
+}
+
+BluetoothSocketSendFunction::BluetoothSocketSendFunction()
+ : io_buffer_size_(0) {}
+
+BluetoothSocketSendFunction::~BluetoothSocketSendFunction() {}
+
+bool BluetoothSocketSendFunction::Prepare() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ params_ = bluetooth_socket::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ io_buffer_size_ = params_->data.size();
+ io_buffer_ = new net::WrappedIOBuffer(params_->data.data());
+ return true;
+}
+
+void BluetoothSocketSendFunction::AsyncWorkStart() {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ socket->Send(io_buffer_,
+ io_buffer_size_,
+ base::Bind(&BluetoothSocketSendFunction::OnSuccess, this),
+ base::Bind(&BluetoothSocketSendFunction::OnError, this));
+}
+
+void BluetoothSocketSendFunction::OnSuccess(int bytes_sent) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ results_ = bluetooth_socket::Send::Results::Create(bytes_sent);
+ AsyncWorkCompleted();
+}
+
+void BluetoothSocketSendFunction::OnError(
+ BluetoothApiSocket::ErrorReason reason,
+ const std::string& message) {
+ DCHECK(BrowserThread::CurrentlyOn(work_thread_id()));
+ error_ = message;
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketGetInfoFunction::BluetoothSocketGetInfoFunction() {}
+
+BluetoothSocketGetInfoFunction::~BluetoothSocketGetInfoFunction() {}
+
+bool BluetoothSocketGetInfoFunction::Prepare() {
+ params_ = bluetooth_socket::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketGetInfoFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ linked_ptr<bluetooth_socket::SocketInfo> socket_info =
+ CreateSocketInfo(params_->socket_id, socket);
+ results_ = bluetooth_socket::GetInfo::Results::Create(*socket_info);
+}
+
+BluetoothSocketGetSocketsFunction::BluetoothSocketGetSocketsFunction() {}
+
+BluetoothSocketGetSocketsFunction::~BluetoothSocketGetSocketsFunction() {}
+
+bool BluetoothSocketGetSocketsFunction::Prepare() { return true; }
+
+void BluetoothSocketGetSocketsFunction::Work() {
+ std::vector<linked_ptr<bluetooth_socket::SocketInfo> > socket_infos;
+ base::hash_set<int>* resource_ids = GetSocketIds();
+ if (resource_ids != NULL) {
+ for (base::hash_set<int>::iterator it = resource_ids->begin();
+ it != resource_ids->end();
+ ++it) {
+ int socket_id = *it;
+ BluetoothApiSocket* socket = GetSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = bluetooth_socket::GetSockets::Results::Create(socket_infos);
+}
+
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h b/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h
new file mode 100644
index 0000000..d809a0a
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h
@@ -0,0 +1,352 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_API_H_
+
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+#include "extensions/common/api/bluetooth_socket.h"
+
+namespace device {
+class BluetoothSocket;
+}
+
+namespace net {
+class IOBuffer;
+}
+
+namespace extensions {
+
+namespace core_api {
+
+class BluetoothSocketEventDispatcher;
+
+// Asynchronous API function that performs its work on the BluetoothApiSocket
+// thread while providing methods to manage resources of that class. This
+// follows the pattern of AsyncApiFunction, but does not derive from it,
+// because BluetoothApiSocket methods must be called on the UI Thread.
+class BluetoothSocketAsyncApiFunction : public AsyncExtensionFunction {
+ public:
+ BluetoothSocketAsyncApiFunction();
+
+ protected:
+ virtual ~BluetoothSocketAsyncApiFunction();
+
+ // AsyncExtensionFunction:
+ virtual bool RunAsync() OVERRIDE;
+
+ bool PrePrepare();
+ bool Respond();
+ void AsyncWorkCompleted();
+
+ virtual bool Prepare() = 0;
+ virtual void Work();
+ virtual void AsyncWorkStart();
+
+ content::BrowserThread::ID work_thread_id() const;
+
+ int AddSocket(BluetoothApiSocket* socket);
+ BluetoothApiSocket* GetSocket(int api_resource_id);
+ void RemoveSocket(int api_resource_id);
+ base::hash_set<int>* GetSocketIds();
+
+ private:
+ ApiResourceManager<BluetoothApiSocket>* manager_;
+};
+
+class BluetoothSocketCreateFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.create", BLUETOOTHSOCKET_CREATE);
+
+ BluetoothSocketCreateFunction();
+
+ protected:
+ virtual ~BluetoothSocketCreateFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<bluetooth_socket::Create::Params> params_;
+};
+
+class BluetoothSocketUpdateFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.update", BLUETOOTHSOCKET_UPDATE);
+
+ BluetoothSocketUpdateFunction();
+
+ protected:
+ virtual ~BluetoothSocketUpdateFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<bluetooth_socket::Update::Params> params_;
+};
+
+class BluetoothSocketSetPausedFunction
+ : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.setPaused",
+ BLUETOOTHSOCKET_SETPAUSED);
+
+ BluetoothSocketSetPausedFunction();
+
+ protected:
+ virtual ~BluetoothSocketSetPausedFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<bluetooth_socket::SetPaused::Params> params_;
+ BluetoothSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class BluetoothSocketListenFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ BluetoothSocketListenFunction();
+
+ virtual bool CreateParams() = 0;
+ virtual void CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) = 0;
+ virtual void CreateResults() = 0;
+
+ virtual int socket_id() const = 0;
+ virtual const std::string& uuid() const = 0;
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void AsyncWorkStart() OVERRIDE;
+
+ protected:
+ virtual ~BluetoothSocketListenFunction();
+
+ virtual void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+ virtual void OnCreateService(scoped_refptr<device::BluetoothSocket> socket);
+ virtual void OnCreateServiceError(const std::string& message);
+
+ BluetoothSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class BluetoothSocketListenUsingRfcommFunction
+ : public BluetoothSocketListenFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.listenUsingRfcomm",
+ BLUETOOTHSOCKET_LISTENUSINGRFCOMM);
+
+ BluetoothSocketListenUsingRfcommFunction();
+
+ // BluetoothSocketListenFunction:
+ virtual int socket_id() const OVERRIDE;
+ virtual const std::string& uuid() const OVERRIDE;
+
+ virtual bool CreateParams() OVERRIDE;
+ virtual void CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) OVERRIDE;
+ virtual void CreateResults() OVERRIDE;
+
+ protected:
+ virtual ~BluetoothSocketListenUsingRfcommFunction();
+
+ private:
+ scoped_ptr<bluetooth_socket::ListenUsingRfcomm::Params> params_;
+};
+
+class BluetoothSocketListenUsingL2capFunction
+ : public BluetoothSocketListenFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.listenUsingL2cap",
+ BLUETOOTHSOCKET_LISTENUSINGL2CAP);
+
+ BluetoothSocketListenUsingL2capFunction();
+
+ // BluetoothSocketListenFunction:
+ virtual int socket_id() const OVERRIDE;
+ virtual const std::string& uuid() const OVERRIDE;
+
+ virtual bool CreateParams() OVERRIDE;
+ virtual void CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) OVERRIDE;
+ virtual void CreateResults() OVERRIDE;
+
+ protected:
+ virtual ~BluetoothSocketListenUsingL2capFunction();
+
+ private:
+ scoped_ptr<bluetooth_socket::ListenUsingL2cap::Params> params_;
+};
+
+class BluetoothSocketAbstractConnectFunction :
+ public BluetoothSocketAsyncApiFunction {
+ public:
+ BluetoothSocketAbstractConnectFunction();
+
+ protected:
+ virtual ~BluetoothSocketAbstractConnectFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void AsyncWorkStart() OVERRIDE;
+
+ // Subclasses should implement this method to connect to the service
+ // registered with |uuid| on the |device|.
+ virtual void ConnectToService(device::BluetoothDevice* device,
+ const device::BluetoothUUID& uuid) = 0;
+
+ virtual void OnConnect(scoped_refptr<device::BluetoothSocket> socket);
+ virtual void OnConnectError(const std::string& message);
+
+ private:
+ virtual void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+
+ scoped_ptr<bluetooth_socket::Connect::Params> params_;
+ BluetoothSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class BluetoothSocketConnectFunction :
+ public BluetoothSocketAbstractConnectFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.connect",
+ BLUETOOTHSOCKET_CONNECT);
+
+ BluetoothSocketConnectFunction();
+
+ protected:
+ virtual ~BluetoothSocketConnectFunction();
+
+ // BluetoothSocketAbstractConnectFunction:
+ virtual void ConnectToService(device::BluetoothDevice* device,
+ const device::BluetoothUUID& uuid) OVERRIDE;
+};
+
+class BluetoothSocketDisconnectFunction
+ : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.disconnect",
+ BLUETOOTHSOCKET_DISCONNECT);
+
+ BluetoothSocketDisconnectFunction();
+
+ protected:
+ virtual ~BluetoothSocketDisconnectFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void AsyncWorkStart() OVERRIDE;
+
+ private:
+ virtual void OnSuccess();
+
+ scoped_ptr<bluetooth_socket::Disconnect::Params> params_;
+};
+
+class BluetoothSocketCloseFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.close", BLUETOOTHSOCKET_CLOSE);
+
+ BluetoothSocketCloseFunction();
+
+ protected:
+ virtual ~BluetoothSocketCloseFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<bluetooth_socket::Close::Params> params_;
+};
+
+class BluetoothSocketSendFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.send", BLUETOOTHSOCKET_SEND);
+
+ BluetoothSocketSendFunction();
+
+ protected:
+ virtual ~BluetoothSocketSendFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void AsyncWorkStart() OVERRIDE;
+
+ private:
+ void OnSuccess(int bytes_sent);
+ void OnError(BluetoothApiSocket::ErrorReason reason,
+ const std::string& message);
+
+ scoped_ptr<bluetooth_socket::Send::Params> params_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+};
+
+class BluetoothSocketGetInfoFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.getInfo",
+ BLUETOOTHSOCKET_GETINFO);
+
+ BluetoothSocketGetInfoFunction();
+
+ protected:
+ virtual ~BluetoothSocketGetInfoFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<bluetooth_socket::GetInfo::Params> params_;
+};
+
+class BluetoothSocketGetSocketsFunction
+ : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.getSockets",
+ BLUETOOTHSOCKET_GETSOCKETS);
+
+ BluetoothSocketGetSocketsFunction();
+
+ protected:
+ virtual ~BluetoothSocketGetSocketsFunction();
+
+ // BluetoothSocketAsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+};
+
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_API_H_
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc b/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc
new file mode 100644
index 0000000..4fefdc5
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc
@@ -0,0 +1,219 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::BluetoothAdapter;
+using device::BluetoothAdapterFactory;
+using device::BluetoothDevice;
+using device::BluetoothSocket;
+using device::BluetoothUUID;
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using device::MockBluetoothSocket;
+using extensions::Extension;
+
+namespace utils = extension_function_test_utils;
+namespace api = extensions::core_api;
+
+namespace {
+
+class BluetoothSocketApiTest : public ExtensionApiTest {
+ public:
+ BluetoothSocketApiTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionApiTest::SetUpOnMainThread();
+ empty_extension_ = utils::CreateEmptyExtension();
+ SetUpMockAdapter();
+ }
+
+ void SetUpMockAdapter() {
+ // The browser will clean this up when it is torn down.
+ mock_adapter_ = new testing::StrictMock<MockBluetoothAdapter>();
+ BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
+
+ mock_device1_.reset(
+ new testing::NiceMock<MockBluetoothDevice>(mock_adapter_.get(),
+ 0,
+ "d1",
+ "11:12:13:14:15:16",
+ true /* paired */,
+ false /* connected */));
+ mock_device2_.reset(
+ new testing::NiceMock<MockBluetoothDevice>(mock_adapter_.get(),
+ 0,
+ "d2",
+ "21:22:23:24:25:26",
+ true /* paired */,
+ false /* connected */));
+ }
+
+ protected:
+ scoped_refptr<testing::StrictMock<MockBluetoothAdapter> > mock_adapter_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > mock_device1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > mock_device2_;
+
+ private:
+ scoped_refptr<Extension> empty_extension_;
+};
+
+// testing::InvokeArgument<N> does not work with base::Callback, fortunately
+// gmock makes it simple to create action templates that do for the various
+// possible numbers of arguments.
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_0_VALUE_PARAMS()) {
+ ::std::tr1::get<k>(args).Run();
+}
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p0)) {
+ ::std::tr1::get<k>(args).Run(p0);
+}
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_2_VALUE_PARAMS(p0, p1)) {
+ ::std::tr1::get<k>(args).Run(p0, p1);
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(BluetoothSocketApiTest, Connect) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Return the right mock device object for the address used by the test,
+ // return NULL for the "Device not found" test.
+ EXPECT_CALL(*mock_adapter_.get(), GetDevice(mock_device1_->GetAddress()))
+ .WillRepeatedly(testing::Return(mock_device1_.get()));
+ EXPECT_CALL(*mock_adapter_.get(), GetDevice(std::string("aa:aa:aa:aa:aa:aa")))
+ .WillOnce(testing::Return(static_cast<BluetoothDevice*>(NULL)));
+
+ // Return a mock socket object as a successful result to the connect() call.
+ BluetoothUUID service_uuid("8e3ad063-db38-4289-aa8f-b30e4223cf40");
+ scoped_refptr<testing::StrictMock<MockBluetoothSocket> > mock_socket
+ = new testing::StrictMock<MockBluetoothSocket>();
+ EXPECT_CALL(*mock_device1_,
+ ConnectToService(service_uuid, testing::_, testing::_))
+ .WillOnce(InvokeCallbackArgument<1>(mock_socket));
+
+ // Since the socket is unpaused, expect a call to Receive() from the socket
+ // dispatcher. Since there is no data, this will not call its callback.
+ EXPECT_CALL(*mock_socket.get(), Receive(testing::_, testing::_, testing::_));
+
+ // The test also cleans up by calling Disconnect and Close.
+ EXPECT_CALL(*mock_socket.get(), Disconnect(testing::_))
+ .WillOnce(InvokeCallbackArgument<0>());
+ EXPECT_CALL(*mock_socket.get(), Close());
+
+ // Run the test.
+ ExtensionTestMessageListener listener("ready", true);
+ scoped_refptr<const Extension> extension(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth_socket/connect")));
+ ASSERT_TRUE(extension.get());
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+#if defined(_LIBCPP_VERSION)
+// This test fails in libc++ builds, see http://crbug.com/392205.
+#define MAYBE_Listen DISABLED_Listen
+#else
+#define MAYBE_Listen Listen
+#endif
+IN_PROC_BROWSER_TEST_F(BluetoothSocketApiTest, MAYBE_Listen) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Return a mock socket object as a successful result to the create service
+ // call.
+ BluetoothUUID service_uuid("2de497f9-ab28-49db-b6d2-066ea69f1737");
+ scoped_refptr<testing::StrictMock<MockBluetoothSocket> > mock_server_socket
+ = new testing::StrictMock<MockBluetoothSocket>();
+ BluetoothAdapter::ServiceOptions service_options;
+ service_options.name.reset(new std::string("MyServiceName"));
+ EXPECT_CALL(
+ *mock_adapter_.get(),
+ CreateRfcommService(
+ service_uuid,
+ testing::Field(&BluetoothAdapter::ServiceOptions::name,
+ testing::Pointee(testing::Eq("MyServiceName"))),
+ testing::_,
+ testing::_)).WillOnce(InvokeCallbackArgument<2>(mock_server_socket));
+
+ // Since the socket is unpaused, expect a call to Accept() from the socket
+ // dispatcher. We'll immediately send back another mock socket to represent
+ // the client API. Further calls will return no data and behave as if
+ // pending.
+ scoped_refptr<testing::StrictMock<MockBluetoothSocket> > mock_client_socket
+ = new testing::StrictMock<MockBluetoothSocket>();
+ EXPECT_CALL(*mock_server_socket.get(), Accept(testing::_, testing::_))
+ .Times(2)
+ .WillOnce(
+ InvokeCallbackArgument<0>(mock_device1_.get(), mock_client_socket))
+ .WillOnce(testing::Return());
+
+ // Run the test, it sends a ready signal once it's ready for us to dispatch
+ // a client connection to it.
+ ExtensionTestMessageListener socket_listening("ready", true);
+ scoped_refptr<const Extension> extension(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth_socket/listen")));
+ ASSERT_TRUE(extension.get());
+ EXPECT_TRUE(socket_listening.WaitUntilSatisfied());
+
+ // Connection events are dispatched using a couple of PostTask to the UI
+ // thread. Waiting until idle ensures the event is dispatched to the
+ // receiver(s).
+ base::RunLoop().RunUntilIdle();
+ ExtensionTestMessageListener listener("ready", true);
+ socket_listening.Reply("go");
+
+ // Second stage of tests checks for error conditions, and will clean up
+ // the existing server and client sockets.
+ EXPECT_CALL(*mock_server_socket.get(), Disconnect(testing::_))
+ .WillOnce(InvokeCallbackArgument<0>());
+ EXPECT_CALL(*mock_server_socket.get(), Close());
+
+ EXPECT_CALL(*mock_client_socket.get(), Disconnect(testing::_))
+ .WillOnce(InvokeCallbackArgument<0>());
+ EXPECT_CALL(*mock_client_socket.get(), Close());
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply("go");
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothSocketApiTest, PermissionDenied) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ // Run the test.
+ scoped_refptr<const Extension> extension(
+ LoadExtension(test_data_dir_.AppendASCII(
+ "bluetooth_socket/permission_denied")));
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc b/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
new file mode 100644
index 0000000..51ca9b0
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
@@ -0,0 +1,370 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h"
+
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth_socket.h"
+#include "net/base/io_buffer.h"
+
+namespace {
+
+namespace bluetooth_socket = extensions::core_api::bluetooth_socket;
+using extensions::BluetoothApiSocket;
+
+int kDefaultBufferSize = 4096;
+
+bluetooth_socket::ReceiveError MapReceiveErrorReason(
+ BluetoothApiSocket::ErrorReason value) {
+ switch (value) {
+ case BluetoothApiSocket::kDisconnected:
+ return bluetooth_socket::RECEIVE_ERROR_DISCONNECTED;
+ case BluetoothApiSocket::kNotConnected:
+ // kNotConnected is impossible since a socket has to be connected to be
+ // able to call Receive() on it.
+ // fallthrough
+ case BluetoothApiSocket::kIOPending:
+ // kIOPending is not relevant to apps, as BluetoothSocketEventDispatcher
+ // handles this specific error.
+ // fallthrough
+ default:
+ return bluetooth_socket::RECEIVE_ERROR_SYSTEM_ERROR;
+ }
+}
+
+bluetooth_socket::AcceptError MapAcceptErrorReason(
+ BluetoothApiSocket::ErrorReason value) {
+ // TODO(keybuk): All values are system error, we may want to seperate these
+ // out to more discrete reasons.
+ switch (value) {
+ case BluetoothApiSocket::kNotListening:
+ // kNotListening is impossible since a socket has to be listening to be
+ // able to call Accept() on it.
+ // fallthrough
+ default:
+ return bluetooth_socket::ACCEPT_ERROR_SYSTEM_ERROR;
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace core_api {
+
+using content::BrowserThread;
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>*
+BluetoothSocketEventDispatcher::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+BluetoothSocketEventDispatcher* BluetoothSocketEventDispatcher::Get(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ return BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>::Get(
+ context);
+}
+
+BluetoothSocketEventDispatcher::BluetoothSocketEventDispatcher(
+ content::BrowserContext* context)
+ : thread_id_(BluetoothApiSocket::kThreadId),
+ browser_context_(context) {
+ ApiResourceManager<BluetoothApiSocket>* manager =
+ ApiResourceManager<BluetoothApiSocket>::Get(browser_context_);
+ DCHECK(manager)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<BluetoothApiSocket>.";
+ sockets_ = manager->data_;
+}
+
+BluetoothSocketEventDispatcher::~BluetoothSocketEventDispatcher() {}
+
+BluetoothSocketEventDispatcher::SocketParams::SocketParams() {}
+
+BluetoothSocketEventDispatcher::SocketParams::~SocketParams() {}
+
+void BluetoothSocketEventDispatcher::OnSocketConnect(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(BrowserThread::CurrentlyOn(thread_id_));
+
+ SocketParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ StartReceive(params);
+}
+
+void BluetoothSocketEventDispatcher::OnSocketListen(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(BrowserThread::CurrentlyOn(thread_id_));
+
+ SocketParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ StartAccept(params);
+}
+
+void BluetoothSocketEventDispatcher::OnSocketResume(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(BrowserThread::CurrentlyOn(thread_id_));
+
+ SocketParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+
+ if (socket->IsConnected()) {
+ StartReceive(params);
+ } else {
+ StartAccept(params);
+ }
+}
+
+// static
+void BluetoothSocketEventDispatcher::StartReceive(const SocketParams& params) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another read if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ int buffer_size = socket->buffer_size();
+ if (buffer_size <= 0)
+ buffer_size = kDefaultBufferSize;
+ socket->Receive(
+ buffer_size,
+ base::Bind(
+ &BluetoothSocketEventDispatcher::ReceiveCallback, params),
+ base::Bind(
+ &BluetoothSocketEventDispatcher::ReceiveErrorCallback, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::ReceiveCallback(
+ const SocketParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ // Dispatch "onReceive" event.
+ bluetooth_socket::ReceiveInfo receive_info;
+ receive_info.socket_id = params.socket_id;
+ receive_info.data = std::string(io_buffer->data(), bytes_read);
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnReceive::Create(receive_info);
+ scoped_ptr<Event> event(
+ new Event(bluetooth_socket::OnReceive::kEventName, args.Pass()));
+ PostEvent(params, event.Pass());
+
+ // Post a task to delay the read until the socket is available, as
+ // calling StartReceive at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&BluetoothSocketEventDispatcher::StartReceive, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::ReceiveErrorCallback(
+ const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ if (error_reason == BluetoothApiSocket::kIOPending) {
+ // This happens when resuming a socket which already had an active "read"
+ // callback. We can safely ignore this error, as the application should not
+ // care.
+ return;
+ }
+
+ // Dispatch "onReceiveError" event but don't start another read to avoid
+ // potential infinite reads if we have a persistent network error.
+ bluetooth_socket::ReceiveErrorInfo receive_error_info;
+ receive_error_info.socket_id = params.socket_id;
+ receive_error_info.error_message = error;
+ receive_error_info.error = MapReceiveErrorReason(error_reason);
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnReceiveError::Create(receive_error_info);
+ scoped_ptr<Event> event(
+ new Event(bluetooth_socket::OnReceiveError::kEventName, args.Pass()));
+ PostEvent(params, event.Pass());
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+}
+
+// static
+void BluetoothSocketEventDispatcher::StartAccept(const SocketParams& params) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another accept if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ socket->Accept(
+ base::Bind(
+ &BluetoothSocketEventDispatcher::AcceptCallback, params),
+ base::Bind(
+ &BluetoothSocketEventDispatcher::AcceptErrorCallback, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::AcceptCallback(
+ const SocketParams& params,
+ const device::BluetoothDevice* device,
+ scoped_refptr<device::BluetoothSocket> socket) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ BluetoothApiSocket* server_api_socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ DCHECK(server_api_socket);
+
+ BluetoothApiSocket* client_api_socket = new BluetoothApiSocket(
+ params.extension_id,
+ socket,
+ device->GetAddress(),
+ server_api_socket->uuid());
+ int client_socket_id = params.sockets->Add(client_api_socket);
+
+ // Dispatch "onAccept" event.
+ bluetooth_socket::AcceptInfo accept_info;
+ accept_info.socket_id = params.socket_id;
+ accept_info.client_socket_id = client_socket_id;
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnAccept::Create(accept_info);
+ scoped_ptr<Event> event(
+ new Event(bluetooth_socket::OnAccept::kEventName, args.Pass()));
+ PostEvent(params, event.Pass());
+
+ // Post a task to delay the accept until the socket is available, as
+ // calling StartAccept at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&BluetoothSocketEventDispatcher::StartAccept, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::AcceptErrorCallback(
+ const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ if (error_reason == BluetoothApiSocket::kIOPending) {
+ // This happens when resuming a socket which already had an active "accept"
+ // callback. We can safely ignore this error, as the application should not
+ // care.
+ return;
+ }
+
+ // Dispatch "onAcceptError" event but don't start another accept to avoid
+ // potential infinite accepts if we have a persistent network error.
+ bluetooth_socket::AcceptErrorInfo accept_error_info;
+ accept_error_info.socket_id = params.socket_id;
+ accept_error_info.error_message = error;
+ accept_error_info.error = MapAcceptErrorReason(error_reason);
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnAcceptError::Create(accept_error_info);
+ scoped_ptr<Event> event(
+ new Event(bluetooth_socket::OnAcceptError::kEventName, args.Pass()));
+ PostEvent(params, event.Pass());
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+}
+
+// static
+void BluetoothSocketEventDispatcher::PostEvent(const SocketParams& params,
+ scoped_ptr<Event> event) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DispatchEvent,
+ params.browser_context_id,
+ params.extension_id,
+ base::Passed(event.Pass())));
+}
+
+// static
+void BluetoothSocketEventDispatcher::DispatchEvent(
+ void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ content::BrowserContext* context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+
+ EventRouter* router = EventRouter::Get(context);
+ if (router)
+ router->DispatchEventToExtension(extension_id, event.Pass());
+}
+
+} // namespace core_api
+} // namespace extensions
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h b/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h
new file mode 100644
index 0000000..50c246a
--- /dev/null
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_EVENT_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_EVENT_DISPATCHER_H_
+
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+class BluetoothDevice;
+class BluetoothSocket;
+}
+
+namespace extensions {
+struct Event;
+class BluetoothApiSocket;
+}
+
+namespace extensions {
+namespace core_api {
+
+// Dispatch events related to "bluetooth" sockets from callback on native socket
+// instances. There is one instance per browser context.
+class BluetoothSocketEventDispatcher
+ : public BrowserContextKeyedAPI,
+ public base::SupportsWeakPtr<BluetoothSocketEventDispatcher> {
+ public:
+ explicit BluetoothSocketEventDispatcher(content::BrowserContext* context);
+ virtual ~BluetoothSocketEventDispatcher();
+
+ // Socket is active, start receiving data from it.
+ void OnSocketConnect(const std::string& extension_id, int socket_id);
+
+ // Socket is active again, start accepting connections from it.
+ void OnSocketListen(const std::string& extension_id, int socket_id);
+
+ // Socket is active again, start receiving data from it.
+ void OnSocketResume(const std::string& extension_id, int socket_id);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>*
+ GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static BluetoothSocketEventDispatcher* Get(content::BrowserContext* context);
+
+ private:
+ typedef ApiResourceManager<BluetoothApiSocket>::ApiResourceData SocketData;
+ friend class BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>;
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothSocketEventDispatcher"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // base::Bind supports methods with up to 6 parameters. SocketParams is used
+ // as a workaround that limitation for invoking StartReceive() and
+ // StartAccept().
+ struct SocketParams {
+ SocketParams();
+ ~SocketParams();
+
+ content::BrowserThread::ID thread_id;
+ void* browser_context_id;
+ std::string extension_id;
+ scoped_refptr<SocketData> sockets;
+ int socket_id;
+ };
+
+ // Start a receive and register a callback.
+ static void StartReceive(const SocketParams& params);
+
+ // Called when socket receive data.
+ static void ReceiveCallback(const SocketParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer);
+
+ // Called when socket receive data.
+ static void ReceiveErrorCallback(const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error);
+
+ // Start an accept and register a callback.
+ static void StartAccept(const SocketParams& params);
+
+ // Called when socket accepts a client connection.
+ static void AcceptCallback(const SocketParams& params,
+ const device::BluetoothDevice* device,
+ scoped_refptr<device::BluetoothSocket> socket);
+
+ // Called when socket encounters an error while accepting a client connection.
+ static void AcceptErrorCallback(const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error);
+
+ // Post an extension event from IO to UI thread
+ static void PostEvent(const SocketParams& params, scoped_ptr<Event> event);
+
+ // Dispatch an extension event on to EventRouter instance on UI thread.
+ static void DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Usually FILE thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ content::BrowserContext* const browser_context_;
+ scoped_refptr<SocketData> sockets_;
+};
+
+} // namespace core_api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_EVENT_DISPATCHER_H_
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index ab22d4c..7c26d6c 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -8,6 +8,12 @@ import("//build/config/features.gni")
source_set("common") {
sources = [
"api/messaging/message.h",
+ "api/bluetooth/bluetooth_manifest_data.cc",
+ "api/bluetooth/bluetooth_manifest_data.h",
+ "api/bluetooth/bluetooth_manifest_handler.cc",
+ "api/bluetooth/bluetooth_manifest_handler.h",
+ "api/bluetooth/bluetooth_manifest_permission.cc",
+ "api/bluetooth/bluetooth_manifest_permission.h",
"api/sockets/sockets_manifest_data.cc",
"api/sockets/sockets_manifest_data.h",
"api/sockets/sockets_manifest_handler.cc",
@@ -207,10 +213,17 @@ source_set("common") {
]
deps += [
+ "//device/bluetooth",
"//device/usb",
]
} else {
sources -= [
+ "api/bluetooth/bluetooth_manifest_data.cc",
+ "api/bluetooth/bluetooth_manifest_data.h",
+ "api/bluetooth/bluetooth_manifest_handler.cc",
+ "api/bluetooth/bluetooth_manifest_handler.h",
+ "api/bluetooth/bluetooth_manifest_permission.cc",
+ "api/bluetooth/bluetooth_manifest_permission.h",
"api/messaging/message.h",
"api/sockets/sockets_manifest_data.cc",
"api/sockets/sockets_manifest_data.h",
diff --git a/extensions/common/DEPS b/extensions/common/DEPS
index 9cfa5de..bef368e1 100644
--- a/extensions/common/DEPS
+++ b/extensions/common/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+device/bluetooth", # For BluetoothPermission
"+device/usb",
"+grit/extensions_strings.h",
"+libxml",
diff --git a/extensions/common/api/bluetooth.idl b/extensions/common/api/bluetooth.idl
new file mode 100644
index 0000000..1febc8b
--- /dev/null
+++ b/extensions/common/api/bluetooth.idl
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.bluetooth</code> API to connect to a Bluetooth
+// device. All functions report failures via chrome.runtime.lastError.
+namespace bluetooth {
+ // Allocation authorities for Vendor IDs.
+ enum VendorIdSource {bluetooth, usb};
+
+ // Common device types recognized by Chrome.
+ enum DeviceType {computer, phone, modem, audio, carAudio, video, peripheral,
+ joystick, gamepad, keyboard, mouse, tablet,
+ keyboardMouseCombo};
+
+ // Information about the state of the Bluetooth adapter.
+ dictionary AdapterState {
+ // The address of the adapter, in the format 'XX:XX:XX:XX:XX:XX'.
+ DOMString address;
+
+ // The human-readable name of the adapter.
+ DOMString name;
+
+ // Indicates whether or not the adapter has power.
+ boolean powered;
+
+ // Indicates whether or not the adapter is available (i.e. enabled).
+ boolean available;
+
+ // Indicates whether or not the adapter is currently discovering.
+ boolean discovering;
+ };
+
+ // Callback from the <code>getAdapterState</code> method.
+ // |adapterInfo| : Object containing the adapter information.
+ callback AdapterStateCallback = void(AdapterState adapterInfo);
+
+ // Information about the state of a known Bluetooth device.
+ dictionary Device {
+ // The address of the device, in the format 'XX:XX:XX:XX:XX:XX'.
+ DOMString address;
+
+ // The human-readable name of the device.
+ DOMString? name;
+
+ // The class of the device, a bit-field defined by
+ // http://www.bluetooth.org/en-us/specification/assigned-numbers/baseband.
+ long? deviceClass;
+
+ // The Device ID record of the device, where available.
+ VendorIdSource? vendorIdSource;
+ long? vendorId;
+ long? productId;
+ long? deviceId;
+
+ // The type of the device, if recognized by Chrome. This is obtained from
+ // the |deviceClass| field and only represents a small fraction of the
+ // possible device types. When in doubt you should use the |deviceClass|
+ // field directly.
+ DeviceType? type;
+
+ // Indicates whether or not the device is paired with the system.
+ boolean? paired;
+
+ // Indicates whether the device is currently connected to the system.
+ boolean? connected;
+
+ // Indicates the RSSI ("received signal strength indication") of the
+ // connection to the device, measured in dBm, to a resolution of 1dBm.
+ // If the device is currently connected, then measures the RSSI of the
+ // connection signal. Otherwise, measures the RSSI of the last inquiry sent
+ // to the device, where available. Absent if unavailable.
+ [nodoc] long? rssi;
+
+ // Indicates the host's current transmit power ("Tx power") for the
+ // connection to the device, measured in dBm, to a resolution of 1dBm.
+ // This value is only available if the device is currently connected.
+ [nodoc] long? currentHostTransmitPower;
+
+ // Indicates the host's maximum transmit power ("Tx power") for the
+ // connection to the device, measured in dBm, to a resolution of 1dBm.
+ // This value is only available if the device is currently connected.
+ [nodoc] long? maximumHostTransmitPower;
+
+ // UUIDs of protocols, profiles and services advertised by the device.
+ // For classic Bluetooth devices, this list is obtained from EIR data and
+ // SDP tables. For Low Energy devices, this list is obtained from AD and
+ // GATT primary services. For dual mode devices this may be obtained from
+ // both.
+ DOMString[]? uuids;
+ };
+
+ // Callback from the <code>getDevice</code> method.
+ // |deviceInfo| : Object containing the device information.
+ callback GetDeviceCallback = void(Device deviceInfo);
+
+ // Callback from the <code>getDevices</code> method.
+ // |deviceInfos| : Array of object containing device information.
+ callback GetDevicesCallback = void(Device[] deviceInfos);
+
+ // Callback from the <code>startDiscovery</code> method.
+ callback StartDiscoveryCallback = void();
+
+ // Callback from the <code>stopDiscovery</code> method.
+ callback StopDiscoveryCallback = void();
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Get information about the Bluetooth adapter.
+ // |callback| : Called with an AdapterState object describing the adapter
+ // state.
+ static void getAdapterState(AdapterStateCallback callback);
+
+ // Get information about a Bluetooth device known to the system.
+ // |deviceAddress| : Address of device to get.
+ // |callback| : Called with the Device object describing the device.
+ static void getDevice(DOMString deviceAddress, GetDeviceCallback callback);
+
+ // Get a list of Bluetooth devices known to the system, including paired
+ // and recently discovered devices.
+ // |callback| : Called when the search is completed.
+ static void getDevices(GetDevicesCallback callback);
+
+ // Start discovery. Newly discovered devices will be returned via the
+ // onDeviceAdded event. Previously discovered devices already known to
+ // the adapter must be obtained using getDevices and will only be updated
+ // using the |onDeviceChanged| event if information about them changes.
+ //
+ // Discovery will fail to start if this application has already called
+ // startDiscovery. Discovery can be resource intensive: stopDiscovery
+ // should be called as soon as possible.
+ // |callback| : Called to indicate success or failure.
+ static void startDiscovery(optional StartDiscoveryCallback callback);
+
+ // Stop discovery.
+ // |callback| : Called to indicate success or failure.
+ static void stopDiscovery(optional StopDiscoveryCallback callback);
+ };
+
+ interface Events {
+ // Fired when the state of the Bluetooth adapter changes.
+ // |state| : The new state of the adapter.
+ static void onAdapterStateChanged(AdapterState state);
+
+ // Fired when information about a new Bluetooth device is available.
+ static void onDeviceAdded(Device device);
+
+ // Fired when information about a known Bluetooth device has changed.
+ static void onDeviceChanged(Device device);
+
+ // Fired when a Bluetooth device that was previously discovered has been
+ // out of range for long enough to be considered unavailable again, and
+ // when a paired device is removed.
+ static void onDeviceRemoved(Device device);
+ };
+};
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_data.cc b/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
new file mode 100644
index 0000000..4d777cf
--- /dev/null
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_permission.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+BluetoothManifestData::BluetoothManifestData(
+ scoped_ptr<BluetoothManifestPermission> permission)
+ : permission_(permission.Pass()) {
+ DCHECK(permission_);
+}
+
+BluetoothManifestData::~BluetoothManifestData() {}
+
+// static
+BluetoothManifestData* BluetoothManifestData::Get(const Extension* extension) {
+ return static_cast<BluetoothManifestData*>(
+ extension->GetManifestData(manifest_keys::kBluetooth));
+}
+
+// static
+bool BluetoothManifestData::CheckRequest(
+ const Extension* extension,
+ const BluetoothPermissionRequest& request) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckRequest(extension, request);
+}
+
+// static
+bool BluetoothManifestData::CheckSocketPermitted(
+ const Extension* extension) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckSocketPermitted(extension);
+}
+
+// static
+bool BluetoothManifestData::CheckLowEnergyPermitted(
+ const Extension* extension) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckLowEnergyPermitted(extension);
+}
+
+// static
+scoped_ptr<BluetoothManifestData> BluetoothManifestData::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<BluetoothManifestPermission> permission =
+ BluetoothManifestPermission::FromValue(value, error);
+ if (!permission)
+ return scoped_ptr<BluetoothManifestData>();
+
+ return scoped_ptr<BluetoothManifestData>(
+ new BluetoothManifestData(permission.Pass())).Pass();
+}
+
+BluetoothPermissionRequest::BluetoothPermissionRequest(
+ const std::string& uuid)
+ : uuid(uuid) {}
+
+BluetoothPermissionRequest::~BluetoothPermissionRequest() {}
+
+} // namespace extensions
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_data.h b/extensions/common/api/bluetooth/bluetooth_manifest_data.h
new file mode 100644
index 0000000..191a1ec
--- /dev/null
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_data.h
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_DATA_H_
+#define EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_DATA_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+class BluetoothManifestPermission;
+struct BluetoothPermissionRequest;
+}
+
+namespace extensions {
+
+// The parsed form of the "bluetooth" manifest entry.
+class BluetoothManifestData : public Extension::ManifestData {
+ public:
+ explicit BluetoothManifestData(
+ scoped_ptr<BluetoothManifestPermission> permission);
+ virtual ~BluetoothManifestData();
+
+ // Gets the BluetoothManifestData for |extension|, or NULL if none was
+ // specified.
+ static BluetoothManifestData* Get(const Extension* extension);
+
+ static bool CheckRequest(const Extension* extension,
+ const BluetoothPermissionRequest& request);
+
+ static bool CheckSocketPermitted(const Extension* extension);
+ static bool CheckLowEnergyPermitted(const Extension* extension);
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<BluetoothManifestData> FromValue(const base::Value& value,
+ base::string16* error);
+
+ const BluetoothManifestPermission* permission() const {
+ return permission_.get();
+ }
+
+ private:
+ scoped_ptr<BluetoothManifestPermission> permission_;
+};
+
+// Used for checking bluetooth permission.
+struct BluetoothPermissionRequest {
+ explicit BluetoothPermissionRequest(const std::string& uuid);
+ ~BluetoothPermissionRequest();
+
+ std::string uuid;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_DATA_H_
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc b/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc
new file mode 100644
index 0000000..da143fa
--- /dev/null
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_handler.h"
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_permission.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+BluetoothManifestHandler::BluetoothManifestHandler() {}
+
+BluetoothManifestHandler::~BluetoothManifestHandler() {}
+
+bool BluetoothManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* bluetooth = NULL;
+ CHECK(extension->manifest()->Get(manifest_keys::kBluetooth, &bluetooth));
+ scoped_ptr<BluetoothManifestData> data =
+ BluetoothManifestData::FromValue(*bluetooth, error);
+ if (!data)
+ return false;
+
+ extension->SetManifestData(manifest_keys::kBluetooth, data.release());
+ return true;
+}
+
+ManifestPermission* BluetoothManifestHandler::CreatePermission() {
+ return new BluetoothManifestPermission();
+}
+
+ManifestPermission* BluetoothManifestHandler::CreateInitialRequiredPermission(
+ const Extension* extension) {
+ BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ if (data)
+ return data->permission()->Clone();
+ return NULL;
+}
+
+const std::vector<std::string> BluetoothManifestHandler::Keys() const {
+ return SingleKey(manifest_keys::kBluetooth);
+}
+
+} // namespace extensions
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_handler.h b/extensions/common/api/bluetooth/bluetooth_manifest_handler.h
new file mode 100644
index 0000000..cd4224e
--- /dev/null
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_handler.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+class Extension;
+class ManifestPermission;
+}
+
+namespace extensions {
+
+// Parses the "bluetooth" manifest key.
+class BluetoothManifestHandler : public ManifestHandler {
+ public:
+ BluetoothManifestHandler();
+ virtual ~BluetoothManifestHandler();
+
+ // ManifestHandler overrides.
+ virtual bool Parse(Extension* extension, base::string16* error) OVERRIDE;
+ virtual ManifestPermission* CreatePermission() OVERRIDE;
+ virtual ManifestPermission* CreateInitialRequiredPermission(
+ const Extension* extension) OVERRIDE;
+
+ private:
+ // ManifestHandler overrides.
+ virtual const std::vector<std::string> Keys() const OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_HANDLER_H_
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc b/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc
new file mode 100644
index 0000000..e441e55
--- /dev/null
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc
@@ -0,0 +1,202 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_permission.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_constants.h"
+#include "grit/extensions_strings.h"
+#include "ipc/ipc_message.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace bluetooth_errors {
+const char kErrorInvalidUuid[] = "Invalid UUID '*'";
+}
+
+namespace errors = bluetooth_errors;
+
+namespace {
+
+bool ParseUuid(BluetoothManifestPermission* permission,
+ const std::string& uuid,
+ base::string16* error) {
+ device::BluetoothUUID bt_uuid(uuid);
+ if (!bt_uuid.IsValid()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kErrorInvalidUuid, uuid);
+ return false;
+ }
+ permission->AddPermission(uuid);
+ return true;
+}
+
+bool ParseUuidArray(BluetoothManifestPermission* permission,
+ const scoped_ptr<std::vector<std::string> >& uuids,
+ base::string16* error) {
+ for (std::vector<std::string>::const_iterator it = uuids->begin();
+ it != uuids->end();
+ ++it) {
+ if (!ParseUuid(permission, *it, error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+BluetoothManifestPermission::BluetoothManifestPermission()
+ : socket_(false),
+ low_energy_(false) {}
+
+BluetoothManifestPermission::~BluetoothManifestPermission() {}
+
+// static
+scoped_ptr<BluetoothManifestPermission> BluetoothManifestPermission::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<core_api::extensions_manifest_types::Bluetooth> bluetooth =
+ core_api::extensions_manifest_types::Bluetooth::FromValue(value, error);
+ if (!bluetooth)
+ return scoped_ptr<BluetoothManifestPermission>();
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ if (bluetooth->uuids) {
+ if (!ParseUuidArray(result.get(), bluetooth->uuids, error)) {
+ return scoped_ptr<BluetoothManifestPermission>();
+ }
+ }
+ if (bluetooth->socket) {
+ result->socket_ = *(bluetooth->socket);
+ }
+ if (bluetooth->low_energy) {
+ result->low_energy_ = *(bluetooth->low_energy);
+ }
+ return result.Pass();
+}
+
+bool BluetoothManifestPermission::CheckRequest(
+ const Extension* extension,
+ const BluetoothPermissionRequest& request) const {
+
+ device::BluetoothUUID param_uuid(request.uuid);
+ for (BluetoothUuidSet::const_iterator it = uuids_.begin();
+ it != uuids_.end();
+ ++it) {
+ device::BluetoothUUID uuid(*it);
+ if (param_uuid == uuid)
+ return true;
+ }
+ return false;
+}
+
+bool BluetoothManifestPermission::CheckSocketPermitted(
+ const Extension* extension) const {
+ return socket_;
+}
+
+bool BluetoothManifestPermission::CheckLowEnergyPermitted(
+ const Extension* extension) const {
+ return low_energy_;
+}
+
+std::string BluetoothManifestPermission::name() const {
+ return manifest_keys::kBluetooth;
+}
+
+std::string BluetoothManifestPermission::id() const { return name(); }
+
+bool BluetoothManifestPermission::HasMessages() const { return true; }
+
+PermissionMessages BluetoothManifestPermission::GetMessages() const {
+ DCHECK(HasMessages());
+ PermissionMessages result;
+
+ result.push_back(PermissionMessage(
+ PermissionMessage::kBluetooth,
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH)));
+
+ if (!uuids_.empty()) {
+ result.push_back(
+ PermissionMessage(PermissionMessage::kBluetoothDevices,
+ l10n_util::GetStringUTF16(
+ IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH_DEVICES)));
+ }
+
+ return result;
+}
+
+bool BluetoothManifestPermission::FromValue(const base::Value* value) {
+ if (!value)
+ return false;
+ base::string16 error;
+ scoped_ptr<BluetoothManifestPermission> manifest_permission(
+ BluetoothManifestPermission::FromValue(*value, &error));
+
+ if (!manifest_permission)
+ return false;
+
+ uuids_ = manifest_permission->uuids_;
+ return true;
+}
+
+scoped_ptr<base::Value> BluetoothManifestPermission::ToValue() const {
+ core_api::extensions_manifest_types::Bluetooth bluetooth;
+ bluetooth.uuids.reset(new std::vector<std::string>(uuids_.begin(),
+ uuids_.end()));
+ return bluetooth.ToValue().PassAs<base::Value>();
+}
+
+ManifestPermission* BluetoothManifestPermission::Diff(
+ const ManifestPermission* rhs) const {
+ const BluetoothManifestPermission* other =
+ static_cast<const BluetoothManifestPermission*>(rhs);
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ result->uuids_ = base::STLSetDifference<BluetoothUuidSet>(
+ uuids_, other->uuids_);
+ return result.release();
+}
+
+ManifestPermission* BluetoothManifestPermission::Union(
+ const ManifestPermission* rhs) const {
+ const BluetoothManifestPermission* other =
+ static_cast<const BluetoothManifestPermission*>(rhs);
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ result->uuids_ = base::STLSetUnion<BluetoothUuidSet>(
+ uuids_, other->uuids_);
+ return result.release();
+}
+
+ManifestPermission* BluetoothManifestPermission::Intersect(
+ const ManifestPermission* rhs) const {
+ const BluetoothManifestPermission* other =
+ static_cast<const BluetoothManifestPermission*>(rhs);
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ result->uuids_ = base::STLSetIntersection<BluetoothUuidSet>(
+ uuids_, other->uuids_);
+ return result.release();
+}
+
+void BluetoothManifestPermission::AddPermission(const std::string& uuid) {
+ uuids_.insert(uuid);
+}
+
+} // namespace extensions
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_permission.h b/extensions/common/api/bluetooth/bluetooth_manifest_permission.h
new file mode 100644
index 0000000..4bbad21
--- /dev/null
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_permission.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_PERMISSION_H_
+#define EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_PERMISSION_H_
+
+#include <set>
+#include <vector>
+
+#include "extensions/common/install_warning.h"
+#include "extensions/common/permissions/manifest_permission.h"
+
+namespace extensions {
+class Extension;
+}
+
+namespace extensions {
+struct BluetoothPermissionRequest;
+}
+
+namespace extensions {
+
+class BluetoothManifestPermission : public ManifestPermission {
+ public:
+ typedef std::set<std::string> BluetoothUuidSet;
+ BluetoothManifestPermission();
+ virtual ~BluetoothManifestPermission();
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<BluetoothManifestPermission> FromValue(
+ const base::Value& value,
+ base::string16* error);
+
+ bool CheckRequest(const Extension* extension,
+ const BluetoothPermissionRequest& request) const;
+ bool CheckSocketPermitted(const Extension* extension) const;
+ bool CheckLowEnergyPermitted(const Extension* extension) const;
+
+ void AddPermission(const std::string& uuid);
+
+ // extensions::ManifestPermission overrides.
+ virtual std::string name() const OVERRIDE;
+ virtual std::string id() const OVERRIDE;
+ virtual bool HasMessages() const OVERRIDE;
+ virtual PermissionMessages GetMessages() const OVERRIDE;
+ virtual bool FromValue(const base::Value* value) OVERRIDE;
+ virtual scoped_ptr<base::Value> ToValue() const OVERRIDE;
+ virtual ManifestPermission* Diff(const ManifestPermission* rhs)
+ const OVERRIDE;
+ virtual ManifestPermission* Union(const ManifestPermission* rhs)
+ const OVERRIDE;
+ virtual ManifestPermission* Intersect(const ManifestPermission* rhs)
+ const OVERRIDE;
+
+ const BluetoothUuidSet& uuids() const {
+ return uuids_;
+ }
+
+ private:
+ BluetoothUuidSet uuids_;
+ bool socket_;
+ bool low_energy_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_PERMISSION_H_
diff --git a/extensions/common/api/bluetooth_low_energy.idl b/extensions/common/api/bluetooth_low_energy.idl
new file mode 100644
index 0000000..517a813
--- /dev/null
+++ b/extensions/common/api/bluetooth_low_energy.idl
@@ -0,0 +1,299 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The <code>chrome.bluetoothLowEnergy</code> API is used to communicate with
+// Bluetooth Smart (Low Energy) devices using the
+// <a href="https://developer.bluetooth.org/TechnologyOverview/Pages/GATT.aspx">
+// Generic Attribute Profile (GATT)</a>.
+namespace bluetoothLowEnergy {
+ // Values representing the possible properties of a characteristic.
+ enum CharacteristicProperty {broadcast, read, writeWithoutResponse, write,
+ notify, indicate, authenticatedSignedWrites,
+ extendedProperties, reliableWrite,
+ writableAuxiliaries};
+
+ // Represents a peripheral's Bluetooth GATT Service, a collection of
+ // characteristics and relationships to other services that encapsulate
+ // the behavior of part of a device.
+ dictionary Service {
+ // The UUID of the service, e.g. 0000180d-0000-1000-8000-00805f9b34fb.
+ DOMString uuid;
+
+ // Indicates whether the type of this service is primary or secondary.
+ boolean isPrimary;
+
+ // Indicates whether this service represents a local service hosted by the
+ // application and available to other peripherals, or a remote service
+ // hosted and received from a remote peripheral.
+ [nodoc] boolean isLocal;
+
+ // Returns the identifier assigned to this service. Use the instance ID to
+ // distinguish between services from a peripheral with the same UUID and
+ // to make function calls that take in a service identifier. Present, if
+ // this instance represents a remote service.
+ DOMString? instanceId;
+
+ // The device address of the remote peripheral that the GATT service belongs
+ // to. Present, if this instance represents a remote service.
+ DOMString? deviceAddress;
+ };
+
+ // Represents a GATT characteristic, which is a basic data element that
+ // provides further information about a peripheral's service.
+ dictionary Characteristic {
+ // The UUID of the characteristic, e.g.
+ // 00002a37-0000-1000-8000-00805f9b34fb.
+ DOMString uuid;
+
+ // Indicates whether this characteristic represents a local characteristic
+ // hosted by the application and available to other peripherals, or a remote
+ // characteristic hosted and received from a remote peripheral.
+ [nodoc] boolean isLocal;
+
+ // The GATT service this characteristic belongs to.
+ Service service;
+
+ // The properties of this characteristic.
+ CharacteristicProperty[] properties;
+
+ // Returns the identifier assigned to this characteristic. Use the instance
+ // ID to distinguish between characteristics from a peripheral with the same
+ // UUID and to make function calls that take in a characteristic identifier.
+ // Present, if this instance represents a remote characteristic.
+ DOMString? instanceId;
+
+ // The currently cached characteristic value. This value gets updated when
+ // the value of the characteristic is read or updated via a notification
+ // or indication.
+ ArrayBuffer? value;
+ };
+
+ // Represents a GATT characteristic descriptor, which provides further
+ // information about a characteristic's value.
+ dictionary Descriptor {
+ // The UUID of the characteristic descriptor, e.g.
+ // 00002902-0000-1000-8000-00805f9b34fb.
+ DOMString uuid;
+
+ // Indicates whether this descriptor represents a local descriptor
+ // hosted by the application and available to other peripherals, or a remote
+ // descriptor hosted and received from a remote peripheral.
+ [nodoc] boolean isLocal;
+
+ // The GATT characteristic this descriptor belongs to.
+ Characteristic characteristic;
+
+ // Returns the identifier assigned to this descriptor. Use the instance ID
+ // to distinguish between descriptors from a peripheral with the same UUID
+ // and to make function calls that take in a descriptor identifier. Present,
+ // if this instance represents a remote characteristic.
+ DOMString? instanceId;
+
+ // The currently cached descriptor value. This value gets updated when
+ // the value of the descriptor is read.
+ ArrayBuffer? value;
+ };
+
+ // The connection properties specified during a call to $(ref:connect).
+ dictionary ConnectProperties {
+ // Flag indicating whether a connection to the device is left open when the
+ // event page of the application is unloaded (see <a
+ // href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is <code>false.</code>
+ boolean persistent;
+ };
+
+ // Optional characteristic notification session properties specified during a
+ // call to $(ref:startCharacteristicNotifications).
+ dictionary NotificationProperties {
+ // Flag indicating whether the app should receive notifications when the
+ // event page of the application is unloaded (see <a
+ // href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is <code>false</code>.
+ boolean persistent;
+ };
+
+ callback CharacteristicCallback = void(Characteristic result);
+ callback CharacteristicsCallback = void(Characteristic[] result);
+ callback DescriptorCallback = void(Descriptor result);
+ callback DescriptorsCallback = void(Descriptor[] result);
+ callback ResultCallback = void();
+ callback ServiceCallback = void(Service result);
+ callback ServicesCallback = void(Service[] result);
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Establishes a connection between the application and the device with the
+ // given address. A device may be already connected and its GATT services
+ // available without calling <code>connect</code>, however, an app that
+ // wants to access GATT services of a device should call this function to
+ // make sure that a connection to the device is maintained. If the device
+ // is not connected, all GATT services of the device will be discovered
+ // after a successful call to <code>connect</code>.
+ // |deviceAddress| : The Bluetooth address of the remote device to which a
+ // GATT connection should be opened.
+ // |properties| : Connection properties (optional).
+ // |callback| : Called when the connect request has completed.
+ static void connect(DOMString deviceAddress,
+ optional ConnectProperties properties,
+ ResultCallback callback);
+
+ // Closes the app's connection to the device with the given address. Note
+ // that this will not always destroy the physical link itself, since there
+ // may be other apps with open connections.
+ // |deviceAddress| : The Bluetooth address of the remote device.
+ // |callback| : Called when the disconnect request has completed.
+ static void disconnect(DOMString deviceAddress,
+ optional ResultCallback callback);
+
+ // Get the GATT service with the given instance ID.
+ // |serviceId| : The instance ID of the requested GATT service.
+ // |callback| : Called with the requested Service object.
+ static void getService(DOMString serviceId, ServiceCallback callback);
+
+ // Get all the GATT services that were discovered on the remote device with
+ // the given device address.
+ // |deviceAddress| : The Bluetooth address of the remote device whose GATT
+ // services should be returned.
+ // |callback| : Called with the list of requested Service objects.
+ static void getServices(DOMString deviceAddress, ServicesCallback callback);
+
+ // Get the GATT characteristic with the given instance ID that belongs to
+ // the given GATT service, if the characteristic exists.
+ // |characteristicId| : The instance ID of the requested GATT
+ // characteristic.
+ // |callback| : Called with the requested Characteristic object.
+ static void getCharacteristic(DOMString characteristicId,
+ CharacteristicCallback callback);
+
+ // Get a list of all discovered GATT characteristics that belong to the
+ // given service.
+ // |serviceId| : The instance ID of the GATT service whose characteristics
+ // should be returned.
+ // |callback| : Called with the list of characteristics that belong to the
+ // given service.
+ static void getCharacteristics(DOMString serviceId,
+ CharacteristicsCallback callback);
+
+ // Get a list of GATT services that are included by the given service.
+ // |serviceId| : The instance ID of the GATT service whose included
+ // services should be returned.
+ // |callback| : Called with the list of GATT services included from the
+ // given service.
+ static void getIncludedServices(DOMString serviceId,
+ ServicesCallback callback);
+
+ // Get the GATT characteristic descriptor with the given instance ID.
+ // |descriptorId| : The instance ID of the requested GATT characteristic
+ // descriptor.
+ // |callback| : Called with the requested Descriptor object.
+ static void getDescriptor(DOMString descriptorId,
+ DescriptorCallback callback);
+
+ // Get a list of GATT characteristic descriptors that belong to the given
+ // characteristic.
+ // |characteristicId| : The instance ID of the GATT characteristic whose
+ // descriptors should be returned.
+ // |callback| : Called with the list of descriptors that belong to the given
+ // characteristic.
+ static void getDescriptors(DOMString characteristicId,
+ DescriptorsCallback callback);
+
+ // Retrieve the value of a specified characteristic from a remote
+ // peripheral.
+ // |characteristicId| : The instance ID of the GATT characteristic whose
+ // value should be read from the remote device.
+ // |callback| : Called with the Characteristic object whose value was
+ // requested. The <code>value</code> field of the returned Characteristic
+ // object contains the result of the read request.
+ static void readCharacteristicValue(DOMString characteristicId,
+ CharacteristicCallback callback);
+
+ // Write the value of a specified characteristic from a remote peripheral.
+ // |characteristicId| : The instance ID of the GATT characteristic whose
+ // value should be written to.
+ // |value| : The value that should be sent to the remote characteristic as
+ // part of the write request.
+ // |callback| : Called when the write request has completed.
+ static void writeCharacteristicValue(DOMString characteristicId,
+ ArrayBuffer value,
+ ResultCallback callback);
+
+ // Enable value notifications/indications from the specified characteristic.
+ // Once enabled, an application can listen to notifications using the
+ // $(ref:onCharacteristicValueChanged) event.
+ // |characteristicId| : The instance ID of the GATT characteristic that
+ // notifications should be enabled on.
+ // |properties| : Notification session properties (optional).
+ // |callback| : Called when the request has completed.
+ static void startCharacteristicNotifications(
+ DOMString characteristicId,
+ optional NotificationProperties properties,
+ ResultCallback callback);
+
+ // Disable value notifications/indications from the specified
+ // characteristic. After a successful call, the application will stop
+ // receiving notifications/indications from this characteristic.
+ // |characteristicId| : The instance ID of the GATT characteristic on which
+ // this app's notification session should be stopped.
+ // |callback| : Called when the request has completed (optional).
+ static void stopCharacteristicNotifications(
+ DOMString characteristicId,
+ optional ResultCallback callback);
+
+ // Retrieve the value of a specified characteristic descriptor from a remote
+ // peripheral.
+ // |descriptorId| : The instance ID of the GATT characteristic descriptor
+ // whose value should be read from the remote device.
+ // |callback| : Called with the Descriptor object whose value was requested.
+ // The <code>value</code> field of the returned Descriptor object contains
+ // the result of the read request.
+ static void readDescriptorValue(DOMString descriptorId,
+ DescriptorCallback callback);
+
+ // Write the value of a specified characteristic descriptor from a remote
+ // peripheral.
+ // |descriptorId| : The instance ID of the GATT characteristic descriptor
+ // whose value should be written to.
+ // |value| : The value that should be sent to the remote descriptor as part
+ // of the write request.
+ // |callback| : Called when the write request has completed.
+ static void writeDescriptorValue(DOMString descriptorId,
+ ArrayBuffer value,
+ ResultCallback callback);
+ };
+
+ interface Events {
+ // Fired whan a new GATT service has been discovered on a remote device.
+ // |service| : The GATT service that was added.
+ static void onServiceAdded(Service service);
+
+ // Fired when the state of a remote GATT service changes. This involves any
+ // characteristics and/or descriptors that get added or removed from the
+ // service, as well as "ServiceChanged" notifications from the remote
+ // device.
+ // |service| : The GATT service whose state has changed.
+ static void onServiceChanged(Service service);
+
+ // Fired when a GATT service that was previously discovered on a remote
+ // device has been removed.
+ // |service| : The GATT service that was removed.
+ static void onServiceRemoved(Service service);
+
+ // Fired when the value of a remote GATT characteristic changes, either as
+ // a result of a read request, or a value change notification/indication
+ // This event will only be sent if the app has enabled notifications by
+ // calling $(ref:startCharacteristicNotifications).
+ // |characteristic| : The GATT characteristic whose value has changed.
+ static void onCharacteristicValueChanged(Characteristic characteristic);
+
+ // Fired when the value of a remote GATT characteristic descriptor changes,
+ // usually as a result of a read request. This event exists
+ // mostly for convenience and will always be sent after a successful
+ // call to $(ref:readDescriptorValue).
+ // |descriptor| : The GATT characteristic descriptor whose value has
+ // changed.
+ static void onDescriptorValueChanged(Descriptor descriptor);
+ };
+};
diff --git a/extensions/common/api/bluetooth_private.json b/extensions/common/api/bluetooth_private.json
new file mode 100644
index 0000000..20c82f1
--- /dev/null
+++ b/extensions/common/api/bluetooth_private.json
@@ -0,0 +1,190 @@
+[
+ {
+ "namespace": "bluetoothPrivate",
+ "description": " Use the <code>chrome.bluetoothPrivate</code> API to control the Bluetooth\n adapter state and handle device pairing.",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/bluetooth/bluetooth_private_api.h"
+ },
+ "functions": [
+ {
+ "name": "setAdapterState",
+ "type": "function",
+ "description": "Changes the state of the Bluetooth adapter.",
+ "parameters": [
+ {
+ "name": "adapterState",
+ "$ref": "NewAdapterState"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setPairingResponse",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "options",
+ "$ref": "SetPairingResponseOptions"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": []
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onPairing",
+ "type": "function",
+ "description": "Fired when a pairing event occurs.",
+ "parameters": [
+ {
+ "name": "pairingEvent",
+ "description": "A pairing event.",
+ "$ref": "PairingEvent"
+ }
+ ],
+ "options":
+ {
+ "maxListeners": 1
+ }
+ }
+ ],
+ "types": [
+ {
+ "type": "string",
+ "id": "PairingEventType",
+ "description": "Events that can occur during pairing. The method used for pairing varies depending on the capability of the two devices.",
+ "enum": [
+ {
+ "name": "requestPincode",
+ "description": "An alphanumeric PIN code is required to be entered by the user."
+ },
+ {
+ "name": "displayPincode",
+ "description": "Display a PIN code to the user."
+ },
+ {
+ "name": "requestPasskey",
+ "description": "A numeric passkey is required to be entered by the user."
+ },
+ {
+ "name": "displayPasskey",
+ "description": "Display a zero padded 6 digit numeric passkey that the user entered on the remote device. This event may occur multiple times during pairing to update the entered passkey."
+ },
+ {
+ "name": "keysEntered",
+ "description": "The number of keys inputted by the user on the remote device when entering a passkey. This event may be called multiple times during pairing to update the number of keys inputted."
+ },
+ {
+ "name": "confirmPasskey",
+ "description": "Requests that a 6 digit passkey be displayed and the user confirms that both devies show the same passkey."
+ },
+ {
+ "name": "requestAuthorization",
+ "description": "Requests authorization for a pairing under the just-works model. It is up to the app to ask for user confirmation."
+ },
+ {
+ "name": "complete",
+ "description": "Pairing is completed"
+ }
+ ]
+ },
+ {
+ "type": "string",
+ "id": "PairingResponse",
+ "description": "Valid pairing responses.",
+ "enum": [ "confirm", "reject", "cancel"]
+ },
+ {
+ "type": "object",
+ "id": "PairingEvent",
+ "description": "A pairing event received from a Bluetooth device.",
+ "properties": {
+ "pairing": {
+ "name": "pairing",
+ "$ref": "PairingEventType"
+ },
+ "device": {
+ "name": "device",
+ "$ref": "bluetooth.Device"
+ },
+ "pincode": {
+ "optional": true,
+ "name": "pincode",
+ "type": "string"
+ },
+ "passkey": {
+ "optional": true,
+ "name": "passkey",
+ "type": "integer"
+ },
+ "enteredKey": {
+ "optional": true,
+ "name": "enteredKey",
+ "type": "integer"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "id": "NewAdapterState",
+ "properties": {
+ "name": {
+ "optional": true,
+ "name": "name",
+ "type": "string",
+ "description": "The human-readable name of the adapter."
+ },
+ "powered": {
+ "optional": true,
+ "name": "powered",
+ "type": "boolean",
+ "description": "Whether or not the adapter has power."
+ },
+ "discoverable": {
+ "optional": true,
+ "name": "discoverable",
+ "type": "boolean",
+ "description": "Whether the adapter is discoverable by other devices."
+ }
+ }
+ },
+ {
+ "type": "object",
+ "id": "SetPairingResponseOptions",
+ "properties": {
+ "device": {
+ "name": "device",
+ "$ref": "bluetooth.Device",
+ "description": "The remote device to send the pairing response."
+ },
+ "response": {
+ "optional": true,
+ "name": "response",
+ "$ref": "PairingResponse",
+ "description": "The response type"
+ },
+ "pincode": {
+ "optional": true,
+ "name": "pincode",
+ "type": "string",
+ "description": "A 1-16 character alphanumeric set in response to <code>requestPincode</code>."
+ },
+ "passkey": {
+ "optional": true,
+ "name": "passkey",
+ "type": "integer",
+ "description": "An integer between 0-999999 set in response to <code>requestPasskey</code>."
+ }
+ }
+ }
+ ]
+ }
+]
diff --git a/extensions/common/api/bluetooth_socket.idl b/extensions/common/api/bluetooth_socket.idl
new file mode 100644
index 0000000..cb95c02
--- /dev/null
+++ b/extensions/common/api/bluetooth_socket.idl
@@ -0,0 +1,316 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.bluetoothSocket</code> API to send and receive data
+// to Bluetooth devices using RFCOMM and L2CAP connections.
+namespace bluetoothSocket {
+ // The socket properties specified in the $ref:create or $ref:update
+ // function. Each property is optional. If a property value is not specified,
+ // a default value is used when calling $ref:create, or the existing value is
+ // preserved when calling $ref:update.
+ dictionary SocketProperties {
+ // Flag indicating whether the socket is left open when the event page of
+ // the application is unloaded (see <a
+ // href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is <code>false.</code> When the
+ // application is loaded, any sockets previously opened with persistent=true
+ // can be fetched with $ref:getSockets.
+ boolean? persistent;
+
+ // An application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. The default value is 4096.
+ long? bufferSize;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created socket. Note that socket IDs created
+ // from this API are not compatible with socket IDs created from other APIs,
+ // such as the <code>$(ref:sockets.tcp)</code> API.
+ long socketId;
+ };
+
+ // Callback from the <code>create</code> method.
+ // |createInfo| : The result of the socket creation.
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Callback from the <code>setPaused</code> method.
+ callback SetPausedCallback = void ();
+
+ // Options that may be passed to the <code>listenUsingRfcomm</code> and
+ // <code>listenUsingL2cap</code> methods. Each property is optional with a
+ // default being used if not specified.
+ dictionary ListenOptions {
+ // The RFCOMM Channel used by <code>listenUsingRfcomm</code>. If specified,
+ // this channel must not be previously in use or the method call will fail.
+ // When not specified, an unused channel will be automatically allocated.
+ long? channel;
+
+ // The L2CAP PSM used by <code>listenUsingL2cap</code>. If specified, this
+ // PSM must not be previously in use or the method call with fail. When
+ // not specified, an unused PSM will be automatically allocated.
+ long? psm;
+
+ // Length of the socket's listen queue. The default value depends on the
+ // operating system's host subsystem.
+ long? backlog;
+ };
+
+ // Callback from the <code>listenUsingRfcomm</code> and
+ // <code>listenUsingL2cap</code> methods.
+ callback ListenCallback = void ();
+
+ // Callback from the <code>connect</code> method.
+ callback ConnectCallback = void ();
+
+ // Callback from the <code>disconnect</code> method.
+ callback DisconnectCallback = void ();
+
+ // Callback from the <code>close</code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>send</code> method.
+ // |bytesSent| : The number of bytes sent.
+ callback SendCallback = void (long bytesSent);
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating if the socket remains open when the event page of the
+ // application is unloaded (see <code>SocketProperties.persistent</code>).
+ // The default value is "false".
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. If no buffer size has been
+ // specified explictly, the value is not provided.
+ long? bufferSize;
+
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data, or whether connection requests on a listening socket are
+ // dispatched through the <code>onAccept</code> event or queued up in the
+ // listen queue backlog.
+ // See <code>setPaused</code>. The default value is "false".
+ boolean paused;
+
+ // Flag indicating whether the socket is connected to a remote peer.
+ boolean connected;
+
+ // If the underlying socket is connected, contains the Bluetooth address of
+ // the device it is connected to.
+ DOMString? address;
+
+ // If the underlying socket is connected, contains information about the
+ // service UUID it is connected to, otherwise if the underlying socket is
+ // listening, contains information about the service UUID it is listening
+ // on.
+ DOMString? uuid;
+ };
+
+ // Callback from the <code>getInfo</code> method.
+ // |socketInfo| : Object containing the socket information.
+ callback GetInfoCallback = void (SocketInfo socketInfo);
+
+ // Callback from the <code>getSockets</code> method.
+ // |socketInfos| : Array of object containing socket information.
+ callback GetSocketsCallback = void (SocketInfo[] sockets);
+
+ // Data from an <code>onAccept</code> event.
+ dictionary AcceptInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The client socket identifier, i.e. the socket identifier of the newly
+ // established connection. This socket identifier should be used only with
+ // functions from the <code>chrome.bluetoothSocket</code> namespace. Note
+ // the client socket is initially paused and must be explictly un-paused by
+ // the application to start receiving data.
+ long clientSocketId;
+ };
+
+ enum AcceptError {
+ // A system error occurred and the connection may be unrecoverable.
+ system_error,
+
+ // The socket is not listening.
+ not_listening
+ };
+
+ // Data from an <code>onAcceptError</code> event.
+ dictionary AcceptErrorInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The error message.
+ DOMString errorMessage;
+
+ // An error code indicating what went wrong.
+ AcceptError error;
+ };
+
+ // Data from an <code>onReceive</code> event.
+ dictionary ReceiveInfo {
+ // The socket identifier.
+ long socketId;
+
+ // The data received, with a maxium size of <code>bufferSize</code>.
+ ArrayBuffer data;
+ };
+
+ enum ReceiveError {
+ // The connection was disconnected.
+ disconnected,
+
+ // A system error occurred and the connection may be unrecoverable.
+ system_error,
+
+ // The socket has not been connected.
+ not_connected
+ };
+
+ // Data from an <code>onReceiveError</code> event.
+ dictionary ReceiveErrorInfo {
+ // The socket identifier.
+ long socketId;
+
+ // The error message.
+ DOMString errorMessage;
+
+ // An error code indicating what went wrong.
+ ReceiveError error;
+ };
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Creates a Bluetooth socket.
+ // |properties| : The socket properties (optional).
+ // |callback| : Called when the socket has been created.
+ static void create(optional SocketProperties properties,
+ CreateCallback callback);
+
+ // Updates the socket properties.
+ // |socketId| : The socket identifier.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ optional UpdateCallback callback);
+
+ // Enables or disables a connected socket from receiving messages from its
+ // peer, or a listening socket from accepting new connections. The default
+ // value is "false". Pausing a connected socket is typically used by an
+ // application to throttle data sent by its peer. When a connected socket
+ // is paused, no <code>onReceive</code>event is raised. When a socket is
+ // connected and un-paused, <code>onReceive</code> events are raised again
+ // when messages are received. When a listening socket is paused, new
+ // connections are accepted until its backlog is full then additional
+ // connection requests are refused. <code>onAccept</code> events are raised
+ // only when the socket is un-paused.
+ static void setPaused(long socketId,
+ boolean paused,
+ optional SetPausedCallback callback);
+
+ // Listen for connections using the RFCOMM protocol.
+ // |socketId| : The socket identifier.
+ // |uuid| : Service UUID to listen on.
+ // |options| : Optional additional options for the service.
+ // |callback| : Called when listen operation completes.
+ static void listenUsingRfcomm(long socketId,
+ DOMString uuid,
+ optional ListenOptions options,
+ ListenCallback callback);
+
+ // Listen for connections using the L2CAP protocol.
+ // |socketId| : The socket identifier.
+ // |uuid| : Service UUID to listen on.
+ // |options| : Optional additional options for the service.
+ // |callback| : Called when listen operation completes.
+ static void listenUsingL2cap(long socketId,
+ DOMString uuid,
+ optional ListenOptions options,
+ ListenCallback callback);
+
+ // Connects the socket to a remote Bluetooth device. When the
+ // <code>connect</code> operation completes successfully,
+ // <code>onReceive</code> events are raised when data is received from the
+ // peer. If a network error occur while the runtime is receiving packets,
+ // a <code>onReceiveError</code> event is raised, at which point no more
+ // <code>onReceive</code> event will be raised for this socket until the
+ // <code>setPaused(false)</code> method is called.
+ // |socketId| : The socket identifier.
+ // |address| : The address of the Bluetooth device.
+ // |uuid| : The UUID of the service to connect to.
+ // |callback| : Called when the connect attempt is complete.
+ static void connect(long socketId,
+ DOMString address,
+ DOMString uuid,
+ ConnectCallback callback);
+
+ // Disconnects the socket. The socket identifier remains valid.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the disconnect attempt is complete.
+ static void disconnect(long socketId,
+ optional DisconnectCallback callback);
+
+ // Disconnects and destroys the socket. Each socket created should be
+ // closed after use. The socket id is no longer valid as soon at the
+ // function is called. However, the socket is guaranteed to be closed only
+ // when the callback is invoked.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the <code>close</code> operation completes.
+ static void close(long socketId,
+ optional CloseCallback callback);
+
+ // Sends data on the given Bluetooth socket.
+ // |socketId| : The socket identifier.
+ // |data| : The data to send.
+ // |callback| : Called with the number of bytes sent.
+ static void send(long socketId,
+ ArrayBuffer data,
+ optional SendCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the socket state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves the list of currently opened sockets owned by the application.
+ // |callback| : Called when the list of sockets is available.
+ static void getSockets(GetSocketsCallback callback);
+ };
+
+ interface Events {
+ // Event raised when a connection has been established for a given socket.
+ // |info| : The event data.
+ static void onAccept(AcceptInfo info);
+
+ // Event raised when a network error occurred while the runtime was waiting
+ // for new connections on the given socket. Once this event is raised, the
+ // socket is set to <code>paused</code> and no more <code>onAccept</code>
+ // events are raised for this socket.
+ // |info| : The event data.
+ static void onAcceptError(AcceptErrorInfo info);
+
+ // Event raised when data has been received for a given socket.
+ // |info| : The event data.
+ static void onReceive(ReceiveInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for data on the socket. Once this event is raised, the socket is set to
+ // <code>paused</code> and no more <code>onReceive</code> events are raised
+ // for this socket.
+ // |info| : The event data.
+ static void onReceiveError(ReceiveErrorInfo info);
+ };
+};
diff --git a/extensions/common/api/extensions_manifest_types.json b/extensions/common/api/extensions_manifest_types.json
index 00262692..6e8e7ee 100644
--- a/extensions/common/api/extensions_manifest_types.json
+++ b/extensions/common/api/extensions_manifest_types.json
@@ -98,6 +98,32 @@
}
}
}
+ },
+ {
+ "id": "bluetooth",
+ "type": "object",
+ "description": "The <code>bluetooth</code> manifest property give permission to an app to use the $(ref:bluetooth) API. A list of UUIDs can be optionally specified to enable communication with devices.",
+ "properties": {
+ "uuids": {
+ "description": "The <code>uuids</code> manifest property declares the list of protocols, profiles and services that an app can communicate using.",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "description": "<p>The list specified as UUID strings.</p>",
+ "type": "string"
+ }
+ },
+ "socket": {
+ "type": "boolean",
+ "description": "If <code>true</code>, gives permission to an app to use the $(ref:bluetoothSocket) API",
+ "optional": true
+ },
+ "low_energy": {
+ "type": "boolean",
+ "description": "If <code>true</code>, gives permission to an app to use the $(ref:bluetoothLowEnergy) API",
+ "optional": true
+ }
+ }
}
]
}
diff --git a/extensions/common/api/schemas.gypi b/extensions/common/api/schemas.gypi
index 52370c9..9d9c2be 100644
--- a/extensions/common/api/schemas.gypi
+++ b/extensions/common/api/schemas.gypi
@@ -15,6 +15,10 @@
'main_schema_files': [
'app_runtime.idl',
'app_view_internal.json',
+ 'bluetooth.idl',
+ 'bluetooth_low_energy.idl',
+ 'bluetooth_private.json',
+ 'bluetooth_socket.idl',
'cast_channel.idl',
'dns.idl',
'extensions_manifest_types.json',
diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp
index ad33421..4ce60a6 100644
--- a/extensions/extensions.gyp
+++ b/extensions/extensions.gyp
@@ -37,6 +37,12 @@
],
'sources': [
# Note: sources list duplicated in GN build.
+ 'common/api/bluetooth/bluetooth_manifest_data.cc',
+ 'common/api/bluetooth/bluetooth_manifest_data.h',
+ 'common/api/bluetooth/bluetooth_manifest_handler.cc',
+ 'common/api/bluetooth/bluetooth_manifest_handler.h',
+ 'common/api/bluetooth/bluetooth_manifest_permission.cc',
+ 'common/api/bluetooth/bluetooth_manifest_permission.h',
'common/api/messaging/message.h',
'common/api/sockets/sockets_manifest_data.cc',
'common/api/sockets/sockets_manifest_data.h',
@@ -215,6 +221,7 @@
'conditions': [
['enable_extensions==1', {
'dependencies': [
+ '../device/bluetooth/bluetooth.gyp:device_bluetooth',
# For Mojo generated headers for generated_api.cc.
'../device/serial/serial.gyp:device_serial_mojo',
'../device/usb/usb.gyp:device_usb',
@@ -224,6 +231,12 @@
],
}, { # enable_extensions == 0
'sources!': [
+ 'common/api/bluetooth/bluetooth_manifest_data.cc',
+ 'common/api/bluetooth/bluetooth_manifest_data.h',
+ 'common/api/bluetooth/bluetooth_manifest_handler.cc',
+ 'common/api/bluetooth/bluetooth_manifest_handler.h',
+ 'common/api/bluetooth/bluetooth_manifest_permission.cc',
+ 'common/api/bluetooth/bluetooth_manifest_permission.h',
'common/api/messaging/message.h',
'common/api/sockets/sockets_manifest_data.cc',
'common/api/sockets/sockets_manifest_data.h',
@@ -260,6 +273,7 @@
'../components/components.gyp:usb_service',
'../components/components.gyp:web_modal',
'../content/content.gyp:content_browser',
+ '../device/bluetooth/bluetooth.gyp:device_bluetooth',
'../device/serial/serial.gyp:device_serial',
'../skia/skia.gyp:skia',
'../third_party/leveldatabase/leveldatabase.gyp:leveldatabase',
@@ -294,6 +308,34 @@
'browser/api/guest_view/guest_view_internal_api.h',
'browser/api/async_api_function.cc',
'browser/api/async_api_function.h',
+ 'browser/api/bluetooth/bluetooth_api.cc',
+ 'browser/api/bluetooth/bluetooth_api.h',
+ 'browser/api/bluetooth/bluetooth_api_pairing_delegate.cc',
+ 'browser/api/bluetooth/bluetooth_api_pairing_delegate.h',
+ 'browser/api/bluetooth/bluetooth_api_utils.cc',
+ 'browser/api/bluetooth/bluetooth_api_utils.h',
+ 'browser/api/bluetooth/bluetooth_event_router.cc',
+ 'browser/api/bluetooth/bluetooth_event_router.h',
+ 'browser/api/bluetooth/bluetooth_extension_function.cc',
+ 'browser/api/bluetooth/bluetooth_extension_function.h',
+ 'browser/api/bluetooth/bluetooth_private_api.cc',
+ 'browser/api/bluetooth/bluetooth_private_api.h',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.cc',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_connection.h',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.h',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.cc',
+ 'browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h',
+ 'browser/api/bluetooth_low_energy/utils.cc',
+ 'browser/api/bluetooth_low_energy/utils.h',
+ 'browser/api/bluetooth_socket/bluetooth_api_socket.cc',
+ 'browser/api/bluetooth_socket/bluetooth_api_socket.h',
+ 'browser/api/bluetooth_socket/bluetooth_socket_api.cc',
+ 'browser/api/bluetooth_socket/bluetooth_socket_api.h',
+ 'browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc',
+ 'browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h',
'browser/api/cast_channel/cast_auth_util.cc',
'browser/api/cast_channel/cast_auth_util.h',
'browser/api/cast_channel/cast_channel_api.cc',
@@ -580,6 +622,7 @@
],
'dependencies!': [
'../components/components.gyp:usb_service',
+ '../device/bluetooth/bluetooth.gyp:device_bluetooth',
'../device/serial/serial.gyp:device_serial',
],
}],
diff --git a/extensions/extensions_strings.grd b/extensions/extensions_strings.grd
index 603e99f..f499f6c 100644
--- a/extensions/extensions_strings.grd
+++ b/extensions/extensions_strings.grd
@@ -199,7 +199,15 @@
<message name="IDS_EXTENSION_MANIFEST_INVALID" desc="">
Manifest file is invalid.
</message>
-
+
+ <!-- Bluetooth access permissions. Please keep alphabetized. -->
+ <message name="IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH" desc="Permission string for general access to the Bluetooth API.">
+ Access information about Bluetooth devices paired with your system and discover nearby Bluetooth devices.
+ </message>
+ <message name="IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH_DEVICES" desc="Permission string for implementing Bluetooth profiles.">
+ Send messages to and receive messages from Bluetooth devices.
+ </message>
+
<!-- Host access permissions. Please keep alphabetized. -->
<message name="IDS_EXTENSION_PROMPT_WARNING_1_HOST" desc="Permission string for access to data on one website.">
Read and change your data on <ph name="WEBSITE_1">$1<ex>www.google.com</ex></ph>
@@ -277,7 +285,7 @@
<message name="IDS_EXTENSION_PROMPT_WARNING_SOCKET_SPECIFIC_HOSTS" desc="Permission string for access to multiple specific computers on the local network or internet.">
Exchange data with the computers named: <ph name="HOSTNAMES">$1<ex>foo.example.com bar.example.com</ex></ph>
</message>
-
+
<!-- Device API strings. Please keep alphabetized. -->
<message name="IDS_EXTENSION_PROMPT_WARNING_HID" desc="Permission string for access to HID devices.">
Access your input devices