diff options
author | thestig <thestig@chromium.org> | 2014-08-29 16:41:06 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-08-29 23:49:43 +0000 |
commit | 8146daf0a5a91e4bedec34cfe0b942803c36aa90 (patch) | |
tree | a693b4354861b7a9640f764f2b3972de77582243 /extensions | |
parent | 386f9301889efde34b202a4600496b1e10aa06c8 (diff) | |
download | chromium_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')
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, ¶ms_.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 |