diff options
author | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-09 03:49:18 +0000 |
---|---|---|
committer | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-09 03:49:18 +0000 |
commit | e882d2cdf5ed5337d68f2b32ba0f885725ca8b3f (patch) | |
tree | c4ca06e4ad129b16aa1f04753db7ac4e98a55240 /device | |
parent | dc5d179b1e34c9147630adc9d20e43e7986f7feb (diff) | |
download | chromium_src-e882d2cdf5ed5337d68f2b32ba0f885725ca8b3f.zip chromium_src-e882d2cdf5ed5337d68f2b32ba0f885725ca8b3f.tar.gz chromium_src-e882d2cdf5ed5337d68f2b32ba0f885725ca8b3f.tar.bz2 |
Bluetooth: Profile support for Chrome OS
Implement support for the BluetoothProfile API for the BlueZ 5.x
stack on Chrome OS, including BluetoothSocket support.
BUG=229636
TEST=device_unittests
Review URL: https://chromiumcodereview.appspot.com/14487002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@199095 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'device')
-rw-r--r-- | device/bluetooth/bluetooth_adapter_experimental_chromeos.h | 2 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_device_experimental_chromeos.cc | 44 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_device_experimental_chromeos.h | 9 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_profile.cc | 17 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_profile_chromeos_unittest.cc | 364 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_profile_experimental_chromeos.cc | 259 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_profile_experimental_chromeos.h | 132 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_socket_experimental_chromeos.cc | 170 | ||||
-rw-r--r-- | device/bluetooth/bluetooth_socket_experimental_chromeos.h | 75 | ||||
-rw-r--r-- | device/device.gyp | 5 |
10 files changed, 1071 insertions, 6 deletions
diff --git a/device/bluetooth/bluetooth_adapter_experimental_chromeos.h b/device/bluetooth/bluetooth_adapter_experimental_chromeos.h index a3a0655..80dce84 100644 --- a/device/bluetooth/bluetooth_adapter_experimental_chromeos.h +++ b/device/bluetooth/bluetooth_adapter_experimental_chromeos.h @@ -65,6 +65,8 @@ class BluetoothAdapterExperimentalChromeOS friend class device::BluetoothAdapterFactory; friend class BluetoothDeviceExperimentalChromeOS; friend class BluetoothExperimentalChromeOSTest; + friend class BluetoothProfileExperimentalChromeOS; + friend class BluetoothProfileChromeOSTest; BluetoothAdapterExperimentalChromeOS(); virtual ~BluetoothAdapterExperimentalChromeOS(); diff --git a/device/bluetooth/bluetooth_device_experimental_chromeos.cc b/device/bluetooth/bluetooth_device_experimental_chromeos.cc index 35478d9..c7296ba 100644 --- a/device/bluetooth/bluetooth_device_experimental_chromeos.cc +++ b/device/bluetooth/bluetooth_device_experimental_chromeos.cc @@ -16,6 +16,7 @@ #include "chromeos/dbus/experimental_bluetooth_input_client.h" #include "dbus/bus.h" #include "device/bluetooth/bluetooth_adapter_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_profile_experimental_chromeos.h" #include "device/bluetooth/bluetooth_socket.h" #include "third_party/cros_system_api/dbus/service_constants.h" @@ -392,8 +393,24 @@ void BluetoothDeviceExperimentalChromeOS::ConnectToProfile( device::BluetoothProfile* profile, const base::Closure& callback, const ErrorCallback& error_callback) { - // TODO(keybuk): implement - error_callback.Run(); + BluetoothProfileExperimentalChromeOS* profile_chromeos = + static_cast<BluetoothProfileExperimentalChromeOS*>(profile); + VLOG(1) << object_path_.value() << ": Connecting profile: " + << profile_chromeos->uuid(); + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + ConnectProfile( + object_path_, + profile_chromeos->uuid(), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnConnectProfile, + weak_ptr_factory_.GetWeakPtr(), + profile, + callback), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnConnectProfileError, + weak_ptr_factory_.GetWeakPtr(), + profile, + error_callback)); } void BluetoothDeviceExperimentalChromeOS::SetOutOfBandPairingData( @@ -806,4 +823,27 @@ bool BluetoothDeviceExperimentalChromeOS::RunPairingCallbacks(Status status) { return callback_run; } +void BluetoothDeviceExperimentalChromeOS::OnConnectProfile( + device::BluetoothProfile* profile, + const base::Closure& callback) { + BluetoothProfileExperimentalChromeOS* profile_chromeos = + static_cast<BluetoothProfileExperimentalChromeOS*>(profile); + VLOG(1) << object_path_.value() << ": Profile connected: " + << profile_chromeos->uuid(); + callback.Run(); +} + +void BluetoothDeviceExperimentalChromeOS::OnConnectProfileError( + device::BluetoothProfile* profile, + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + BluetoothProfileExperimentalChromeOS* profile_chromeos = + static_cast<BluetoothProfileExperimentalChromeOS*>(profile); + VLOG(1) << object_path_.value() << ": Profile connection failed: " + << profile_chromeos->uuid() << ": " + << error_name << ": " << error_message; + error_callback.Run(); +} + } // namespace chromeos diff --git a/device/bluetooth/bluetooth_device_experimental_chromeos.h b/device/bluetooth/bluetooth_device_experimental_chromeos.h index fce7cd4..18b48e9 100644 --- a/device/bluetooth/bluetooth_device_experimental_chromeos.h +++ b/device/bluetooth/bluetooth_device_experimental_chromeos.h @@ -173,6 +173,15 @@ class BluetoothDeviceExperimentalChromeOS // pairing. Returns true if any callbacks were run, false if not. bool RunPairingCallbacks(Status status); + // Called by dbus:: on completion of the D-Bus method call to + // connect a peofile. + void OnConnectProfile(device::BluetoothProfile* profile, + const base::Closure& callback); + void OnConnectProfileError(device::BluetoothProfile* profile, + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + // Return the object path of the device; used by // BluetoothAdapterExperimentalChromeOS const dbus::ObjectPath& object_path() const { return object_path_; } diff --git a/device/bluetooth/bluetooth_profile.cc b/device/bluetooth/bluetooth_profile.cc index 2ef9425..6c59e5b 100644 --- a/device/bluetooth/bluetooth_profile.cc +++ b/device/bluetooth/bluetooth_profile.cc @@ -4,7 +4,9 @@ #include "device/bluetooth/bluetooth_profile.h" -#if defined(OS_MACOSX) +#if defined(OS_CHROMEOS) +#include "device/bluetooth/bluetooth_profile_experimental_chromeos.h" +#elif defined(OS_MACOSX) #include "base/mac/mac_util.h" #include "device/bluetooth/bluetooth_profile_mac.h" #elif defined(OS_WIN) @@ -43,16 +45,23 @@ BluetoothProfile::~BluetoothProfile() { void BluetoothProfile::Register(const std::string& uuid, const Options& options, const ProfileCallback& callback) { +#if defined(OS_CHROMEOS) + chromeos::BluetoothProfileExperimentalChromeOS* profile = NULL; + profile = new chromeos::BluetoothProfileExperimentalChromeOS(); + profile->Init(uuid, options, callback); +#elif defined(OS_MACOSX) BluetoothProfile* profile = NULL; -#if defined(OS_MACOSX) if (base::mac::IsOSLionOrLater()) profile = new BluetoothProfileMac(uuid, options.name); + callback.Run(profile); #elif defined(OS_WIN) + BluetoothProfile* profile = NULL; profile = new BluetoothProfileWin(uuid, options.name); -#endif - callback.Run(profile); +#else + callback.Run(NULL); +#endif } } // namespace device diff --git a/device/bluetooth/bluetooth_profile_chromeos_unittest.cc b/device/bluetooth/bluetooth_profile_chromeos_unittest.cc new file mode 100644 index 0000000..620116c --- /dev/null +++ b/device/bluetooth/bluetooth_profile_chromeos_unittest.cc @@ -0,0 +1,364 @@ +// Copyright (c) 2013 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/message_loop.h" +#include "chromeos/chromeos_switches.h" +#include "chromeos/dbus/fake_bluetooth_device_client.h" +#include "chromeos/dbus/fake_bluetooth_profile_manager_client.h" +#include "chromeos/dbus/fake_bluetooth_profile_service_provider.h" +#include "chromeos/dbus/mock_dbus_thread_manager_without_gmock.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_adapter_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_profile.h" +#include "device/bluetooth/bluetooth_profile_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_experimental_chromeos.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using device::BluetoothAdapter; +using device::BluetoothDevice; +using device::BluetoothProfile; +using device::BluetoothSocket; + +namespace chromeos { + +class BluetoothProfileChromeOSTest : public testing::Test { + public: + BluetoothProfileChromeOSTest() + : message_loop_(MessageLoop::TYPE_IO), + callback_count_(0), + error_callback_count_(0), + profile_callback_count_(0), + connection_callback_count_(0), + last_profile_(NULL), + last_device_(NULL) {} + + virtual void SetUp() { + if (!CommandLine::ForCurrentProcess()->HasSwitch( + chromeos::switches::kEnableExperimentalBluetooth)) + CommandLine::ForCurrentProcess()->AppendSwitch( + chromeos::switches::kEnableExperimentalBluetooth); + + mock_dbus_thread_manager_ = + new MockDBusThreadManagerWithoutGMock(); + DBusThreadManager::InitializeForTesting(mock_dbus_thread_manager_); + + fake_bluetooth_profile_manager_client_ = + mock_dbus_thread_manager_->fake_bluetooth_profile_manager_client(); + + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&BluetoothProfileChromeOSTest::AdapterCallback, + base::Unretained(this))); + ASSERT_TRUE(adapter_ != NULL); + ASSERT_TRUE(adapter_->IsInitialized()); + ASSERT_TRUE(adapter_->IsPresent()); + + adapter_->SetPowered( + true, + base::Bind(&base::DoNothing), + base::Bind(&base::DoNothing)); + ASSERT_TRUE(adapter_->IsPowered()); + } + + virtual void TearDown() { + adapter_ = NULL; + DBusThreadManager::Shutdown(); + } + + void AdapterCallback(scoped_refptr<BluetoothAdapter> adapter) { + adapter_ = adapter; + } + + void Callback() { + ++callback_count_; + } + + void ErrorCallback() { + ++error_callback_count_; + + message_loop_.Quit(); + } + + void ProfileCallback(BluetoothProfile* profile) { + ++profile_callback_count_; + last_profile_ = profile; + } + + void ConnectionCallback(const BluetoothDevice *device, + scoped_refptr<BluetoothSocket> socket) { + ++connection_callback_count_; + last_device_ = device; + last_socket_ = socket; + + message_loop_.Quit(); + } + + protected: + base::MessageLoop message_loop_; + + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client_; + MockDBusThreadManagerWithoutGMock* mock_dbus_thread_manager_; + scoped_refptr<BluetoothAdapter> adapter_; + + unsigned int callback_count_; + unsigned int error_callback_count_; + unsigned int profile_callback_count_; + unsigned int connection_callback_count_; + BluetoothProfile* last_profile_; + const BluetoothDevice* last_device_; + scoped_refptr<BluetoothSocket> last_socket_; +}; + +TEST_F(BluetoothProfileChromeOSTest, L2capEndToEnd) { + // Register the profile and expect the profile object to be passed to the + // callback. + BluetoothProfile::Options options; + BluetoothProfile::Register( + FakeBluetoothProfileManagerClient::kL2capUuid, + options, + base::Bind(&BluetoothProfileChromeOSTest::ProfileCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, profile_callback_count_); + EXPECT_TRUE(last_profile_ != NULL); + BluetoothProfile* profile = last_profile_; + + // Make sure we have a profile service provider for it. + FakeBluetoothProfileServiceProvider* profile_service_provider = + fake_bluetooth_profile_manager_client_->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kL2capUuid); + EXPECT_TRUE(profile_service_provider != NULL); + + // Register the connection callback. + profile->SetConnectionCallback( + base::Bind(&BluetoothProfileChromeOSTest::ConnectionCallback, + base::Unretained(this))); + + // Connect to the device, expect the success callback to be called and the + // connection callback to be called with the device we passed and a new + // socket instance. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + + device->ConnectToProfile( + profile, + base::Bind(&BluetoothProfileChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothProfileChromeOSTest::ErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(1U, callback_count_); + EXPECT_EQ(0U, error_callback_count_); + + EXPECT_EQ(1U, connection_callback_count_); + EXPECT_EQ(device, last_device_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take the ownership of the socket for the remainder of the test and set + // up buffers for read/write tests. + scoped_refptr<BluetoothSocket> socket = last_socket_; + last_socket_ = NULL; + + bool success; + scoped_refptr<net::GrowableIOBuffer> read_buffer; + + scoped_refptr<net::StringIOBuffer> base_buffer( + new net::StringIOBuffer("test")); + scoped_refptr<net::DrainableIOBuffer> write_buffer; + + // Read data from the socket; since no data should be waiting, this should + // return success but no data. + read_buffer = new net::GrowableIOBuffer; + success = socket->Receive(read_buffer); + EXPECT_TRUE(success); + EXPECT_EQ(0, read_buffer->capacity()); + EXPECT_EQ(0, read_buffer->offset()); + EXPECT_EQ("", socket->GetLastErrorMessage()); + + // Write data to the socket; the data should be consumed and no bytes should + // be remaining. + write_buffer = new net::DrainableIOBuffer(base_buffer, base_buffer->size()); + success = socket->Send(write_buffer); + EXPECT_TRUE(success); + EXPECT_EQ(base_buffer->size(), write_buffer->BytesConsumed()); + EXPECT_EQ(0, write_buffer->BytesRemaining()); + EXPECT_EQ("", socket->GetLastErrorMessage()); + + // Read data from the socket; this should match the data we sent since the + // server just echoes us. We have to spin here until there is actually data + // to read. + read_buffer = new net::GrowableIOBuffer; + do { + success = socket->Receive(read_buffer); + } while (success && read_buffer->offset() == 0); + EXPECT_TRUE(success); + EXPECT_NE(0, read_buffer->capacity()); + EXPECT_EQ(base_buffer->size(), read_buffer->offset()); + EXPECT_EQ("", socket->GetLastErrorMessage()); + + std::string data = std::string(read_buffer->StartOfBuffer(), + read_buffer->offset()); + EXPECT_EQ("test", data); + + // Write data to the socket; since the socket is closed, this should return + // an error without writing the data and "Disconnected" as the message. + write_buffer = new net::DrainableIOBuffer(base_buffer, base_buffer->size()); + success = socket->Send(write_buffer); + EXPECT_FALSE(success); + EXPECT_EQ(0, write_buffer->BytesConsumed()); + EXPECT_EQ(base_buffer->size(), write_buffer->BytesRemaining()); + EXPECT_EQ("Disconnected", socket->GetLastErrorMessage()); + + // Read data from the socket; since the socket is closed, this should return + // an error with "Disconnected" as the last message. + read_buffer = new net::GrowableIOBuffer; + success = socket->Receive(read_buffer); + EXPECT_FALSE(success); + EXPECT_EQ(0, read_buffer->capacity()); + EXPECT_EQ(0, read_buffer->offset()); + EXPECT_EQ("Disconnected", socket->GetLastErrorMessage()); + + // Close our end of the socket. + socket = NULL; + + // Unregister the profile, make sure it's no longer registered. + last_profile_->Unregister(); + + profile_service_provider = + fake_bluetooth_profile_manager_client_->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kL2capUuid); + EXPECT_TRUE(profile_service_provider == NULL); +} + +TEST_F(BluetoothProfileChromeOSTest, RfcommEndToEnd) { + // Register the profile and expect the profile object to be passed to the + // callback. + BluetoothProfile::Options options; + BluetoothProfile::Register( + FakeBluetoothProfileManagerClient::kRfcommUuid, + options, + base::Bind(&BluetoothProfileChromeOSTest::ProfileCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, profile_callback_count_); + EXPECT_TRUE(last_profile_ != NULL); + BluetoothProfile* profile = last_profile_; + + // Make sure we have a profile service provider for it. + FakeBluetoothProfileServiceProvider* profile_service_provider = + fake_bluetooth_profile_manager_client_->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kRfcommUuid); + EXPECT_TRUE(profile_service_provider != NULL); + + // Register the connection callback. + profile->SetConnectionCallback( + base::Bind(&BluetoothProfileChromeOSTest::ConnectionCallback, + base::Unretained(this))); + + // Connect to the device, expect the success callback to be called and the + // connection callback to be called with the device we passed and a new + // socket instance. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + + device->ConnectToProfile( + profile, + base::Bind(&BluetoothProfileChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothProfileChromeOSTest::ErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(1U, callback_count_); + EXPECT_EQ(0U, error_callback_count_); + + EXPECT_EQ(1U, connection_callback_count_); + EXPECT_EQ(device, last_device_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take the ownership of the socket for the remainder of the test and set + // up buffers for read/write tests. + scoped_refptr<BluetoothSocket> socket = last_socket_; + last_socket_ = NULL; + + bool success; + scoped_refptr<net::GrowableIOBuffer> read_buffer; + + scoped_refptr<net::StringIOBuffer> base_buffer( + new net::StringIOBuffer("test")); + scoped_refptr<net::DrainableIOBuffer> write_buffer; + + // Read data from the socket; since no data should be waiting, this should + // return success but no data. + read_buffer = new net::GrowableIOBuffer; + success = socket->Receive(read_buffer); + EXPECT_TRUE(success); + EXPECT_EQ(0, read_buffer->offset()); + EXPECT_EQ("", socket->GetLastErrorMessage()); + + // Write data to the socket; the data should be consumed and no bytes should + // be remaining. + write_buffer = new net::DrainableIOBuffer(base_buffer, base_buffer->size()); + success = socket->Send(write_buffer); + EXPECT_TRUE(success); + EXPECT_EQ(base_buffer->size(), write_buffer->BytesConsumed()); + EXPECT_EQ(0, write_buffer->BytesRemaining()); + EXPECT_EQ("", socket->GetLastErrorMessage()); + + // Read data from the socket; this should match the data we sent since the + // server just echoes us. We have to spin here until there is actually data + // to read. + read_buffer = new net::GrowableIOBuffer; + do { + success = socket->Receive(read_buffer); + } while (success && read_buffer->offset() == 0); + EXPECT_TRUE(success); + EXPECT_NE(0, read_buffer->capacity()); + EXPECT_EQ(base_buffer->size(), read_buffer->offset()); + EXPECT_EQ("", socket->GetLastErrorMessage()); + + std::string data = std::string(read_buffer->StartOfBuffer(), + read_buffer->offset()); + EXPECT_EQ("test", data); + + // Write data to the socket; since the socket is closed, this should return + // an error without writing the data and "Disconnected" as the message. + write_buffer = new net::DrainableIOBuffer(base_buffer, base_buffer->size()); + success = socket->Send(write_buffer); + EXPECT_FALSE(success); + EXPECT_EQ(0, write_buffer->BytesConsumed()); + EXPECT_EQ(base_buffer->size(), write_buffer->BytesRemaining()); + EXPECT_EQ("Disconnected", socket->GetLastErrorMessage()); + + // Read data from the socket; since the socket is closed, this should return + // an error with "Disconnected" as the last message. + read_buffer = new net::GrowableIOBuffer; + success = socket->Receive(read_buffer); + EXPECT_FALSE(success); + EXPECT_EQ(0, read_buffer->offset()); + EXPECT_EQ("Disconnected", socket->GetLastErrorMessage()); + + // Close our end of the socket. + socket = NULL; + + // Unregister the profile, make sure it's no longer registered. + last_profile_->Unregister(); + + profile_service_provider = + fake_bluetooth_profile_manager_client_->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kRfcommUuid); + EXPECT_TRUE(profile_service_provider == NULL); +} + +} // namespace chromeos diff --git a/device/bluetooth/bluetooth_profile_experimental_chromeos.cc b/device/bluetooth/bluetooth_profile_experimental_chromeos.cc new file mode 100644 index 0000000..2ccea24 --- /dev/null +++ b/device/bluetooth/bluetooth_profile_experimental_chromeos.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2013 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 "device/bluetooth/bluetooth_profile_experimental_chromeos.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/task_runner_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/worker_pool.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/experimental_bluetooth_profile_manager_client.h" +#include "chromeos/dbus/experimental_bluetooth_profile_service_provider.h" +#include "dbus/bus.h" +#include "dbus/file_descriptor.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_adapter_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_profile.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_experimental_chromeos.h" + +using device::BluetoothAdapter; +using device::BluetoothAdapterFactory; +using device::BluetoothDevice; +using device::BluetoothProfile; +using device::BluetoothSocket; + +namespace { + +// Check the validity of a file descriptor received from D-Bus. Must be run +// on a thread where i/o is permitted. +scoped_ptr<dbus::FileDescriptor> CheckValidity( + scoped_ptr<dbus::FileDescriptor> fd) { + base::ThreadRestrictions::AssertIOAllowed(); + fd->CheckValidity(); + return fd.Pass(); +} + +} // namespace + + +namespace chromeos { + +BluetoothProfileExperimentalChromeOS::BluetoothProfileExperimentalChromeOS() + : weak_ptr_factory_(this) { +} + +BluetoothProfileExperimentalChromeOS::~BluetoothProfileExperimentalChromeOS() { + DCHECK(object_path_.value().empty()); + DCHECK(profile_.get() == NULL); +} + +void BluetoothProfileExperimentalChromeOS::Init( + const std::string& uuid, + const device::BluetoothProfile::Options& options, + const ProfileCallback& callback) { + DCHECK(object_path_.value().empty()); + DCHECK(profile_.get() == NULL); + + if (!BluetoothDevice::IsUUIDValid(uuid)) { + callback.Run(NULL); + return; + } + + uuid_ = uuid; + + ExperimentalBluetoothProfileManagerClient::Options bluetooth_options; + bluetooth_options.name = options.name; + bluetooth_options.service = uuid; + bluetooth_options.channel = options.channel; + bluetooth_options.psm = options.psm; + bluetooth_options.require_authentication = options.require_authentication; + bluetooth_options.require_authorization = options.require_authorization; + bluetooth_options.auto_connect = options.auto_connect; + bluetooth_options.version = options.version; + bluetooth_options.features = options.features; + + // The object path is relatively meaningless, but has to be unique, so we + // use the UUID of the profile. + std::string uuid_path; + ReplaceChars(uuid, ":-", "_", &uuid_path); + + object_path_ = dbus::ObjectPath("/org/chromium/bluetooth_profile/" + + uuid_path); + + dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); + profile_.reset(ExperimentalBluetoothProfileServiceProvider::Create( + system_bus, object_path_, this)); + DCHECK(profile_.get()); + + VLOG(1) << object_path_.value() << ": Register profile"; + DBusThreadManager::Get()->GetExperimentalBluetoothProfileManagerClient()-> + RegisterProfile( + object_path_, + uuid, + bluetooth_options, + base::Bind( + &BluetoothProfileExperimentalChromeOS::OnRegisterProfile, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind( + &BluetoothProfileExperimentalChromeOS::OnRegisterProfileError, + weak_ptr_factory_.GetWeakPtr(), + callback)); +} + +void BluetoothProfileExperimentalChromeOS::Unregister() { + DCHECK(!object_path_.value().empty()); + DCHECK(profile_.get()); + + profile_.reset(); + + VLOG(1) << object_path_.value() << ": Unregister profile"; + DBusThreadManager::Get()->GetExperimentalBluetoothProfileManagerClient()-> + UnregisterProfile( + object_path_, + base::Bind( + &BluetoothProfileExperimentalChromeOS::OnUnregisterProfile, + weak_ptr_factory_.GetWeakPtr()), + base::Bind( + &BluetoothProfileExperimentalChromeOS::OnUnregisterProfileError, + weak_ptr_factory_.GetWeakPtr())); +} + +void BluetoothProfileExperimentalChromeOS::SetConnectionCallback( + const ConnectionCallback& callback) { + connection_callback_ = callback; +} + +void BluetoothProfileExperimentalChromeOS::Release() { + VLOG(1) << object_path_.value() << ": Release"; +} + +void BluetoothProfileExperimentalChromeOS::NewConnection( + const dbus::ObjectPath& device_path, + scoped_ptr<dbus::FileDescriptor> fd, + const ExperimentalBluetoothProfileServiceProvider::Delegate::Options& + options, + const ConfirmationCallback& callback) { + VLOG(1) << object_path_.value() << ": New connection from device: " + << device_path.value();; + if (connection_callback_.is_null()) { + callback.Run(REJECTED); + return; + } + + // Punt descriptor validity check to a worker thread where i/o is permitted; + // on return we'll fetch the adapter and then call the connection callback. + // + // base::Passed is used to take ownership of the file descriptor during the + // CheckValidity() call and pass that ownership to the GetAdapter() call. + base::PostTaskAndReplyWithResult( + base::WorkerPool::GetTaskRunner(false), + FROM_HERE, + base::Bind(&CheckValidity, base::Passed(&fd)), + base::Bind(&BluetoothProfileExperimentalChromeOS::GetAdapter, + weak_ptr_factory_.GetWeakPtr(), + device_path, + options, + callback)); +} + +void BluetoothProfileExperimentalChromeOS::RequestDisconnection( + const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) { + VLOG(1) << object_path_.value() << ": Request disconnection"; + callback.Run(SUCCESS); +} + +void BluetoothProfileExperimentalChromeOS::Cancel() { + VLOG(1) << object_path_.value() << ": Cancel"; +} + +void BluetoothProfileExperimentalChromeOS::OnRegisterProfile( + const ProfileCallback& callback) { + VLOG(1) << object_path_.value() << ": Profile registered"; + callback.Run(this); +} + +void BluetoothProfileExperimentalChromeOS::OnRegisterProfileError( + const ProfileCallback& callback, + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to register profile: " + << error_name << ": " << error_message; + callback.Run(NULL); + + Unregister(); +} + +void BluetoothProfileExperimentalChromeOS::OnUnregisterProfile() { + VLOG(1) << object_path_.value() << ": Profile unregistered"; + object_path_ = dbus::ObjectPath(""); + delete this; +} + +void BluetoothProfileExperimentalChromeOS::OnUnregisterProfileError( + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to unregister profile: " + << error_name << ": " << error_message; + object_path_ = dbus::ObjectPath(""); + delete this; +} + +void BluetoothProfileExperimentalChromeOS::GetAdapter( + const dbus::ObjectPath& device_path, + const ExperimentalBluetoothProfileServiceProvider::Delegate::Options& + options, + const ConfirmationCallback& callback, + scoped_ptr<dbus::FileDescriptor> fd) { + VLOG(1) << object_path_.value() << ": Validity check complete"; + if (!fd->is_valid()) { + callback.Run(REJECTED); + return; + } + + BluetoothAdapterFactory::GetAdapter( + base::Bind(&BluetoothProfileExperimentalChromeOS::OnGetAdapter, + weak_ptr_factory_.GetWeakPtr(), + device_path, + options, + callback, + base::Passed(&fd))); +} + +void BluetoothProfileExperimentalChromeOS::OnGetAdapter( + const dbus::ObjectPath& device_path, + const ExperimentalBluetoothProfileServiceProvider::Delegate::Options& + options, + const ConfirmationCallback& callback, + scoped_ptr<dbus::FileDescriptor> fd, + scoped_refptr<BluetoothAdapter> adapter) { + VLOG(1) << object_path_.value() << ": Obtained adapter reference"; + callback.Run(SUCCESS); + + BluetoothDeviceExperimentalChromeOS* device = + static_cast<BluetoothAdapterExperimentalChromeOS*>(adapter.get())-> + GetDeviceWithPath(device_path); + DCHECK(device); + + scoped_refptr<BluetoothSocket> socket(( + BluetoothSocketExperimentalChromeOS::Create(fd.get()))); + connection_callback_.Run(device, socket); +} + +} // namespace chromeos diff --git a/device/bluetooth/bluetooth_profile_experimental_chromeos.h b/device/bluetooth/bluetooth_profile_experimental_chromeos.h new file mode 100644 index 0000000..61d4c44 --- /dev/null +++ b/device/bluetooth/bluetooth_profile_experimental_chromeos.h @@ -0,0 +1,132 @@ +// Copyright (c) 2013 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 DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_EXPERIMENTAL_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_EXPERIMENTAL_CHROMEOS_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "chromeos/chromeos_export.h" +#include "chromeos/dbus/experimental_bluetooth_profile_service_provider.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_profile.h" + +namespace dbus { + +class FileDescriptor; + +} // namespace dbus + +namespace device { + +class BluetoothAdapter; + +} // namespace device + +namespace chromeos { + +// The BluetoothProfileExperimentalChromeOS class implements BluetoothProfile +// for the Chrome OS platform using the Bluetooth Smart capable backend (there +// is no implementation for the older backend). It will be renamed to +// BluetoothProfileChromeOS, once the backend is the sole implementation. +class CHROMEOS_EXPORT BluetoothProfileExperimentalChromeOS + : public device::BluetoothProfile, + private ExperimentalBluetoothProfileServiceProvider::Delegate { + public: + // BluetoothProfile override. + virtual void Unregister() OVERRIDE; + virtual void SetConnectionCallback( + const ConnectionCallback& callback) OVERRIDE; + + // Return the UUID of the profile. + const std::string& uuid() const { return uuid_; } + + private: + friend class BluetoothProfile; + + BluetoothProfileExperimentalChromeOS(); + virtual ~BluetoothProfileExperimentalChromeOS(); + + // Called by BluetoothProfile::Register to initialize the profile object + // asynchronously. |uuid|, |options| and |callback| are the arguments to + // BluetoothProfile::Register. + void Init(const std::string& uuid, + const device::BluetoothProfile::Options& options, + const ProfileCallback& callback); + + // ExperimentalBluetoothProfileServiceProvider::Delegate override. + virtual void Release() OVERRIDE; + virtual void NewConnection( + const dbus::ObjectPath& device_path, + scoped_ptr<dbus::FileDescriptor> fd, + const ExperimentalBluetoothProfileServiceProvider::Delegate::Options& + options, + const ConfirmationCallback& callback) OVERRIDE; + virtual void RequestDisconnection( + const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + + // Called by dbus:: on completion of the D-Bus method call to register the + // profile object. + void OnRegisterProfile(const ProfileCallback& callback); + void OnRegisterProfileError(const ProfileCallback& callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the D-Bus method call to unregister + // the profile object. + void OnUnregisterProfile(); + void OnUnregisterProfileError(const std::string& error_name, + const std::string& error_message); + + // Method run once the file descriptor has been validated in order to get + // the default adapter, and method run once the default adapter has been + // obtained in order to get the device object to be passed to the connection + // callback. + // + // The |fd| argument is moved compared to the NewConnection() call since it + // becomes the result of a PostTaskAndReplyWithResult() call. + void GetAdapter( + const dbus::ObjectPath& device_path, + const ExperimentalBluetoothProfileServiceProvider::Delegate::Options& + options, + const ConfirmationCallback& callback, + scoped_ptr<dbus::FileDescriptor> fd); + void OnGetAdapter( + const dbus::ObjectPath& device_path, + const ExperimentalBluetoothProfileServiceProvider::Delegate::Options& + options, + const ConfirmationCallback& callback, + scoped_ptr<dbus::FileDescriptor> fd, + scoped_refptr<device::BluetoothAdapter>); + + // UUID of the profile passed during initialization. + std::string uuid_; + + // Object path of the local profile D-Bus object. + dbus::ObjectPath object_path_; + + // Local profile D-Bus object used for receiving profile delegate methods + // from BlueZ. + scoped_ptr<ExperimentalBluetoothProfileServiceProvider> profile_; + + // Callback used on both outgoing and incoming connections to pass the + // connected socket to profile object owner. + ConnectionCallback connection_callback_; + + // 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<BluetoothProfileExperimentalChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothProfileExperimentalChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_EXPERIMENTAL_CHROMEOS_H_ diff --git a/device/bluetooth/bluetooth_socket_experimental_chromeos.cc b/device/bluetooth/bluetooth_socket_experimental_chromeos.cc new file mode 100644 index 0000000..21d281a --- /dev/null +++ b/device/bluetooth/bluetooth_socket_experimental_chromeos.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2013 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 "device/bluetooth/bluetooth_socket_experimental_chromeos.h" + +#include <errno.h> +#include <poll.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <string> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" +#include "base/threading/thread_restrictions.h" +#include "dbus/file_descriptor.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "net/base/io_buffer.h" + +namespace chromeos { + +BluetoothSocketExperimentalChromeOS::BluetoothSocketExperimentalChromeOS( + int fd) + : fd_(fd) { + // Fetch the socket type so we read from it correctly. + int optval; + socklen_t opt_len = sizeof optval; + if (getsockopt(fd_, SOL_SOCKET, SO_TYPE, &optval, &opt_len) < 0) { + // Sequenced packet is the safest assumption since it won't result in + // truncated packets. + LOG(WARNING) << "Unable to get socket type: " << safe_strerror(errno); + optval = SOCK_SEQPACKET; + } + + if (optval == SOCK_DGRAM || optval == SOCK_SEQPACKET) { + socket_type_ = L2CAP; + } else { + socket_type_ = RFCOMM; + } +} + +BluetoothSocketExperimentalChromeOS::~BluetoothSocketExperimentalChromeOS() { + HANDLE_EINTR(close(fd_)); +} + +bool BluetoothSocketExperimentalChromeOS::Receive( + net::GrowableIOBuffer *buffer) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (socket_type_ == L2CAP) { + int count; + if (ioctl(fd_, FIONREAD, &count) < 0) { + error_message_ = safe_strerror(errno); + LOG(WARNING) << "Unable to get waiting data size: " << error_message_; + return true; + } + + // No bytes waiting can mean either nothing to read, or the other end has + // been closed, and reading zero bytes always returns zero. + // + // We can't do a short read for fear of a race where data arrives between + // calls and we trunctate it. So use poll() to check for the POLLHUP flag. + if (count == 0) { + struct pollfd pollfd; + + pollfd.fd = fd_; + pollfd.events = 0; + pollfd.revents = 0; + + // Timeout parameter set to 0 so this call will not block. + if (HANDLE_EINTR(poll(&pollfd, 1, 0)) < 0) { + error_message_ = safe_strerror(errno); + LOG(WARNING) << "Unable to check whether socket is closed: " + << error_message_; + return false; + } + + if (pollfd.revents & POLLHUP) { + // TODO(keybuk, youngki): Agree a common way to flag disconnected. + error_message_ = "Disconnected"; + return false; + } + } + + buffer->SetCapacity(count); + } else { + buffer->SetCapacity(1024); + } + + ssize_t bytes_read; + do { + if (buffer->RemainingCapacity() == 0) + buffer->SetCapacity(buffer->capacity() * 2); + bytes_read = + HANDLE_EINTR(read(fd_, buffer->data(), buffer->RemainingCapacity())); + if (bytes_read > 0) + buffer->set_offset(buffer->offset() + bytes_read); + } while (socket_type_ == RFCOMM && bytes_read > 0); + + // Ignore an error if at least one read() call succeeded; it'll be returned + // the next read() call. + if (buffer->offset() > 0) + return true; + + if (bytes_read < 0) { + if (errno == ECONNRESET || errno == ENOTCONN) { + // TODO(keybuk, youngki): Agree a common way to flag disconnected. + error_message_ = "Disconnected"; + return false; + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + error_message_ = safe_strerror(errno); + return false; + } + } + + if (bytes_read == 0 && socket_type_ == RFCOMM) { + // TODO(keybuk, youngki): Agree a common way to flag disconnected. + error_message_ = "Disconnected"; + return false; + } + + return true; +} + +bool BluetoothSocketExperimentalChromeOS::Send( + net::DrainableIOBuffer *buffer) { + base::ThreadRestrictions::AssertIOAllowed(); + + ssize_t bytes_written; + do { + bytes_written = + HANDLE_EINTR(write(fd_, buffer->data(), buffer->BytesRemaining())); + if (bytes_written > 0) + buffer->DidConsume(bytes_written); + } while (buffer->BytesRemaining() > 0 && bytes_written > 0); + + if (bytes_written < 0) { + if (errno == EPIPE || errno == ECONNRESET || errno == ENOTCONN) { + // TODO(keybuk, youngki): Agree a common way to flag disconnected. + error_message_ = "Disconnected"; + return false; + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + error_message_ = safe_strerror(errno); + return false; + } + } + + return true; +} + +std::string BluetoothSocketExperimentalChromeOS::GetLastErrorMessage() const { + return error_message_; +} + +// static +scoped_refptr<device::BluetoothSocket> +BluetoothSocketExperimentalChromeOS::Create(dbus::FileDescriptor* fd) { + DCHECK(fd->is_valid()); + + BluetoothSocketExperimentalChromeOS* bluetooth_socket = + new BluetoothSocketExperimentalChromeOS(fd->TakeValue());; + return scoped_refptr<BluetoothSocketExperimentalChromeOS>(bluetooth_socket); +} + +} // namespace chromeos diff --git a/device/bluetooth/bluetooth_socket_experimental_chromeos.h b/device/bluetooth/bluetooth_socket_experimental_chromeos.h new file mode 100644 index 0000000..c0e1af0 --- /dev/null +++ b/device/bluetooth/bluetooth_socket_experimental_chromeos.h @@ -0,0 +1,75 @@ +// Copyright (c) 2013 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 DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_EXPERIMENTAL_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_EXPERIMENTAL_CHROMEOS_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "chromeos/chromeos_export.h" +#include "device/bluetooth/bluetooth_socket.h" + +namespace dbus { + +class FileDescriptor; + +} // namespace dbus + +namespace net { + +class DrainableIOBuffer; +class GrowableIOBuffer; + +} // namespace net + +namespace chromeos { + +// The BluetoothSocketExperimentalChromeOS class is an alternate implementation +// of BluetoothSocket for the Chrome OS platform using the Bluetooth Smart +// capable backend. It will become the sole implementation for Chrome OS, and +// be renamed to BluetoothSocketChromeOS, once the backend is switched. +class CHROMEOS_EXPORT BluetoothSocketExperimentalChromeOS + : public device::BluetoothSocket { + public: + // BluetoothSocket override. + virtual bool Receive(net::GrowableIOBuffer* buffer) OVERRIDE; + virtual bool Send(net::DrainableIOBuffer* buffer) OVERRIDE; + virtual std::string GetLastErrorMessage() const OVERRIDE; + + // Create an instance of a BluetoothSocket from the passed file descriptor + // received over D-Bus in |fd|, the descriptor will be taken from that object + // and ownership passed to the returned object. + static scoped_refptr<device::BluetoothSocket> Create( + dbus::FileDescriptor* fd); + + protected: + virtual ~BluetoothSocketExperimentalChromeOS(); + + private: + BluetoothSocketExperimentalChromeOS(int fd); + + // The different socket types have different reading patterns; l2cap sockets + // have to be read with boundaries between datagrams preserved while rfcomm + // sockets do not. + enum SocketType { + L2CAP, + RFCOMM + }; + + // File descriptor and socket type of the socket. + const int fd_; + SocketType socket_type_; + + // Last error message, set during Receive() and Send() and retrieved using + // GetLastErrorMessage(). + std::string error_message_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothSocketExperimentalChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_EXPERIMENTAL_CHROMEOS_H_ diff --git a/device/device.gyp b/device/device.gyp index 12de0c4..68cb89d 100644 --- a/device/device.gyp +++ b/device/device.gyp @@ -46,6 +46,8 @@ 'bluetooth/bluetooth_out_of_band_pairing_data.h', 'bluetooth/bluetooth_profile.cc', 'bluetooth/bluetooth_profile.h', + 'bluetooth/bluetooth_profile_experimental_chromeos.cc', + 'bluetooth/bluetooth_profile_experimental_chromeos.h', 'bluetooth/bluetooth_profile_mac.h', 'bluetooth/bluetooth_profile_mac.mm', 'bluetooth/bluetooth_profile_win.cc', @@ -61,6 +63,8 @@ 'bluetooth/bluetooth_socket.h', 'bluetooth/bluetooth_socket_chromeos.cc', 'bluetooth/bluetooth_socket_chromeos.h', + 'bluetooth/bluetooth_socket_experimental_chromeos.cc', + 'bluetooth/bluetooth_socket_experimental_chromeos.h', 'bluetooth/bluetooth_socket_mac.h', 'bluetooth/bluetooth_socket_mac.mm', 'bluetooth/bluetooth_socket_win.cc', @@ -173,6 +177,7 @@ 'bluetooth/bluetooth_adapter_win_unittest.cc', 'bluetooth/bluetooth_device_win_unittest.cc', 'bluetooth/bluetooth_experimental_chromeos_unittest.cc', + 'bluetooth/bluetooth_profile_chromeos_unittest.cc', 'bluetooth/bluetooth_service_record_chromeos_unittest.cc', 'bluetooth/bluetooth_service_record_mac_unittest.mm', 'bluetooth/bluetooth_service_record_win_unittest.cc', |