From e882d2cdf5ed5337d68f2b32ba0f885725ca8b3f Mon Sep 17 00:00:00 2001 From: "keybuk@chromium.org" Date: Thu, 9 May 2013 03:49:18 +0000 Subject: 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 --- ...erimental_bluetooth_profile_service_provider.cc | 8 +- ...perimental_bluetooth_profile_service_provider.h | 16 ++- chromeos/dbus/fake_bluetooth_device_client.cc | 153 ++++++++++++++++++++- chromeos/dbus/fake_bluetooth_device_client.h | 12 ++ .../dbus/fake_bluetooth_profile_manager_client.cc | 66 ++++++++- .../dbus/fake_bluetooth_profile_manager_client.h | 32 +++++ .../fake_bluetooth_profile_service_provider.cc | 18 ++- .../dbus/fake_bluetooth_profile_service_provider.h | 5 +- 8 files changed, 292 insertions(+), 18 deletions(-) (limited to 'chromeos/dbus') diff --git a/chromeos/dbus/experimental_bluetooth_profile_service_provider.cc b/chromeos/dbus/experimental_bluetooth_profile_service_provider.cc index a58c8bc..2b2384f 100644 --- a/chromeos/dbus/experimental_bluetooth_profile_service_provider.cc +++ b/chromeos/dbus/experimental_bluetooth_profile_service_provider.cc @@ -113,10 +113,10 @@ class ExperimentalBluetoothProfileServiceProviderImpl dbus::MessageReader reader(method_call); dbus::ObjectPath device_path; - dbus::FileDescriptor fd; + scoped_ptr fd(new dbus::FileDescriptor()); dbus::MessageReader array_reader(NULL); if (!reader.PopObjectPath(&device_path) || - !reader.PopFileDescriptor(&fd) || + !reader.PopFileDescriptor(fd.get()) || !reader.PopArray(&array_reader)) { LOG(WARNING) << "NewConnection called with incorrect paramters: " << method_call->ToString(); @@ -145,9 +145,7 @@ class ExperimentalBluetoothProfileServiceProviderImpl method_call, response_sender); - delegate_->NewConnection(device_path, &fd, options, callback); - - response_sender.Run(dbus::Response::FromMethodCall(method_call)); + delegate_->NewConnection(device_path, fd.Pass(), options, callback); } // Called by dbus:: when the Bluetooth daemon is about to disconnect the diff --git a/chromeos/dbus/experimental_bluetooth_profile_service_provider.h b/chromeos/dbus/experimental_bluetooth_profile_service_provider.h index 71e09d5..4649696 100644 --- a/chromeos/dbus/experimental_bluetooth_profile_service_provider.h +++ b/chromeos/dbus/experimental_bluetooth_profile_service_provider.h @@ -9,6 +9,7 @@ #include "base/basictypes.h" #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "chromeos/chromeos_export.h" #include "dbus/bus.h" #include "dbus/file_descriptor.h" @@ -74,13 +75,16 @@ class CHROMEOS_EXPORT ExperimentalBluetoothProfileServiceProvider { // // A file descriptor for the connection socket is provided in |fd|, and // details about the specific implementation of the profile in |options|. - // The delegate should take the value and ownership - - // The file descriptor is owned by the delegate after this call so must be - // cleaned up if the connection is cancelled or rejected, the |options| - // structure is not so information out of it must be copied if required. + // + // IMPORTANT: Ownership of the file descriptor object |fd| is passed to + // the delegate by this call. The delegate is responsible for checking the + // validity of |fd| on a thread where I/O is permitted before taking the + // value. If the value is not taken, the file descriptor is closed. + // + // Ownership of |options| is NOT passed so information out of it must be + // copied if required. virtual void NewConnection(const dbus::ObjectPath& device_path, - dbus::FileDescriptor* fd, + scoped_ptr fd, const Options& options, const ConfirmationCallback& callback) = 0; diff --git a/chromeos/dbus/fake_bluetooth_device_client.cc b/chromeos/dbus/fake_bluetooth_device_client.cc index 4d3e189..b1ed840 100644 --- a/chromeos/dbus/fake_bluetooth_device_client.cc +++ b/chromeos/dbus/fake_bluetooth_device_client.cc @@ -4,6 +4,11 @@ #include "chromeos/dbus/fake_bluetooth_device_client.h" +#include +#include +#include +#include + #include #include #include @@ -12,14 +17,19 @@ #include "base/bind.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/stl_util.h" +#include "base/threading/worker_pool.h" #include "base/time.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/fake_bluetooth_adapter_client.h" #include "chromeos/dbus/fake_bluetooth_agent_manager_client.h" #include "chromeos/dbus/fake_bluetooth_agent_service_provider.h" #include "chromeos/dbus/fake_bluetooth_input_client.h" +#include "chromeos/dbus/fake_bluetooth_profile_manager_client.h" +#include "chromeos/dbus/fake_bluetooth_profile_service_provider.h" +#include "dbus/file_descriptor.h" #include "dbus/object_path.h" #include "third_party/cros_system_api/dbus/service_constants.h" @@ -28,6 +38,30 @@ namespace { // Default interval between simulated events. const int kSimulationIntervalMs = 750; + +void SimulatedProfileSocket(int fd) { + // Simulate a server-side socket of a profile; read data from the socket, + // write it back, and then close. + char buf[1024]; + ssize_t len; + ssize_t count; + + len = read(fd, buf, sizeof buf); + if (len < 0) { + close(fd); + return; + } + + count = len; + len = write(fd, buf, count); + if (len < 0) { + close(fd); + return; + } + + close(fd); +} + } namespace chromeos { @@ -267,7 +301,63 @@ void FakeBluetoothDeviceClient::ConnectProfile( const base::Closure& callback, const ErrorCallback& error_callback) { VLOG(1) << "ConnectProfile: " << object_path.value() << " " << uuid; - error_callback.Run(kNoResponseError, ""); + + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client = + static_cast( + DBusThreadManager::Get()-> + GetExperimentalBluetoothProfileManagerClient()); + FakeBluetoothProfileServiceProvider* profile_service_provider = + fake_bluetooth_profile_manager_client->GetProfileServiceProvider(uuid); + if (profile_service_provider == NULL) { + error_callback.Run(kNoResponseError, "Missing profile"); + return; + } + + // Make a socket pair of a compatible type with the type used by Bluetooth; + // spin up a thread to simulate the server side and wrap the client side in + // a D-Bus file descriptor object. + int socket_type = SOCK_STREAM; + if (uuid == FakeBluetoothProfileManagerClient::kL2capUuid) + socket_type = SOCK_SEQPACKET; + + int fds[2]; + if (socketpair(AF_UNIX, socket_type, 0, fds) < 0) { + error_callback.Run(kNoResponseError, "socketpair call failed"); + return; + } + + int args; + args = fcntl(fds[1], F_GETFL, NULL); + if (args < 0) { + error_callback.Run(kNoResponseError, "failed to get socket flags"); + return; + } + + args |= O_NONBLOCK; + if (fcntl(fds[1], F_SETFL, args) < 0) { + error_callback.Run(kNoResponseError, "failed to set socket non-blocking"); + return; + } + + base::WorkerPool::GetTaskRunner(false)->PostTask( + FROM_HERE, + base::Bind(&SimulatedProfileSocket, + fds[0])); + + scoped_ptr fd(new dbus::FileDescriptor(fds[1])); + + // Post the new connection to the service provider. + ExperimentalBluetoothProfileServiceProvider::Delegate::Options options; + + profile_service_provider->NewConnection( + object_path, + fd.Pass(), + options, + base::Bind(&FakeBluetoothDeviceClient::ConnectionCallback, + base::Unretained(this), + object_path, + callback, + error_callback)); } void FakeBluetoothDeviceClient::DisconnectProfile( @@ -276,7 +366,25 @@ void FakeBluetoothDeviceClient::DisconnectProfile( const base::Closure& callback, const ErrorCallback& error_callback) { VLOG(1) << "DisconnectProfile: " << object_path.value() << " " << uuid; - error_callback.Run(kNoResponseError, ""); + + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client = + static_cast( + DBusThreadManager::Get()-> + GetExperimentalBluetoothProfileManagerClient()); + FakeBluetoothProfileServiceProvider* profile_service_provider = + fake_bluetooth_profile_manager_client->GetProfileServiceProvider(uuid); + if (profile_service_provider == NULL) { + error_callback.Run(kNoResponseError, "Missing profile"); + return; + } + + profile_service_provider->RequestDisconnection( + object_path, + base::Bind(&FakeBluetoothDeviceClient::DisconnectionCallback, + base::Unretained(this), + object_path, + callback, + error_callback)); } void FakeBluetoothDeviceClient::Pair( @@ -920,4 +1028,45 @@ void FakeBluetoothDeviceClient::SimulateKeypress( } } +void FakeBluetoothDeviceClient::ConnectionCallback( + const dbus::ObjectPath& object_path, + const base::Closure& callback, + const ErrorCallback& error_callback, + ExperimentalBluetoothProfileServiceProvider::Delegate::Status status) { + VLOG(1) << "ConnectionCallback: " << object_path.value(); + + if (status == + ExperimentalBluetoothProfileServiceProvider::Delegate::SUCCESS) { + callback.Run(); + } else if (status == + ExperimentalBluetoothProfileServiceProvider::Delegate::CANCELLED) { + // TODO(keybuk): tear down this side of the connection + error_callback.Run(bluetooth_adapter::kErrorFailed, "Canceled"); + } else if (status == + ExperimentalBluetoothProfileServiceProvider::Delegate::REJECTED) { + // TODO(keybuk): tear down this side of the connection + error_callback.Run(bluetooth_adapter::kErrorFailed, "Rejected"); + } +} + +void FakeBluetoothDeviceClient::DisconnectionCallback( + const dbus::ObjectPath& object_path, + const base::Closure& callback, + const ErrorCallback& error_callback, + ExperimentalBluetoothProfileServiceProvider::Delegate::Status status) { + VLOG(1) << "DisconnectionCallback: " << object_path.value(); + + if (status == + ExperimentalBluetoothProfileServiceProvider::Delegate::SUCCESS) { + // TODO(keybuk): tear down this side of the connection + callback.Run(); + } else if (status == + ExperimentalBluetoothProfileServiceProvider::Delegate::CANCELLED) { + error_callback.Run(bluetooth_adapter::kErrorFailed, "Canceled"); + } else if (status == + ExperimentalBluetoothProfileServiceProvider::Delegate::REJECTED) { + error_callback.Run(bluetooth_adapter::kErrorFailed, "Rejected"); + } +} + } // namespace chromeos diff --git a/chromeos/dbus/fake_bluetooth_device_client.h b/chromeos/dbus/fake_bluetooth_device_client.h index 9ce85b2..6a90863 100644 --- a/chromeos/dbus/fake_bluetooth_device_client.h +++ b/chromeos/dbus/fake_bluetooth_device_client.h @@ -15,6 +15,7 @@ #include "chromeos/dbus/dbus_client_implementation_type.h" #include "chromeos/dbus/experimental_bluetooth_agent_service_provider.h" #include "chromeos/dbus/experimental_bluetooth_device_client.h" +#include "chromeos/dbus/experimental_bluetooth_profile_service_provider.h" #include "dbus/object_path.h" #include "dbus/property.h" @@ -186,6 +187,17 @@ class CHROMEOS_EXPORT FakeBluetoothDeviceClient const base::Closure& callback, const ErrorCallback& error_callback); + void ConnectionCallback( + const dbus::ObjectPath& object_path, + const base::Closure& callback, + const ErrorCallback& error_callback, + ExperimentalBluetoothProfileServiceProvider::Delegate::Status status); + void DisconnectionCallback( + const dbus::ObjectPath& object_path, + const base::Closure& callback, + const ErrorCallback& error_callback, + ExperimentalBluetoothProfileServiceProvider::Delegate::Status status); + // List of observers interested in event notifications from us. ObserverList observers_; diff --git a/chromeos/dbus/fake_bluetooth_profile_manager_client.cc b/chromeos/dbus/fake_bluetooth_profile_manager_client.cc index 0547e4b1..2f2fb8e 100644 --- a/chromeos/dbus/fake_bluetooth_profile_manager_client.cc +++ b/chromeos/dbus/fake_bluetooth_profile_manager_client.cc @@ -4,8 +4,12 @@ #include "chromeos/dbus/fake_bluetooth_profile_manager_client.h" +#include +#include + #include "base/bind.h" #include "base/logging.h" +#include "chromeos/dbus/fake_bluetooth_profile_service_provider.h" #include "dbus/bus.h" #include "dbus/message.h" #include "dbus/object_path.h" @@ -14,6 +18,11 @@ namespace chromeos { +const char FakeBluetoothProfileManagerClient::kL2capUuid[] = + "4d995052-33cc-4fdf-b446-75f32942a076"; +const char FakeBluetoothProfileManagerClient::kRfcommUuid[] = + "3f6d6dbf-a6ad-45fc-9653-47dc912ef70e"; + FakeBluetoothProfileManagerClient::FakeBluetoothProfileManagerClient() { } @@ -27,7 +36,23 @@ void FakeBluetoothProfileManagerClient::RegisterProfile( const base::Closure& callback, const ErrorCallback& error_callback) { VLOG(1) << "RegisterProfile: " << profile_path.value() << ": " << uuid; - callback.Run(); + + // check options for channel & psm + + ServiceProviderMap::iterator iter = service_provider_map_.find(profile_path); + if (iter == service_provider_map_.end()) { + error_callback.Run(bluetooth_adapter::kErrorFailed, + "No profile created"); + } else { + ProfileMap::iterator piter = profile_map_.find(uuid); + if (piter != profile_map_.end()) { + error_callback.Run(bluetooth_adapter::kErrorAlreadyExists, + "Profile already registered"); + } else { + profile_map_[uuid] = profile_path; + callback.Run(); + } + } } void FakeBluetoothProfileManagerClient::UnregisterProfile( @@ -35,7 +60,44 @@ void FakeBluetoothProfileManagerClient::UnregisterProfile( const base::Closure& callback, const ErrorCallback& error_callback) { VLOG(1) << "UnregisterProfile: " << profile_path.value(); - callback.Run(); + + ServiceProviderMap::iterator iter = service_provider_map_.find(profile_path); + if (iter != service_provider_map_.end()) { + error_callback.Run(bluetooth_adapter::kErrorFailed, + "Profile still registered"); + } else { + for (ProfileMap::iterator piter = profile_map_.begin(); + piter != profile_map_.end(); ++piter) { + if (piter->second == profile_path) { + profile_map_.erase(piter); + break; + } + } + + callback.Run(); + } +} + +void FakeBluetoothProfileManagerClient::RegisterProfileServiceProvider( + FakeBluetoothProfileServiceProvider* service_provider) { + service_provider_map_[service_provider->object_path_] = service_provider; +} + +void FakeBluetoothProfileManagerClient::UnregisterProfileServiceProvider( + FakeBluetoothProfileServiceProvider* service_provider) { + ServiceProviderMap::iterator iter = + service_provider_map_.find(service_provider->object_path_); + if (iter != service_provider_map_.end() && iter->second == service_provider) + service_provider_map_.erase(iter); +} + +FakeBluetoothProfileServiceProvider* +FakeBluetoothProfileManagerClient::GetProfileServiceProvider( + const std::string& uuid) { + ProfileMap::iterator iter = profile_map_.find(uuid); + if (iter == profile_map_.end()) + return NULL; + return service_provider_map_[iter->second]; } } // namespace chromeos diff --git a/chromeos/dbus/fake_bluetooth_profile_manager_client.h b/chromeos/dbus/fake_bluetooth_profile_manager_client.h index e3cfcd4..bba6732 100644 --- a/chromeos/dbus/fake_bluetooth_profile_manager_client.h +++ b/chromeos/dbus/fake_bluetooth_profile_manager_client.h @@ -5,6 +5,9 @@ #ifndef CHROMEOS_DBUS_FAKE_BLUETOOTH_PROFILE_MANAGER_CLIENT_H_ #define CHROMEOS_DBUS_FAKE_BLUETOOTH_PROFILE_MANAGER_CLIENT_H_ +#include +#include + #include "base/bind.h" #include "base/callback.h" #include "base/observer_list.h" @@ -16,6 +19,8 @@ namespace chromeos { +class FakeBluetoothProfileServiceProvider; + // FakeBluetoothProfileManagerClient simulates the behavior of the Bluetooth // Daemon's profile manager object and is used both in test cases in place of a // mock and on the Linux desktop. @@ -34,6 +39,33 @@ class CHROMEOS_EXPORT FakeBluetoothProfileManagerClient virtual void UnregisterProfile(const dbus::ObjectPath& profile_path, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; + + // Register, unregister and retrieve pointers to profile server providers. + void RegisterProfileServiceProvider( + FakeBluetoothProfileServiceProvider* service_provider); + void UnregisterProfileServiceProvider( + FakeBluetoothProfileServiceProvider* service_provider); + FakeBluetoothProfileServiceProvider* GetProfileServiceProvider( + const std::string& uuid); + + // UUIDs recognised for testing. + static const char kL2capUuid[]; + static const char kRfcommUuid[]; + + private: + // Map of a D-Bus object path to the FakeBluetoothProfileServiceProvider + // registered for it; maintained by RegisterProfileServiceProvider() and + // UnregisterProfileServiceProvicer() called by the constructor and + // destructor of FakeBluetoothProfileServiceProvider. + typedef std::map + ServiceProviderMap; + ServiceProviderMap service_provider_map_; + + // Map of Profile UUID to the D-Bus object path of the service provider + // in |service_provider_map_|. Maintained by RegisterProfile() and + // UnregisterProfile() in response to BluetoothProfile methods. + typedef std::map ProfileMap; + ProfileMap profile_map_; }; } // namespace chromeos diff --git a/chromeos/dbus/fake_bluetooth_profile_service_provider.cc b/chromeos/dbus/fake_bluetooth_profile_service_provider.cc index 999da6f..a021464 100644 --- a/chromeos/dbus/fake_bluetooth_profile_service_provider.cc +++ b/chromeos/dbus/fake_bluetooth_profile_service_provider.cc @@ -4,6 +4,8 @@ #include "chromeos/dbus/fake_bluetooth_profile_service_provider.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/fake_bluetooth_profile_manager_client.h" #include "dbus/object_path.h" namespace chromeos { @@ -14,10 +16,22 @@ FakeBluetoothProfileServiceProvider::FakeBluetoothProfileServiceProvider( : object_path_(object_path), delegate_(delegate) { VLOG(1) << "Creating Bluetooth Profile: " << object_path_.value(); + + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client = + static_cast( + DBusThreadManager::Get()-> + GetExperimentalBluetoothProfileManagerClient()); + fake_bluetooth_profile_manager_client->RegisterProfileServiceProvider(this); } FakeBluetoothProfileServiceProvider::~FakeBluetoothProfileServiceProvider() { VLOG(1) << "Cleaning up Bluetooth Profile: " << object_path_.value(); + + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client = + static_cast( + DBusThreadManager::Get()-> + GetExperimentalBluetoothProfileManagerClient()); + fake_bluetooth_profile_manager_client->UnregisterProfileServiceProvider(this); } void FakeBluetoothProfileServiceProvider::Release() { @@ -27,12 +41,12 @@ void FakeBluetoothProfileServiceProvider::Release() { void FakeBluetoothProfileServiceProvider::NewConnection( const dbus::ObjectPath& device_path, - dbus::FileDescriptor* fd, + scoped_ptr fd, const Delegate::Options& options, const Delegate::ConfirmationCallback& callback) { VLOG(1) << object_path_.value() << ": NewConnection for " << device_path.value(); - delegate_->NewConnection(device_path, fd, options, callback); + delegate_->NewConnection(device_path, fd.Pass(), options, callback); } void FakeBluetoothProfileServiceProvider::RequestDisconnection( diff --git a/chromeos/dbus/fake_bluetooth_profile_service_provider.h b/chromeos/dbus/fake_bluetooth_profile_service_provider.h index 5af7382..c886f71 100644 --- a/chromeos/dbus/fake_bluetooth_profile_service_provider.h +++ b/chromeos/dbus/fake_bluetooth_profile_service_provider.h @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "chromeos/chromeos_export.h" #include "chromeos/dbus/dbus_client_implementation_type.h" #include "chromeos/dbus/experimental_bluetooth_profile_service_provider.h" @@ -31,7 +32,7 @@ class CHROMEOS_EXPORT FakeBluetoothProfileServiceProvider virtual void Release(); virtual void NewConnection( const dbus::ObjectPath& device_path, - dbus::FileDescriptor* fd, + scoped_ptr fd, const Delegate::Options& options, const Delegate::ConfirmationCallback& callback); virtual void RequestDisconnection( @@ -40,6 +41,8 @@ class CHROMEOS_EXPORT FakeBluetoothProfileServiceProvider virtual void Cancel(); private: + friend class FakeBluetoothProfileManagerClient; + // D-Bus object path we are faking. dbus::ObjectPath object_path_; -- cgit v1.1