diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/device | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/device')
179 files changed, 20194 insertions, 3959 deletions
diff --git a/chromium/device/DEPS b/chromium/device/DEPS index 79b8e8ba13b..80961ee9052 100644 --- a/chromium/device/DEPS +++ b/chromium/device/DEPS @@ -1,3 +1,4 @@ include_rules = [ "+chromeos", + "+mojo/public", ] diff --git a/chromium/device/OWNERS b/chromium/device/OWNERS index a83889f764b..f6716f4c5a0 100644 --- a/chromium/device/OWNERS +++ b/chromium/device/OWNERS @@ -1,3 +1,5 @@ keybuk@chromium.org gdk@chromium.org miket@chromium.org +rockot@chromium.org +rpaquay@chromium.org diff --git a/chromium/device/bluetooth/DEPS b/chromium/device/bluetooth/DEPS index 7f93e426e9f..93c11ef1e66 100644 --- a/chromium/device/bluetooth/DEPS +++ b/chromium/device/bluetooth/DEPS @@ -3,6 +3,7 @@ include_rules = [ "+dbus", "+grit", "+net/base", + "+net/socket", "+ui/base/l10n", "+third_party/cros_system_api/dbus", "+third_party/libxml/chromium", diff --git a/chromium/device/bluetooth/OWNERS b/chromium/device/bluetooth/OWNERS index 5bdf96a9ede..9226f87bcd7 100644 --- a/chromium/device/bluetooth/OWNERS +++ b/chromium/device/bluetooth/OWNERS @@ -1,2 +1,7 @@ +set noparent + keybuk@chromium.org -youngki@chromium.org +armansito@chromium.org + +per-file *_mac*=rpaquay@chromium.org +per-file *_win*=rpaquay@chromium.org diff --git a/chromium/device/bluetooth/bluetooth.gyp b/chromium/device/bluetooth/bluetooth.gyp index c5d8cadec1e..4d206ecef3d 100644 --- a/chromium/device/bluetooth/bluetooth.gyp +++ b/chromium/device/bluetooth/bluetooth.gyp @@ -15,8 +15,9 @@ '../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', '../../net/net.gyp:net', '../../third_party/libxml/libxml.gyp:libxml', + '../../ui/base/ui_base.gyp:ui_base', '../../ui/gfx/gfx.gyp:gfx', - '../../ui/ui.gyp:ui', + '../../ui/gfx/gfx.gyp:gfx_geometry', 'bluetooth_strings.gyp:device_bluetooth_strings', ], 'sources': [ @@ -30,6 +31,8 @@ 'bluetooth_adapter_mac.mm', 'bluetooth_adapter_win.cc', 'bluetooth_adapter_win.h', + 'bluetooth_channel_mac.mm', + 'bluetooth_channel_mac.h', 'bluetooth_device.cc', 'bluetooth_device.h', 'bluetooth_device_chromeos.cc', @@ -38,34 +41,56 @@ 'bluetooth_device_mac.mm', 'bluetooth_device_win.cc', 'bluetooth_device_win.h', + 'bluetooth_discovery_manager_mac.mm', + 'bluetooth_discovery_manager_mac.h', + 'bluetooth_discovery_session.cc', + 'bluetooth_discovery_session.h', + 'bluetooth_gatt_characteristic.cc', + 'bluetooth_gatt_characteristic.h', + 'bluetooth_gatt_connection.cc', + 'bluetooth_gatt_connection.h', + 'bluetooth_gatt_connection_chromeos.cc', + 'bluetooth_gatt_connection_chromeos.h', + 'bluetooth_gatt_descriptor.cc', + 'bluetooth_gatt_descriptor.h', + 'bluetooth_gatt_notify_session.cc', + 'bluetooth_gatt_notify_session.h', + 'bluetooth_gatt_notify_session_chromeos.cc', + 'bluetooth_gatt_notify_session_chromeos.h', + 'bluetooth_gatt_service.cc', + 'bluetooth_gatt_service.h', 'bluetooth_init_win.cc', 'bluetooth_init_win.h', - 'bluetooth_out_of_band_pairing_data.h', - 'bluetooth_profile.cc', - 'bluetooth_profile.h', - 'bluetooth_profile_chromeos.cc', - 'bluetooth_profile_chromeos.h', - 'bluetooth_profile_mac.h', - 'bluetooth_profile_mac.mm', - 'bluetooth_profile_win.cc', - 'bluetooth_profile_win.h', - 'bluetooth_service_record.cc', - 'bluetooth_service_record.h', - 'bluetooth_service_record_mac.h', - 'bluetooth_service_record_mac.mm', + 'bluetooth_l2cap_channel_mac.mm', + 'bluetooth_l2cap_channel_mac.h', + 'bluetooth_pairing_chromeos.cc', + 'bluetooth_pairing_chromeos.h', + 'bluetooth_remote_gatt_characteristic_chromeos.cc', + 'bluetooth_remote_gatt_characteristic_chromeos.h', + 'bluetooth_remote_gatt_descriptor_chromeos.cc', + 'bluetooth_remote_gatt_descriptor_chromeos.h', + 'bluetooth_remote_gatt_service_chromeos.cc', + 'bluetooth_remote_gatt_service_chromeos.h', + 'bluetooth_rfcomm_channel_mac.mm', + 'bluetooth_rfcomm_channel_mac.h', 'bluetooth_service_record_win.cc', 'bluetooth_service_record_win.h', + 'bluetooth_socket.cc', 'bluetooth_socket.h', 'bluetooth_socket_chromeos.cc', 'bluetooth_socket_chromeos.h', 'bluetooth_socket_mac.h', 'bluetooth_socket_mac.mm', + 'bluetooth_socket_net.cc', + 'bluetooth_socket_net.h', + 'bluetooth_socket_thread.cc', + 'bluetooth_socket_thread.h', 'bluetooth_socket_win.cc', 'bluetooth_socket_win.h', 'bluetooth_task_manager_win.cc', 'bluetooth_task_manager_win.h', - 'bluetooth_utils.cc', - 'bluetooth_utils.h', + 'bluetooth_uuid.cc', + 'bluetooth_uuid.h', ], 'conditions': [ ['chromeos==1', { @@ -112,8 +137,18 @@ 'test/mock_bluetooth_adapter.h', 'test/mock_bluetooth_device.cc', 'test/mock_bluetooth_device.h', - 'test/mock_bluetooth_profile.cc', - 'test/mock_bluetooth_profile.h', + 'test/mock_bluetooth_discovery_session.cc', + 'test/mock_bluetooth_discovery_session.h', + 'test/mock_bluetooth_gatt_characteristic.cc', + 'test/mock_bluetooth_gatt_characteristic.h', + 'test/mock_bluetooth_gatt_connection.cc', + 'test/mock_bluetooth_gatt_connection.h', + 'test/mock_bluetooth_gatt_descriptor.cc', + 'test/mock_bluetooth_gatt_descriptor.h', + 'test/mock_bluetooth_gatt_notify_session.cc', + 'test/mock_bluetooth_gatt_notify_session.h', + 'test/mock_bluetooth_gatt_service.cc', + 'test/mock_bluetooth_gatt_service.h', 'test/mock_bluetooth_socket.cc', 'test/mock_bluetooth_socket.h', ], diff --git a/chromium/device/bluetooth/bluetooth_adapter.cc b/chromium/device/bluetooth/bluetooth_adapter.cc index 8cce6ece2c1..1f5922292d0 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.cc +++ b/chromium/device/bluetooth/bluetooth_adapter.cc @@ -4,18 +4,46 @@ #include "device/bluetooth/bluetooth_adapter.h" +#include "base/bind.h" #include "base/stl_util.h" #include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_discovery_session.h" namespace device { -BluetoothAdapter::BluetoothAdapter() { +#if !defined(OS_CHROMEOS) && !defined(OS_WIN) && !defined(OS_MACOSX) +//static +base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( + const InitCallback& init_callback) { + return base::WeakPtr<BluetoothAdapter>(); +} +#endif // !defined(OS_CHROMEOS) && !defined(OS_WIN) && !defined(OS_MACOSX) + +const int BluetoothAdapter::kChannelAuto = 0; +const int BluetoothAdapter::kPsmAuto = 0; + +BluetoothAdapter::BluetoothAdapter() + : weak_ptr_factory_(this) { } BluetoothAdapter::~BluetoothAdapter() { STLDeleteValues(&devices_); } +base::WeakPtr<BluetoothAdapter> BluetoothAdapter::GetWeakPtrForTesting() { + return weak_ptr_factory_.GetWeakPtr(); +} + +void BluetoothAdapter::StartDiscoverySession( + const DiscoverySessionCallback& callback, + const ErrorCallback& error_callback) { + AddDiscoverySession( + base::Bind(&BluetoothAdapter::OnStartDiscoverySession, + weak_ptr_factory_.GetWeakPtr(), + callback), + error_callback); +} + BluetoothAdapter::DeviceList BluetoothAdapter::GetDevices() { ConstDeviceList const_devices = const_cast<const BluetoothAdapter *>(this)->GetDevices(); @@ -45,11 +73,78 @@ BluetoothDevice* BluetoothAdapter::GetDevice(const std::string& address) { const BluetoothDevice* BluetoothAdapter::GetDevice( const std::string& address) const { - DevicesMap::const_iterator iter = devices_.find(address); + std::string canonicalized_address = + BluetoothDevice::CanonicalizeAddress(address); + if (canonicalized_address.empty()) + return NULL; + + DevicesMap::const_iterator iter = devices_.find(canonicalized_address); if (iter != devices_.end()) return iter->second; return NULL; } +void BluetoothAdapter::AddPairingDelegate( + BluetoothDevice::PairingDelegate* pairing_delegate, + PairingDelegatePriority priority) { + // Remove the delegate, if it already exists, before inserting to allow a + // change of priority. + RemovePairingDelegate(pairing_delegate); + + // Find the first point with a lower priority, or the end of the list. + std::list<PairingDelegatePair>::iterator iter = pairing_delegates_.begin(); + while (iter != pairing_delegates_.end() && iter->second >= priority) + ++iter; + + pairing_delegates_.insert(iter, std::make_pair(pairing_delegate, priority)); +} + +void BluetoothAdapter::RemovePairingDelegate( + BluetoothDevice::PairingDelegate* pairing_delegate) { + for (std::list<PairingDelegatePair>::iterator iter = + pairing_delegates_.begin(); iter != pairing_delegates_.end(); ++iter) { + if (iter->first == pairing_delegate) { + RemovePairingDelegateInternal(pairing_delegate); + pairing_delegates_.erase(iter); + return; + } + } +} + +BluetoothDevice::PairingDelegate* BluetoothAdapter::DefaultPairingDelegate() { + if (pairing_delegates_.empty()) + return NULL; + + return pairing_delegates_.front().first; +} + +void BluetoothAdapter::OnStartDiscoverySession( + const DiscoverySessionCallback& callback) { + VLOG(1) << "Discovery session started!"; + scoped_ptr<BluetoothDiscoverySession> discovery_session( + new BluetoothDiscoverySession(scoped_refptr<BluetoothAdapter>(this))); + discovery_sessions_.insert(discovery_session.get()); + callback.Run(discovery_session.Pass()); +} + +void BluetoothAdapter::MarkDiscoverySessionsAsInactive() { + // As sessions are marked as inactive they will notify the adapter that they + // have become inactive, upon which the adapter will remove them from + // |discovery_sessions_|. To avoid invalidating the iterator, make a copy + // here. + std::set<BluetoothDiscoverySession*> temp(discovery_sessions_); + for (std::set<BluetoothDiscoverySession*>::iterator + iter = temp.begin(); + iter != temp.end(); ++iter) { + (*iter)->MarkAsInactive(); + } +} + +void BluetoothAdapter::DiscoverySessionBecameInactive( + BluetoothDiscoverySession* discovery_session) { + DCHECK(!discovery_session->IsActive()); + discovery_sessions_.erase(discovery_session); +} + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter.h b/chromium/device/bluetooth/bluetooth_adapter.h index ed615860120..cec051fffba 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.h +++ b/chromium/device/bluetooth/bluetooth_adapter.h @@ -5,22 +5,26 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_H_ +#include <list> #include <map> +#include <set> #include <string> -#include <vector> +#include <utility> #include "base/callback.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "device/bluetooth/bluetooth_device.h" namespace device { -class BluetoothDevice; - -struct BluetoothOutOfBandPairingData; +class BluetoothDiscoverySession; +class BluetoothSocket; +class BluetoothUUID; // BluetoothAdapter represents a local Bluetooth adapter which may be used to // interact with remote Bluetooth devices. As well as providing support for -// determining whether an adapter is present, and whether the radio is powered, +// determining whether an adapter is present and whether the radio is powered, // this class also provides support for obtaining the list of remote devices // known to the adapter, discovering new devices, and providing notification of // updates to device information. @@ -31,138 +35,299 @@ class BluetoothAdapter : public base::RefCounted<BluetoothAdapter> { public: virtual ~Observer() {} - // Called when the presence of the adapter |adapter| changes, when - // |present| is true the adapter is now present, false means the adapter - // has been removed from the system. + // Called when the presence of the adapter |adapter| changes. When |present| + // is true the adapter is now present, false means the adapter has been + // removed from the system. virtual void AdapterPresentChanged(BluetoothAdapter* adapter, bool present) {} - // Called when the radio power state of the adapter |adapter| changes, - // when |powered| is true the adapter radio is powered, false means the - // adapter radio is off. + // Called when the radio power state of the adapter |adapter| changes. When + // |powered| is true the adapter radio is powered, false means the adapter + // radio is off. virtual void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) {} - // Called when the discovering state of the adapter |adapter| changes, - // when |discovering| is true the adapter is seeking new devices, false - // means it is not. + // Called when the discoverability state of the adapter |adapter| changes. + // When |discoverable| is true the adapter is discoverable by other devices, + // false means the adapter is not discoverable. + virtual void AdapterDiscoverableChanged(BluetoothAdapter* adapter, + bool discoverable) {} + + // Called when the discovering state of the adapter |adapter| changes. When + // |discovering| is true the adapter is seeking new devices, false means it + // is not. virtual void AdapterDiscoveringChanged(BluetoothAdapter* adapter, bool discovering) {} // Called when a new device |device| is added to the adapter |adapter|, // either because it has been discovered or a connection made. |device| - // should not be cached, instead copy its address. + // should not be cached. Instead, copy its Bluetooth address. virtual void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) {} // Called when properties of the device |device| known to the adapter - // |adapter| change. |device| should not be cached, instead copy its - // address. + // |adapter| change. |device| should not be cached. Instead, copy its + // Bluetooth address. virtual void DeviceChanged(BluetoothAdapter* adapter, BluetoothDevice* device) {} // Called when the device |device| is removed from the adapter |adapter|, // either as a result of a discovered device being lost between discovering - // phases or pairing information deleted. |device| should not be cached. + // phases or pairing information deleted. |device| should not be + // cached. Instead, copy its Bluetooth address. virtual void DeviceRemoved(BluetoothAdapter* adapter, BluetoothDevice* device) {} }; - // The ErrorCallback is used for methods that can fail in which case it - // is called, in the success case the callback is simply not called. - typedef base::Callback<void()> ErrorCallback; - - // The BluetoothOutOfBandPairingDataCallback is used to return - // BluetoothOutOfBandPairingData to the caller. - typedef base::Callback<void(const BluetoothOutOfBandPairingData& data)> - BluetoothOutOfBandPairingDataCallback; - - // Adds and removes observers for events on this bluetooth adapter, - // if monitoring multiple adapters check the |adapter| parameter of - // observer methods to determine which adapter is issuing the event. + // The ErrorCallback is used for methods that can fail in which case it is + // called, in the success case the callback is simply not called. + typedef base::Closure ErrorCallback; + + // The InitCallback is used to trigger a callback after asynchronous + // initialization, if initialization is asynchronous on the platform. + typedef base::Callback<void()> InitCallback; + + // Returns a weak pointer to a new adapter. For platforms with asynchronous + // initialization, the returned adapter will run the |init_callback| once + // asynchronous initialization is complete. + // Caution: The returned pointer also transfers ownership of the adapter. The + // caller is expected to call |AddRef()| on the returned pointer, typically by + // storing it into a |scoped_refptr|. + static base::WeakPtr<BluetoothAdapter> CreateAdapter( + const InitCallback& init_callback); + + // Returns a weak pointer to an existing adapter for testing purposes only. + base::WeakPtr<BluetoothAdapter> GetWeakPtrForTesting(); + + // Adds and removes observers for events on this bluetooth adapter. If + // monitoring multiple adapters, check the |adapter| parameter of observer + // methods to determine which adapter is issuing the event. virtual void AddObserver(BluetoothAdapter::Observer* observer) = 0; virtual void RemoveObserver( BluetoothAdapter::Observer* observer) = 0; - // The address of this adapter. The address format is "XX:XX:XX:XX:XX:XX", + // The address of this adapter. The address format is "XX:XX:XX:XX:XX:XX", // where each XX is a hexadecimal number. virtual std::string GetAddress() const = 0; // The name of the adapter. virtual std::string GetName() const = 0; + // Set the human-readable name of the adapter to |name|. On success, + // |callback| will be called. On failure, |error_callback| will be called. + virtual void SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + // Indicates whether the adapter is initialized and ready to use. virtual bool IsInitialized() const = 0; - // Indicates whether the adapter is actually present on the system, for - // the default adapter this indicates whether any adapter is present. An - // adapter is only considered present if the address has been obtained. + // Indicates whether the adapter is actually present on the system. For the + // default adapter, this indicates whether any adapter is present. An adapter + // is only considered present if the address has been obtained. virtual bool IsPresent() const = 0; // Indicates whether the adapter radio is powered. virtual bool IsPowered() const = 0; - // Requests a change to the adapter radio power, setting |powered| to true - // will turn on the radio and false will turn it off. On success, callback - // will be called. On failure, |error_callback| will be called. + // Requests a change to the adapter radio power. Setting |powered| to true + // will turn on the radio and false will turn it off. On success, |callback| + // will be called. On failure, |error_callback| will be called. virtual void SetPowered(bool powered, const base::Closure& callback, const ErrorCallback& error_callback) = 0; + // Indicates whether the adapter radio is discoverable. + virtual bool IsDiscoverable() const = 0; + + // Requests that the adapter change its discoverability state. If + // |discoverable| is true, then it will be discoverable by other Bluetooth + // devices. On successly changing the adapter's discoverability, |callback| + // will be called. On failure, |error_callback| will be called. + virtual void SetDiscoverable(bool discoverable, + const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + // Indicates whether the adapter is currently discovering new devices. virtual bool IsDiscovering() const = 0; - // Requests that the adapter begin discovering new devices, code must - // always call this method if they require the adapter be in discovery - // and should not make it conditional on the value of IsDiscovering() - // as other adapter users may be making the same request. Code must also - // call StopDiscovering() when done. On success |callback| will be called, - // on failure |error_callback| will be called instead. + // Requests the adapter to start a new discovery session. On success, a new + // instance of BluetoothDiscoverySession will be returned to the caller via + // |callback| and the adapter will be discovering nearby Bluetooth devices. + // The returned BluetoothDiscoverySession is owned by the caller and it's the + // owner's responsibility to properly clean it up and stop the session when + // device discovery is no longer needed. // - // Since discovery may already be in progress when this method is called, - // callers should retrieve the current set of discovered devices by calling - // GetDevices() and checking for those with IsPaired() as false. - virtual void StartDiscovering(const base::Closure& callback, - const ErrorCallback& error_callback) = 0; - - // Requests that an earlier call to StartDiscovering() be cancelled; the - // adapter may not actually cease discovering devices if other callers - // have called StartDiscovering() and not yet called this method. On - // success |callback| will be called, on failure |error_callback| will be - // called instead. - virtual void StopDiscovering(const base::Closure& callback, - const ErrorCallback& error_callback) = 0; - - // Requests the list of devices from the adapter, all are returned - // including those currently connected and those paired. Use the - // returned device pointers to determine which they are. + // If clients desire device discovery to run, they should always call this + // method and never make it conditional on the value of IsDiscovering(), as + // another client might cause discovery to stop unexpectedly. Hence, clients + // should always obtain a BluetoothDiscoverySession and call + // BluetoothDiscoverySession::Stop when done. When this method gets called, + // device discovery may actually be in progress. Clients can call GetDevices() + // and check for those with IsPaired() as false to obtain the list of devices + // that have been discovered so far. Otherwise, clients can be notified of all + // new and lost devices by implementing the Observer methods "DeviceAdded" and + // "DeviceRemoved". + typedef base::Callback<void(scoped_ptr<BluetoothDiscoverySession>)> + DiscoverySessionCallback; + virtual void StartDiscoverySession(const DiscoverySessionCallback& callback, + const ErrorCallback& error_callback); + + // Requests the list of devices from the adapter. All devices are returned, + // including those currently connected and those paired. Use the returned + // device pointers to determine which they are. typedef std::vector<BluetoothDevice*> DeviceList; virtual DeviceList GetDevices(); typedef std::vector<const BluetoothDevice*> ConstDeviceList; virtual ConstDeviceList GetDevices() const; - // Returns a pointer to the device with the given address |address| or - // NULL if no such device is known. + // Returns a pointer to the device with the given address |address| or NULL if + // no such device is known. virtual BluetoothDevice* GetDevice(const std::string& address); - virtual const BluetoothDevice* GetDevice( - const std::string& address) const; + virtual const BluetoothDevice* GetDevice(const std::string& address) const; - // Requests the local Out Of Band pairing data. - virtual void ReadLocalOutOfBandPairingData( - const BluetoothOutOfBandPairingDataCallback& callback, - const ErrorCallback& error_callback) = 0; + // Possible priorities for AddPairingDelegate(), low is intended for + // permanent UI and high is intended for interactive UI or applications. + enum PairingDelegatePriority { + PAIRING_DELEGATE_PRIORITY_LOW, + PAIRING_DELEGATE_PRIORITY_HIGH + }; + + // Adds a default pairing delegate with priority |priority|. Method calls + // will be made on |pairing_delegate| for incoming pairing requests if the + // priority is higher than any other registered; or for those of the same + // priority, the first registered. + // + // |pairing_delegate| must not be freed without first calling + // RemovePairingDelegate(). + virtual void AddPairingDelegate( + BluetoothDevice::PairingDelegate* pairing_delegate, + PairingDelegatePriority priority); + + // Removes a previously added pairing delegate. + virtual void RemovePairingDelegate( + BluetoothDevice::PairingDelegate* pairing_delegate); + + // Returns the first registered pairing delegate with the highest priority, + // or NULL if no delegate is registered. Used to select the delegate for + // incoming pairing requests. + virtual BluetoothDevice::PairingDelegate* DefaultPairingDelegate(); + + // Creates an RFCOMM service on this adapter advertised with UUID |uuid|, + // listening on channel |channel|, which may be the constant |kChannelAuto| + // to automatically allocate one. |callback| will be called on success with a + // BluetoothSocket instance that is to be owned by the received. + // |error_callback| will be called on failure with a message indicating the + // cause. + typedef base::Callback<void(scoped_refptr<BluetoothSocket>)> + CreateServiceCallback; + typedef base::Callback<void(const std::string& message)> + CreateServiceErrorCallback; + static const int kChannelAuto; + virtual void CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) = 0; + + // Creates an L2CAP service on this adapter advertised with UUID |uuid|, + // listening on PSM |psm|, which may be the constant |kPsmAuto| to + // automatically allocate one. |callback| will be called on success with a + // BluetoothSocket instance that is to be owned by the received. + // |error_callback| will be called on failure with a message indicating the + // cause. + static const int kPsmAuto; + virtual void CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) = 0; protected: friend class base::RefCounted<BluetoothAdapter>; + friend class BluetoothDiscoverySession; BluetoothAdapter(); virtual ~BluetoothAdapter(); + // Internal methods for initiating and terminating device discovery sessions. + // An implementation of BluetoothAdapter keeps an internal reference count to + // make sure that the underlying controller is constantly searching for nearby + // devices and retrieving information from them as long as there are clients + // who have requested discovery. These methods behave in the following way: + // + // On a call to AddDiscoverySession: + // - If there is a pending request to the subsystem, queue this request to + // execute once the pending requests are done. + // - If the count is 0, issue a request to the subsystem to start + // device discovery. On success, increment the count to 1. + // - If the count is greater than 0, increment the count and return + // success. + // As long as the count is non-zero, the underlying controller will be + // discovering for devices. This means that Chrome will restart device + // scan and inquiry sessions if they ever end, unless these sessions + // terminate due to an unexpected reason. + // + // On a call to RemoveDiscoverySession: + // - If there is a pending request to the subsystem, queue this request to + // execute once the pending requests are done. + // - If the count is 0, return failure, as there is no active discovery + // session. + // - If the count is 1, issue a request to the subsystem to stop device + // discovery and decrement the count to 0 on success. + // - If the count is greater than 1, decrement the count and return + // success. + // + // These methods invoke |callback| for success and |error_callback| for + // failures. + virtual void AddDiscoverySession(const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + virtual void RemoveDiscoverySession(const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + + // Called by RemovePairingDelegate() in order to perform any class-specific + // internal functionality necessary to remove the pairing delegate, such as + // cleaning up ongoing pairings using it. + virtual void RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) = 0; + + // Success callback passed to AddDiscoverySession by StartDiscoverySession. + void OnStartDiscoverySession(const DiscoverySessionCallback& callback); + + // Marks all known DiscoverySession instances as inactive. Called by + // BluetoothAdapter in the event that the adapter unexpectedly stops + // discovering. This should be called by all platform implementations. + void MarkDiscoverySessionsAsInactive(); + + // Removes |discovery_session| from |discovery_sessions_|, if its in there. + // Called by DiscoverySession when an instance is destroyed or becomes + // inactive. + void DiscoverySessionBecameInactive( + BluetoothDiscoverySession* discovery_session); + // Devices paired with, connected to, discovered by, or visible to the - // adapter. The key is the Bluetooth address of the device and the value - // is the BluetoothDevice object whose lifetime is managed by the - // adapter instance. + // adapter. The key is the Bluetooth address of the device and the value is + // the BluetoothDevice object whose lifetime is managed by the adapter + // instance. typedef std::map<const std::string, BluetoothDevice*> DevicesMap; DevicesMap devices_; + + // Default pairing delegates registered with the adapter. + typedef std::pair<BluetoothDevice::PairingDelegate*, + PairingDelegatePriority> PairingDelegatePair; + std::list<PairingDelegatePair> pairing_delegates_; + + private: + // List of active DiscoverySession objects. This is used to notify sessions to + // become inactive in case of an unexpected change to the adapter discovery + // state. We keep raw pointers, with the invariant that a DiscoverySession + // will remove itself from this list when it gets destroyed or becomes + // inactive by calling DiscoverySessionBecameInactive(), hence no pointers to + // deallocated sessions are kept. + std::set<BluetoothDiscoverySession*> discovery_sessions_; + + // 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<BluetoothAdapter> weak_ptr_factory_; }; } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_chromeos.cc b/chromium/device/bluetooth/bluetooth_adapter_chromeos.cc index 319a6e99a63..c392af98fb3 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_chromeos.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_chromeos.cc @@ -9,25 +9,82 @@ #include "base/bind.h" #include "base/logging.h" #include "base/metrics/histogram.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" #include "base/sys_info.h" +#include "base/thread_task_runner_handle.h" #include "chromeos/dbus/bluetooth_adapter_client.h" +#include "chromeos/dbus/bluetooth_agent_manager_client.h" +#include "chromeos/dbus/bluetooth_agent_service_provider.h" #include "chromeos/dbus/bluetooth_device_client.h" #include "chromeos/dbus/bluetooth_input_client.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_device_chromeos.h" +#include "device/bluetooth/bluetooth_pairing_chromeos.h" +#include "device/bluetooth/bluetooth_socket_chromeos.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "third_party/cros_system_api/dbus/service_constants.h" using device::BluetoothAdapter; using device::BluetoothDevice; +using device::BluetoothSocket; +using device::BluetoothUUID; + +namespace { + +// The agent path is relatively meaningless since BlueZ only permits one to +// exist per D-Bus connection, it just has to be unique within Chromium. +const char kAgentPath[] = "/org/chromium/bluetooth_agent"; + +void OnUnregisterAgentError(const std::string& error_name, + const std::string& error_message) { + // It's okay if the agent didn't exist, it means we never saw an adapter. + if (error_name == bluetooth_agent_manager::kErrorDoesNotExist) + return; + + LOG(WARNING) << "Failed to unregister pairing agent: " + << error_name << ": " << error_message; +} + +} // namespace + +namespace device { + +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( + const InitCallback& init_callback) { + return chromeos::BluetoothAdapterChromeOS::CreateAdapter(); +} + +} namespace chromeos { +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapterChromeOS::CreateAdapter() { + BluetoothAdapterChromeOS* adapter = new BluetoothAdapterChromeOS(); + return adapter->weak_ptr_factory_.GetWeakPtr(); +} + BluetoothAdapterChromeOS::BluetoothAdapterChromeOS() - : weak_ptr_factory_(this) { + : num_discovery_sessions_(0), + discovery_request_pending_(false), + weak_ptr_factory_(this) { + ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); + socket_thread_ = device::BluetoothSocketThread::Get(); + DBusThreadManager::Get()->GetBluetoothAdapterClient()->AddObserver(this); DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this); DBusThreadManager::Get()->GetBluetoothInputClient()->AddObserver(this); + // Register the pairing agent. + dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); + agent_.reset(BluetoothAgentServiceProvider::Create( + system_bus, dbus::ObjectPath(kAgentPath), this)); + DCHECK(agent_.get()); + std::vector<dbus::ObjectPath> object_paths = DBusThreadManager::Get()->GetBluetoothAdapterClient()->GetAdapters(); @@ -41,6 +98,13 @@ BluetoothAdapterChromeOS::~BluetoothAdapterChromeOS() { DBusThreadManager::Get()->GetBluetoothAdapterClient()->RemoveObserver(this); DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this); DBusThreadManager::Get()->GetBluetoothInputClient()->RemoveObserver(this); + + VLOG(1) << "Unregistering pairing agent"; + DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> + UnregisterAgent( + dbus::ObjectPath(kAgentPath), + base::Bind(&base::DoNothing), + base::Bind(&OnUnregisterAgentError)); } void BluetoothAdapterChromeOS::AddObserver( @@ -64,7 +128,7 @@ std::string BluetoothAdapterChromeOS::GetAddress() const { GetProperties(object_path_); DCHECK(properties); - return properties->address.value(); + return BluetoothDevice::CanonicalizeAddress(properties->address.value()); } std::string BluetoothAdapterChromeOS::GetName() const { @@ -79,6 +143,21 @@ std::string BluetoothAdapterChromeOS::GetName() const { return properties->alias.value(); } +void BluetoothAdapterChromeOS::SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (!IsPresent()) + error_callback.Run(); + + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> + GetProperties(object_path_)->alias.Set( + name, + base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted, + weak_ptr_factory_.GetWeakPtr(), + callback, + error_callback)); +} + bool BluetoothAdapterChromeOS::IsInitialized() const { return true; } @@ -102,16 +181,19 @@ void BluetoothAdapterChromeOS::SetPowered( bool powered, const base::Closure& callback, const ErrorCallback& error_callback) { + if (!IsPresent()) + error_callback.Run(); + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_)->powered.Set( powered, - base::Bind(&BluetoothAdapterChromeOS::OnSetPowered, + base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted, weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); } -bool BluetoothAdapterChromeOS::IsDiscovering() const { +bool BluetoothAdapterChromeOS::IsDiscoverable() const { if (!IsPresent()) return false; @@ -119,45 +201,92 @@ bool BluetoothAdapterChromeOS::IsDiscovering() const { DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); - return properties->discovering.value(); + return properties->discoverable.value(); } -void BluetoothAdapterChromeOS::StartDiscovering( +void BluetoothAdapterChromeOS::SetDiscoverable( + bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) { - // BlueZ counts discovery sessions, and permits multiple sessions for a - // single connection, so issue a StartDiscovery() call for every use - // within Chromium for the right behavior. + if (!IsPresent()) + error_callback.Run(); + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> - StartDiscovery( - object_path_, - base::Bind(&BluetoothAdapterChromeOS::OnStartDiscovery, - weak_ptr_factory_.GetWeakPtr(), - callback), - base::Bind(&BluetoothAdapterChromeOS::OnStartDiscoveryError, + GetProperties(object_path_)->discoverable.Set( + discoverable, + base::Bind(&BluetoothAdapterChromeOS::OnSetDiscoverable, weak_ptr_factory_.GetWeakPtr(), + callback, error_callback)); } -void BluetoothAdapterChromeOS::StopDiscovering( - const base::Closure& callback, - const ErrorCallback& error_callback) { - // Inform BlueZ to stop one of our open discovery sessions. - DBusThreadManager::Get()->GetBluetoothAdapterClient()-> - StopDiscovery( - object_path_, - base::Bind(&BluetoothAdapterChromeOS::OnStopDiscovery, - weak_ptr_factory_.GetWeakPtr(), - callback), - base::Bind(&BluetoothAdapterChromeOS::OnStopDiscoveryError, - weak_ptr_factory_.GetWeakPtr(), - error_callback)); +bool BluetoothAdapterChromeOS::IsDiscovering() const { + if (!IsPresent()) + return false; + + BluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> + GetProperties(object_path_); + + return properties->discovering.value(); } -void BluetoothAdapterChromeOS::ReadLocalOutOfBandPairingData( - const BluetoothAdapter::BluetoothOutOfBandPairingDataCallback& callback, - const ErrorCallback& error_callback) { - error_callback.Run(); +void BluetoothAdapterChromeOS::CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Creating RFCOMM service: " + << uuid.canonical_value(); + scoped_refptr<BluetoothSocketChromeOS> socket = + BluetoothSocketChromeOS::CreateBluetoothSocket( + ui_task_runner_, + socket_thread_, + NULL, + net::NetLog::Source()); + socket->Listen(this, + BluetoothSocketChromeOS::kRfcomm, + uuid, + channel, + base::Bind(callback, socket), + error_callback); +} + +void BluetoothAdapterChromeOS::CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Creating L2CAP service: " + << uuid.canonical_value(); + scoped_refptr<BluetoothSocketChromeOS> socket = + BluetoothSocketChromeOS::CreateBluetoothSocket( + ui_task_runner_, + socket_thread_, + NULL, + net::NetLog::Source()); + socket->Listen(this, + BluetoothSocketChromeOS::kL2cap, + uuid, + psm, + base::Bind(callback, socket), + error_callback); +} + +void BluetoothAdapterChromeOS::RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) { + // Before removing a pairing delegate make sure that there aren't any devices + // currently using it; if there are, clear the pairing context which will + // make any responses no-ops. + for (DevicesMap::iterator iter = devices_.begin(); + iter != devices_.end(); ++iter) { + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(iter->second); + + BluetoothPairingChromeOS* pairing = device_chromeos->GetPairing(); + if (pairing && pairing->GetPairingDelegate() == pairing_delegate) + device_chromeos->EndPairing(); + } } void BluetoothAdapterChromeOS::AdapterAdded( @@ -185,6 +314,8 @@ void BluetoothAdapterChromeOS::AdapterPropertyChanged( if (property_name == properties->powered.name()) PoweredChanged(properties->powered.value()); + else if (property_name == properties->discoverable.name()) + DiscoverableChanged(properties->discoverable.value()); else if (property_name == properties->discovering.name()) DiscoveringChanged(properties->discovering.value()); } @@ -198,7 +329,10 @@ void BluetoothAdapterChromeOS::DeviceAdded( return; BluetoothDeviceChromeOS* device_chromeos = - new BluetoothDeviceChromeOS(this, object_path); + new BluetoothDeviceChromeOS(this, + object_path, + ui_task_runner_, + socket_thread_); DCHECK(devices_.find(device_chromeos->GetAddress()) == devices_.end()); devices_[device_chromeos->GetAddress()] = device_chromeos; @@ -241,11 +375,27 @@ void BluetoothAdapterChromeOS::DevicePropertyChanged( property_name == properties->paired.name() || property_name == properties->trusted.name() || property_name == properties->connected.name() || - property_name == properties->uuids.name()) + property_name == properties->uuids.name() || + property_name == properties->rssi.name() || + property_name == properties->connection_rssi.name() || + property_name == properties->connection_tx_power.name()) NotifyDeviceChanged(device_chromeos); + // When a device becomes paired, mark it as trusted so that the user does + // not need to approve every incoming connection + if (property_name == properties->paired.name() && + properties->paired.value() && !properties->trusted.value()) + device_chromeos->SetTrusted(); + // UMA connection counting if (property_name == properties->connected.name()) { + // PlayStation joystick tries to reconnect after disconnection from USB. + // If it is still not trusted, set it, so it becomes available on the + // list of known devices. + if (properties->connected.value() && device_chromeos->IsTrustable() && + !properties->trusted.value()) + device_chromeos->SetTrusted(); + int count = 0; for (DevicesMap::iterator iter = devices_.begin(); @@ -277,6 +427,175 @@ void BluetoothAdapterChromeOS::InputPropertyChanged( NotifyDeviceChanged(device_chromeos); } +void BluetoothAdapterChromeOS::Released() { + DCHECK(agent_.get()); + VLOG(1) << "Release"; + + // Called after we unregister the pairing agent, e.g. when changing I/O + // capabilities. Nothing much to be done right now. +} + +void BluetoothAdapterChromeOS::RequestPinCode( + const dbus::ObjectPath& device_path, + const PinCodeCallback& callback) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": RequestPinCode"; + + BluetoothPairingChromeOS* pairing = GetPairing(device_path); + if (!pairing) { + callback.Run(REJECTED, ""); + return; + } + + pairing->RequestPinCode(callback); +} + +void BluetoothAdapterChromeOS::DisplayPinCode( + const dbus::ObjectPath& device_path, + const std::string& pincode) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": DisplayPinCode: " << pincode; + + BluetoothPairingChromeOS* pairing = GetPairing(device_path); + if (!pairing) + return; + + pairing->DisplayPinCode(pincode); +} + +void BluetoothAdapterChromeOS::RequestPasskey( + const dbus::ObjectPath& device_path, + const PasskeyCallback& callback) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": RequestPasskey"; + + BluetoothPairingChromeOS* pairing = GetPairing(device_path); + if (!pairing) { + callback.Run(REJECTED, 0); + return; + } + + pairing->RequestPasskey(callback); +} + +void BluetoothAdapterChromeOS::DisplayPasskey( + const dbus::ObjectPath& device_path, + uint32 passkey, + uint16 entered) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": DisplayPasskey: " << passkey + << " (" << entered << " entered)"; + + BluetoothPairingChromeOS* pairing = GetPairing(device_path); + if (!pairing) + return; + + if (entered == 0) + pairing->DisplayPasskey(passkey); + + pairing->KeysEntered(entered); +} + +void BluetoothAdapterChromeOS::RequestConfirmation( + const dbus::ObjectPath& device_path, + uint32 passkey, + const ConfirmationCallback& callback) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": RequestConfirmation: " << passkey; + + BluetoothPairingChromeOS* pairing = GetPairing(device_path); + if (!pairing) { + callback.Run(REJECTED); + return; + } + + pairing->RequestConfirmation(passkey, callback); +} + +void BluetoothAdapterChromeOS::RequestAuthorization( + const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": RequestAuthorization"; + + BluetoothPairingChromeOS* pairing = GetPairing(device_path); + if (!pairing) { + callback.Run(REJECTED); + return; + } + + pairing->RequestAuthorization(callback); +} + +void BluetoothAdapterChromeOS::AuthorizeService( + const dbus::ObjectPath& device_path, + const std::string& uuid, + const ConfirmationCallback& callback) { + DCHECK(agent_.get()); + VLOG(1) << device_path.value() << ": AuthorizeService: " << uuid; + + BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(device_path); + if (!device_chromeos) { + callback.Run(CANCELLED); + return; + } + + // We always set paired devices to Trusted, so the only reason that this + // method call would ever be called is in the case of a race condition where + // our "Set('Trusted', true)" method call is still pending in the Bluetooth + // daemon because it's busy handling the incoming connection. + if (device_chromeos->IsPaired()) { + callback.Run(SUCCESS); + return; + } + + // TODO(keybuk): reject service authorizations when not paired, determine + // whether this is acceptable long-term. + LOG(WARNING) << "Rejecting service connection from unpaired device " + << device_chromeos->GetAddress() << " for UUID " << uuid; + callback.Run(REJECTED); +} + +void BluetoothAdapterChromeOS::Cancel() { + DCHECK(agent_.get()); + VLOG(1) << "Cancel"; +} + +void BluetoothAdapterChromeOS::OnRegisterAgent() { + VLOG(1) << "Pairing agent registered, requesting to be made default"; + + DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> + RequestDefaultAgent( + dbus::ObjectPath(kAgentPath), + base::Bind(&BluetoothAdapterChromeOS::OnRequestDefaultAgent, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&BluetoothAdapterChromeOS::OnRequestDefaultAgentError, + weak_ptr_factory_.GetWeakPtr())); + +} + +void BluetoothAdapterChromeOS::OnRegisterAgentError( + const std::string& error_name, + const std::string& error_message) { + // Our agent being already registered isn't an error. + if (error_name == bluetooth_agent_manager::kErrorAlreadyExists) + return; + + LOG(WARNING) << ": Failed to register pairing agent: " + << error_name << ": " << error_message; +} + +void BluetoothAdapterChromeOS::OnRequestDefaultAgent() { + VLOG(1) << "Pairing agent now default"; +} + +void BluetoothAdapterChromeOS::OnRequestDefaultAgentError( + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << ": Failed to make pairing agent default: " + << error_name << ": " << error_message; +} + BluetoothDeviceChromeOS* BluetoothAdapterChromeOS::GetDeviceWithPath( const dbus::ObjectPath& object_path) { @@ -291,13 +610,46 @@ BluetoothAdapterChromeOS::GetDeviceWithPath( return NULL; } +BluetoothPairingChromeOS* BluetoothAdapterChromeOS::GetPairing( + const dbus::ObjectPath& object_path) +{ + BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(object_path); + if (!device_chromeos) { + LOG(WARNING) << "Pairing Agent request for unknown device: " + << object_path.value(); + return NULL; + } + + BluetoothPairingChromeOS* pairing = device_chromeos->GetPairing(); + if (pairing) + return pairing; + + // The device doesn't have its own pairing context, so this is an incoming + // pairing request that should use our best default delegate (if we have one). + BluetoothDevice::PairingDelegate* pairing_delegate = DefaultPairingDelegate(); + if (!pairing_delegate) + return NULL; + + return device_chromeos->BeginPairing(pairing_delegate); +} + void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { DCHECK(!IsPresent()); object_path_ = object_path; VLOG(1) << object_path_.value() << ": using adapter."; - SetAdapterName(); + VLOG(1) << "Registering pairing agent"; + DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> + RegisterAgent( + dbus::ObjectPath(kAgentPath), + bluetooth_agent_manager::kKeyboardDisplayCapability, + base::Bind(&BluetoothAdapterChromeOS::OnRegisterAgent, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&BluetoothAdapterChromeOS::OnRegisterAgentError, + weak_ptr_factory_.GetWeakPtr())); + + SetDefaultAdapterName(); BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> @@ -307,6 +659,8 @@ void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { if (properties->powered.value()) PoweredChanged(true); + if (properties->discoverable.value()) + DiscoverableChanged(true); if (properties->discovering.value()) DiscoveringChanged(true); @@ -316,17 +670,11 @@ void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { for (std::vector<dbus::ObjectPath>::iterator iter = device_paths.begin(); iter != device_paths.end(); ++iter) { - BluetoothDeviceChromeOS* device_chromeos = - new BluetoothDeviceChromeOS(this, *iter); - - devices_[device_chromeos->GetAddress()] = device_chromeos; - - FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, - DeviceAdded(this, device_chromeos)); + DeviceAdded(*iter); } } -void BluetoothAdapterChromeOS::SetAdapterName() { +void BluetoothAdapterChromeOS::SetDefaultAdapterName() { std::string board = base::SysInfo::GetLsbReleaseBoard(); std::string alias; if (board.substr(0, 6) == "stumpy") { @@ -337,16 +685,7 @@ void BluetoothAdapterChromeOS::SetAdapterName() { alias = "Chromebook"; } - DBusThreadManager::Get()->GetBluetoothAdapterClient()-> - GetProperties(object_path_)->alias.Set( - alias, - base::Bind(&BluetoothAdapterChromeOS::OnSetAlias, - weak_ptr_factory_.GetWeakPtr())); -} - -void BluetoothAdapterChromeOS::OnSetAlias(bool success) { - LOG_IF(WARNING, !success) << object_path_.value() - << ": Failed to set adapter alias"; + SetName(alias, base::Bind(&base::DoNothing), base::Bind(&base::DoNothing)); } void BluetoothAdapterChromeOS::RemoveAdapter() { @@ -361,6 +700,8 @@ void BluetoothAdapterChromeOS::RemoveAdapter() { if (properties->powered.value()) PoweredChanged(false); + if (properties->discoverable.value()) + DiscoverableChanged(false); if (properties->discovering.value()) DiscoveringChanged(false); @@ -384,8 +725,22 @@ void BluetoothAdapterChromeOS::PoweredChanged(bool powered) { AdapterPoweredChanged(this, powered)); } +void BluetoothAdapterChromeOS::DiscoverableChanged(bool discoverable) { + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + AdapterDiscoverableChanged(this, discoverable)); +} + void BluetoothAdapterChromeOS::DiscoveringChanged( bool discovering) { + // If the adapter stopped discovery due to a reason other than a request by + // us, reset the count to 0. + VLOG(1) << "Discovering changed: " << discovering; + if (!discovering && !discovery_request_pending_ + && num_discovery_sessions_ > 0) { + VLOG(1) << "Marking sessions as inactive."; + num_discovery_sessions_ = 0; + MarkDiscoverySessionsAsInactive(); + } FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterDiscoveringChanged(this, discovering)); } @@ -403,30 +758,169 @@ void BluetoothAdapterChromeOS::NotifyDeviceChanged( DeviceChanged(this, device)); } -void BluetoothAdapterChromeOS::OnSetPowered(const base::Closure& callback, - const ErrorCallback& error_callback, - bool success) { +void BluetoothAdapterChromeOS::OnSetDiscoverable( + const base::Closure& callback, + const ErrorCallback& error_callback, + bool success) { + // Set the discoverable_timeout property to zero so the adapter remains + // discoverable forever. + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> + GetProperties(object_path_)->discoverable_timeout.Set( + 0, + base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted, + weak_ptr_factory_.GetWeakPtr(), + callback, + error_callback)); +} + +void BluetoothAdapterChromeOS::OnPropertyChangeCompleted( + const base::Closure& callback, + const ErrorCallback& error_callback, + bool success) { if (success) callback.Run(); else error_callback.Run(); } +void BluetoothAdapterChromeOS::AddDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << __func__; + if (discovery_request_pending_) { + // The pending request is either to stop a previous session or to start a + // new one. Either way, queue this one. + DCHECK(num_discovery_sessions_ == 1 || num_discovery_sessions_ == 0); + VLOG(1) << "Pending request to start/stop device discovery. Queueing " + << "request to start a new discovery session."; + discovery_request_queue_.push(std::make_pair(callback, error_callback)); + return; + } + + // The adapter is already discovering. + if (num_discovery_sessions_ > 0) { + DCHECK(IsDiscovering()); + DCHECK(!discovery_request_pending_); + num_discovery_sessions_++; + callback.Run(); + return; + } + + // There are no active discovery sessions. + DCHECK(num_discovery_sessions_ == 0); + + // This is the first request to start device discovery. + discovery_request_pending_ = true; + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> + StartDiscovery( + object_path_, + base::Bind(&BluetoothAdapterChromeOS::OnStartDiscovery, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothAdapterChromeOS::OnStartDiscoveryError, + weak_ptr_factory_.GetWeakPtr(), + callback, + error_callback)); +} + +void BluetoothAdapterChromeOS::RemoveDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << __func__; + // There are active sessions other than the one currently being removed. + if (num_discovery_sessions_ > 1) { + DCHECK(IsDiscovering()); + DCHECK(!discovery_request_pending_); + num_discovery_sessions_--; + callback.Run(); + return; + } + + // If there is a pending request to BlueZ, then queue this request. + if (discovery_request_pending_) { + VLOG(1) << "Pending request to start/stop device discovery. Queueing " + << "request to stop discovery session."; + error_callback.Run(); + return; + } + + // There are no active sessions. Return error. + if (num_discovery_sessions_ == 0) { + // TODO(armansito): This should never happen once we have the + // DiscoverySession API. Replace this case with an assert once it's + // the deprecated methods have been removed. (See crbug.com/3445008). + VLOG(1) << "No active discovery sessions. Returning error."; + error_callback.Run(); + return; + } + + // There is exactly one active discovery session. Request BlueZ to stop + // discovery. + DCHECK(num_discovery_sessions_ == 1); + discovery_request_pending_ = true; + DBusThreadManager::Get()->GetBluetoothAdapterClient()-> + StopDiscovery( + object_path_, + base::Bind(&BluetoothAdapterChromeOS::OnStopDiscovery, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothAdapterChromeOS::OnStopDiscoveryError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + void BluetoothAdapterChromeOS::OnStartDiscovery(const base::Closure& callback) { + // Report success on the original request and increment the count. + VLOG(1) << __func__; + DCHECK(discovery_request_pending_); + DCHECK(num_discovery_sessions_ == 0); + discovery_request_pending_ = false; + num_discovery_sessions_++; callback.Run(); + + // Try to add a new discovery session for each queued request. + ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::OnStartDiscoveryError( + const base::Closure& callback, const ErrorCallback& error_callback, const std::string& error_name, const std::string& error_message) { LOG(WARNING) << object_path_.value() << ": Failed to start discovery: " << error_name << ": " << error_message; - error_callback.Run(); + + // Failed to start discovery. This can only happen if the count is at 0. + DCHECK(num_discovery_sessions_ == 0); + DCHECK(discovery_request_pending_); + discovery_request_pending_ = false; + + // Discovery request may fail if discovery was previously initiated by Chrome, + // but the session were invalidated due to the discovery state unexpectedly + // changing to false and then back to true. In this case, report success. + if (error_name == bluetooth_device::kErrorInProgress && IsDiscovering()) { + VLOG(1) << "Discovery previously initiated. Reporting success."; + num_discovery_sessions_++; + callback.Run(); + } else { + error_callback.Run(); + } + + // Try to add a new discovery session for each queued request. + ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::OnStopDiscovery(const base::Closure& callback) { + // Report success on the original request and decrement the count. + VLOG(1) << __func__; + DCHECK(discovery_request_pending_); + DCHECK(num_discovery_sessions_ == 1); + discovery_request_pending_ = false; + num_discovery_sessions_--; callback.Run(); + + // Try to add a new discovery session for each queued request. + ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::OnStopDiscoveryError( @@ -435,7 +929,30 @@ void BluetoothAdapterChromeOS::OnStopDiscoveryError( const std::string& error_message) { LOG(WARNING) << object_path_.value() << ": Failed to stop discovery: " << error_name << ": " << error_message; + + // Failed to stop discovery. This can only happen if the count is at 1. + DCHECK(discovery_request_pending_); + DCHECK(num_discovery_sessions_ == 1); + discovery_request_pending_ = false; error_callback.Run(); + + // Try to add a new discovery session for each queued request. + ProcessQueuedDiscoveryRequests(); +} + +void BluetoothAdapterChromeOS::ProcessQueuedDiscoveryRequests() { + while (!discovery_request_queue_.empty()) { + VLOG(1) << "Process queued discovery request."; + DiscoveryCallbackPair callbacks = discovery_request_queue_.front(); + discovery_request_queue_.pop(); + AddDiscoverySession(callbacks.first, callbacks.second); + + // If the queued request resulted in a pending call, then let it + // asynchonously process the remaining queued requests once the pending + // call returns. + if (discovery_request_pending_) + return; + } } } // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_adapter_chromeos.h b/chromium/device/bluetooth/bluetooth_adapter_chromeos.h index 74beb3521db..4225744e029 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_chromeos.h +++ b/chromium/device/bluetooth/bluetooth_adapter_chromeos.h @@ -5,41 +5,50 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_CHROMEOS_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_CHROMEOS_H_ +#include <queue> #include <string> #include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" #include "chromeos/dbus/bluetooth_adapter_client.h" +#include "chromeos/dbus/bluetooth_agent_service_provider.h" #include "chromeos/dbus/bluetooth_device_client.h" #include "chromeos/dbus/bluetooth_input_client.h" #include "dbus/object_path.h" #include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" namespace device { - -class BluetoothAdapterFactory; - +class BluetoothSocketThread; } // namespace device namespace chromeos { class BluetoothChromeOSTest; class BluetoothDeviceChromeOS; +class BluetoothPairingChromeOS; // The BluetoothAdapterChromeOS class implements BluetoothAdapter for the // Chrome OS platform. class BluetoothAdapterChromeOS : public device::BluetoothAdapter, - private chromeos::BluetoothAdapterClient::Observer, - private chromeos::BluetoothDeviceClient::Observer, - private chromeos::BluetoothInputClient::Observer { + public chromeos::BluetoothAdapterClient::Observer, + public chromeos::BluetoothDeviceClient::Observer, + public chromeos::BluetoothInputClient::Observer, + public chromeos::BluetoothAgentServiceProvider::Delegate { public: - // BluetoothAdapter override + static base::WeakPtr<BluetoothAdapter> CreateAdapter(); + + // BluetoothAdapter: virtual void AddObserver( device::BluetoothAdapter::Observer* observer) OVERRIDE; virtual void RemoveObserver( device::BluetoothAdapter::Observer* observer) OVERRIDE; virtual std::string GetAddress() const OVERRIDE; virtual std::string GetName() const OVERRIDE; + virtual void SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; virtual bool IsInitialized() const OVERRIDE; virtual bool IsPresent() const OVERRIDE; virtual bool IsPowered() const OVERRIDE; @@ -47,24 +56,48 @@ class BluetoothAdapterChromeOS bool powered, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - virtual bool IsDiscovering() const OVERRIDE; - virtual void StartDiscovering( - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void StopDiscovering( + virtual bool IsDiscoverable() const OVERRIDE; + virtual void SetDiscoverable( + bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - virtual void ReadLocalOutOfBandPairingData( - const device::BluetoothAdapter::BluetoothOutOfBandPairingDataCallback& - callback, - const ErrorCallback& error_callback) OVERRIDE; + virtual bool IsDiscovering() const OVERRIDE; + virtual void CreateRfcommService( + const device::BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE; + virtual void CreateL2capService( + const device::BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE; + + // Locates the device object by object path (the devices map and + // BluetoothDevice methods are by address). + BluetoothDeviceChromeOS* GetDeviceWithPath( + const dbus::ObjectPath& object_path); + + // Announce to observers a change in device state that is not reflected by + // its D-Bus properties. + void NotifyDeviceChanged(BluetoothDeviceChromeOS* device); + + // Returns the object path of the adapter. + const dbus::ObjectPath& object_path() const { return object_path_; } + + protected: + // BluetoothAdapter: + virtual void RemovePairingDelegateInternal( + device::BluetoothDevice::PairingDelegate* pairing_delegate) OVERRIDE; private: - friend class device::BluetoothAdapterFactory; friend class BluetoothChromeOSTest; - friend class BluetoothDeviceChromeOS; - friend class BluetoothProfileChromeOS; - friend class BluetoothProfileChromeOSTest; + + // typedef for callback parameters that are passed to AddDiscoverySession + // and RemoveDiscoverySession. This is used to queue incoming requests while + // a call to BlueZ is pending. + typedef std::pair<base::Closure, ErrorCallback> DiscoveryCallbackPair; + typedef std::queue<DiscoveryCallbackPair> DiscoveryCallbackQueue; BluetoothAdapterChromeOS(); virtual ~BluetoothAdapterChromeOS(); @@ -86,19 +119,53 @@ class BluetoothAdapterChromeOS virtual void InputPropertyChanged(const dbus::ObjectPath& object_path, const std::string& property_name) OVERRIDE; - // Internal method used to locate the device object by object path - // (the devices map and BluetoothDevice methods are by address) - BluetoothDeviceChromeOS* GetDeviceWithPath( - const dbus::ObjectPath& object_path); + // BluetoothAgentServiceProvider::Delegate override. + virtual void Released() OVERRIDE; + virtual void RequestPinCode(const dbus::ObjectPath& device_path, + const PinCodeCallback& callback) OVERRIDE; + virtual void DisplayPinCode(const dbus::ObjectPath& device_path, + const std::string& pincode) OVERRIDE; + virtual void RequestPasskey(const dbus::ObjectPath& device_path, + const PasskeyCallback& callback) OVERRIDE; + virtual void DisplayPasskey(const dbus::ObjectPath& device_path, + uint32 passkey, uint16 entered) OVERRIDE; + virtual void RequestConfirmation(const dbus::ObjectPath& device_path, + uint32 passkey, + const ConfirmationCallback& callback) + OVERRIDE; + virtual void RequestAuthorization(const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) + OVERRIDE; + virtual void AuthorizeService(const dbus::ObjectPath& device_path, + const std::string& uuid, + const ConfirmationCallback& callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + + // Called by dbus:: on completion of the D-Bus method call to register the + // pairing agent. + void OnRegisterAgent(); + void OnRegisterAgentError(const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the D-Bus method call to request that + // the pairing agent be made the default. + void OnRequestDefaultAgent(); + void OnRequestDefaultAgentError(const std::string& error_name, + const std::string& error_message); + + // Internal method to obtain a BluetoothPairingChromeOS object for the device + // with path |object_path|. Returns the existing pairing object if the device + // already has one (usually an outgoing connection in progress) or a new + // pairing object with the default pairing delegate if not. If no default + // pairing object exists, NULL will be returned. + BluetoothPairingChromeOS* GetPairing(const dbus::ObjectPath& object_path); // Set the tracked adapter to the one in |object_path|, this object will // subsequently operate on that adapter until it is removed. void SetAdapter(const dbus::ObjectPath& object_path); - // Set the adapter name to one chosen from the system information, and method - // called by dbus:: on completion of the alias property change. - void SetAdapterName(); - void OnSetAlias(bool success); + // Set the adapter name to one chosen from the system information. + void SetDefaultAdapterName(); // Remove the currently tracked adapter. IsPresent() will return false after // this is called. @@ -106,21 +173,32 @@ class BluetoothAdapterChromeOS // Announce to observers a change in the adapter state. void PoweredChanged(bool powered); + void DiscoverableChanged(bool discoverable); void DiscoveringChanged(bool discovering); void PresentChanged(bool present); - // Announce to observers a change in device state that is not reflected by - // its D-Bus properties. - void NotifyDeviceChanged(BluetoothDeviceChromeOS* device); + // Called by dbus:: on completion of the discoverable property change. + void OnSetDiscoverable(const base::Closure& callback, + const ErrorCallback& error_callback, + bool success); - // Called by dbus:: on completion of the powered property change. - void OnSetPowered(const base::Closure& callback, - const ErrorCallback& error_callback, - bool success); + // Called by dbus:: on completion of an adapter property change. + void OnPropertyChangeCompleted(const base::Closure& callback, + const ErrorCallback& error_callback, + bool success); + + // BluetoothAdapter: + virtual void AddDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void RemoveDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; // Called by dbus:: on completion of the D-Bus method call to start discovery. void OnStartDiscovery(const base::Closure& callback); - void OnStartDiscoveryError(const ErrorCallback& error_callback, + void OnStartDiscoveryError(const base::Closure& callback, + const ErrorCallback& error_callback, const std::string& error_name, const std::string& error_message); @@ -130,12 +208,43 @@ class BluetoothAdapterChromeOS const std::string& error_name, const std::string& error_message); + // Processes the queued discovery requests. For each DiscoveryCallbackPair in + // the queue, this method will try to add a new discovery session. This method + // is called whenever a pending D-Bus call to start or stop discovery has + // ended (with either success or failure). + void ProcessQueuedDiscoveryRequests(); + + // Number of discovery sessions that have been added. + int num_discovery_sessions_; + + // True, if there is a pending request to start or stop discovery. + bool discovery_request_pending_; + + // List of queued requests to add new discovery sessions. While there is a + // pending request to BlueZ to start or stop discovery, many requests from + // within Chrome to start or stop discovery sessions may occur. We only + // queue requests to add new sessions to be processed later. All requests to + // remove a session while a call is pending immediately return failure. Note + // that since BlueZ keeps its own reference count of applications that have + // requested discovery, dropping our count to 0 won't necessarily result in + // the controller actually stopping discovery if, for example, an application + // other than Chrome, such as bt_console, was also used to start discovery. + DiscoveryCallbackQueue discovery_request_queue_; + // Object path of the adapter we track. dbus::ObjectPath object_path_; // List of observers interested in event notifications from us. ObserverList<device::BluetoothAdapter::Observer> observers_; + // Instance of the D-Bus agent object used for pairing, initialized with + // our own class as its delegate. + scoped_ptr<BluetoothAgentServiceProvider> agent_; + + // UI thread task runner and socket thread object used to create sockets. + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + scoped_refptr<device::BluetoothSocketThread> socket_thread_; + // 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<BluetoothAdapterChromeOS> weak_ptr_factory_; diff --git a/chromium/device/bluetooth/bluetooth_adapter_factory.cc b/chromium/device/bluetooth/bluetooth_adapter_factory.cc index c15d26a514b..33950531312 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_factory.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_factory.cc @@ -12,27 +12,22 @@ #include "base/memory/weak_ptr.h" #include "device/bluetooth/bluetooth_adapter.h" -#if defined(OS_CHROMEOS) -#include "device/bluetooth/bluetooth_adapter_chromeos.h" -#elif defined(OS_WIN) -#include "device/bluetooth/bluetooth_adapter_win.h" -#elif defined(OS_MACOSX) +#if defined(OS_MACOSX) #include "base/mac/mac_util.h" -#include "device/bluetooth/bluetooth_adapter_mac.h" #endif -namespace { +namespace device { -using device::BluetoothAdapter; -using device::BluetoothAdapterFactory; +namespace { -// Shared default adapter instance, we don't want to keep this class around -// if nobody is using it so use a WeakPtr and create the object when needed; -// since Google C++ Style (and clang's static analyzer) forbids us having -// exit-time destructors we use a leaky lazy instance for it. -base::LazyInstance<base::WeakPtr<device::BluetoothAdapter> >::Leaky - default_adapter = LAZY_INSTANCE_INITIALIZER; +// Shared default adapter instance. We don't want to keep this class around +// if nobody is using it, so use a WeakPtr and create the object when needed. +// Since Google C++ Style (and clang's static analyzer) forbids us having +// exit-time destructors, we use a leaky lazy instance for it. +base::LazyInstance<base::WeakPtr<BluetoothAdapter> >::Leaky default_adapter = + LAZY_INSTANCE_INITIALIZER; +#if defined(OS_WIN) typedef std::vector<BluetoothAdapterFactory::AdapterCallback> AdapterCallbackList; @@ -42,9 +37,8 @@ typedef std::vector<BluetoothAdapterFactory::AdapterCallback> base::LazyInstance<AdapterCallbackList> adapter_callbacks = LAZY_INSTANCE_INITIALIZER; -#if defined(OS_WIN) void RunAdapterCallbacks() { - CHECK(default_adapter.Get().get()); + DCHECK(default_adapter.Get()); scoped_refptr<BluetoothAdapter> adapter(default_adapter.Get().get()); for (std::vector<BluetoothAdapterFactory::AdapterCallback>::const_iterator iter = adapter_callbacks.Get().begin(); @@ -58,49 +52,57 @@ void RunAdapterCallbacks() { } // namespace -namespace device { - // static bool BluetoothAdapterFactory::IsBluetoothAdapterAvailable() { -#if defined(OS_CHROMEOS) - return true; -#elif defined(OS_WIN) + // SetAdapterForTesting() may be used to provide a test or mock adapter + // instance even on platforms that would otherwise not support it. + if (default_adapter.Get()) + return true; +#if defined(OS_CHROMEOS) || defined(OS_WIN) return true; #elif defined(OS_MACOSX) return base::mac::IsOSLionOrLater(); -#endif +#else return false; +#endif } // static void BluetoothAdapterFactory::GetAdapter(const AdapterCallback& callback) { - if (!default_adapter.Get().get()) { -#if defined(OS_CHROMEOS) - chromeos::BluetoothAdapterChromeOS* new_adapter = - new chromeos::BluetoothAdapterChromeOS(); - default_adapter.Get() = new_adapter->weak_ptr_factory_.GetWeakPtr(); -#elif defined(OS_WIN) - BluetoothAdapterWin* new_adapter = new BluetoothAdapterWin( - base::Bind(&RunAdapterCallbacks)); - new_adapter->Init(); - default_adapter.Get() = new_adapter->weak_ptr_factory_.GetWeakPtr(); -#elif defined(OS_MACOSX) - BluetoothAdapterMac* new_adapter = new BluetoothAdapterMac(); - new_adapter->Init(); - default_adapter.Get() = new_adapter->weak_ptr_factory_.GetWeakPtr(); -#endif + DCHECK(IsBluetoothAdapterAvailable()); + +#if defined(OS_WIN) + if (!default_adapter.Get()) { + default_adapter.Get() = + BluetoothAdapter::CreateAdapter(base::Bind(&RunAdapterCallbacks)); + DCHECK(!default_adapter.Get()->IsInitialized()); } - if (default_adapter.Get()->IsInitialized()) { - callback.Run(scoped_refptr<BluetoothAdapter>(default_adapter.Get().get())); - } else { + if (!default_adapter.Get()->IsInitialized()) adapter_callbacks.Get().push_back(callback); +#else // !defined(OS_WIN) + if (!default_adapter.Get()) { + default_adapter.Get() = + BluetoothAdapter::CreateAdapter(BluetoothAdapter::InitCallback()); } + + DCHECK(default_adapter.Get()->IsInitialized()); +#endif // defined(OS_WIN) + + if (default_adapter.Get()->IsInitialized()) + callback.Run(scoped_refptr<BluetoothAdapter>(default_adapter.Get().get())); + +} + +// static +void BluetoothAdapterFactory::SetAdapterForTesting( + scoped_refptr<BluetoothAdapter> adapter) { + default_adapter.Get() = adapter->GetWeakPtrForTesting(); } // static -scoped_refptr<BluetoothAdapter> BluetoothAdapterFactory::MaybeGetAdapter() { - return scoped_refptr<BluetoothAdapter>(default_adapter.Get().get()); +bool BluetoothAdapterFactory::HasSharedInstanceForTesting() { + return default_adapter.Get(); } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_factory.h b/chromium/device/bluetooth/bluetooth_adapter_factory.h index 3b6a2c27ea5..0e61a2c4231 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_factory.h +++ b/chromium/device/bluetooth/bluetooth_adapter_factory.h @@ -5,17 +5,14 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_FACTORY_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_FACTORY_H_ -#include <string> - #include "base/callback.h" #include "base/memory/ref_counted.h" #include "device/bluetooth/bluetooth_adapter.h" namespace device { -// BluetoothAdapterFactory is a class that contains static methods, which -// instantiate either a specific Bluetooth adapter, or the generic "default -// adapter" which may change depending on availability. +// A factory class for building a Bluetooth adapter on platforms where Bluetooth +// is available. class BluetoothAdapterFactory { public: typedef base::Callback<void(scoped_refptr<BluetoothAdapter> adapter)> @@ -31,10 +28,14 @@ class BluetoothAdapterFactory { // use. static void GetAdapter(const AdapterCallback& callback); - // Returns the shared instance of the adapter that has already been created, - // but may or may not have been initialized. - // It returns NULL if no adapter has been created at the time. - static scoped_refptr<BluetoothAdapter> MaybeGetAdapter(); + // Sets the shared instance of the default adapter for testing purposes only, + // no reference is retained after completion of the call, removing the last + // reference will reset the factory. + static void SetAdapterForTesting(scoped_refptr<BluetoothAdapter> adapter); + + // Returns true iff the implementation has a (non-NULL) shared instance of the + // adapter. Exposed for testing. + static bool HasSharedInstanceForTesting(); }; } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac.h b/chromium/device/bluetooth/bluetooth_adapter_mac.h index 7054acf5d0e..7040932db28 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac.h +++ b/chromium/device/bluetooth/bluetooth_adapter_mac.h @@ -11,24 +11,16 @@ #include <vector> #include "base/containers/hash_tables.h" +#include "base/mac/scoped_nsobject.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_discovery_manager_mac.h" -#ifdef __OBJC__ -@class BluetoothAdapterMacDelegate; @class IOBluetoothDevice; -@class IOBluetoothDeviceInquiry; @class NSArray; @class NSDate; -#else -class BluetoothAdapterMacDelegate; -class IOBluetoothDevice; -class IOBluetoothDeviceInquiry; -class NSArray; -class NSDate; -#endif namespace base { @@ -40,13 +32,19 @@ namespace device { class BluetoothAdapterMacTest; -class BluetoothAdapterMac : public BluetoothAdapter { +class BluetoothAdapterMac : public BluetoothAdapter, + public BluetoothDiscoveryManagerMac::Observer { public: - // BluetoothAdapter override + static base::WeakPtr<BluetoothAdapter> CreateAdapter(); + + // BluetoothAdapter: virtual void AddObserver(BluetoothAdapter::Observer* observer) OVERRIDE; virtual void RemoveObserver(BluetoothAdapter::Observer* observer) OVERRIDE; virtual std::string GetAddress() const OVERRIDE; virtual std::string GetName() const OVERRIDE; + virtual void SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; virtual bool IsInitialized() const OVERRIDE; virtual bool IsPresent() const OVERRIDE; virtual bool IsPowered() const OVERRIDE; @@ -54,66 +52,68 @@ class BluetoothAdapterMac : public BluetoothAdapter { bool powered, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - virtual bool IsDiscovering() const OVERRIDE; - - virtual void StartDiscovering( - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void StopDiscovering( + virtual bool IsDiscoverable() const OVERRIDE; + virtual void SetDiscoverable( + bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - virtual void ReadLocalOutOfBandPairingData( - const BluetoothOutOfBandPairingDataCallback& callback, - const ErrorCallback& error_callback) OVERRIDE; - - // called by BluetoothAdapterMacDelegate. - void DeviceInquiryStarted(IOBluetoothDeviceInquiry* inquiry); - void DeviceFound(IOBluetoothDeviceInquiry* inquiry, - IOBluetoothDevice* device); - void DeviceInquiryComplete(IOBluetoothDeviceInquiry* inquiry, - IOReturn error, - bool aborted); + virtual bool IsDiscovering() const OVERRIDE; + virtual void CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE; + virtual void CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE; + + // BluetoothDiscoveryManagerMac::Observer overrides + virtual void DeviceFound(BluetoothDiscoveryManagerMac* manager, + IOBluetoothDevice* device) OVERRIDE; + virtual void DiscoveryStopped(BluetoothDiscoveryManagerMac* manager, + bool unexpected) OVERRIDE; + + // Registers that a new |device| has connected to the local host. + void DeviceConnected(IOBluetoothDevice* device); + + protected: + // BluetoothAdapter: + virtual void RemovePairingDelegateInternal( + device::BluetoothDevice::PairingDelegate* pairing_delegate) OVERRIDE; private: - friend class BluetoothAdapterFactory; friend class BluetoothAdapterMacTest; - enum DiscoveryStatus { - NOT_DISCOVERING, - DISCOVERY_STARTING, - DISCOVERING, - DISCOVERY_STOPPING - }; - BluetoothAdapterMac(); virtual ~BluetoothAdapterMac(); + // BluetoothAdapter: + virtual void AddDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void RemoveDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + void Init(); void InitForTest(scoped_refptr<base::SequencedTaskRunner> ui_task_runner); void PollAdapter(); - // Updates |devices_| to be consistent with |devices|. - void UpdateDevices(NSArray* devices); - - void MaybeStartDeviceInquiry(); - void MaybeStopDeviceInquiry(); - - typedef std::vector<std::pair<base::Closure, ErrorCallback> > - DiscoveryCallbackList; - void RunCallbacks(const DiscoveryCallbackList& callback_list, - bool success) const; + // Updates |devices_| to include the currently paired devices, as well as any + // connected, but unpaired, devices. Notifies observers if any previously + // paired or connected devices are no longer present. + void UpdateDevices(); std::string address_; std::string name_; bool powered_; - DiscoveryStatus discovery_status_; - DiscoveryCallbackList on_start_discovery_callbacks_; - DiscoveryCallbackList on_stop_discovery_callbacks_; - size_t num_discovery_listeners_; + int num_discovery_sessions_; - BluetoothAdapterMacDelegate* adapter_delegate_; - IOBluetoothDeviceInquiry* device_inquiry_; + // Discovery manager for Bluetooth Classic. + scoped_ptr<BluetoothDiscoveryManagerMac> classic_discovery_manager_; // A list of discovered device addresses. // This list is used to check if the same device is discovered twice during @@ -122,7 +122,7 @@ class BluetoothAdapterMac : public BluetoothAdapter { // Timestamp for the recently accessed device. // Used to determine if |devices_| needs an update. - NSDate* recently_accessed_device_timestamp_; + base::scoped_nsobject<NSDate> recently_accessed_device_timestamp_; scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac.mm b/chromium/device/bluetooth/bluetooth_adapter_mac.mm index e15b894a9cc..d44a0f37773 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac.mm +++ b/chromium/device/bluetooth/bluetooth_adapter_mac.mm @@ -5,7 +5,6 @@ #include "device/bluetooth/bluetooth_adapter_mac.h" #import <IOBluetooth/objc/IOBluetoothDevice.h> -#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h> #import <IOBluetooth/objc/IOBluetoothHostController.h> #include <string> @@ -14,6 +13,7 @@ #include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "base/location.h" +#include "base/mac/sdk_forward_declarations.h" #include "base/memory/scoped_ptr.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" @@ -21,66 +21,8 @@ #include "base/thread_task_runner_handle.h" #include "base/time/time.h" #include "device/bluetooth/bluetooth_device_mac.h" - -// Replicate specific 10.7 SDK declarations for building with prior SDKs. -#if !defined(MAC_OS_X_VERSION_10_7) || \ -MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 - -@interface IOBluetoothHostController (LionSDKDeclarations) -- (NSString*)nameAsString; -- (BluetoothHCIPowerState)powerState; -@end - -@interface IOBluetoothDevice (LionSDKDeclarations) -- (NSString*)addressString; -@end - -@protocol IOBluetoothDeviceInquiryDelegate -- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender; -- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender - device:(IOBluetoothDevice*)device; -- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender - error:(IOReturn)error - aborted:(BOOL)aborted; -@end - -#endif // MAC_OS_X_VERSION_10_7 - -@interface BluetoothAdapterMacDelegate - : NSObject <IOBluetoothDeviceInquiryDelegate> { - @private - device::BluetoothAdapterMac* adapter_; // weak -} - -- (id)initWithAdapter:(device::BluetoothAdapterMac*)adapter; - -@end - -@implementation BluetoothAdapterMacDelegate - -- (id)initWithAdapter:(device::BluetoothAdapterMac*)adapter { - if ((self = [super init])) - adapter_ = adapter; - - return self; -} - -- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender { - adapter_->DeviceInquiryStarted(sender); -} - -- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender - device:(IOBluetoothDevice*)device { - adapter_->DeviceFound(sender, device); -} - -- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender - error:(IOReturn)error - aborted:(BOOL)aborted { - adapter_->DeviceInquiryComplete(sender, error, aborted); -} - -@end +#include "device/bluetooth/bluetooth_socket_mac.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace { @@ -90,23 +32,30 @@ const int kPollIntervalMs = 500; namespace device { +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( + const InitCallback& init_callback) { + return BluetoothAdapterMac::CreateAdapter(); +} + +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapterMac::CreateAdapter() { + BluetoothAdapterMac* adapter = new BluetoothAdapterMac(); + adapter->Init(); + return adapter->weak_ptr_factory_.GetWeakPtr(); +} + BluetoothAdapterMac::BluetoothAdapterMac() : BluetoothAdapter(), powered_(false), - discovery_status_(NOT_DISCOVERING), - adapter_delegate_( - [[BluetoothAdapterMacDelegate alloc] initWithAdapter:this]), - device_inquiry_( - [[IOBluetoothDeviceInquiry - inquiryWithDelegate:adapter_delegate_] retain]), - recently_accessed_device_timestamp_(nil), + num_discovery_sessions_(0), + classic_discovery_manager_( + BluetoothDiscoveryManagerMac::CreateClassic(this)), weak_ptr_factory_(this) { + DCHECK(classic_discovery_manager_.get()); } BluetoothAdapterMac::~BluetoothAdapterMac() { - [device_inquiry_ release]; - [adapter_delegate_ release]; - [recently_accessed_device_timestamp_ release]; } void BluetoothAdapterMac::AddObserver(BluetoothAdapter::Observer* observer) { @@ -127,6 +76,12 @@ std::string BluetoothAdapterMac::GetName() const { return name_; } +void BluetoothAdapterMac::SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + bool BluetoothAdapterMac::IsInitialized() const { return true; } @@ -142,40 +97,152 @@ bool BluetoothAdapterMac::IsPowered() const { void BluetoothAdapterMac::SetPowered(bool powered, const base::Closure& callback, const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +bool BluetoothAdapterMac::IsDiscoverable() const { + NOTIMPLEMENTED(); + return false; +} + +void BluetoothAdapterMac::SetDiscoverable( + bool discoverable, + const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); } bool BluetoothAdapterMac::IsDiscovering() const { - return discovery_status_ == DISCOVERING || - discovery_status_ == DISCOVERY_STOPPING; + return classic_discovery_manager_->IsDiscovering(); +} + +void BluetoothAdapterMac::CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket(); + socket->ListenUsingRfcomm( + this, uuid, channel, base::Bind(callback, socket), error_callback); +} + +void BluetoothAdapterMac::CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket(); + socket->ListenUsingL2cap( + this, uuid, psm, base::Bind(callback, socket), error_callback); +} + +void BluetoothAdapterMac::DeviceFound(BluetoothDiscoveryManagerMac* manager, + IOBluetoothDevice* device) { + // TODO(isherman): The list of discovered devices is never reset. This should + // probably key off of |devices_| instead. Currently, if a device is paired, + // then unpaired, then paired again, the app would never hear about the second + // pairing. + std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); + if (discovered_devices_.find(device_address) == discovered_devices_.end()) { + BluetoothDeviceMac device_mac(device); + FOR_EACH_OBSERVER( + BluetoothAdapter::Observer, observers_, DeviceAdded(this, &device_mac)); + discovered_devices_.insert(device_address); + } +} + +void BluetoothAdapterMac::DiscoveryStopped( + BluetoothDiscoveryManagerMac* manager, + bool unexpected) { + DCHECK_EQ(manager, classic_discovery_manager_.get()); + if (unexpected) { + DVLOG(1) << "Discovery stopped unexpectedly"; + num_discovery_sessions_ = 0; + MarkDiscoverySessionsAsInactive(); + } + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, + observers_, + AdapterDiscoveringChanged(this, false)); +} + +void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) { + // TODO(isherman): Call -registerForDisconnectNotification:selector:, and + // investigate whether this method can be replaced with a call to + // +registerForConnectNotifications:selector:. + std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); + DVLOG(1) << "Adapter registered a new connection from device with address: " + << device_address; + + // Only notify once per device. + if (devices_.count(device_address)) + return; + + scoped_ptr<BluetoothDeviceMac> device_mac(new BluetoothDeviceMac(device)); + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, + observers_, + DeviceAdded(this, device_mac.get())); + devices_[device_address] = device_mac.release(); } -void BluetoothAdapterMac::StartDiscovering( +void BluetoothAdapterMac::AddDiscoverySession( const base::Closure& callback, const ErrorCallback& error_callback) { - if (discovery_status_ == DISCOVERING) { - num_discovery_listeners_++; + DVLOG(1) << __func__; + if (num_discovery_sessions_ > 0) { + DCHECK(IsDiscovering()); + num_discovery_sessions_++; callback.Run(); return; } - on_start_discovery_callbacks_.push_back( - std::make_pair(callback, error_callback)); - MaybeStartDeviceInquiry(); -} -void BluetoothAdapterMac::StopDiscovering(const base::Closure& callback, - const ErrorCallback& error_callback) { - if (discovery_status_ == NOT_DISCOVERING) { + DCHECK_EQ(0, num_discovery_sessions_); + + if (!classic_discovery_manager_->StartDiscovery()) { + DVLOG(1) << "Failed to add a discovery session"; error_callback.Run(); return; } - on_stop_discovery_callbacks_.push_back( - std::make_pair(callback, error_callback)); - MaybeStopDeviceInquiry(); + + DVLOG(1) << "Added a discovery session"; + num_discovery_sessions_++; + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, + observers_, + AdapterDiscoveringChanged(this, true)); + callback.Run(); } -void BluetoothAdapterMac::ReadLocalOutOfBandPairingData( - const BluetoothOutOfBandPairingDataCallback& callback, +void BluetoothAdapterMac::RemoveDiscoverySession( + const base::Closure& callback, const ErrorCallback& error_callback) { + DVLOG(1) << __func__; + + if (num_discovery_sessions_ > 1) { + // There are active sessions other than the one currently being removed. + DCHECK(IsDiscovering()); + num_discovery_sessions_--; + callback.Run(); + return; + } + + if (num_discovery_sessions_ == 0) { + DVLOG(1) << "No active discovery sessions. Returning error."; + error_callback.Run(); + return; + } + + if (!classic_discovery_manager_->StopDiscovery()) { + DVLOG(1) << "Failed to stop discovery"; + error_callback.Run(); + return; + } + + DVLOG(1) << "Discovery stopped"; + num_discovery_sessions_--; + callback.Run(); +} + +void BluetoothAdapterMac::RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) { } void BluetoothAdapterMac::Init() { @@ -191,15 +258,16 @@ void BluetoothAdapterMac::InitForTest( void BluetoothAdapterMac::PollAdapter() { bool was_present = IsPresent(); - std::string name = ""; - std::string address = ""; + std::string name; + std::string address; bool powered = false; IOBluetoothHostController* controller = [IOBluetoothHostController defaultController]; if (controller != nil) { name = base::SysNSStringToUTF8([controller nameAsString]); - address = base::SysNSStringToUTF8([controller addressAsString]); + address = BluetoothDevice::CanonicalizeAddress( + base::SysNSStringToUTF8([controller addressAsString])); powered = ([controller powerState] == kBluetoothHCIPowerStateON); } @@ -217,6 +285,7 @@ void BluetoothAdapterMac::PollAdapter() { AdapterPoweredChanged(this, powered_)); } + // TODO(isherman): This doesn't detect when a device is unpaired. IOBluetoothDevice* recent_device = [[IOBluetoothDevice recentDevices:1] lastObject]; NSDate* access_timestamp = [recent_device recentAccessDate]; @@ -224,9 +293,8 @@ void BluetoothAdapterMac::PollAdapter() { access_timestamp == nil || [recently_accessed_device_timestamp_ compare:access_timestamp] == NSOrderedAscending) { - UpdateDevices([IOBluetoothDevice pairedDevices]); - [recently_accessed_device_timestamp_ release]; - recently_accessed_device_timestamp_ = [access_timestamp copy]; + UpdateDevices(); + recently_accessed_device_timestamp_.reset([access_timestamp copy]); } ui_task_runner_->PostDelayedTask( @@ -236,104 +304,39 @@ void BluetoothAdapterMac::PollAdapter() { base::TimeDelta::FromMilliseconds(kPollIntervalMs)); } -void BluetoothAdapterMac::UpdateDevices(NSArray* devices) { - STLDeleteValues(&devices_); - for (IOBluetoothDevice* device in devices) { - std::string device_address = - base::SysNSStringToUTF8([device addressString]); - devices_[device_address] = new BluetoothDeviceMac(device); +void BluetoothAdapterMac::UpdateDevices() { + // Snapshot the devices observers were previously notified of. + // Note that the code below is careful to take ownership of any values that + // are erased from the map, since the map owns the memory for all its mapped + // devices. + DevicesMap old_devices = devices_; + + // Add all the paired devices. + devices_.clear(); + for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) { + std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device); + scoped_ptr<BluetoothDevice> device_mac(old_devices[device_address]); + if (!device_mac) + device_mac.reset(new BluetoothDeviceMac(device)); + devices_[device_address] = device_mac.release(); + old_devices.erase(device_address); } -} - -void BluetoothAdapterMac::DeviceInquiryStarted( - IOBluetoothDeviceInquiry* inquiry) { - DCHECK(device_inquiry_ == inquiry); - if (discovery_status_ == DISCOVERING) - return; - discovery_status_ = DISCOVERING; - RunCallbacks(on_start_discovery_callbacks_, true); - num_discovery_listeners_ = on_start_discovery_callbacks_.size(); - on_start_discovery_callbacks_.clear(); - - FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, - AdapterDiscoveringChanged(this, true)); - MaybeStopDeviceInquiry(); -} + // Add any unpaired connected devices. + for (const auto& old_device : old_devices) { + if (!old_device.second->IsConnected()) + continue; -void BluetoothAdapterMac::DeviceFound(IOBluetoothDeviceInquiry* inquiry, - IOBluetoothDevice* device) { - DCHECK(device_inquiry_ == inquiry); - std::string device_address = base::SysNSStringToUTF8([device addressString]); - if (discovered_devices_.find(device_address) == discovered_devices_.end()) { - scoped_ptr<BluetoothDeviceMac> device_mac(new BluetoothDeviceMac(device)); - FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, - DeviceAdded(this, device_mac.get())); - discovered_devices_.insert(device_address); - } -} - -void BluetoothAdapterMac::DeviceInquiryComplete( - IOBluetoothDeviceInquiry* inquiry, - IOReturn error, - bool aborted) { - DCHECK(device_inquiry_ == inquiry); - if (discovery_status_ == DISCOVERING && - [device_inquiry_ start] == kIOReturnSuccess) { - return; + const std::string& device_address = old_device.first; + DCHECK(!devices_.count(device_address)); + devices_[device_address] = old_device.second; + old_devices.erase(device_address); } - // Device discovery is done. - discovered_devices_.clear(); - discovery_status_ = NOT_DISCOVERING; - RunCallbacks(on_stop_discovery_callbacks_, error == kIOReturnSuccess); - num_discovery_listeners_ = 0; - on_stop_discovery_callbacks_.clear(); - FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, - AdapterDiscoveringChanged(this, false)); - MaybeStartDeviceInquiry(); -} - -void BluetoothAdapterMac::MaybeStartDeviceInquiry() { - if (discovery_status_ == NOT_DISCOVERING && - !on_start_discovery_callbacks_.empty()) { - discovery_status_ = DISCOVERY_STARTING; - if ([device_inquiry_ start] != kIOReturnSuccess) { - discovery_status_ = NOT_DISCOVERING; - RunCallbacks(on_start_discovery_callbacks_, false); - on_start_discovery_callbacks_.clear(); - } - } -} - -void BluetoothAdapterMac::MaybeStopDeviceInquiry() { - if (discovery_status_ != DISCOVERING) - return; - - if (on_stop_discovery_callbacks_.size() < num_discovery_listeners_) { - RunCallbacks(on_stop_discovery_callbacks_, true); - num_discovery_listeners_ -= on_stop_discovery_callbacks_.size(); - on_stop_discovery_callbacks_.clear(); - return; - } - - discovery_status_ = DISCOVERY_STOPPING; - if ([device_inquiry_ stop] != kIOReturnSuccess) { - RunCallbacks(on_stop_discovery_callbacks_, false); - on_stop_discovery_callbacks_.clear(); - } -} - -void BluetoothAdapterMac::RunCallbacks( - const DiscoveryCallbackList& callback_list, bool success) const { - for (DiscoveryCallbackList::const_iterator iter = callback_list.begin(); - iter != callback_list.end(); - ++iter) { - if (success) - ui_task_runner_->PostTask(FROM_HERE, iter->first); - else - ui_task_runner_->PostTask(FROM_HERE, iter->second); - } + // TODO(isherman): Notify observers of any devices that are no longer in + // range. Note that it's possible for a device to be neither paired nor + // connected, but to still be in range. + STLDeleteValues(&old_devices); } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc new file mode 100644 index 00000000000..19ff6033847 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc @@ -0,0 +1,192 @@ +// 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/ref_counted.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" +#include "testing/gtest/include/gtest/gtest.h" + +using device::BluetoothAdapter; +using device::BluetoothDevice; + +namespace device { + +class TestBluetoothAdapter : public BluetoothAdapter { + public: + TestBluetoothAdapter() { + } + + virtual void AddObserver(BluetoothAdapter::Observer* observer) OVERRIDE { + } + + virtual void RemoveObserver(BluetoothAdapter::Observer* observer) OVERRIDE { + + } + + virtual std::string GetAddress() const OVERRIDE { + return ""; + } + + virtual std::string GetName() const OVERRIDE { + return ""; + } + + virtual void SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE { + } + + virtual bool IsInitialized() const OVERRIDE { + return false; + } + + virtual bool IsPresent() const OVERRIDE { + return false; + } + + virtual bool IsPowered() const OVERRIDE { + return false; + } + + virtual void SetPowered( + bool powered, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE { + } + + virtual bool IsDiscoverable() const OVERRIDE { + return false; + } + + virtual void SetDiscoverable( + bool discoverable, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE { + } + + virtual bool IsDiscovering() const OVERRIDE { + return false; + } + + virtual void StartDiscoverySession( + const DiscoverySessionCallback& callback, + const ErrorCallback& error_callback) OVERRIDE { + } + + virtual void CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE { + } + + virtual void CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE { + } + + protected: + virtual ~TestBluetoothAdapter() {} + + virtual void AddDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE { + } + + virtual void RemoveDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE { + } + + virtual void RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) OVERRIDE { + } +}; + +class TestPairingDelegate : public BluetoothDevice::PairingDelegate { + public: + virtual void RequestPinCode(BluetoothDevice* device) OVERRIDE {} + virtual void RequestPasskey(BluetoothDevice* device) OVERRIDE {} + virtual void DisplayPinCode(BluetoothDevice* device, + const std::string& pincode) OVERRIDE {} + virtual void DisplayPasskey(BluetoothDevice* device, + uint32 passkey) OVERRIDE {} + virtual void KeysEntered(BluetoothDevice* device, + uint32 entered) OVERRIDE {} + virtual void ConfirmPasskey(BluetoothDevice* device, + uint32 passkey) OVERRIDE {} + virtual void AuthorizePairing(BluetoothDevice* device) OVERRIDE {} +}; + + +TEST(BluetoothAdapterTest, NoDefaultPairingDelegate) { + scoped_refptr<BluetoothAdapter> adapter = new TestBluetoothAdapter(); + + // Verify that when there is no registered pairing delegate, NULL is returned. + EXPECT_TRUE(adapter->DefaultPairingDelegate() == NULL); +} + +TEST(BluetoothAdapterTest, OneDefaultPairingDelegate) { + scoped_refptr<BluetoothAdapter> adapter = new TestBluetoothAdapter(); + + // Verify that when there is one registered pairing delegate, it is returned. + TestPairingDelegate delegate; + + adapter->AddPairingDelegate(&delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW); + + EXPECT_EQ(&delegate, adapter->DefaultPairingDelegate()); +} + +TEST(BluetoothAdapterTest, SamePriorityDelegates) { + scoped_refptr<BluetoothAdapter> adapter = new TestBluetoothAdapter(); + + // Verify that when there are two registered pairing delegates of the same + // priority, the first one registered is returned. + TestPairingDelegate delegate1, delegate2; + + adapter->AddPairingDelegate(&delegate1, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW); + adapter->AddPairingDelegate(&delegate2, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW); + + EXPECT_EQ(&delegate1, adapter->DefaultPairingDelegate()); + + // After unregistering the first, the second can be returned. + adapter->RemovePairingDelegate(&delegate1); + + EXPECT_EQ(&delegate2, adapter->DefaultPairingDelegate()); +} + +TEST(BluetoothAdapterTest, HighestPriorityDelegate) { + scoped_refptr<BluetoothAdapter> adapter = new TestBluetoothAdapter(); + + // Verify that when there are two registered pairing delegates, the one with + // the highest priority is returned. + TestPairingDelegate delegate1, delegate2; + + adapter->AddPairingDelegate(&delegate1, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW); + adapter->AddPairingDelegate(&delegate2, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); + + EXPECT_EQ(&delegate2, adapter->DefaultPairingDelegate()); +} + +TEST(BluetoothAdapterTest, UnregisterDelegate) { + scoped_refptr<BluetoothAdapter> adapter = new TestBluetoothAdapter(); + + // Verify that after unregistering a delegate, NULL is returned. + TestPairingDelegate delegate; + + adapter->AddPairingDelegate(&delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW); + adapter->RemovePairingDelegate(&delegate); + + EXPECT_TRUE(adapter->DefaultPairingDelegate() == NULL); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_win.cc b/chromium/device/bluetooth/bluetooth_adapter_win.cc index f6ca1d65ebe..8ef54705f18 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_win.cc @@ -14,10 +14,27 @@ #include "base/stl_util.h" #include "base/thread_task_runner_handle.h" #include "device/bluetooth/bluetooth_device_win.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "device/bluetooth/bluetooth_socket_win.h" #include "device/bluetooth/bluetooth_task_manager_win.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace device { +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( + const InitCallback& init_callback) { + return BluetoothAdapterWin::CreateAdapter(init_callback); +} + +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapterWin::CreateAdapter( + const InitCallback& init_callback) { + BluetoothAdapterWin* adapter = new BluetoothAdapterWin(init_callback); + adapter->Init(); + return adapter->weak_ptr_factory_.GetWeakPtr(); +} + BluetoothAdapterWin::BluetoothAdapterWin(const InitCallback& init_callback) : BluetoothAdapter(), init_callback_(init_callback), @@ -53,6 +70,12 @@ std::string BluetoothAdapterWin::GetName() const { return name_; } +void BluetoothAdapterWin::SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + // TODO(youngki): Return true when |task_manager_| initializes the adapter // state. bool BluetoothAdapterWin::IsInitialized() const { @@ -74,35 +97,21 @@ void BluetoothAdapterWin::SetPowered( task_manager_->PostSetPoweredBluetoothTask(powered, callback, error_callback); } -bool BluetoothAdapterWin::IsDiscovering() const { - return discovery_status_ == DISCOVERING || - discovery_status_ == DISCOVERY_STOPPING; +bool BluetoothAdapterWin::IsDiscoverable() const { + NOTIMPLEMENTED(); + return false; } -// If the method is called when |discovery_status_| is DISCOVERY_STOPPING, -// starting again is handled by BluetoothAdapterWin::DiscoveryStopped(). -void BluetoothAdapterWin::StartDiscovering( +void BluetoothAdapterWin::SetDiscoverable( + bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) { - if (discovery_status_ == DISCOVERING) { - num_discovery_listeners_++; - callback.Run(); - return; - } - on_start_discovery_callbacks_.push_back( - std::make_pair(callback, error_callback)); - MaybePostStartDiscoveryTask(); + NOTIMPLEMENTED(); } -void BluetoothAdapterWin::StopDiscovering( - const base::Closure& callback, - const ErrorCallback& error_callback) { - if (discovery_status_ == NOT_DISCOVERING) { - error_callback.Run(); - return; - } - on_stop_discovery_callbacks_.push_back(callback); - MaybePostStopDiscoveryTask(); +bool BluetoothAdapterWin::IsDiscovering() const { + return discovery_status_ == DISCOVERING || + discovery_status_ == DISCOVERY_STOPPING; } void BluetoothAdapterWin::DiscoveryStarted(bool success) { @@ -152,19 +161,42 @@ void BluetoothAdapterWin::DiscoveryStopped() { MaybePostStartDiscoveryTask(); } -void BluetoothAdapterWin::ReadLocalOutOfBandPairingData( - const BluetoothOutOfBandPairingDataCallback& callback, - const ErrorCallback& error_callback) { +void BluetoothAdapterWin::CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + scoped_refptr<BluetoothSocketWin> socket = + BluetoothSocketWin::CreateBluetoothSocket( + ui_task_runner_, + socket_thread_, + NULL, + net::NetLog::Source()); + socket->Listen(this, uuid, channel, + base::Bind(callback, socket), + error_callback); +} + +void BluetoothAdapterWin::CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + // TODO(keybuk): implement. NOTIMPLEMENTED(); } +void BluetoothAdapterWin::RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) { +} + void BluetoothAdapterWin::AdapterStateChanged( const BluetoothTaskManagerWin::AdapterState& state) { DCHECK(thread_checker_.CalledOnValidThread()); name_ = state.name; bool was_present = IsPresent(); bool is_present = !state.address.empty(); - address_ = state.address; + address_ = BluetoothDevice::CanonicalizeAddress(state.address); if (was_present != is_present) { FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterPresentChanged(this, is_present)); @@ -189,7 +221,8 @@ void BluetoothAdapterWin::DevicesDiscovered( ++iter) { if (discovered_devices_.find((*iter)->address) == discovered_devices_.end()) { - BluetoothDeviceWin device_win(**iter); + BluetoothDeviceWin device_win( + **iter, ui_task_runner_, socket_thread_, NULL, net::NetLog::Source()); FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, DeviceAdded(this, &device_win)); discovered_devices_.insert((*iter)->address); @@ -204,12 +237,40 @@ void BluetoothAdapterWin::DevicesUpdated( devices.begin(); iter != devices.end(); ++iter) { - devices_[(*iter)->address] = new BluetoothDeviceWin(**iter); + devices_[(*iter)->address] = new BluetoothDeviceWin( + **iter, ui_task_runner_, socket_thread_, NULL, net::NetLog::Source()); } } +// If the method is called when |discovery_status_| is DISCOVERY_STOPPING, +// starting again is handled by BluetoothAdapterWin::DiscoveryStopped(). +void BluetoothAdapterWin::AddDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (discovery_status_ == DISCOVERING) { + num_discovery_listeners_++; + callback.Run(); + return; + } + on_start_discovery_callbacks_.push_back( + std::make_pair(callback, error_callback)); + MaybePostStartDiscoveryTask(); +} + +void BluetoothAdapterWin::RemoveDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (discovery_status_ == NOT_DISCOVERING) { + error_callback.Run(); + return; + } + on_stop_discovery_callbacks_.push_back(callback); + MaybePostStopDiscoveryTask(); +} + void BluetoothAdapterWin::Init() { ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); + socket_thread_ = BluetoothSocketThread::Get(); task_manager_ = new BluetoothTaskManagerWin(ui_task_runner_); task_manager_->AddObserver(this); diff --git a/chromium/device/bluetooth/bluetooth_adapter_win.h b/chromium/device/bluetooth/bluetooth_adapter_win.h index a017e29831d..80e185989bf 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win.h +++ b/chromium/device/bluetooth/bluetooth_adapter_win.h @@ -18,45 +18,53 @@ #include "device/bluetooth/bluetooth_task_manager_win.h" namespace base { - class SequencedTaskRunner; - +class Thread; } // namespace base namespace device { -class BluetoothAdapterFactory; class BluetoothAdapterWinTest; class BluetoothDevice; +class BluetoothSocketThread; class BluetoothAdapterWin : public BluetoothAdapter, public BluetoothTaskManagerWin::Observer { public: - typedef base::Callback<void()> InitCallback; + static base::WeakPtr<BluetoothAdapter> CreateAdapter( + const InitCallback& init_callback); - // BluetoothAdapter override + // BluetoothAdapter: virtual void AddObserver(BluetoothAdapter::Observer* observer) OVERRIDE; virtual void RemoveObserver(BluetoothAdapter::Observer* observer) OVERRIDE; virtual std::string GetAddress() const OVERRIDE; virtual std::string GetName() const OVERRIDE; + virtual void SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; virtual bool IsInitialized() const OVERRIDE; virtual bool IsPresent() const OVERRIDE; virtual bool IsPowered() const OVERRIDE; virtual void SetPowered( - bool powered, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual bool IsDiscovering() const OVERRIDE; - - virtual void StartDiscovering( + bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - virtual void StopDiscovering( + virtual bool IsDiscoverable() const OVERRIDE; + virtual void SetDiscoverable( + bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - virtual void ReadLocalOutOfBandPairingData( - const BluetoothOutOfBandPairingDataCallback& callback, - const ErrorCallback& error_callback) OVERRIDE; + virtual bool IsDiscovering() const OVERRIDE; + virtual void CreateRfcommService( + const BluetoothUUID& uuid, + int channel, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE; + virtual void CreateL2capService( + const BluetoothUUID& uuid, + int psm, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) OVERRIDE; // BluetoothTaskManagerWin::Observer override virtual void AdapterStateChanged( @@ -71,8 +79,19 @@ class BluetoothAdapterWin : public BluetoothAdapter, const ScopedVector<BluetoothTaskManagerWin::DeviceState>& devices) OVERRIDE; + const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner() const { + return ui_task_runner_; + } + const scoped_refptr<BluetoothSocketThread>& socket_thread() const { + return socket_thread_; + } + + protected: + // BluetoothAdapter: + virtual void RemovePairingDelegateInternal( + device::BluetoothDevice::PairingDelegate* pairing_delegate) OVERRIDE; + private: - friend class BluetoothAdapterFactory; friend class BluetoothAdapterWinTest; enum DiscoveryStatus { @@ -85,6 +104,14 @@ class BluetoothAdapterWin : public BluetoothAdapter, explicit BluetoothAdapterWin(const InitCallback& init_callback); virtual ~BluetoothAdapterWin(); + // BluetoothAdapter: + virtual void AddDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void RemoveDiscoverySession( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + void Init(); void InitForTest( scoped_refptr<base::SequencedTaskRunner> ui_task_runner, @@ -107,6 +134,7 @@ class BluetoothAdapterWin : public BluetoothAdapter, size_t num_discovery_listeners_; scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + scoped_refptr<BluetoothSocketThread> socket_thread_; scoped_refptr<BluetoothTaskManagerWin> task_manager_; base::ThreadChecker thread_checker_; diff --git a/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc index 4ea7a1e6f3d..c3dc9101ec4 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc @@ -15,7 +15,7 @@ namespace { -const char kAdapterAddress[] = "Bluetooth Adapter Address"; +const char kAdapterAddress[] = "A1:B2:C3:D4:E5:F6"; const char kAdapterName[] = "Bluetooth Adapter Name"; @@ -131,6 +131,18 @@ class BluetoothAdapterWinTest : public testing::Test { num_stop_discovery_error_callbacks_++; } + void CallAddDiscoverySession( + const base::Closure& callback, + const BluetoothAdapter::ErrorCallback& error_callback) { + adapter_win_->AddDiscoverySession(callback, error_callback); + } + + void CallRemoveDiscoverySession( + const base::Closure& callback, + const BluetoothAdapter::ErrorCallback& error_callback) { + adapter_win_->RemoveDiscoverySession(callback, error_callback); + } + protected: scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_; scoped_refptr<base::TestSimpleTaskRunner> bluetooth_task_runner_; @@ -194,7 +206,7 @@ TEST_F(BluetoothAdapterWinTest, AdapterInitialized) { TEST_F(BluetoothAdapterWinTest, SingleStartDiscovery) { bluetooth_task_runner_->ClearPendingTasks(); - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -210,7 +222,7 @@ TEST_F(BluetoothAdapterWinTest, SingleStartDiscovery) { } TEST_F(BluetoothAdapterWinTest, SingleStartDiscoveryFailure) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks, @@ -226,7 +238,7 @@ TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveries) { bluetooth_task_runner_->ClearPendingTasks(); int num_discoveries = 5; for (int i = 0; i < num_discoveries; i++) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), @@ -246,7 +258,7 @@ TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveries) { TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveriesFailure) { int num_discoveries = 5; for (int i = 0; i < num_discoveries; i++) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks, @@ -260,7 +272,7 @@ TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveriesFailure) { } TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveriesAfterDiscovering) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -272,7 +284,7 @@ TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveriesAfterDiscovering) { bluetooth_task_runner_->ClearPendingTasks(); for (int i = 0; i < 5; i++) { int num_start_discovery_callbacks = num_start_discovery_callbacks_; - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), @@ -287,7 +299,7 @@ TEST_F(BluetoothAdapterWinTest, MultipleStartDiscoveriesAfterDiscovering) { } TEST_F(BluetoothAdapterWinTest, StartDiscoveryAfterDiscoveringFailure) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks, @@ -297,7 +309,7 @@ TEST_F(BluetoothAdapterWinTest, StartDiscoveryAfterDiscoveringFailure) { EXPECT_FALSE(adapter_->IsDiscovering()); EXPECT_EQ(1, num_start_discovery_error_callbacks_); - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -308,11 +320,11 @@ TEST_F(BluetoothAdapterWinTest, StartDiscoveryAfterDiscoveringFailure) { } TEST_F(BluetoothAdapterWinTest, SingleStopDiscovery) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); adapter_win_->DiscoveryStarted(true); ui_task_runner_->ClearPendingTasks(); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -330,14 +342,14 @@ TEST_F(BluetoothAdapterWinTest, SingleStopDiscovery) { TEST_F(BluetoothAdapterWinTest, MultipleStopDiscoveries) { int num_discoveries = 5; for (int i = 0; i < num_discoveries; i++) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); } adapter_win_->DiscoveryStarted(true); ui_task_runner_->ClearPendingTasks(); bluetooth_task_runner_->ClearPendingTasks(); for (int i = 0; i < num_discoveries - 1; i++) { - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -345,7 +357,7 @@ TEST_F(BluetoothAdapterWinTest, MultipleStopDiscoveries) { ui_task_runner_->RunPendingTasks(); EXPECT_EQ(i + 1, num_stop_discovery_callbacks_); } - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -360,23 +372,23 @@ TEST_F(BluetoothAdapterWinTest, MultipleStopDiscoveries) { TEST_F(BluetoothAdapterWinTest, StartDiscoveryAndStartDiscoveryAndStopDiscoveries) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); adapter_win_->DiscoveryStarted(true); - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); ui_task_runner_->ClearPendingTasks(); bluetooth_task_runner_->ClearPendingTasks(); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); EXPECT_TRUE(bluetooth_task_runner_->GetPendingTasks().empty()); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); @@ -385,27 +397,27 @@ TEST_F(BluetoothAdapterWinTest, TEST_F(BluetoothAdapterWinTest, StartDiscoveryAndStopDiscoveryAndStartDiscovery) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); adapter_win_->DiscoveryStarted(true); EXPECT_TRUE(adapter_->IsDiscovering()); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); adapter_win_->DiscoveryStopped(); EXPECT_FALSE(adapter_->IsDiscovering()); - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); adapter_win_->DiscoveryStarted(true); EXPECT_TRUE(adapter_->IsDiscovering()); } TEST_F(BluetoothAdapterWinTest, StartDiscoveryBeforeDiscoveryStopped) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); adapter_win_->DiscoveryStarted(true); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); bluetooth_task_runner_->ClearPendingTasks(); adapter_win_->DiscoveryStopped(); @@ -413,7 +425,7 @@ TEST_F(BluetoothAdapterWinTest, StartDiscoveryBeforeDiscoveryStopped) { } TEST_F(BluetoothAdapterWinTest, StopDiscoveryWithoutStartDiscovery) { - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Closure(), base::Bind( &BluetoothAdapterWinTest::IncrementNumStopDiscoveryErrorCallbacks, @@ -422,9 +434,9 @@ TEST_F(BluetoothAdapterWinTest, StopDiscoveryWithoutStartDiscovery) { } TEST_F(BluetoothAdapterWinTest, StopDiscoveryBeforeDiscoveryStarted) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Closure(), BluetoothAdapter::ErrorCallback()); bluetooth_task_runner_->ClearPendingTasks(); adapter_win_->DiscoveryStarted(true); @@ -435,14 +447,14 @@ TEST_F(BluetoothAdapterWinTest, StartAndStopBeforeDiscoveryStarted) { int num_expected_start_discoveries = 3; int num_expected_stop_discoveries = 2; for (int i = 0; i < num_expected_start_discoveries; i++) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks, base::Unretained(this)), BluetoothAdapter::ErrorCallback()); } for (int i = 0; i < num_expected_stop_discoveries; i++) { - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind( &BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), @@ -457,12 +469,12 @@ TEST_F(BluetoothAdapterWinTest, StartAndStopBeforeDiscoveryStarted) { } TEST_F(BluetoothAdapterWinTest, StopDiscoveryBeforeDiscoveryStartedAndFailed) { - adapter_win_->StartDiscovering( + CallAddDiscoverySession( base::Closure(), base::Bind( &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks, base::Unretained(this))); - adapter_win_->StopDiscovering( + CallRemoveDiscoverySession( base::Bind( &BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks, base::Unretained(this)), diff --git a/chromium/device/bluetooth/bluetooth_channel_mac.h b/chromium/device/bluetooth/bluetooth_channel_mac.h new file mode 100644 index 00000000000..588a45c5558 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_channel_mac.h @@ -0,0 +1,64 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_CHANNEL_MAC_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_CHANNEL_MAC_H_ + +#import <IOKit/IOReturn.h> + +#include <string> + +#include "base/macros.h" + +@class IOBluetoothDevice; + +namespace device { + +class BluetoothSocketMac; + +// Wraps a native RFCOMM or L2CAP channel. +class BluetoothChannelMac { + public: + BluetoothChannelMac(); + virtual ~BluetoothChannelMac(); + + // Sets the channel's owning socket to |socket|. Should only be called if the + // socket was previously unset. Note: This can synchronously call back into + // socket->OnChannelOpenComplete(). + virtual void SetSocket(BluetoothSocketMac* socket); + + // Returns the Bluetooth address for the device associated with |this| + // channel. + std::string GetDeviceAddress(); + + // Returns the Bluetooth device associated with |this| channel. + virtual IOBluetoothDevice* GetDevice() = 0; + + // Returns the outgoing MTU (maximum transmission unit) for the channel. + virtual uint16_t GetOutgoingMTU() = 0; + + // Writes |data| of length |length| bytes into the channel. The |refcon| is a + // user-supplied value that gets passed to the write callback. + // Returns kIOReturnSuccess if the data was buffered successfully. + // If the return value is an error condition none of the data was sent. + // The number of bytes to be sent must not exceed the channel MTU. + // + // Once the data has been successfully passed to the hardware to be + // transmitted, the socket's method OnChannelWriteComplete() will be called + // with the |refcon| that was passed to this method. + virtual IOReturn WriteAsync(void* data, uint16_t length, void* refcon) = 0; + + protected: + BluetoothSocketMac* socket() { return socket_; } + + private: + // The socket that owns |this|. + BluetoothSocketMac* socket_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothChannelMac); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_CHANNEL_MAC_H_ diff --git a/chromium/device/bluetooth/bluetooth_channel_mac.mm b/chromium/device/bluetooth/bluetooth_channel_mac.mm new file mode 100644 index 00000000000..50f31b991fc --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_channel_mac.mm @@ -0,0 +1,29 @@ +// 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 "device/bluetooth/bluetooth_channel_mac.h" + +#import <IOBluetooth/IOBluetooth.h> + +#include "base/logging.h" +#include "device/bluetooth/bluetooth_device_mac.h" + +namespace device { + +BluetoothChannelMac::BluetoothChannelMac() : socket_(NULL) { +} + +BluetoothChannelMac::~BluetoothChannelMac() { +} + +void BluetoothChannelMac::SetSocket(BluetoothSocketMac* socket) { + DCHECK(!socket_); + socket_ = socket; +} + +std::string BluetoothChannelMac::GetDeviceAddress() { + return BluetoothDeviceMac::GetDeviceAddress(GetDevice()); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_chromeos_unittest.cc b/chromium/device/bluetooth/bluetooth_chromeos_unittest.cc index dea13221e6a..379d1dfd25f 100644 --- a/chromium/device/bluetooth/bluetooth_chromeos_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_chromeos_unittest.cc @@ -2,11 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/scoped_vector.h" #include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "chromeos/dbus/fake_bluetooth_adapter_client.h" #include "chromeos/dbus/fake_bluetooth_agent_manager_client.h" #include "chromeos/dbus/fake_bluetooth_device_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_service_client.h" #include "chromeos/dbus/fake_bluetooth_input_client.h" #include "chromeos/dbus/fake_dbus_thread_manager.h" #include "dbus/object_path.h" @@ -15,19 +17,27 @@ #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_device_chromeos.h" +#include "device/bluetooth/bluetooth_discovery_session.h" +#include "device/bluetooth/bluetooth_pairing_chromeos.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/cros_system_api/dbus/service_constants.h" using device::BluetoothAdapter; using device::BluetoothAdapterFactory; using device::BluetoothDevice; +using device::BluetoothDiscoverySession; +using device::BluetoothUUID; namespace chromeos { +namespace { + class TestObserver : public BluetoothAdapter::Observer { public: TestObserver(scoped_refptr<BluetoothAdapter> adapter) : present_changed_count_(0), powered_changed_count_(0), + discoverable_changed_count_(0), discovering_changed_count_(0), last_present_(false), last_powered_(false), @@ -37,8 +47,12 @@ class TestObserver : public BluetoothAdapter::Observer { device_removed_count_(0), last_device_(NULL), adapter_(adapter) { + adapter_->AddObserver(this); + } + + virtual ~TestObserver() { + adapter_->RemoveObserver(this); } - virtual ~TestObserver() {} virtual void AdapterPresentChanged(BluetoothAdapter* adapter, bool present) OVERRIDE { @@ -56,6 +70,13 @@ class TestObserver : public BluetoothAdapter::Observer { last_powered_ = powered; } + virtual void AdapterDiscoverableChanged(BluetoothAdapter* adapter, + bool discoverable) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++discoverable_changed_count_; + } + virtual void AdapterDiscoveringChanged(BluetoothAdapter* adapter, bool discovering) OVERRIDE { EXPECT_EQ(adapter_, adapter); @@ -99,6 +120,7 @@ class TestObserver : public BluetoothAdapter::Observer { int present_changed_count_; int powered_changed_count_; + int discoverable_changed_count_; int discovering_changed_count_; bool last_present_; bool last_powered_; @@ -121,6 +143,8 @@ class TestObserver : public BluetoothAdapter::Observer { scoped_refptr<BluetoothAdapter> adapter_; }; +} // namespace + class TestPairingDelegate : public BluetoothDevice::PairingDelegate { public: TestPairingDelegate() @@ -131,7 +155,7 @@ class TestPairingDelegate : public BluetoothDevice::PairingDelegate { display_passkey_count_(0), keys_entered_count_(0), confirm_passkey_count_(0), - dismiss_count_(0), + authorize_pairing_count_(0), last_passkey_(9999999U), last_entered_(999U) {} virtual ~TestPairingDelegate() {} @@ -179,9 +203,9 @@ class TestPairingDelegate : public BluetoothDevice::PairingDelegate { QuitMessageLoop(); } - virtual void DismissDisplayOrConfirm() OVERRIDE { + virtual void AuthorizePairing(BluetoothDevice* device) OVERRIDE { ++call_count_; - ++dismiss_count_; + ++authorize_pairing_count_; QuitMessageLoop(); } @@ -192,7 +216,7 @@ class TestPairingDelegate : public BluetoothDevice::PairingDelegate { int display_passkey_count_; int keys_entered_count_; int confirm_passkey_count_; - int dismiss_count_; + int authorize_pairing_count_; uint32 last_passkey_; uint32 last_entered_; std::string last_pincode_; @@ -222,14 +246,37 @@ class BluetoothChromeOSTest : public testing::Test { fake_dbus_thread_manager->SetBluetoothAgentManagerClient( scoped_ptr<BluetoothAgentManagerClient>( new FakeBluetoothAgentManagerClient)); + fake_dbus_thread_manager->SetBluetoothGattServiceClient( + scoped_ptr<BluetoothGattServiceClient>( + new FakeBluetoothGattServiceClient)); DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager); + fake_bluetooth_adapter_client_->SetSimulationIntervalMs(10); + callback_count_ = 0; error_callback_count_ = 0; last_connect_error_ = BluetoothDevice::ERROR_UNKNOWN; + last_client_error_ = ""; } virtual void TearDown() { + for (ScopedVector<BluetoothDiscoverySession>::iterator iter = + discovery_sessions_.begin(); + iter != discovery_sessions_.end(); + ++iter) { + BluetoothDiscoverySession* session = *iter; + if (!session->IsActive()) + continue; + callback_count_ = 0; + session->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + ASSERT_EQ(1, callback_count_); + } + discovery_sessions_.clear(); adapter_ = NULL; DBusThreadManager::Shutdown(); } @@ -237,13 +284,29 @@ class BluetoothChromeOSTest : public testing::Test { // Generic callbacks void Callback() { ++callback_count_; + QuitMessageLoop(); + } + + void DiscoverySessionCallback( + scoped_ptr<BluetoothDiscoverySession> discovery_session) { + ++callback_count_; + discovery_sessions_.push_back(discovery_session.release()); + QuitMessageLoop(); } void ErrorCallback() { ++error_callback_count_; + QuitMessageLoop(); + } + + void DBusErrorCallback(const std::string& error_name, + const std::string& error_message) { + ++error_callback_count_; + last_client_error_ = error_name; + QuitMessageLoop(); } - void ConnectErrorCallback(enum BluetoothDevice::ConnectErrorCode error) { + void ConnectErrorCallback(BluetoothDevice::ConnectErrorCode error) { ++error_callback_count_; last_connect_error_ = error; } @@ -262,17 +325,10 @@ class BluetoothChromeOSTest : public testing::Test { // without using this function. void DiscoverDevice(const std::string& address) { ASSERT_TRUE(adapter_.get() != NULL); - - if (base::MessageLoop::current() == NULL) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); - DiscoverDevices(); - return; - } - + ASSERT_TRUE(base::MessageLoop::current() != NULL); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); adapter_->SetPowered( true, @@ -280,13 +336,16 @@ class BluetoothChromeOSTest : public testing::Test { base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); - adapter_->StartDiscovering( - base::Bind(&BluetoothChromeOSTest::Callback, + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); + base::MessageLoop::current()->Run(); ASSERT_EQ(2, callback_count_); ASSERT_EQ(0, error_callback_count_); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + ASSERT_TRUE(discovery_sessions_[0]->IsActive()); callback_count_ = 0; ASSERT_TRUE(adapter_->IsPowered()); @@ -296,18 +355,17 @@ class BluetoothChromeOSTest : public testing::Test { observer.last_device_address_ != address) base::MessageLoop::current()->Run(); - adapter_->StopDiscovering( + discovery_sessions_[0]->Stop( base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); + base::MessageLoop::current()->Run(); ASSERT_EQ(1, callback_count_); ASSERT_EQ(0, error_callback_count_); callback_count_ = 0; ASSERT_FALSE(adapter_->IsDiscovering()); - - adapter_->RemoveObserver(&observer); } // Run a discovery phase so we have devices that can be paired with. @@ -318,6 +376,7 @@ class BluetoothChromeOSTest : public testing::Test { } protected: + base::MessageLoop message_loop_; FakeBluetoothAdapterClient* fake_bluetooth_adapter_client_; FakeBluetoothDeviceClient* fake_bluetooth_device_client_; scoped_refptr<BluetoothAdapter> adapter_; @@ -325,6 +384,17 @@ class BluetoothChromeOSTest : public testing::Test { int callback_count_; int error_callback_count_; enum BluetoothDevice::ConnectErrorCode last_connect_error_; + std::string last_client_error_; + ScopedVector<BluetoothDiscoverySession> discovery_sessions_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } }; TEST_F(BluetoothChromeOSTest, AlreadyPresent) { @@ -353,7 +423,6 @@ TEST_F(BluetoothChromeOSTest, BecomePresent) { // Install an observer; expect the AdapterPresentChanged to be called // with true, and IsPresent() to return true. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); fake_bluetooth_adapter_client_->SetVisible(true); @@ -381,7 +450,6 @@ TEST_F(BluetoothChromeOSTest, BecomeNotPresent) { // Install an observer; expect the AdapterPresentChanged to be called // with false, and IsPresent() to return false. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); fake_bluetooth_adapter_client_->SetVisible(false); @@ -409,7 +477,6 @@ TEST_F(BluetoothChromeOSTest, SecondAdapter) { // Install an observer, then add a second adapter. Nothing should change, // we ignore the second adapter. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); fake_bluetooth_adapter_client_->SetSecondVisible(true); @@ -456,7 +523,6 @@ TEST_F(BluetoothChromeOSTest, BecomePowered) { // Install an observer; expect the AdapterPoweredChanged to be called // with true, and IsPowered() to return true. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); adapter_->SetPowered( true, @@ -490,7 +556,6 @@ TEST_F(BluetoothChromeOSTest, BecomeNotPowered) { // Install an observer; expect the AdapterPoweredChanged to be called // with false, and IsPowered() to return false. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); adapter_->SetPowered( false, @@ -507,36 +572,33 @@ TEST_F(BluetoothChromeOSTest, BecomeNotPowered) { EXPECT_FALSE(adapter_->IsPowered()); } -TEST_F(BluetoothChromeOSTest, StopDiscovery) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); - +TEST_F(BluetoothChromeOSTest, ChangeAdapterName) { GetAdapter(); - adapter_->SetPowered( - true, - base::Bind(&BluetoothChromeOSTest::Callback, - base::Unretained(this)), - base::Bind(&BluetoothChromeOSTest::ErrorCallback, - base::Unretained(this))); - adapter_->StartDiscovering( + static const std::string new_name(".__."); + + adapter_->SetName( + new_name, base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); - EXPECT_EQ(2, callback_count_); + EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); - callback_count_ = 0; - ASSERT_TRUE(adapter_->IsPowered()); - ASSERT_TRUE(adapter_->IsDiscovering()); + EXPECT_EQ(new_name, adapter_->GetName()); +} - // Install an observer; aside from the callback, expect the - // AdapterDiscoveringChanged method to be called and no longer to be - // discovering, +TEST_F(BluetoothChromeOSTest, BecomeDiscoverable) { + GetAdapter(); + ASSERT_FALSE(adapter_->IsDiscoverable()); + + // Install an observer; expect the AdapterDiscoverableChanged to be called + // with true, and IsDiscoverable() to return true. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); - adapter_->StopDiscovering( + adapter_->SetDiscoverable( + true, base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, @@ -544,74 +606,78 @@ TEST_F(BluetoothChromeOSTest, StopDiscovery) { EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); - EXPECT_EQ(1, observer.discovering_changed_count_); - EXPECT_FALSE(observer.last_discovering_); + EXPECT_EQ(1, observer.discoverable_changed_count_); - EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_TRUE(adapter_->IsDiscoverable()); } -TEST_F(BluetoothChromeOSTest, StopDiscoveryAfterTwoStarts) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); - +TEST_F(BluetoothChromeOSTest, BecomeNotDiscoverable) { GetAdapter(); - - adapter_->SetPowered( + adapter_->SetDiscoverable( true, base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); - adapter_->StartDiscovering( - base::Bind(&BluetoothChromeOSTest::Callback, - base::Unretained(this)), - base::Bind(&BluetoothChromeOSTest::ErrorCallback, - base::Unretained(this))); - EXPECT_EQ(2, callback_count_); + EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); callback_count_ = 0; - ASSERT_TRUE(adapter_->IsPowered()); - ASSERT_TRUE(adapter_->IsDiscovering()); + ASSERT_TRUE(adapter_->IsDiscoverable()); - // Install an observer and start discovering again; only the callback - // should be called since we were already discovering to begin with. + // Install an observer; expect the AdapterDiscoverableChanged to be called + // with false, and IsDiscoverable() to return false. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); - adapter_->StartDiscovering( + adapter_->SetDiscoverable( + false, base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); - callback_count_ = 0; - EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_EQ(1, observer.discoverable_changed_count_); + + EXPECT_FALSE(adapter_->IsDiscoverable()); +} + +TEST_F(BluetoothChromeOSTest, StopDiscovery) { + GetAdapter(); - // Stop discovering; only the callback should be called since we're still - // discovering. The adapter should be still discovering. - adapter_->StopDiscovering( + adapter_->SetPowered( + true, base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); - EXPECT_EQ(1, callback_count_); + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(2, callback_count_); EXPECT_EQ(0, error_callback_count_); callback_count_ = 0; - EXPECT_EQ(0, observer.discovering_changed_count_); - - EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + ASSERT_TRUE(discovery_sessions_[0]->IsActive()); - // Stop discovering one more time; aside from the callback, expect the + // Install an observer; aside from the callback, expect the // AdapterDiscoveringChanged method to be called and no longer to be // discovering, - adapter_->StopDiscovering( + TestObserver observer(adapter_); + + discovery_sessions_[0]->Stop( base::Bind(&BluetoothChromeOSTest::Callback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); + message_loop_.Run(); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); @@ -623,13 +689,10 @@ TEST_F(BluetoothChromeOSTest, StopDiscoveryAfterTwoStarts) { TEST_F(BluetoothChromeOSTest, Discovery) { // Test a simulated discovery session. - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); - fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); adapter_->SetPowered( true, @@ -637,32 +700,35 @@ TEST_F(BluetoothChromeOSTest, Discovery) { base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); - adapter_->StartDiscovering( - base::Bind(&BluetoothChromeOSTest::Callback, + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); + message_loop_.Run(); EXPECT_EQ(2, callback_count_); EXPECT_EQ(0, error_callback_count_); callback_count_ = 0; ASSERT_TRUE(adapter_->IsPowered()); ASSERT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + ASSERT_TRUE(discovery_sessions_[0]->IsActive()); - // First device to appear should be an Apple Mouse. - message_loop.Run(); + // First two devices to appear. + message_loop_.Run(); - EXPECT_EQ(1, observer.device_added_count_); - EXPECT_EQ(FakeBluetoothDeviceClient::kAppleMouseAddress, + EXPECT_EQ(2, observer.device_added_count_); + EXPECT_EQ(FakeBluetoothDeviceClient::kLowEnergyAddress, observer.last_device_address_); // Next we should get another two devices... - message_loop.Run(); - EXPECT_EQ(3, observer.device_added_count_); + message_loop_.Run(); + EXPECT_EQ(4, observer.device_added_count_); // Okay, let's run forward until a device is actually removed... while (!observer.device_removed_count_) - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(1, observer.device_removed_count_); EXPECT_EQ(FakeBluetoothDeviceClient::kVanishingDeviceAddress, @@ -670,8 +736,6 @@ TEST_F(BluetoothChromeOSTest, Discovery) { } TEST_F(BluetoothChromeOSTest, PoweredAndDiscovering) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); - GetAdapter(); adapter_->SetPowered( true, @@ -679,14 +743,17 @@ TEST_F(BluetoothChromeOSTest, PoweredAndDiscovering) { base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); - adapter_->StartDiscovering( - base::Bind(&BluetoothChromeOSTest::Callback, + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, base::Unretained(this)), base::Bind(&BluetoothChromeOSTest::ErrorCallback, base::Unretained(this))); + message_loop_.Run(); EXPECT_EQ(2, callback_count_); EXPECT_EQ(0, error_callback_count_); callback_count_ = 0; + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + ASSERT_TRUE(discovery_sessions_[0]->IsActive()); // Stop the timers that the simulation uses fake_bluetooth_device_client_->EndDiscoverySimulation( @@ -697,13 +764,13 @@ TEST_F(BluetoothChromeOSTest, PoweredAndDiscovering) { fake_bluetooth_adapter_client_->SetVisible(false); ASSERT_FALSE(adapter_->IsPresent()); + ASSERT_FALSE(discovery_sessions_[0]->IsActive()); // Install an observer; expect the AdapterPresentChanged, // AdapterPoweredChanged and AdapterDiscoveringChanged methods to be called // with true, and IsPresent(), IsPowered() and IsDiscovering() to all // return true. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); fake_bluetooth_adapter_client_->SetVisible(true); @@ -740,6 +807,635 @@ TEST_F(BluetoothChromeOSTest, PoweredAndDiscovering) { EXPECT_FALSE(adapter_->IsDiscovering()); } +// This unit test asserts that the basic reference counting logic works +// correctly for discovery requests done via the BluetoothAdapter. +TEST_F(BluetoothChromeOSTest, MultipleDiscoverySessions) { + GetAdapter(); + adapter_->SetPowered( + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + callback_count_ = 0; + + TestObserver observer(adapter_); + + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // Request device discovery 3 times. + for (int i = 0; i < 3; i++) { + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + // Run only once, as there should have been one D-Bus call. + message_loop_.Run(); + + // The observer should have received the discovering changed event exactly + // once, the success callback should have been called 3 times and the adapter + // should be discovering. + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(3, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + + // Request to stop discovery twice. + for (int i = 0; i < 2; i++) { + discovery_sessions_[i]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + + // The observer should have received no additional discovering changed events, + // the success callback should have been called 2 times and the adapter should + // still be discovering. + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(5, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + EXPECT_TRUE(adapter_->IsDiscovering()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); + EXPECT_FALSE(discovery_sessions_[1]->IsActive()); + EXPECT_TRUE(discovery_sessions_[2]->IsActive()); + + // Request device discovery 3 times. + for (int i = 0; i < 3; i++) { + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + + // The observer should have received no additional discovering changed events, + // the success callback should have been called 3 times and the adapter should + // still be discovering. + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(8, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)6, discovery_sessions_.size()); + + // Request to stop discovery 4 times. + for (int i = 2; i < 6; i++) { + discovery_sessions_[i]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + // Run only once, as there should have been one D-Bus call. + message_loop_.Run(); + + // The observer should have received the discovering changed event exactly + // once, the success callback should have been called 4 times and the adapter + // should no longer be discovering. + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_EQ(12, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // All discovery sessions should be inactive. + for (int i = 0; i < 6; i++) + EXPECT_FALSE(discovery_sessions_[i]->IsActive()); + + // Request to stop discovery on of the inactive sessions. + discovery_sessions_[0]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + + // The call should have failed. + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_EQ(12, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); +} + +// This unit test asserts that the reference counting logic works correctly in +// the cases when the adapter gets reset and D-Bus calls are made outside of +// the BluetoothAdapter. +TEST_F(BluetoothChromeOSTest, + UnexpectedChangesDuringMultipleDiscoverySessions) { + GetAdapter(); + adapter_->SetPowered( + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + callback_count_ = 0; + + TestObserver observer(adapter_); + + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // Request device discovery 3 times. + for (int i = 0; i < 3; i++) { + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + // Run only once, as there should have been one D-Bus call. + message_loop_.Run(); + + // The observer should have received the discovering changed event exactly + // once, the success callback should have been called 3 times and the adapter + // should be discovering. + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(3, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + + for (int i = 0; i < 3; i++) + EXPECT_TRUE(discovery_sessions_[i]->IsActive()); + + // Stop the timers that the simulation uses + fake_bluetooth_device_client_->EndDiscoverySimulation( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath)); + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + // Stop device discovery behind the adapter. The adapter and the observer + // should be notified of the change and the reference count should be reset. + // Even though FakeBluetoothAdapterClient does its own reference counting and + // we called 3 BluetoothAdapter::StartDiscoverySession 3 times, the + // FakeBluetoothAdapterClient's count should be only 1 and a single call to + // FakeBluetoothAdapterClient::StopDiscovery should work. + fake_bluetooth_adapter_client_->StopDiscovery( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_EQ(4, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // All discovery session instances should have been updated. + for (int i = 0; i < 3; i++) + EXPECT_FALSE(discovery_sessions_[i]->IsActive()); + discovery_sessions_.clear(); + + // It should be possible to successfully start discovery. + for (int i = 0; i < 2; i++) { + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + // Run only once, as there should have been one D-Bus call. + message_loop_.Run(); + EXPECT_EQ(3, observer.discovering_changed_count_); + EXPECT_EQ(6, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)2, discovery_sessions_.size()); + + for (int i = 0; i < 2; i++) + EXPECT_TRUE(discovery_sessions_[i]->IsActive()); + + fake_bluetooth_device_client_->EndDiscoverySimulation( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath)); + + // Make the adapter disappear and appear. This will make it come back as + // discovering. When this happens, the reference count should become and + // remain 0 as no new request was made through the BluetoothAdapter. + fake_bluetooth_adapter_client_->SetVisible(false); + ASSERT_FALSE(adapter_->IsPresent()); + EXPECT_EQ(4, observer.discovering_changed_count_); + EXPECT_EQ(6, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + for (int i = 0; i < 2; i++) + EXPECT_FALSE(discovery_sessions_[i]->IsActive()); + discovery_sessions_.clear(); + + fake_bluetooth_adapter_client_->SetVisible(true); + ASSERT_TRUE(adapter_->IsPresent()); + EXPECT_EQ(5, observer.discovering_changed_count_); + EXPECT_EQ(6, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + + // Start and stop discovery. At this point, FakeBluetoothAdapterClient has + // a reference count that is equal to 1. Pretend that this was done by an + // application other than us. Starting and stopping discovery will succeed + // but it won't cause the discovery state to change. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); // Run the loop, as there should have been a D-Bus call. + EXPECT_EQ(5, observer.discovering_changed_count_); + EXPECT_EQ(7, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[0]->IsActive()); + + discovery_sessions_[0]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); // Run the loop, as there should have been a D-Bus call. + EXPECT_EQ(5, observer.discovering_changed_count_); + EXPECT_EQ(8, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); + discovery_sessions_.clear(); + + // Start discovery again. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); // Run the loop, as there should have been a D-Bus call. + EXPECT_EQ(5, observer.discovering_changed_count_); + EXPECT_EQ(9, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[0]->IsActive()); + + // Stop discovery via D-Bus. The fake client's reference count will drop but + // the discovery state won't change since our BluetoothAdapter also just + // requested it via D-Bus. + fake_bluetooth_adapter_client_->StopDiscovery( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(5, observer.discovering_changed_count_); + EXPECT_EQ(10, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + + // Now end the discovery session. This should change the adapter's discovery + // state. + discovery_sessions_[0]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(6, observer.discovering_changed_count_); + EXPECT_EQ(11, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); +} + +TEST_F(BluetoothChromeOSTest, InvalidatedDiscoverySessions) { + GetAdapter(); + adapter_->SetPowered( + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + callback_count_ = 0; + + TestObserver observer(adapter_); + + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // Request device discovery 3 times. + for (int i = 0; i < 3; i++) { + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + // Run only once, as there should have been one D-Bus call. + message_loop_.Run(); + + // The observer should have received the discovering changed event exactly + // once, the success callback should have been called 3 times and the adapter + // should be discovering. + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(3, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + + for (int i = 0; i < 3; i++) + EXPECT_TRUE(discovery_sessions_[i]->IsActive()); + + // Stop the timers that the simulation uses + fake_bluetooth_device_client_->EndDiscoverySimulation( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath)); + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + // Delete all but one discovery session. + discovery_sessions_.pop_back(); + discovery_sessions_.pop_back(); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[0]->IsActive()); + EXPECT_TRUE(adapter_->IsDiscovering()); + + // Stop device discovery behind the adapter. The one active discovery session + // should become inactive, but more importantly, we shouldn't run into any + // memory errors as the sessions that we explicitly deleted should get + // cleaned up. + fake_bluetooth_adapter_client_->StopDiscovery( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_EQ(4, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); +} + +TEST_F(BluetoothChromeOSTest, QueuedDiscoveryRequests) { + GetAdapter(); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + callback_count_ = 0; + + TestObserver observer(adapter_); + + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // Request to start discovery. The call should be pending. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, callback_count_); + + fake_bluetooth_device_client_->EndDiscoverySimulation( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath)); + + // The underlying adapter has started discovery, but our call hasn't returned + // yet. + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + EXPECT_TRUE(discovery_sessions_.empty()); + + // Request to start discovery twice. These should get queued and there should + // be no change in state. + for (int i = 0; i < 2; i++) { + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + EXPECT_TRUE(discovery_sessions_.empty()); + + // Process the pending call. The queued calls should execute and the discovery + // session reference count should increase. + message_loop_.Run(); + EXPECT_EQ(3, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + + // Verify the reference count by removing sessions 3 times. The last request + // should remain pending. + for (int i = 0; i < 3; i++) { + discovery_sessions_[i]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + } + EXPECT_EQ(5, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); + EXPECT_FALSE(discovery_sessions_[1]->IsActive()); + EXPECT_TRUE(discovery_sessions_[2]->IsActive()); + + // Request to stop the session whose call is pending should fail. + discovery_sessions_[2]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(5, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_TRUE(discovery_sessions_[2]->IsActive()); + + // Request to start should get queued. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(5, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + + // Run the pending request. + message_loop_.Run(); + EXPECT_EQ(6, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(3, observer.discovering_changed_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + EXPECT_FALSE(discovery_sessions_[2]->IsActive()); + + // The queued request to start discovery should have been issued but is still + // pending. Run the loop and verify. + message_loop_.Run(); + EXPECT_EQ(7, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(3, observer.discovering_changed_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)4, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[3]->IsActive()); +} + +TEST_F(BluetoothChromeOSTest, StartDiscoverySession) { + GetAdapter(); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + callback_count_ = 0; + + TestObserver observer(adapter_); + + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_TRUE(discovery_sessions_.empty()); + + // Request a new discovery session. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[0]->IsActive()); + + // Start another session. A new one should be returned in the callback, which + // in turn will destroy the previous session. Adapter should still be + // discovering and the reference count should be 1. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(2, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)2, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[0]->IsActive()); + + // Request a new session. + adapter_->StartDiscoverySession( + base::Bind(&BluetoothChromeOSTest::DiscoverySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(3, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[1]->IsActive()); + EXPECT_NE(discovery_sessions_[0], discovery_sessions_[1]); + + // Stop the previous discovery session. The session should end but discovery + // should continue. + discovery_sessions_[0]->Stop( + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_EQ(4, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + ASSERT_EQ((size_t)3, discovery_sessions_.size()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); + EXPECT_TRUE(discovery_sessions_[1]->IsActive()); + + // Delete the current active session. Discovery should eventually stop. + discovery_sessions_.clear(); + while (observer.last_discovering_) + message_loop_.RunUntilIdle(); + + EXPECT_EQ(2, observer.discovering_changed_count_); + EXPECT_EQ(4, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); +} + TEST_F(BluetoothChromeOSTest, DeviceProperties) { GetAdapter(); @@ -749,7 +1445,7 @@ TEST_F(BluetoothChromeOSTest, DeviceProperties) { devices[0]->GetAddress()); // Verify the other device properties. - EXPECT_EQ(UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName), + EXPECT_EQ(base::UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName), devices[0]->GetName()); EXPECT_EQ(BluetoothDevice::DEVICE_COMPUTER, devices[0]->GetDeviceType()); EXPECT_TRUE(devices[0]->IsPaired()); @@ -759,11 +1455,12 @@ TEST_F(BluetoothChromeOSTest, DeviceProperties) { // Non HID devices are always connectable. EXPECT_TRUE(devices[0]->IsConnectable()); - BluetoothDevice::ServiceList uuids = devices[0]->GetServices(); + BluetoothDevice::UUIDList uuids = devices[0]->GetUUIDs(); ASSERT_EQ(2U, uuids.size()); - EXPECT_EQ(uuids[0], "00001800-0000-1000-8000-00805f9b34fb"); - EXPECT_EQ(uuids[1], "00001801-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[0], BluetoothUUID("1800")); + EXPECT_EQ(uuids[1], BluetoothUUID("1801")); + EXPECT_EQ(BluetoothDevice::VENDOR_ID_USB, devices[0]->GetVendorIDSource()); EXPECT_EQ(0x05ac, devices[0]->GetVendorID()); EXPECT_EQ(0x030d, devices[0]->GetProductID()); EXPECT_EQ(0x0306, devices[0]->GetDeviceID()); @@ -783,7 +1480,6 @@ TEST_F(BluetoothChromeOSTest, DeviceClassChanged) { // Install an observer; expect the DeviceChanged method to be called when // we change the class of the device. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( @@ -805,13 +1501,12 @@ TEST_F(BluetoothChromeOSTest, DeviceNameChanged) { ASSERT_EQ(1U, devices.size()); ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, devices[0]->GetAddress()); - ASSERT_EQ(UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName), + ASSERT_EQ(base::UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName), devices[0]->GetName()); // Install an observer; expect the DeviceChanged method to be called when // we change the alias of the device. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( @@ -823,7 +1518,7 @@ TEST_F(BluetoothChromeOSTest, DeviceNameChanged) { EXPECT_EQ(1, observer.device_changed_count_); EXPECT_EQ(devices[0], observer.last_device_); - EXPECT_EQ(UTF8ToUTF16(new_name), devices[0]->GetName()); + EXPECT_EQ(base::UTF8ToUTF16(new_name), devices[0]->GetName()); } TEST_F(BluetoothChromeOSTest, DeviceUuidsChanged) { @@ -835,37 +1530,39 @@ TEST_F(BluetoothChromeOSTest, DeviceUuidsChanged) { ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, devices[0]->GetAddress()); - BluetoothDevice::ServiceList uuids = devices[0]->GetServices(); + BluetoothDevice::UUIDList uuids = devices[0]->GetUUIDs(); ASSERT_EQ(2U, uuids.size()); - ASSERT_EQ(uuids[0], "00001800-0000-1000-8000-00805f9b34fb"); - ASSERT_EQ(uuids[1], "00001801-0000-1000-8000-00805f9b34fb"); + ASSERT_EQ(uuids[0], BluetoothUUID("1800")); + ASSERT_EQ(uuids[1], BluetoothUUID("1801")); // Install an observer; expect the DeviceChanged method to be called when // we change the class of the device. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( dbus::ObjectPath(FakeBluetoothDeviceClient::kPairedDevicePath)); - uuids.push_back("0000110c-0000-1000-8000-00805f9b34fb"); - uuids.push_back("0000110e-0000-1000-8000-00805f9b34fb"); - uuids.push_back("0000110a-0000-1000-8000-00805f9b34fb"); + std::vector<std::string> new_uuids; + new_uuids.push_back(uuids[0].canonical_value()); + new_uuids.push_back(uuids[1].canonical_value()); + new_uuids.push_back("0000110c-0000-1000-8000-00805f9b34fb"); + new_uuids.push_back("0000110e-0000-1000-8000-00805f9b34fb"); + new_uuids.push_back("0000110a-0000-1000-8000-00805f9b34fb"); - properties->uuids.ReplaceValue(uuids); + properties->uuids.ReplaceValue(new_uuids); EXPECT_EQ(1, observer.device_changed_count_); EXPECT_EQ(devices[0], observer.last_device_); // Fetching the value should give the new one. - uuids = devices[0]->GetServices(); + uuids = devices[0]->GetUUIDs(); ASSERT_EQ(5U, uuids.size()); - EXPECT_EQ(uuids[0], "00001800-0000-1000-8000-00805f9b34fb"); - EXPECT_EQ(uuids[1], "00001801-0000-1000-8000-00805f9b34fb"); - EXPECT_EQ(uuids[2], "0000110c-0000-1000-8000-00805f9b34fb"); - EXPECT_EQ(uuids[3], "0000110e-0000-1000-8000-00805f9b34fb"); - EXPECT_EQ(uuids[4], "0000110a-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[0], BluetoothUUID("1800")); + EXPECT_EQ(uuids[1], BluetoothUUID("1801")); + EXPECT_EQ(uuids[2], BluetoothUUID("110c")); + EXPECT_EQ(uuids[3], BluetoothUUID("110e")); + EXPECT_EQ(uuids[4], BluetoothUUID("110a")); } TEST_F(BluetoothChromeOSTest, ForgetDevice) { @@ -881,7 +1578,6 @@ TEST_F(BluetoothChromeOSTest, ForgetDevice) { // Install an observer; expect the DeviceRemoved method to be called // with the device we remove. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); devices[0]->Forget( base::Bind(&BluetoothChromeOSTest::ErrorCallback, @@ -901,7 +1597,7 @@ TEST_F(BluetoothChromeOSTest, ForgetUnpairedDevice) { DiscoverDevices(); BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kMicrosoftMouseAddress); + FakeBluetoothDeviceClient::kConnectUnpairableAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); @@ -923,13 +1619,12 @@ TEST_F(BluetoothChromeOSTest, ForgetUnpairedDevice) { // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kMicrosoftMousePath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kConnectUnpairablePath)); ASSERT_TRUE(properties->trusted.value()); // Install an observer; expect the DeviceRemoved method to be called // with the device we remove. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); device->Forget( base::Bind(&BluetoothChromeOSTest::ErrorCallback, @@ -937,12 +1632,12 @@ TEST_F(BluetoothChromeOSTest, ForgetUnpairedDevice) { EXPECT_EQ(0, error_callback_count_); EXPECT_EQ(1, observer.device_removed_count_); - EXPECT_EQ(FakeBluetoothDeviceClient::kMicrosoftMouseAddress, + EXPECT_EQ(FakeBluetoothDeviceClient::kConnectUnpairableAddress, observer.last_device_address_); // GetDevices shouldn't return the device either. device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kMicrosoftMouseAddress); + FakeBluetoothDeviceClient::kConnectUnpairableAddress); EXPECT_FALSE(device != NULL); } @@ -955,7 +1650,6 @@ TEST_F(BluetoothChromeOSTest, ConnectPairedDevice) { ASSERT_TRUE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); // Connect without a pairing delegate; since the device is already Paired // this should succeed and the device should become connected. @@ -983,12 +1677,11 @@ TEST_F(BluetoothChromeOSTest, ConnectUnpairableDevice) { DiscoverDevices(); BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kMicrosoftMouseAddress); + FakeBluetoothDeviceClient::kConnectUnpairableAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); // Connect without a pairing delegate; since the device does not require // pairing, this should succeed and the device should become connected. @@ -1013,13 +1706,13 @@ TEST_F(BluetoothChromeOSTest, ConnectUnpairableDevice) { // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kMicrosoftMousePath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kConnectUnpairablePath)); EXPECT_TRUE(properties->trusted.value()); // Verify is a HID device and is not connectable. - BluetoothDevice::ServiceList uuids = device->GetServices(); + BluetoothDevice::UUIDList uuids = device->GetUUIDs(); ASSERT_EQ(1U, uuids.size()); - EXPECT_EQ(uuids[0], "00001124-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[0], BluetoothUUID("1124")); EXPECT_FALSE(device->IsConnectable()); } @@ -1047,7 +1740,6 @@ TEST_F(BluetoothChromeOSTest, ConnectConnectedDevice) { // Connect again; since the device is already Connected, this shouldn't do // anything to initiate the connection. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); device->Connect( NULL, @@ -1072,12 +1764,11 @@ TEST_F(BluetoothChromeOSTest, ConnectDeviceFails) { DiscoverDevices(); BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kAppleMouseAddress); + FakeBluetoothDeviceClient::kLegacyAutopairAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); // Connect without a pairing delegate; since the device requires pairing, // this should fail with an error. @@ -1123,7 +1814,6 @@ TEST_F(BluetoothChromeOSTest, DisconnectDevice) { // Disconnect the device, we should see the observer method fire and the // device get dropped. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); device->Disconnect( base::Bind(&BluetoothChromeOSTest::Callback, @@ -1152,7 +1842,6 @@ TEST_F(BluetoothChromeOSTest, DisconnectUnconnectedDevice) { // Disconnect the device, we should see the observer method fire and the // device get dropped. TestObserver observer(adapter_); - adapter_->AddObserver(&observer); device->Disconnect( base::Bind(&BluetoothChromeOSTest::Callback, @@ -1168,22 +1857,20 @@ TEST_F(BluetoothChromeOSTest, DisconnectUnconnectedDevice) { EXPECT_FALSE(device->IsConnected()); } -TEST_F(BluetoothChromeOSTest, PairAppleMouse) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); +TEST_F(BluetoothChromeOSTest, PairLegacyAutopair) { fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); DiscoverDevices(); - // The Apple Mouse requires no PIN or Passkey to pair; this is equivalent - // to Simple Secure Pairing or a device with a fixed 0000 PIN. + // The Legacy Autopair device requires no PIN or Passkey to pair because + // the daemon provides 0000 to the device for us. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kAppleMouseAddress); + FakeBluetoothDeviceClient::kLegacyAutopairAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1196,7 +1883,7 @@ TEST_F(BluetoothChromeOSTest, PairAppleMouse) { EXPECT_EQ(0, pairing_delegate.call_count_); EXPECT_TRUE(device->IsConnecting()); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); @@ -1213,38 +1900,31 @@ TEST_F(BluetoothChromeOSTest, PairAppleMouse) { EXPECT_TRUE(device->IsPaired()); // Verify is a HID device and is connectable. - BluetoothDevice::ServiceList uuids = device->GetServices(); + BluetoothDevice::UUIDList uuids = device->GetUUIDs(); ASSERT_EQ(1U, uuids.size()); - EXPECT_EQ(uuids[0], "00001124-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[0], BluetoothUUID("1124")); EXPECT_TRUE(device->IsConnectable()); - // Pairing dialog should be dismissed - EXPECT_EQ(1, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); - // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kAppleMousePath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kLegacyAutopairPath)); EXPECT_TRUE(properties->trusted.value()); } -TEST_F(BluetoothChromeOSTest, PairAppleKeyboard) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); +TEST_F(BluetoothChromeOSTest, PairDisplayPinCode) { fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); DiscoverDevices(); - // The Apple Keyboard requires that we display a randomly generated - // PIN on the screen. + // Requires that we display a randomly generated PIN on the screen. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kAppleKeyboardAddress); + FakeBluetoothDeviceClient::kDisplayPinCodeAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1259,7 +1939,7 @@ TEST_F(BluetoothChromeOSTest, PairAppleKeyboard) { EXPECT_EQ("123456", pairing_delegate.last_pincode_); EXPECT_TRUE(device->IsConnecting()); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); @@ -1276,37 +1956,32 @@ TEST_F(BluetoothChromeOSTest, PairAppleKeyboard) { EXPECT_TRUE(device->IsPaired()); // Verify is a HID device and is connectable. - BluetoothDevice::ServiceList uuids = device->GetServices(); + BluetoothDevice::UUIDList uuids = device->GetUUIDs(); ASSERT_EQ(1U, uuids.size()); - EXPECT_EQ(uuids[0], "00001124-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[0], BluetoothUUID("1124")); EXPECT_TRUE(device->IsConnectable()); - // Pairing dialog should be dismissed - EXPECT_EQ(1, pairing_delegate.dismiss_count_); - // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kAppleKeyboardPath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kDisplayPinCodePath)); EXPECT_TRUE(properties->trusted.value()); } -TEST_F(BluetoothChromeOSTest, PairMotorolaKeyboard) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); +TEST_F(BluetoothChromeOSTest, PairDisplayPasskey) { fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); DiscoverDevices(); - // The Motorola Keyboard requires that we display a randomly generated - // Passkey on the screen, and notifies us as it's typed in. + // Requires that we display a randomly generated Passkey on the screen, + // and notifies us as it's typed in. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kMotorolaKeyboardAddress); + FakeBluetoothDeviceClient::kDisplayPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1327,18 +2002,18 @@ TEST_F(BluetoothChromeOSTest, PairMotorolaKeyboard) { // One call to KeysEntered() for each key, including [enter]. for(int i = 1; i <= 7; ++i) { - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(2 + i, pairing_delegate.call_count_); EXPECT_EQ(1 + i, pairing_delegate.keys_entered_count_); EXPECT_EQ(static_cast<uint32_t>(i), pairing_delegate.last_entered_); } - message_loop.Run(); + message_loop_.Run(); - // 8 KeysEntered notifications (0 to 7, inclusive). Two aditional calls for - // DisplayPasskey() and DismissDisplayOrConfirm(). - EXPECT_EQ(10, pairing_delegate.call_count_); + // 8 KeysEntered notifications (0 to 7, inclusive) and one aditional call for + // DisplayPasskey(). + EXPECT_EQ(9, pairing_delegate.call_count_); EXPECT_EQ(8, pairing_delegate.keys_entered_count_); EXPECT_EQ(7U, pairing_delegate.last_entered_); @@ -1357,38 +2032,33 @@ TEST_F(BluetoothChromeOSTest, PairMotorolaKeyboard) { EXPECT_TRUE(device->IsPaired()); // Verify is a HID device. - BluetoothDevice::ServiceList uuids = device->GetServices(); + BluetoothDevice::UUIDList uuids = device->GetUUIDs(); ASSERT_EQ(1U, uuids.size()); - EXPECT_EQ(uuids[0], "00001124-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[0], BluetoothUUID("1124")); - // Fake MotorolaKeyboard is not connectable. + // And usually not connectable. EXPECT_FALSE(device->IsConnectable()); - // Pairing dialog should be dismissed - EXPECT_EQ(1, pairing_delegate.dismiss_count_); - // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kMotorolaKeyboardPath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kDisplayPasskeyPath)); EXPECT_TRUE(properties->trusted.value()); } -TEST_F(BluetoothChromeOSTest, PairSonyHeadphones) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); +TEST_F(BluetoothChromeOSTest, PairRequestPinCode) { fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); DiscoverDevices(); - // The Sony Headphones fake requires that the user enters a PIN for them. + // Requires that the user enters a PIN for them. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kSonyHeadphonesAddress); + FakeBluetoothDeviceClient::kRequestPinCodeAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1404,7 +2074,7 @@ TEST_F(BluetoothChromeOSTest, PairSonyHeadphones) { // Set the PIN. device->SetPinCode("1234"); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); @@ -1420,38 +2090,32 @@ TEST_F(BluetoothChromeOSTest, PairSonyHeadphones) { EXPECT_TRUE(device->IsPaired()); // Verify is not a HID device. - BluetoothDevice::ServiceList uuids = device->GetServices(); + BluetoothDevice::UUIDList uuids = device->GetUUIDs(); ASSERT_EQ(0U, uuids.size()); // Non HID devices are always connectable. EXPECT_TRUE(device->IsConnectable()); - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); - // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kSonyHeadphonesPath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath)); EXPECT_TRUE(properties->trusted.value()); } -TEST_F(BluetoothChromeOSTest, PairPhone) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); +TEST_F(BluetoothChromeOSTest, PairConfirmPasskey) { fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); DiscoverDevices(); - // The fake phone requests that we confirm a displayed passkey. + // Requests that we confirm a displayed passkey. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kPhoneAddress); + FakeBluetoothDeviceClient::kConfirmPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1468,7 +2132,7 @@ TEST_F(BluetoothChromeOSTest, PairPhone) { // Confirm the passkey. device->ConfirmPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); @@ -1486,34 +2150,27 @@ TEST_F(BluetoothChromeOSTest, PairPhone) { // Non HID devices are always connectable. EXPECT_TRUE(device->IsConnectable()); - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); - // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kPhonePath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath)); EXPECT_TRUE(properties->trusted.value()); } -TEST_F(BluetoothChromeOSTest, PairWeirdDevice) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); +TEST_F(BluetoothChromeOSTest, PairRequestPasskey) { fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); DiscoverDevices(); - // Use the "weird device" fake that requires that the user enters a Passkey, - // this would be some kind of device that has a display, but doesn't use - // "just works" - maybe a car? + // Requires that the user enters a Passkey, this would be some kind of + // device that has a display, but doesn't use "just works" - maybe a car? BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kWeirdDeviceAddress); + FakeBluetoothDeviceClient::kRequestPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1529,7 +2186,7 @@ TEST_F(BluetoothChromeOSTest, PairWeirdDevice) { // Set the Passkey. device->SetPasskey(1234); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(1, callback_count_); EXPECT_EQ(0, error_callback_count_); @@ -1547,19 +2204,64 @@ TEST_F(BluetoothChromeOSTest, PairWeirdDevice) { // Non HID devices are always connectable. EXPECT_TRUE(device->IsConnectable()); - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothChromeOSTest, PairJustWorks) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Uses just-works pairing, since this is an outgoing pairing, no delegate + // interaction is required. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kJustWorksAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, pairing_delegate.call_count_); + + message_loop_.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // Two changes for connecting, one change for connected, one for paired and + // two for trusted (after pairing and connection). + EXPECT_EQ(6, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Non HID devices are always connectable. + EXPECT_TRUE(device->IsConnectable()); // Make sure the trusted property has been set to true. FakeBluetoothDeviceClient::Properties* properties = fake_bluetooth_device_client_->GetProperties( - dbus::ObjectPath(FakeBluetoothDeviceClient::kWeirdDevicePath)); + dbus::ObjectPath(FakeBluetoothDeviceClient::kJustWorksPath)); EXPECT_TRUE(properties->trusted.value()); } TEST_F(BluetoothChromeOSTest, PairUnpairableDeviceFails) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1571,7 +2273,6 @@ TEST_F(BluetoothChromeOSTest, PairUnpairableDeviceFails) { ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1585,7 +2286,7 @@ TEST_F(BluetoothChromeOSTest, PairUnpairableDeviceFails) { EXPECT_TRUE(device->IsConnecting()); // Run the loop to get the error.. - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1595,14 +2296,9 @@ TEST_F(BluetoothChromeOSTest, PairUnpairableDeviceFails) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(1, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingFails) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1615,7 +2311,6 @@ TEST_F(BluetoothChromeOSTest, PairingFails) { ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1629,7 +2324,7 @@ TEST_F(BluetoothChromeOSTest, PairingFails) { EXPECT_TRUE(device->IsConnecting()); // Run the loop to get the error.. - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1639,14 +2334,9 @@ TEST_F(BluetoothChromeOSTest, PairingFails) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(1, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingFailsAtConnection) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1660,7 +2350,6 @@ TEST_F(BluetoothChromeOSTest, PairingFailsAtConnection) { ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1673,7 +2362,7 @@ TEST_F(BluetoothChromeOSTest, PairingFailsAtConnection) { EXPECT_EQ(0, pairing_delegate.call_count_); EXPECT_TRUE(device->IsConnecting()); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1689,10 +2378,6 @@ TEST_F(BluetoothChromeOSTest, PairingFailsAtConnection) { EXPECT_TRUE(device->IsPaired()); - // Pairing dialog should be dismissed - EXPECT_EQ(1, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); - // Make sure the trusted property has been set to true still (since pairing // worked). FakeBluetoothDeviceClient::Properties* properties = @@ -1703,7 +2388,6 @@ TEST_F(BluetoothChromeOSTest, PairingFailsAtConnection) { } TEST_F(BluetoothChromeOSTest, PairingRejectedAtPinCode) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1711,12 +2395,11 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtPinCode) { // Reject the pairing after we receive a request for the PIN code. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kSonyHeadphonesAddress); + FakeBluetoothDeviceClient::kRequestPinCodeAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1732,7 +2415,7 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtPinCode) { // Reject the pairing. device->RejectPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1743,14 +2426,9 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtPinCode) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingCancelledAtPinCode) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1758,12 +2436,11 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtPinCode) { // Cancel the pairing after we receive a request for the PIN code. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kSonyHeadphonesAddress); + FakeBluetoothDeviceClient::kRequestPinCodeAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1779,7 +2456,7 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtPinCode) { // Cancel the pairing. device->CancelPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1790,14 +2467,9 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtPinCode) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingRejectedAtPasskey) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1805,12 +2477,11 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtPasskey) { // Reject the pairing after we receive a request for the passkey. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kWeirdDeviceAddress); + FakeBluetoothDeviceClient::kRequestPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1826,7 +2497,7 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtPasskey) { // Reject the pairing. device->RejectPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1837,14 +2508,9 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtPasskey) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingCancelledAtPasskey) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1852,12 +2518,11 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtPasskey) { // Cancel the pairing after we receive a request for the passkey. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kWeirdDeviceAddress); + FakeBluetoothDeviceClient::kRequestPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1873,7 +2538,7 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtPasskey) { // Cancel the pairing. device->CancelPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1884,14 +2549,9 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtPasskey) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingRejectedAtConfirmation) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1899,12 +2559,11 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtConfirmation) { // Reject the pairing after we receive a request for passkey confirmation. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kPhoneAddress); + FakeBluetoothDeviceClient::kConfirmPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1920,7 +2579,7 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtConfirmation) { // Reject the pairing. device->RejectPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1931,14 +2590,9 @@ TEST_F(BluetoothChromeOSTest, PairingRejectedAtConfirmation) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingCancelledAtConfirmation) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1946,12 +2600,11 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtConfirmation) { // Cancel the pairing after we receive a request for the passkey. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kPhoneAddress); + FakeBluetoothDeviceClient::kConfirmPasskeyAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -1967,7 +2620,7 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtConfirmation) { // Cancel the pairing. device->CancelPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -1978,14 +2631,9 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledAtConfirmation) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); - - // Pairing dialog should be dismissed - EXPECT_EQ(2, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); } TEST_F(BluetoothChromeOSTest, PairingCancelledInFlight) { - base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT); fake_bluetooth_device_client_->SetSimulationIntervalMs(10); GetAdapter(); @@ -1993,12 +2641,11 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledInFlight) { // Cancel the pairing while we're waiting for the remote host. BluetoothDevice* device = adapter_->GetDevice( - FakeBluetoothDeviceClient::kAppleMouseAddress); + FakeBluetoothDeviceClient::kLegacyAutopairAddress); ASSERT_TRUE(device != NULL); ASSERT_FALSE(device->IsPaired()); TestObserver observer(adapter_); - adapter_->AddObserver(&observer); TestPairingDelegate pairing_delegate; device->Connect( @@ -2013,7 +2660,7 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledInFlight) { // Cancel the pairing. device->CancelPairing(); - message_loop.Run(); + message_loop_.Run(); EXPECT_EQ(0, callback_count_); EXPECT_EQ(1, error_callback_count_); @@ -2024,10 +2671,512 @@ TEST_F(BluetoothChromeOSTest, PairingCancelledInFlight) { EXPECT_FALSE(device->IsConnected()); EXPECT_FALSE(device->IsConnecting()); EXPECT_FALSE(device->IsPaired()); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairRequestPinCode) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + TestPairingDelegate pairing_delegate; + adapter_->AddPairingDelegate( + &pairing_delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); + + // Requires that we provide a PIN code. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kRequestPinCodeAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + + // Set the PIN. + device->SetPinCode("1234"); + message_loop_.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for paired, and one for trusted. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsPaired()); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath)); + ASSERT_TRUE(properties->trusted.value()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairConfirmPasskey) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + TestPairingDelegate pairing_delegate; + adapter_->AddPairingDelegate( + &pairing_delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); + + // Requests that we confirm a displayed passkey. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kConfirmPasskeyAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.confirm_passkey_count_); + EXPECT_EQ(123456U, pairing_delegate.last_passkey_); + + // Confirm the passkey. + device->ConfirmPairing(); + message_loop_.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for paired, and one for trusted. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsPaired()); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath)); + ASSERT_TRUE(properties->trusted.value()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairRequestPasskey) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + TestPairingDelegate pairing_delegate; + adapter_->AddPairingDelegate( + &pairing_delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); + + // Requests that we provide a Passkey. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kRequestPasskeyAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_passkey_count_); + + // Set the Passkey. + device->SetPasskey(1234); + message_loop_.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for paired, and one for trusted. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsPaired()); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath)); + ASSERT_TRUE(properties->trusted.value()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairJustWorks) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + TestPairingDelegate pairing_delegate; + adapter_->AddPairingDelegate( + &pairing_delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); + + // Uses just-works pairing so, sinec this an incoming pairing, require + // authorization from the user. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kJustWorksPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kJustWorksAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kJustWorksPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.authorize_pairing_count_); + + // Confirm the pairing. + device->ConfirmPairing(); + message_loop_.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for paired, and one for trusted. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsPaired()); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kJustWorksPath)); + ASSERT_TRUE(properties->trusted.value()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairRequestPinCodeWithoutDelegate) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + // Requires that we provide a PIN Code, without a pairing delegate, + // that will be rejected. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kRequestPinCodeAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(bluetooth_device::kErrorAuthenticationRejected, last_client_error_); + + // No changes should be observer. + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsPaired()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairConfirmPasskeyWithoutDelegate) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + // Requests that we confirm a displayed passkey, without a pairing delegate, + // that will be rejected. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kConfirmPasskeyAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(bluetooth_device::kErrorAuthenticationRejected, last_client_error_); + + // No changes should be observer. + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsPaired()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairRequestPasskeyWithoutDelegate) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + // Requests that we provide a displayed passkey, without a pairing delegate, + // that will be rejected. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kRequestPasskeyAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(bluetooth_device::kErrorAuthenticationRejected, last_client_error_); + + // No changes should be observer. + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsPaired()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, IncomingPairJustWorksWithoutDelegate) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + // Uses just-works pairing and thus requires authorization for incoming + // pairings, without a pairing delegate, that will be rejected. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kJustWorksPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kJustWorksAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kJustWorksPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(bluetooth_device::kErrorAuthenticationRejected, last_client_error_); + + // No changes should be observer. + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsPaired()); + + // No pairing context should remain on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); +} + +TEST_F(BluetoothChromeOSTest, RemovePairingDelegateDuringPairing) { + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + + TestPairingDelegate pairing_delegate; + adapter_->AddPairingDelegate( + &pairing_delegate, + BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); + + // Requests that we provide a Passkey. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kRequestPasskeyAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + + fake_bluetooth_device_client_->SimulatePairing( + dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPasskeyPath), + true, + base::Bind(&BluetoothChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothChromeOSTest::DBusErrorCallback, + base::Unretained(this))); - // Pairing dialog should be dismissed EXPECT_EQ(1, pairing_delegate.call_count_); - EXPECT_EQ(1, pairing_delegate.dismiss_count_); + EXPECT_EQ(1, pairing_delegate.request_passkey_count_); + + // A pairing context should now be set on the device. + BluetoothDeviceChromeOS* device_chromeos = + static_cast<BluetoothDeviceChromeOS*>(device); + ASSERT_TRUE(device_chromeos->GetPairing() != NULL); + + // Removing the pairing delegate should remove that pairing context. + adapter_->RemovePairingDelegate(&pairing_delegate); + + EXPECT_TRUE(device_chromeos->GetPairing() == NULL); + + // Set the Passkey, this should now have no effect since the pairing has + // been, in-effect, cancelled + device->SetPasskey(1234); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsPaired()); +} + +TEST_F(BluetoothChromeOSTest, DeviceId) { + GetAdapter(); + + // Use the built-in paired device for this test, grab its Properties + // structure so we can adjust the underlying modalias property. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kPairedDevicePath)); + + ASSERT_TRUE(device != NULL); + ASSERT_TRUE(properties != NULL); + + // Valid USB IF-assigned identifier. + ASSERT_EQ("usb:v05ACp030Dd0306", properties->modalias.value()); + + EXPECT_EQ(BluetoothDevice::VENDOR_ID_USB, device->GetVendorIDSource()); + EXPECT_EQ(0x05ac, device->GetVendorID()); + EXPECT_EQ(0x030d, device->GetProductID()); + EXPECT_EQ(0x0306, device->GetDeviceID()); + + // Valid Bluetooth SIG-assigned identifier. + properties->modalias.ReplaceValue("bluetooth:v00E0p2400d0400"); + + EXPECT_EQ(BluetoothDevice::VENDOR_ID_BLUETOOTH, device->GetVendorIDSource()); + EXPECT_EQ(0x00e0, device->GetVendorID()); + EXPECT_EQ(0x2400, device->GetProductID()); + EXPECT_EQ(0x0400, device->GetDeviceID()); + + // Invalid USB IF-assigned identifier. + properties->modalias.ReplaceValue("usb:x00E0p2400d0400"); + + EXPECT_EQ(BluetoothDevice::VENDOR_ID_UNKNOWN, device->GetVendorIDSource()); + EXPECT_EQ(0, device->GetVendorID()); + EXPECT_EQ(0, device->GetProductID()); + EXPECT_EQ(0, device->GetDeviceID()); + + // Invalid Bluetooth SIG-assigned identifier. + properties->modalias.ReplaceValue("bluetooth:x00E0p2400d0400"); + + EXPECT_EQ(BluetoothDevice::VENDOR_ID_UNKNOWN, device->GetVendorIDSource()); + EXPECT_EQ(0, device->GetVendorID()); + EXPECT_EQ(0, device->GetProductID()); + EXPECT_EQ(0, device->GetDeviceID()); + + // Unknown vendor specification identifier. + properties->modalias.ReplaceValue("chrome:v00E0p2400d0400"); + + EXPECT_EQ(BluetoothDevice::VENDOR_ID_UNKNOWN, device->GetVendorIDSource()); + EXPECT_EQ(0, device->GetVendorID()); + EXPECT_EQ(0, device->GetProductID()); + EXPECT_EQ(0, device->GetDeviceID()); } } // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_device.cc b/chromium/device/bluetooth/bluetooth_device.cc index 7b6ee0d927e..87c11901496 100644 --- a/chromium/device/bluetooth/bluetooth_device.cc +++ b/chromium/device/bluetooth/bluetooth_device.cc @@ -6,35 +6,32 @@ #include <string> +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" -#include "device/bluetooth/bluetooth_utils.h" +#include "device/bluetooth/bluetooth_gatt_service.h" #include "grit/device_bluetooth_strings.h" #include "ui/base/l10n/l10n_util.h" namespace device { -// static -bool BluetoothDevice::IsUUIDValid(const std::string& uuid) { - return !bluetooth_utils::CanonicalUuid(uuid).empty(); -} - BluetoothDevice::BluetoothDevice() { } BluetoothDevice::~BluetoothDevice() { + STLDeleteValues(&gatt_services_); } -string16 BluetoothDevice::GetName() const { +base::string16 BluetoothDevice::GetName() const { std::string name = GetDeviceName(); if (!name.empty()) { - return UTF8ToUTF16(name); + return base::UTF8ToUTF16(name); } else { return GetAddressWithLocalizedDeviceTypeName(); } } -string16 BluetoothDevice::GetAddressWithLocalizedDeviceTypeName() const { - string16 address_utf16 = UTF8ToUTF16(GetAddress()); +base::string16 BluetoothDevice::GetAddressWithLocalizedDeviceTypeName() const { + base::string16 address_utf16 = base::UTF8ToUTF16(GetAddress()); BluetoothDevice::DeviceType device_type = GetDeviceType(); switch (device_type) { case DEVICE_COMPUTER: @@ -165,26 +162,79 @@ bool BluetoothDevice::IsPairable() const { std::string vendor = GetAddress().substr(0, 8); // Verbatim "Bluetooth Mouse", model 96674 - if ((type == DEVICE_MOUSE && vendor == "00:12:A1") || + if (type == DEVICE_MOUSE && vendor == "00:12:A1") + return false; // Microsoft "Microsoft Bluetooth Notebook Mouse 5000", model X807028-001 - (type == DEVICE_MOUSE && vendor == "7C:ED:8D")) - return false; + if (type == DEVICE_MOUSE && vendor == "7C:ED:8D") + return false; + // Sony PlayStation Dualshock3 + if (IsTrustable()) + return false; + // TODO: Move this database into a config file. return true; } -bool BluetoothDevice::ProvidesServiceWithUUID( - const std::string& uuid) const { - std::string canonical_uuid = bluetooth_utils::CanonicalUuid(uuid); - BluetoothDevice::ServiceList services = GetServices(); - for (BluetoothDevice::ServiceList::const_iterator iter = services.begin(); - iter != services.end(); - ++iter) { - if (bluetooth_utils::CanonicalUuid(*iter) == canonical_uuid) - return true; - } +bool BluetoothDevice::IsTrustable() const { + // Sony PlayStation Dualshock3 + if ((GetVendorID() == 0x054c && GetProductID() == 0x0268 && + GetDeviceName() == "PLAYSTATION(R)3 Controller")) + return true; + return false; } +std::vector<BluetoothGattService*> + BluetoothDevice::GetGattServices() const { + std::vector<BluetoothGattService*> services; + for (GattServiceMap::const_iterator iter = gatt_services_.begin(); + iter != gatt_services_.end(); ++iter) + services.push_back(iter->second); + return services; +} + +BluetoothGattService* BluetoothDevice::GetGattService( + const std::string& identifier) const { + GattServiceMap::const_iterator iter = gatt_services_.find(identifier); + if (iter != gatt_services_.end()) + return iter->second; + return NULL; +} + +// static +std::string BluetoothDevice::CanonicalizeAddress(const std::string& address) { + std::string canonicalized = address; + if (address.size() == 12) { + // Might be an address in the format "1A2B3C4D5E6F". Add separators. + for (size_t i = 2; i < canonicalized.size(); i += 3) { + canonicalized.insert(i, ":"); + } + } + + // Verify that the length matches the canonical format "1A:2B:3C:4D:5E:6F". + const size_t kCanonicalAddressLength = 17; + if (canonicalized.size() != kCanonicalAddressLength) + return std::string(); + + const char separator = canonicalized[2]; + for (size_t i = 0; i < canonicalized.size(); ++i) { + bool is_separator = (i + 1) % 3 == 0; + if (is_separator) { + // All separators in the input |address| should be consistent. + if (canonicalized[i] != separator) + return std::string(); + + canonicalized[i] = ':'; + } else { + if (!IsHexDigit(canonicalized[i])) + return std::string(); + + canonicalized[i] = base::ToUpperASCII(canonicalized[i]); + } + } + + return canonicalized; +} + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_device.h b/chromium/device/bluetooth/bluetooth_device.h index aaf4e93c7f2..582caf34fd2 100644 --- a/chromium/device/bluetooth/bluetooth_device.h +++ b/chromium/device/bluetooth/bluetooth_device.h @@ -5,20 +5,23 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_H_ +#include <map> #include <string> +#include <vector> #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_vector.h" #include "base/strings/string16.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "net/base/net_log.h" namespace device { -class BluetoothProfile; -class BluetoothServiceRecord; +class BluetoothGattConnection; +class BluetoothGattService; class BluetoothSocket; - -struct BluetoothOutOfBandPairingData; +class BluetoothUUID; // BluetoothDevice represents a remote Bluetooth device, both its properties and // capabilities as discovered by a local adapter and actions that may be @@ -33,6 +36,15 @@ struct BluetoothOutOfBandPairingData; // for devices coming and going, as well as properties being updated. class BluetoothDevice { public: + // Possible values that may be returned by GetVendorIDSource(), + // indicating different organisations that allocate the identifiers returned + // by GetVendorID(). + enum VendorIDSource { + VENDOR_ID_UNKNOWN, + VENDOR_ID_BLUETOOTH, + VENDOR_ID_USB + }; + // Possible values that may be returned by GetDeviceType(), representing // different types of bluetooth device that we support or are aware of // decoded from the bluetooth class information. @@ -53,6 +65,9 @@ class BluetoothDevice { DEVICE_KEYBOARD_MOUSE_COMBO }; + // The value returned if the RSSI or transmit power cannot be read. + static const int kUnknownPower = 127; + // Possible errors passed back to an error callback function in case of a // failed call to Connect(). enum ConnectErrorCode { @@ -71,6 +86,18 @@ class BluetoothDevice { public: virtual ~Observer() {} + // Called when a new GATT service |service| is added to the device |device|, + // as the service is received from the device. Don't cache |service|. Store + // its identifier instead (i.e. BluetoothGattService::GetIdentifier). + virtual void GattServiceAdded(BluetoothDevice* device, + BluetoothGattService* service) {} + + // Called when the GATT service |service| is removed from the device + // |device|. This can happen if the attribute database of the remote device + // changes or when |device| gets removed. + virtual void GattServiceRemoved(BluetoothDevice* device, + BluetoothGattService* service) {} + // TODO(keybuk): add observers for pairing and connection. }; @@ -102,9 +129,7 @@ class BluetoothDevice { // This method will be called when the Bluetooth daemon requires that the // user enter the PIN code |pincode| into the device |device| so that it - // may be authenticated. The DismissDisplayOrConfirm() method - // will be called to dismiss the display once pairing is complete or - // cancelled. + // may be authenticated. // // This is used for Bluetooth 2.0 and earlier keyboard devices, the // |pincode| will always be a six-digit numeric in the range 000000-999999 @@ -114,8 +139,7 @@ class BluetoothDevice { // This method will be called when the Bluetooth daemon requires that the // user enter the Passkey |passkey| into the device |device| so that it - // may be authenticated. The DismissDisplayOrConfirm() method will be - // called to dismiss the display once pairing is complete or cancelled. + // may be authenticated. // // This is used for Bluetooth 2.1 and later devices that support input // but not display, such as keyboards. The Passkey is a numeric in the @@ -129,8 +153,7 @@ class BluetoothDevice { // using a PIN code or a Passkey. // // This method will be called only after DisplayPinCode() or - // DisplayPasskey() is called and before the corresponding - // DismissDisplayOrConfirm() is called, but is not warranted to be called + // DisplayPasskey() method is called, but is not warranted to be called // on every pairing process that requires a PIN code or a Passkey because // some device may not support this feature. // @@ -155,18 +178,24 @@ class BluetoothDevice { virtual void ConfirmPasskey(BluetoothDevice* device, uint32 passkey) = 0; - // This method will be called when any previous DisplayPinCode(), - // DisplayPasskey() or ConfirmPasskey() request should be concluded - // and removed from the user. - virtual void DismissDisplayOrConfirm() = 0; + // This method will be called when the Bluetooth daemon requires that a + // pairing request, usually only incoming, using the just-works model is + // authorized. The delegate should decide whether the user should confirm + // or not, then call ConfirmPairing() on the device to confirm the pairing + // (whether by user action or by default), RejectPairing() on the device to + // reject or CancelPairing() on the device to cancel authorization for + // any other reason. + virtual void AuthorizePairing(BluetoothDevice* device) = 0; }; - // Returns true if uuid is in a a valid canonical format - // (see utils::CanonicalUuid). - static bool IsUUIDValid(const std::string& uuid); - virtual ~BluetoothDevice(); + // Adds and removes observers for events on this Bluetooth device. If + // monitoring multiple devices, check the |device| parameter of the observer + // methods to determine which device is issuing the event. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + // Returns the Bluetooth class of the device, used by GetDeviceType() // and metrics logging, virtual uint32 GetBluetoothClass() const = 0; @@ -175,6 +204,10 @@ class BluetoothDevice { // a unique key to identify the device and copied where needed. virtual std::string GetAddress() const = 0; + // Returns the allocation source of the identifier returned by GetVendorID(), + // where available, or VENDOR_ID_UNKNOWN where not. + virtual VendorIDSource GetVendorIDSource() const = 0; + // Returns the Vendor ID of the device, where available. virtual uint16 GetVendorID() const = 0; @@ -188,7 +221,7 @@ class BluetoothDevice { // Returns the name of the device suitable for displaying, this may // be a synthesized string containing the address and localized type name // if the device has no obtained name. - virtual string16 GetName() const; + virtual base::string16 GetName() const; // Returns the type of the device, limited to those we support or are // aware of, by decoding the bluetooth class information. The returned @@ -196,6 +229,28 @@ class BluetoothDevice { // DEVICE_PERIPHERAL. DeviceType GetDeviceType() const; + // Gets the "received signal strength indication" (RSSI) of the current + // connection to the device. The RSSI indicates the power present in the + // received radio signal, measured in dBm, to a resolution of 1dBm. Larger + // (typically, less negative) values indicate a stronger signal. + // If the device is not currently connected, then returns the RSSI read from + // the last inquiry that returned the device, where available. In case of an + // error, returns |kUnknownPower|. Otherwise, returns the connection's RSSI. + virtual int GetRSSI() const = 0; + + // These two methods are used to read the current or maximum transmit power + // ("Tx power") of the current connection to the device. The transmit power + // indicates the strength of the signal broadcast from the host's Bluetooth + // antenna when communicating with the device, measured in dBm, to a + // resolution of 1dBm. Larger (typically, less negative) values + // indicate a stronger signal. + // It is only meaningful to call this method when there is a connection + // established to the device. If there is no connection, or in case of an + // error, returns |kUnknownPower|. Otherwise, returns the connection's + // transmit power. + virtual int GetCurrentHostTransmitPower() const = 0; + virtual int GetMaximumHostTransmitPower() const = 0; + // Indicates whether the device is known to support pairing based on its // device class and address. bool IsPairable() const; @@ -220,10 +275,16 @@ class BluetoothDevice { // were called after the corresponding call to Connect(). virtual bool IsConnecting() const = 0; - // Returns the services (as UUID strings) that this device provides. - // TODO(youngki): Rename this to GetProfiles(). - typedef std::vector<std::string> ServiceList; - virtual ServiceList GetServices() const = 0; + // Indicates whether the device can be trusted, based on device properties, + // such as vendor and product id. + bool IsTrustable() const; + + // Returns the set of UUIDs that this device supports. For classic Bluetooth + // devices this data is collected from both the EIR data and SDP tables, + // for Low Energy devices this data is collected from AD and GATT primary + // services, for dual mode devices this may be collected from both./ + typedef std::vector<BluetoothUUID> UUIDList; + virtual UUIDList GetUUIDs() const = 0; // The ErrorCallback is used for methods that can fail in which case it // is called, in the success case the callback is simply not called. @@ -234,25 +295,6 @@ class BluetoothDevice { // In the success case this callback is not called. typedef base::Callback<void(enum ConnectErrorCode)> ConnectErrorCallback; - // Returns the services (as BluetoothServiceRecord objects) that this device - // provides. - typedef ScopedVector<BluetoothServiceRecord> ServiceRecordList; - typedef base::Callback<void(const ServiceRecordList&)> ServiceRecordsCallback; - virtual void GetServiceRecords(const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Indicates whether this device provides the given service. - virtual bool ProvidesServiceWithUUID(const std::string& uuid) const; - - // The ProvidesServiceCallback is used by ProvidesServiceWithName to indicate - // whether or not a matching service was found. - typedef base::Callback<void(bool)> ProvidesServiceCallback; - - // Indicates whether this device provides the given service. - virtual void ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) = 0; - // Indicates whether the device is currently pairing and expecting a // PIN Code to be returned. virtual bool ExpectingPinCode() const = 0; @@ -265,12 +307,6 @@ class BluetoothDevice { // confirmation of a displayed passkey. virtual bool ExpectingConfirmation() const = 0; - // SocketCallback is used by ConnectToService to return a BluetoothSocket to - // the caller, or NULL if there was an error. The socket will remain open - // until the last reference to the returned BluetoothSocket is released. - typedef base::Callback<void(scoped_refptr<BluetoothSocket>)> - SocketCallback; - // Initiates a connection to the device, pairing first if necessary. // // Method calls will be made on the supplied object |pairing_delegate| @@ -283,7 +319,7 @@ class BluetoothDevice { // If the request fails, |error_callback| will be called; otherwise, // |callback| is called when the request is complete. // After calling Connect, CancelPairing should be called to cancel the pairing - // process and release |pairing_delegate_| if user cancels the pairing and + // process and release the pairing delegate if user cancels the pairing and // closes the pairing UI. virtual void Connect(PairingDelegate* pairing_delegate, const base::Closure& callback, @@ -309,8 +345,8 @@ class BluetoothDevice { // Rejects a pairing or connection request from a remote device. virtual void RejectPairing() = 0; - // Cancels a pairing or connection attempt to a remote device or release - // |pairing_deleage_| and |agent_|. + // Cancels a pairing or connection attempt to a remote device, releasing + // the pairing delegate. virtual void CancelPairing() = 0; // Disconnects the device, terminating the low-level ACL connection @@ -330,35 +366,55 @@ class BluetoothDevice { // before that callback would be called. virtual void Forget(const ErrorCallback& error_callback) = 0; - // Attempts to open a socket to a service matching |uuid| on this device. If - // the connection is successful, |callback| is called with a BluetoothSocket. - // Otherwise |callback| is called with NULL. The socket is closed as soon as - // all references to the BluetoothSocket are released. Note that the - // BluetoothSocket object can outlive both this BluetoothDevice and the - // BluetoothAdapter for this device. - virtual void ConnectToService(const std::string& service_uuid, - const SocketCallback& callback) = 0; - - // Attempts to initiate an outgoing connection to this device for the profile - // identified by |profile|, on success the profile's connection callback - // will be called as well as |callback|; on failure |error_callback| will be - // called. - virtual void ConnectToProfile(BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) = 0; - - // Sets the Out Of Band pairing data for this device to |data|. Exactly one - // of |callback| or |error_callback| will be run. - virtual void SetOutOfBandPairingData( - const BluetoothOutOfBandPairingData& data, - const base::Closure& callback, - const ErrorCallback& error_callback) = 0; - - // Clears the Out Of Band pairing data for this device. Exactly one of - // |callback| or |error_callback| will be run. - virtual void ClearOutOfBandPairingData( - const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + // Attempts to initiate an outgoing L2CAP or RFCOMM connection to the + // advertised service on this device matching |uuid|, performing an SDP lookup + // if necessary to determine the correct protocol and channel for the + // connection. |callback| will be called on a successful connection with a + // BluetoothSocket instance that is to be owned by the receiver. + // |error_callback| will be called on failure with a message indicating the + // cause. + typedef base::Callback<void(scoped_refptr<BluetoothSocket>)> + ConnectToServiceCallback; + typedef base::Callback<void(const std::string& message)> + ConnectToServiceErrorCallback; + virtual void ConnectToService( + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) = 0; + + // Opens a new GATT connection to this device. On success, a new + // BluetoothGattConnection will be handed to the caller via |callback|. On + // error, |error_callback| will be called. The connection will be kept alive, + // as long as there is at least one active GATT connection. In the case that + // the underlying connection gets terminated, either due to a call to + // BluetoothDevice::Disconnect or other unexpected circumstances, the + // returned BluetoothGattConnection will be automatically marked as inactive. + // To monitor the state of the connection, observe the + // BluetoothAdapter::Observer::DeviceChanged method. + typedef base::Callback<void(scoped_ptr<BluetoothGattConnection>)> + GattConnectionCallback; + virtual void CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) = 0; + + // Starts monitoring the connection properties, RSSI and TX power. These + // properties will be tracked, and updated when their values change. Exactly + // one of |callback| or |error_callback| will be run. + virtual void StartConnectionMonitor(const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + + // Returns the list of discovered GATT services. + virtual std::vector<BluetoothGattService*> GetGattServices() const; + + // Returns the GATT service with device-specific identifier |identifier|. + // Returns NULL, if no such service exists. + virtual BluetoothGattService* GetGattService( + const std::string& identifier) const; + + // Returns the |address| in the canoncial format: XX:XX:XX:XX:XX:XX, where + // each 'X' is a hex digit. If the input |address| is invalid, returns an + // empty string. + static std::string CanonicalizeAddress(const std::string& address); protected: BluetoothDevice(); @@ -366,10 +422,15 @@ class BluetoothDevice { // Returns the internal name of the Bluetooth device, used by GetName(). virtual std::string GetDeviceName() const = 0; + // Mapping from the platform-specific GATT service identifiers to + // BluetoothGattService objects. + typedef std::map<std::string, BluetoothGattService*> GattServiceMap; + GattServiceMap gatt_services_; + private: // Returns a localized string containing the device's bluetooth address and // a device type for display when |name_| is empty. - string16 GetAddressWithLocalizedDeviceTypeName() const; + base::string16 GetAddressWithLocalizedDeviceTypeName() const; }; } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_device_chromeos.cc b/chromium/device/bluetooth/bluetooth_device_chromeos.cc index 76e20972ad9..2045a1edb21 100644 --- a/chromium/device/bluetooth/bluetooth_device_chromeos.cc +++ b/chromium/device/bluetooth/bluetooth_device_chromeos.cc @@ -4,44 +4,35 @@ #include "device/bluetooth/bluetooth_device_chromeos.h" +#include <stdio.h> + #include "base/bind.h" +#include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "chromeos/dbus/bluetooth_adapter_client.h" -#include "chromeos/dbus/bluetooth_agent_manager_client.h" -#include "chromeos/dbus/bluetooth_agent_service_provider.h" #include "chromeos/dbus/bluetooth_device_client.h" +#include "chromeos/dbus/bluetooth_gatt_service_client.h" #include "chromeos/dbus/bluetooth_input_client.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "dbus/bus.h" #include "device/bluetooth/bluetooth_adapter_chromeos.h" -#include "device/bluetooth/bluetooth_profile_chromeos.h" +#include "device/bluetooth/bluetooth_gatt_connection_chromeos.h" +#include "device/bluetooth/bluetooth_pairing_chromeos.h" +#include "device/bluetooth/bluetooth_remote_gatt_service_chromeos.h" #include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_chromeos.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "device/bluetooth/bluetooth_uuid.h" #include "third_party/cros_system_api/dbus/service_constants.h" using device::BluetoothDevice; +using device::BluetoothSocket; +using device::BluetoothUUID; namespace { -// The agent path is relatively meaningless since BlueZ only supports one -// at time and will fail in an attempt to register another with "Already Exists" -// (which we fail in OnRegisterAgentError with ERROR_INPROGRESS). -const char kAgentPath[] = "/org/chromium/bluetooth_agent"; - -// Histogram enumerations for pairing methods. -enum UMAPairingMethod { - UMA_PAIRING_METHOD_NONE, - UMA_PAIRING_METHOD_REQUEST_PINCODE, - UMA_PAIRING_METHOD_REQUEST_PASSKEY, - UMA_PAIRING_METHOD_DISPLAY_PINCODE, - UMA_PAIRING_METHOD_DISPLAY_PASSKEY, - UMA_PAIRING_METHOD_CONFIRM_PASSKEY, - // NOTE: Add new pairing methods immediately above this line. Make sure to - // update the enum list in tools/histogram/histograms.xml accordinly. - UMA_PAIRING_METHOD_COUNT -}; - // Histogram enumerations for pairing results. enum UMAPairingResult { UMA_PAIRING_RESULT_SUCCESS, @@ -59,35 +50,37 @@ enum UMAPairingResult { }; void ParseModalias(const dbus::ObjectPath& object_path, - uint16 *vendor_id, - uint16 *product_id, - uint16 *device_id) { + BluetoothDevice::VendorIDSource* vendor_id_source, + uint16* vendor_id, + uint16* product_id, + uint16* device_id) { chromeos::BluetoothDeviceClient::Properties* properties = chromeos::DBusThreadManager::Get()->GetBluetoothDeviceClient()-> GetProperties(object_path); DCHECK(properties); std::string modalias = properties->modalias.value(); - if (StartsWithASCII(modalias, "usb:", false) && modalias.length() == 19) { - // usb:vXXXXpXXXXdXXXX - if (modalias[4] == 'v' && vendor_id != NULL) { - uint64 component = 0; - base::HexStringToUInt64(modalias.substr(5, 4), &component); - *vendor_id = component; - } - - if (modalias[9] == 'p' && product_id != NULL) { - uint64 component = 0; - base::HexStringToUInt64(modalias.substr(10, 4), &component); - *product_id = component; - } - - if (modalias[14] == 'd' && device_id != NULL) { - uint64 component = 0; - base::HexStringToUInt64(modalias.substr(15, 4), &component); - *device_id = component; - } + BluetoothDevice::VendorIDSource source_value; + int vendor_value, product_value, device_value; + + if (sscanf(modalias.c_str(), "bluetooth:v%04xp%04xd%04x", + &vendor_value, &product_value, &device_value) == 3) { + source_value = BluetoothDevice::VENDOR_ID_BLUETOOTH; + } else if (sscanf(modalias.c_str(), "usb:v%04xp%04xd%04x", + &vendor_value, &product_value, &device_value) == 3) { + source_value = BluetoothDevice::VENDOR_ID_USB; + } else { + return; } + + if (vendor_id_source != NULL) + *vendor_id_source = source_value; + if (vendor_id != NULL) + *vendor_id = vendor_value; + if (product_id != NULL) + *product_id = product_value; + if (device_id != NULL) + *device_id = device_value; } void RecordPairingResult(BluetoothDevice::ConnectErrorCode error_code) { @@ -129,16 +122,53 @@ namespace chromeos { BluetoothDeviceChromeOS::BluetoothDeviceChromeOS( BluetoothAdapterChromeOS* adapter, - const dbus::ObjectPath& object_path) + const dbus::ObjectPath& object_path, + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<device::BluetoothSocketThread> socket_thread) : adapter_(adapter), object_path_(object_path), num_connecting_calls_(0), - pairing_delegate_(NULL), - pairing_delegate_used_(false), + connection_monitor_started_(false), + ui_task_runner_(ui_task_runner), + socket_thread_(socket_thread), weak_ptr_factory_(this) { + DBusThreadManager::Get()->GetBluetoothGattServiceClient()->AddObserver(this); + + // Add all known GATT services. + const std::vector<dbus::ObjectPath> gatt_services = + DBusThreadManager::Get()->GetBluetoothGattServiceClient()->GetServices(); + for (std::vector<dbus::ObjectPath>::const_iterator it = gatt_services.begin(); + it != gatt_services.end(); ++it) { + GattServiceAdded(*it); + } } BluetoothDeviceChromeOS::~BluetoothDeviceChromeOS() { + DBusThreadManager::Get()->GetBluetoothGattServiceClient()-> + RemoveObserver(this); + + // Copy the GATT services list here and clear the original so that when we + // send GattServiceRemoved(), GetGattServices() returns no services. + GattServiceMap gatt_services = gatt_services_; + gatt_services_.clear(); + for (GattServiceMap::iterator iter = gatt_services.begin(); + iter != gatt_services.end(); ++iter) { + FOR_EACH_OBSERVER(BluetoothDevice::Observer, observers_, + GattServiceRemoved(this, iter->second)); + delete iter->second; + } +} + +void BluetoothDeviceChromeOS::AddObserver( + device::BluetoothDevice::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothDeviceChromeOS::RemoveObserver( + device::BluetoothDevice::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); } uint32 BluetoothDeviceChromeOS::GetBluetoothClass() const { @@ -165,27 +195,70 @@ std::string BluetoothDeviceChromeOS::GetAddress() const { GetProperties(object_path_); DCHECK(properties); - return properties->address.value(); + return CanonicalizeAddress(properties->address.value()); +} + +BluetoothDevice::VendorIDSource +BluetoothDeviceChromeOS::GetVendorIDSource() const { + VendorIDSource vendor_id_source = VENDOR_ID_UNKNOWN; + ParseModalias(object_path_, &vendor_id_source, NULL, NULL, NULL); + return vendor_id_source; } uint16 BluetoothDeviceChromeOS::GetVendorID() const { uint16 vendor_id = 0; - ParseModalias(object_path_, &vendor_id, NULL, NULL); + ParseModalias(object_path_, NULL, &vendor_id, NULL, NULL); return vendor_id; } uint16 BluetoothDeviceChromeOS::GetProductID() const { uint16 product_id = 0; - ParseModalias(object_path_, NULL, &product_id, NULL); + ParseModalias(object_path_, NULL, NULL, &product_id, NULL); return product_id; } uint16 BluetoothDeviceChromeOS::GetDeviceID() const { uint16 device_id = 0; - ParseModalias(object_path_, NULL, NULL, &device_id); + ParseModalias(object_path_, NULL, NULL, NULL, &device_id); return device_id; } +int BluetoothDeviceChromeOS::GetRSSI() const { + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()->GetProperties( + object_path_); + DCHECK(properties); + + if (!IsConnected()) { + NOTIMPLEMENTED(); + return kUnknownPower; + } + + return connection_monitor_started_ ? properties->connection_rssi.value() + : kUnknownPower; +} + +int BluetoothDeviceChromeOS::GetCurrentHostTransmitPower() const { + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()->GetProperties( + object_path_); + DCHECK(properties); + + return IsConnected() && connection_monitor_started_ + ? properties->connection_tx_power.value() + : kUnknownPower; +} + +int BluetoothDeviceChromeOS::GetMaximumHostTransmitPower() const { + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()->GetProperties( + object_path_); + DCHECK(properties); + + return IsConnected() ? properties->connection_tx_power_max.value() + : kUnknownPower; +} + bool BluetoothDeviceChromeOS::IsPaired() const { BluetoothDeviceClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothDeviceClient()-> @@ -223,40 +296,33 @@ bool BluetoothDeviceChromeOS::IsConnecting() const { return num_connecting_calls_ > 0; } -BluetoothDeviceChromeOS::ServiceList BluetoothDeviceChromeOS::GetServices() - const { +BluetoothDeviceChromeOS::UUIDList BluetoothDeviceChromeOS::GetUUIDs() const { BluetoothDeviceClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothDeviceClient()-> GetProperties(object_path_); DCHECK(properties); - return properties->uuids.value(); -} - -void BluetoothDeviceChromeOS::GetServiceRecords( - const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) { - // TODO(keybuk): not implemented; remove - error_callback.Run(); -} - -void BluetoothDeviceChromeOS::ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) { - // TODO(keybuk): not implemented; remove - callback.Run(false); + std::vector<device::BluetoothUUID> uuids; + const std::vector<std::string> &dbus_uuids = properties->uuids.value(); + for (std::vector<std::string>::const_iterator iter = dbus_uuids.begin(); + iter != dbus_uuids.end(); ++iter) { + device::BluetoothUUID uuid(*iter); + DCHECK(uuid.IsValid()); + uuids.push_back(uuid); + } + return uuids; } bool BluetoothDeviceChromeOS::ExpectingPinCode() const { - return !pincode_callback_.is_null(); + return pairing_.get() && pairing_->ExpectingPinCode(); } bool BluetoothDeviceChromeOS::ExpectingPasskey() const { - return !passkey_callback_.is_null(); + return pairing_.get() && pairing_->ExpectingPasskey(); } bool BluetoothDeviceChromeOS::ExpectingConfirmation() const { - return !confirmation_callback_.is_null(); + return pairing_.get() && pairing_->ExpectingConfirmation(); } void BluetoothDeviceChromeOS::Connect( @@ -274,77 +340,72 @@ void BluetoothDeviceChromeOS::Connect( ConnectInternal(false, callback, error_callback); } else { // Initiate high-security connection with pairing. - DCHECK(!pairing_delegate_); - DCHECK(agent_.get() == NULL); - - pairing_delegate_ = pairing_delegate; - pairing_delegate_used_ = false; - - // The agent path is relatively meaningless since BlueZ only supports - // one per application at a time. - dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); - agent_.reset(BluetoothAgentServiceProvider::Create( - system_bus, dbus::ObjectPath(kAgentPath), this)); - DCHECK(agent_.get()); - - VLOG(1) << object_path_.value() << ": Registering agent for pairing"; - DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> - RegisterAgent( - dbus::ObjectPath(kAgentPath), - bluetooth_agent_manager::kKeyboardDisplayCapability, - base::Bind(&BluetoothDeviceChromeOS::OnRegisterAgent, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback), - base::Bind(&BluetoothDeviceChromeOS::OnRegisterAgentError, - weak_ptr_factory_.GetWeakPtr(), - error_callback)); + BeginPairing(pairing_delegate); + + DBusThreadManager::Get()->GetBluetoothDeviceClient()-> + Pair(object_path_, + base::Bind(&BluetoothDeviceChromeOS::OnPair, + weak_ptr_factory_.GetWeakPtr(), + callback, error_callback), + base::Bind(&BluetoothDeviceChromeOS::OnPairError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); } } void BluetoothDeviceChromeOS::SetPinCode(const std::string& pincode) { - if (!agent_.get() || pincode_callback_.is_null()) + if (!pairing_.get()) return; - pincode_callback_.Run(SUCCESS, pincode); - pincode_callback_.Reset(); + pairing_->SetPinCode(pincode); } void BluetoothDeviceChromeOS::SetPasskey(uint32 passkey) { - if (!agent_.get() || passkey_callback_.is_null()) + if (!pairing_.get()) return; - passkey_callback_.Run(SUCCESS, passkey); - passkey_callback_.Reset(); + pairing_->SetPasskey(passkey); } void BluetoothDeviceChromeOS::ConfirmPairing() { - if (!agent_.get() || confirmation_callback_.is_null()) + if (!pairing_.get()) return; - confirmation_callback_.Run(SUCCESS); - confirmation_callback_.Reset(); + pairing_->ConfirmPairing(); } void BluetoothDeviceChromeOS::RejectPairing() { - RunPairingCallbacks(REJECTED); + if (!pairing_.get()) + return; + + pairing_->RejectPairing(); } void BluetoothDeviceChromeOS::CancelPairing() { - // If there wasn't a callback in progress that we can reply to then we - // have to send a CancelPairing() to the device instead. - if (!RunPairingCallbacks(CANCELLED)) { + bool canceled = false; + + // If there is a callback in progress that we can reply to then use that + // to cancel the current pairing request. + if (pairing_.get() && pairing_->CancelPairing()) + canceled = true; + + // If not we have to send an explicit CancelPairing() to the device instead. + if (!canceled) { + VLOG(1) << object_path_.value() << ": No pairing context or callback. " + << "Sending explicit cancel"; DBusThreadManager::Get()->GetBluetoothDeviceClient()-> CancelPairing( object_path_, base::Bind(&base::DoNothing), base::Bind(&BluetoothDeviceChromeOS::OnCancelPairingError, weak_ptr_factory_.GetWeakPtr())); - - // Since there's no calback to this method, it's possible that the pairing - // delegate is going to be freed before things complete. - UnregisterAgent(); } + + // Since there is no callback to this method it's possible that the pairing + // delegate is going to be freed before things complete (indeed it's + // documented that this is the method you should call while freeing the + // pairing delegate), so clear our the context holding on to it. + EndPairing(); } void BluetoothDeviceChromeOS::Disconnect(const base::Closure& callback, @@ -365,7 +426,7 @@ void BluetoothDeviceChromeOS::Forget(const ErrorCallback& error_callback) { VLOG(1) << object_path_.value() << ": Removing device"; DBusThreadManager::Get()->GetBluetoothAdapterClient()-> RemoveDevice( - adapter_->object_path_, + adapter_->object_path(), object_path_, base::Bind(&base::DoNothing), base::Bind(&BluetoothDeviceChromeOS::OnForgetError, @@ -374,177 +435,107 @@ void BluetoothDeviceChromeOS::Forget(const ErrorCallback& error_callback) { } void BluetoothDeviceChromeOS::ConnectToService( - const std::string& service_uuid, - const SocketCallback& callback) { - // TODO(keybuk): implement - callback.Run(scoped_refptr<device::BluetoothSocket>()); -} - -void BluetoothDeviceChromeOS::ConnectToProfile( - device::BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) { - BluetoothProfileChromeOS* profile_chromeos = - static_cast<BluetoothProfileChromeOS*>(profile); - VLOG(1) << object_path_.value() << ": Connecting profile: " - << profile_chromeos->uuid(); - DBusThreadManager::Get()->GetBluetoothDeviceClient()-> - ConnectProfile( - object_path_, - profile_chromeos->uuid(), - base::Bind( - &BluetoothDeviceChromeOS::OnConnectProfile, - weak_ptr_factory_.GetWeakPtr(), - profile, - callback), - base::Bind( - &BluetoothDeviceChromeOS::OnConnectProfileError, - weak_ptr_factory_.GetWeakPtr(), - profile, - error_callback)); -} - -void BluetoothDeviceChromeOS::SetOutOfBandPairingData( - const device::BluetoothOutOfBandPairingData& data, - const base::Closure& callback, - const ErrorCallback& error_callback) { - // TODO(keybuk): implement - error_callback.Run(); + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Connecting to service: " + << uuid.canonical_value(); + scoped_refptr<BluetoothSocketChromeOS> socket = + BluetoothSocketChromeOS::CreateBluetoothSocket( + ui_task_runner_, + socket_thread_, + NULL, + net::NetLog::Source()); + socket->Connect(this, uuid, + base::Bind(callback, socket), + error_callback); +} + +void BluetoothDeviceChromeOS::CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) { + // TODO(armansito): Until there is a way to create a reference counted GATT + // connection in bluetoothd, simply do a regular connect. + Connect(NULL, + base::Bind(&BluetoothDeviceChromeOS::OnCreateGattConnection, + weak_ptr_factory_.GetWeakPtr(), + callback), + error_callback); } -void BluetoothDeviceChromeOS::ClearOutOfBandPairingData( +void BluetoothDeviceChromeOS::StartConnectionMonitor( const base::Closure& callback, const ErrorCallback& error_callback) { - // TODO(keybuk): implement - error_callback.Run(); -} - - -void BluetoothDeviceChromeOS::Release() { - DCHECK(agent_.get()); - DCHECK(pairing_delegate_); - VLOG(1) << object_path_.value() << ": Release"; - - pincode_callback_.Reset(); - passkey_callback_.Reset(); - confirmation_callback_.Reset(); - - UnregisterAgent(); + DBusThreadManager::Get()->GetBluetoothDeviceClient()->StartConnectionMonitor( + object_path_, + base::Bind(&BluetoothDeviceChromeOS::OnStartConnectionMonitor, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothDeviceChromeOS::OnStartConnectionMonitorError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); } -void BluetoothDeviceChromeOS::RequestPinCode( - const dbus::ObjectPath& device_path, - const PinCodeCallback& callback) { - DCHECK(agent_.get()); - DCHECK(device_path == object_path_); - VLOG(1) << object_path_.value() << ": RequestPinCode"; - - UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", - UMA_PAIRING_METHOD_REQUEST_PINCODE, - UMA_PAIRING_METHOD_COUNT); - - DCHECK(pairing_delegate_); - DCHECK(pincode_callback_.is_null()); - pincode_callback_ = callback; - pairing_delegate_->RequestPinCode(this); - pairing_delegate_used_ = true; +BluetoothPairingChromeOS* BluetoothDeviceChromeOS::BeginPairing( + BluetoothDevice::PairingDelegate* pairing_delegate) { + pairing_.reset(new BluetoothPairingChromeOS(this, pairing_delegate)); + return pairing_.get(); } -void BluetoothDeviceChromeOS::DisplayPinCode( - const dbus::ObjectPath& device_path, - const std::string& pincode) { - DCHECK(agent_.get()); - DCHECK(device_path == object_path_); - VLOG(1) << object_path_.value() << ": DisplayPinCode: " << pincode; - - UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", - UMA_PAIRING_METHOD_DISPLAY_PINCODE, - UMA_PAIRING_METHOD_COUNT); - - DCHECK(pairing_delegate_); - pairing_delegate_->DisplayPinCode(this, pincode); - pairing_delegate_used_ = true; +void BluetoothDeviceChromeOS::EndPairing() { + pairing_.reset(); } -void BluetoothDeviceChromeOS::RequestPasskey( - const dbus::ObjectPath& device_path, - const PasskeyCallback& callback) { - DCHECK(agent_.get()); - DCHECK(device_path == object_path_); - VLOG(1) << object_path_.value() << ": RequestPasskey"; - - UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", - UMA_PAIRING_METHOD_REQUEST_PASSKEY, - UMA_PAIRING_METHOD_COUNT); - - DCHECK(pairing_delegate_); - DCHECK(passkey_callback_.is_null()); - passkey_callback_ = callback; - pairing_delegate_->RequestPasskey(this); - pairing_delegate_used_ = true; +BluetoothPairingChromeOS* BluetoothDeviceChromeOS::GetPairing() const { + return pairing_.get(); } -void BluetoothDeviceChromeOS::DisplayPasskey( - const dbus::ObjectPath& device_path, - uint32 passkey, - uint16 entered) { - DCHECK(agent_.get()); - DCHECK(device_path == object_path_); - VLOG(1) << object_path_.value() << ": DisplayPasskey: " << passkey - << " (" << entered << " entered)"; - - if (entered == 0) - UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", - UMA_PAIRING_METHOD_DISPLAY_PASSKEY, - UMA_PAIRING_METHOD_COUNT); +void BluetoothDeviceChromeOS::GattServiceAdded( + const dbus::ObjectPath& object_path) { + if (GetGattService(object_path.value())) { + VLOG(1) << "Remote GATT service already exists: " << object_path.value(); + return; + } - DCHECK(pairing_delegate_); - if (entered == 0) - pairing_delegate_->DisplayPasskey(this, passkey); - pairing_delegate_->KeysEntered(this, entered); - pairing_delegate_used_ = true; -} + BluetoothGattServiceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattServiceClient()-> + GetProperties(object_path); + DCHECK(properties); + if (properties->device.value() != object_path_) { + VLOG(2) << "Remote GATT service does not belong to this device."; + return; + } -void BluetoothDeviceChromeOS::RequestConfirmation( - const dbus::ObjectPath& device_path, - uint32 passkey, - const ConfirmationCallback& callback) { - DCHECK(agent_.get()); - DCHECK(device_path == object_path_); - VLOG(1) << object_path_.value() << ": RequestConfirmation: " << passkey; + VLOG(1) << "Adding new remote GATT service for device: " << GetAddress(); - UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", - UMA_PAIRING_METHOD_CONFIRM_PASSKEY, - UMA_PAIRING_METHOD_COUNT); + BluetoothRemoteGattServiceChromeOS* service = + new BluetoothRemoteGattServiceChromeOS(adapter_, this, object_path); - DCHECK(pairing_delegate_); - DCHECK(confirmation_callback_.is_null()); - confirmation_callback_ = callback; - pairing_delegate_->ConfirmPasskey(this, passkey); - pairing_delegate_used_ = true; -} + gatt_services_[service->GetIdentifier()] = service; + DCHECK(service->object_path() == object_path); + DCHECK(service->GetUUID().IsValid()); -void BluetoothDeviceChromeOS::RequestAuthorization( - const dbus::ObjectPath& device_path, - const ConfirmationCallback& callback) { - // TODO(keybuk): implement - callback.Run(CANCELLED); + FOR_EACH_OBSERVER(device::BluetoothDevice::Observer, observers_, + GattServiceAdded(this, service)); } -void BluetoothDeviceChromeOS::AuthorizeService( - const dbus::ObjectPath& device_path, - const std::string& uuid, - const ConfirmationCallback& callback) { - // TODO(keybuk): implement - callback.Run(CANCELLED); -} +void BluetoothDeviceChromeOS::GattServiceRemoved( + const dbus::ObjectPath& object_path) { + GattServiceMap::iterator iter = gatt_services_.find(object_path.value()); + if (iter == gatt_services_.end()) { + VLOG(2) << "Unknown GATT service removed: " << object_path.value(); + return; + } -void BluetoothDeviceChromeOS::Cancel() { - DCHECK(agent_.get()); - VLOG(1) << object_path_.value() << ": Cancel"; + VLOG(1) << "Removing remote GATT service from device: " << GetAddress(); - DCHECK(pairing_delegate_); - pairing_delegate_->DismissDisplayOrConfirm(); + BluetoothRemoteGattServiceChromeOS* service = + static_cast<BluetoothRemoteGattServiceChromeOS*>(iter->second); + DCHECK(service->object_path() == object_path); + gatt_services_.erase(iter); + FOR_EACH_OBSERVER(device::BluetoothDevice::Observer, observers_, + GattServiceRemoved(this, service)); + delete service; } void BluetoothDeviceChromeOS::ConnectInternal( @@ -584,6 +575,14 @@ void BluetoothDeviceChromeOS::OnConnect(bool after_pairing, callback.Run(); } +void BluetoothDeviceChromeOS::OnCreateGattConnection( + const GattConnectionCallback& callback) { + scoped_ptr<device::BluetoothGattConnection> conn( + new BluetoothGattConnectionChromeOS( + adapter_, GetAddress(), object_path_)); + callback.Run(conn.Pass()); +} + void BluetoothDeviceChromeOS::OnConnectError( bool after_pairing, const ConnectErrorCallback& error_callback, @@ -613,56 +612,13 @@ void BluetoothDeviceChromeOS::OnConnectError( error_callback.Run(error_code); } -void BluetoothDeviceChromeOS::OnRegisterAgent( - const base::Closure& callback, - const ConnectErrorCallback& error_callback) { - VLOG(1) << object_path_.value() << ": Agent registered, now pairing"; - - DBusThreadManager::Get()->GetBluetoothDeviceClient()-> - Pair(object_path_, - base::Bind(&BluetoothDeviceChromeOS::OnPair, - weak_ptr_factory_.GetWeakPtr(), - callback, error_callback), - base::Bind(&BluetoothDeviceChromeOS::OnPairError, - weak_ptr_factory_.GetWeakPtr(), - error_callback)); -} - -void BluetoothDeviceChromeOS::OnRegisterAgentError( - const ConnectErrorCallback& error_callback, - const std::string& error_name, - const std::string& error_message) { - if (--num_connecting_calls_ == 0) - adapter_->NotifyDeviceChanged(this); - - DCHECK(num_connecting_calls_ >= 0); - LOG(WARNING) << object_path_.value() << ": Failed to register agent: " - << error_name << ": " << error_message; - VLOG(1) << object_path_.value() << ": " << num_connecting_calls_ - << " still in progress"; - - UnregisterAgent(); - - // Determine the error code from error_name. - ConnectErrorCode error_code = ERROR_UNKNOWN; - if (error_name == bluetooth_agent_manager::kErrorAlreadyExists) - error_code = ERROR_INPROGRESS; - - RecordPairingResult(error_code); - error_callback.Run(error_code); -} - void BluetoothDeviceChromeOS::OnPair( const base::Closure& callback, const ConnectErrorCallback& error_callback) { VLOG(1) << object_path_.value() << ": Paired"; - if (!pairing_delegate_used_) - UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", - UMA_PAIRING_METHOD_NONE, - UMA_PAIRING_METHOD_COUNT); - UnregisterAgent(); - SetTrusted(); + EndPairing(); + ConnectInternal(true, callback, error_callback); } @@ -679,7 +635,7 @@ void BluetoothDeviceChromeOS::OnPairError( VLOG(1) << object_path_.value() << ": " << num_connecting_calls_ << " still in progress"; - UnregisterAgent(); + EndPairing(); // Determine the error code from error_name. ConnectErrorCode error_code = ERROR_UNKNOWN; @@ -725,36 +681,20 @@ void BluetoothDeviceChromeOS::OnSetTrusted(bool success) { << ": Failed to set device as trusted"; } -void BluetoothDeviceChromeOS::UnregisterAgent() { - if (!agent_.get()) - return; - - DCHECK(pairing_delegate_); - - DCHECK(pincode_callback_.is_null()); - DCHECK(passkey_callback_.is_null()); - DCHECK(confirmation_callback_.is_null()); - - pairing_delegate_->DismissDisplayOrConfirm(); - pairing_delegate_ = NULL; - - agent_.reset(); - - // Clean up after ourselves. - VLOG(1) << object_path_.value() << ": Unregistering pairing agent"; - DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> - UnregisterAgent( - dbus::ObjectPath(kAgentPath), - base::Bind(&base::DoNothing), - base::Bind(&BluetoothDeviceChromeOS::OnUnregisterAgentError, - weak_ptr_factory_.GetWeakPtr())); +void BluetoothDeviceChromeOS::OnStartConnectionMonitor( + const base::Closure& callback) { + connection_monitor_started_ = true; + callback.Run(); } -void BluetoothDeviceChromeOS::OnUnregisterAgentError( +void BluetoothDeviceChromeOS::OnStartConnectionMonitorError( + const ErrorCallback& error_callback, const std::string& error_name, const std::string& error_message) { - LOG(WARNING) << object_path_.value() << ": Failed to unregister agent: " - << error_name << ": " << error_message; + LOG(WARNING) << object_path_.value() + << ": Failed to start connection monitor: " << error_name << ": " + << error_message; + error_callback.Run(); } void BluetoothDeviceChromeOS::OnDisconnect(const base::Closure& callback) { @@ -780,53 +720,4 @@ void BluetoothDeviceChromeOS::OnForgetError( error_callback.Run(); } -bool BluetoothDeviceChromeOS::RunPairingCallbacks(Status status) { - if (!agent_.get()) - return false; - - bool callback_run = false; - if (!pincode_callback_.is_null()) { - pincode_callback_.Run(status, ""); - pincode_callback_.Reset(); - callback_run = true; - } - - if (!passkey_callback_.is_null()) { - passkey_callback_.Run(status, 0); - passkey_callback_.Reset(); - callback_run = true; - } - - if (!confirmation_callback_.is_null()) { - confirmation_callback_.Run(status); - confirmation_callback_.Reset(); - callback_run = true; - } - - return callback_run; -} - -void BluetoothDeviceChromeOS::OnConnectProfile( - device::BluetoothProfile* profile, - const base::Closure& callback) { - BluetoothProfileChromeOS* profile_chromeos = - static_cast<BluetoothProfileChromeOS*>(profile); - VLOG(1) << object_path_.value() << ": Profile connected: " - << profile_chromeos->uuid(); - callback.Run(); -} - -void BluetoothDeviceChromeOS::OnConnectProfileError( - device::BluetoothProfile* profile, - const ErrorCallback& error_callback, - const std::string& error_name, - const std::string& error_message) { - BluetoothProfileChromeOS* profile_chromeos = - static_cast<BluetoothProfileChromeOS*>(profile); - VLOG(1) << object_path_.value() << ": Profile connection failed: " - << profile_chromeos->uuid() << ": " - << error_name << ": " << error_message; - error_callback.Run(); -} - } // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_device_chromeos.h b/chromium/device/bluetooth/bluetooth_device_chromeos.h index fef7cfdcfae..b7247f5571a 100644 --- a/chromium/device/bluetooth/bluetooth_device_chromeos.h +++ b/chromium/device/bluetooth/bluetooth_device_chromeos.h @@ -7,40 +7,50 @@ #include <string> +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" -#include "chromeos/dbus/bluetooth_agent_service_provider.h" +#include "base/observer_list.h" +#include "base/sequenced_task_runner.h" #include "chromeos/dbus/bluetooth_device_client.h" +#include "chromeos/dbus/bluetooth_gatt_service_client.h" #include "dbus/object_path.h" #include "device/bluetooth/bluetooth_device.h" +namespace device { +class BluetoothSocketThread; +} // namespace device + namespace chromeos { class BluetoothAdapterChromeOS; +class BluetoothPairingChromeOS; // The BluetoothDeviceChromeOS class implements BluetoothDevice for the // Chrome OS platform. class BluetoothDeviceChromeOS : public device::BluetoothDevice, - private chromeos::BluetoothAgentServiceProvider::Delegate { + public BluetoothGattServiceClient::Observer { public: // BluetoothDevice override + virtual void AddObserver( + device::BluetoothDevice::Observer* observer) OVERRIDE; + virtual void RemoveObserver( + device::BluetoothDevice::Observer* observer) OVERRIDE; virtual uint32 GetBluetoothClass() const OVERRIDE; virtual std::string GetAddress() const OVERRIDE; + virtual VendorIDSource GetVendorIDSource() const OVERRIDE; virtual uint16 GetVendorID() const OVERRIDE; virtual uint16 GetProductID() const OVERRIDE; virtual uint16 GetDeviceID() const OVERRIDE; + virtual int GetRSSI() const OVERRIDE; + virtual int GetCurrentHostTransmitPower() const OVERRIDE; + virtual int GetMaximumHostTransmitPower() const OVERRIDE; virtual bool IsPaired() const OVERRIDE; virtual bool IsConnected() const OVERRIDE; virtual bool IsConnectable() const OVERRIDE; virtual bool IsConnecting() const OVERRIDE; - virtual ServiceList GetServices() const OVERRIDE; - virtual void GetServiceRecords( - const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) OVERRIDE; + virtual UUIDList GetUUIDs() const OVERRIDE; virtual bool ExpectingPinCode() const OVERRIDE; virtual bool ExpectingPasskey() const OVERRIDE; virtual bool ExpectingConfirmation() const OVERRIDE; @@ -58,20 +68,32 @@ class BluetoothDeviceChromeOS const ErrorCallback& error_callback) OVERRIDE; virtual void Forget(const ErrorCallback& error_callback) OVERRIDE; virtual void ConnectToService( - const std::string& service_uuid, - const SocketCallback& callback) OVERRIDE; - virtual void ConnectToProfile( - device::BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void SetOutOfBandPairingData( - const device::BluetoothOutOfBandPairingData& data, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void ClearOutOfBandPairingData( + const device::BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) OVERRIDE; + virtual void CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) OVERRIDE; + virtual void StartConnectionMonitor( const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; + // Creates a pairing object with the given delegate |pairing_delegate| and + // establishes it as the pairing context for this device. All pairing-related + // method calls will be forwarded to this object until it is released. + BluetoothPairingChromeOS* BeginPairing( + BluetoothDevice::PairingDelegate* pairing_delegate); + + // Releases the current pairing object, any pairing-related method calls will + // be ignored. + void EndPairing(); + + // Returns the current pairing object or NULL if no pairing is in progress. + BluetoothPairingChromeOS* GetPairing() const; + + // Returns the object path of the device. + const dbus::ObjectPath& object_path() const { return object_path_; } + protected: // BluetoothDevice override virtual std::string GetDeviceName() const OVERRIDE; @@ -79,31 +101,16 @@ class BluetoothDeviceChromeOS private: friend class BluetoothAdapterChromeOS; - BluetoothDeviceChromeOS(BluetoothAdapterChromeOS* adapter, - const dbus::ObjectPath& object_path); + BluetoothDeviceChromeOS( + BluetoothAdapterChromeOS* adapter, + const dbus::ObjectPath& object_path, + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<device::BluetoothSocketThread> socket_thread); virtual ~BluetoothDeviceChromeOS(); - // BluetoothAgentServiceProvider::Delegate override. - virtual void Release() OVERRIDE; - virtual void RequestPinCode(const dbus::ObjectPath& device_path, - const PinCodeCallback& callback) OVERRIDE; - virtual void DisplayPinCode(const dbus::ObjectPath& device_path, - const std::string& pincode) OVERRIDE; - virtual void RequestPasskey(const dbus::ObjectPath& device_path, - const PasskeyCallback& callback) OVERRIDE; - virtual void DisplayPasskey(const dbus::ObjectPath& device_path, - uint32 passkey, uint16 entered) OVERRIDE; - virtual void RequestConfirmation(const dbus::ObjectPath& device_path, - uint32 passkey, - const ConfirmationCallback& callback) - OVERRIDE; - virtual void RequestAuthorization(const dbus::ObjectPath& device_path, - const ConfirmationCallback& callback) - OVERRIDE; - virtual void AuthorizeService(const dbus::ObjectPath& device_path, - const std::string& uuid, - const ConfirmationCallback& callback) OVERRIDE; - virtual void Cancel() OVERRIDE; + // BluetoothGattServiceClient::Observer overrides. + virtual void GattServiceAdded(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void GattServiceRemoved(const dbus::ObjectPath& object_path) OVERRIDE; // Internal method to initiate a connection to this device, and methods called // by dbus:: on completion of the D-Bus method call. @@ -112,19 +119,12 @@ class BluetoothDeviceChromeOS const ConnectErrorCallback& error_callback); void OnConnect(bool after_pairing, const base::Closure& callback); + void OnCreateGattConnection(const GattConnectionCallback& callback); void OnConnectError(bool after_pairing, const ConnectErrorCallback& error_callback, const std::string& error_name, const std::string& error_message); - // Called by dbus:: on completion of the D-Bus method call to register the - // pairing agent. - void OnRegisterAgent(const base::Closure& callback, - const ConnectErrorCallback& error_callback); - void OnRegisterAgentError(const ConnectErrorCallback& error_callback, - const std::string& error_name, - const std::string& error_message); - // Called by dbus:: on completion of the D-Bus method call to pair the device. void OnPair(const base::Closure& callback, const ConnectErrorCallback& error_callback); @@ -145,13 +145,6 @@ class BluetoothDeviceChromeOS void SetTrusted(); void OnSetTrusted(bool success); - // Internal method to unregister the pairing agent and method called by dbus:: - // on failure of the D-Bus method call. No completion call as success is - // ignored. - void UnregisterAgent(); - void OnUnregisterAgentError(const std::string& error_name, - const std::string& error_message); - // Called by dbus:: on completion of the D-Bus method call to disconnect the // device. void OnDisconnect(const base::Closure& callback); @@ -166,21 +159,12 @@ class BluetoothDeviceChromeOS const std::string& error_name, const std::string& error_message); - // Run any outstanding pairing callbacks passing |status| as the result of - // 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 BluetoothAdapterChromeOS - const dbus::ObjectPath& object_path() const { return object_path_; } + // Called by dbus:: on completion of the D-Bus method call to start the + // connection monitor. + void OnStartConnectionMonitor(const base::Closure& callback); + void OnStartConnectionMonitorError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); // The adapter that owns this device instance. BluetoothAdapterChromeOS* adapter_; @@ -188,28 +172,24 @@ class BluetoothDeviceChromeOS // The dbus object path of the device object. dbus::ObjectPath object_path_; + // List of observers interested in event notifications from us. + ObserverList<device::BluetoothDevice::Observer> observers_; + // Number of ongoing calls to Connect(). int num_connecting_calls_; + // True if the connection monitor has been started, tracking the connection + // RSSI and TX power. + bool connection_monitor_started_; + + // UI thread task runner and socket thread object used to create sockets. + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + scoped_refptr<device::BluetoothSocketThread> socket_thread_; + // During pairing this is set to an object that we don't own, but on which // we can make method calls to request, display or confirm PIN Codes and // Passkeys. Generally it is the object that owns this one. - PairingDelegate* pairing_delegate_; - - // Flag to indicate whether a pairing delegate method has been called during - // pairing. - bool pairing_delegate_used_; - - // During pairing this is set to an instance of a D-Bus agent object - // intialized with our own class as its delegate. - scoped_ptr<BluetoothAgentServiceProvider> agent_; - - // During pairing these callbacks are set to those provided by method calls - // made on us by |agent_| and are called by our own method calls such as - // SetPinCode() and SetPasskey(). - PinCodeCallback pincode_callback_; - PasskeyCallback passkey_callback_; - ConfirmationCallback confirmation_callback_; + scoped_ptr<BluetoothPairingChromeOS> pairing_; // Note: This should remain the last member so it'll be destroyed and // invalidate its weak pointers before any other members are destroyed. diff --git a/chromium/device/bluetooth/bluetooth_device_mac.h b/chromium/device/bluetooth/bluetooth_device_mac.h index bc887b97aca..462cd35c344 100644 --- a/chromium/device/bluetooth/bluetooth_device_mac.h +++ b/chromium/device/bluetooth/bluetooth_device_mac.h @@ -5,16 +5,16 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_MAC_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_MAC_H_ +#import <IOBluetooth/IOBluetooth.h> + #include <string> #include "base/basictypes.h" +#include "base/mac/scoped_nsobject.h" +#include "base/observer_list.h" #include "device/bluetooth/bluetooth_device.h" -#ifdef __OBJC__ @class IOBluetoothDevice; -#else -class IOBluetoothDevice; -#endif namespace device { @@ -24,22 +24,24 @@ class BluetoothDeviceMac : public BluetoothDevice { virtual ~BluetoothDeviceMac(); // BluetoothDevice override + virtual void AddObserver( + device::BluetoothDevice::Observer* observer) OVERRIDE; + virtual void RemoveObserver( + device::BluetoothDevice::Observer* observer) OVERRIDE; virtual uint32 GetBluetoothClass() const OVERRIDE; virtual std::string GetAddress() const OVERRIDE; + virtual VendorIDSource GetVendorIDSource() const OVERRIDE; virtual uint16 GetVendorID() const OVERRIDE; virtual uint16 GetProductID() const OVERRIDE; virtual uint16 GetDeviceID() const OVERRIDE; + virtual int GetRSSI() const OVERRIDE; + virtual int GetCurrentHostTransmitPower() const OVERRIDE; + virtual int GetMaximumHostTransmitPower() const OVERRIDE; virtual bool IsPaired() const OVERRIDE; virtual bool IsConnected() const OVERRIDE; virtual bool IsConnectable() const OVERRIDE; virtual bool IsConnecting() const OVERRIDE; - virtual ServiceList GetServices() const OVERRIDE; - virtual void GetServiceRecords( - const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) OVERRIDE; + virtual UUIDList GetUUIDs() const OVERRIDE; virtual bool ExpectingPinCode() const OVERRIDE; virtual bool ExpectingPasskey() const OVERRIDE; virtual bool ExpectingConfirmation() const OVERRIDE; @@ -57,20 +59,20 @@ class BluetoothDeviceMac : public BluetoothDevice { const ErrorCallback& error_callback) OVERRIDE; virtual void Forget(const ErrorCallback& error_callback) OVERRIDE; virtual void ConnectToService( - const std::string& service_uuid, - const SocketCallback& callback) OVERRIDE; - virtual void ConnectToProfile( - device::BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void SetOutOfBandPairingData( - const BluetoothOutOfBandPairingData& data, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void ClearOutOfBandPairingData( + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) OVERRIDE; + virtual void CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) OVERRIDE; + virtual void StartConnectionMonitor( const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; + // Returns the Bluetooth address for the |device|. The returned address has a + // normalized format (see below). + static std::string GetDeviceAddress(IOBluetoothDevice* device); + protected: // BluetoothDevice override virtual std::string GetDeviceName() const OVERRIDE; @@ -78,7 +80,15 @@ class BluetoothDeviceMac : public BluetoothDevice { private: friend class BluetoothAdapterMac; - IOBluetoothDevice* device_; + // Implementation to read the host's transmit power level of type + // |power_level_type|. + int GetHostTransmitPower( + BluetoothHCITransmitPowerLevelType power_level_type) const; + + // List of observers interested in event notifications from us. + ObserverList<Observer> observers_; + + base::scoped_nsobject<IOBluetoothDevice> device_; DISALLOW_COPY_AND_ASSIGN(BluetoothDeviceMac); }; diff --git a/chromium/device/bluetooth/bluetooth_device_mac.mm b/chromium/device/bluetooth/bluetooth_device_mac.mm index 39acbef4347..43d9fb66d6b 100644 --- a/chromium/device/bluetooth/bluetooth_device_mac.mm +++ b/chromium/device/bluetooth/bluetooth_device_mac.mm @@ -4,70 +4,75 @@ #include "device/bluetooth/bluetooth_device_mac.h" -#include <IOBluetooth/Bluetooth.h> -#import <IOBluetooth/objc/IOBluetoothDevice.h> -#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> -#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> - #include <string> #include "base/basictypes.h" +#include "base/bind.h" #include "base/hash.h" +#include "base/mac/sdk_forward_declarations.h" +#include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/stringprintf.h" +#include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" -#include "device/bluetooth/bluetooth_out_of_band_pairing_data.h" -#include "device/bluetooth/bluetooth_profile_mac.h" -#include "device/bluetooth/bluetooth_service_record_mac.h" #include "device/bluetooth/bluetooth_socket_mac.h" - -// Replicate specific 10.7 SDK declarations for building with prior SDKs. -#if !defined(MAC_OS_X_VERSION_10_7) || \ - MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 - -@interface IOBluetoothDevice (LionSDKDeclarations) -- (NSString*)addressString; -- (NSString*)name; -- (unsigned int)classOfDevice; -- (NSArray*)services; +#include "device/bluetooth/bluetooth_uuid.h" + +// Undocumented API for accessing the Bluetooth transmit power level. +// Similar to the API defined here [ http://goo.gl/20Q5vE ]. +@interface IOBluetoothHostController (UndocumentedAPI) +- (IOReturn) + BluetoothHCIReadTransmitPowerLevel:(BluetoothConnectionHandle)connection + inType:(BluetoothHCITransmitPowerLevelType)type + outTransmitPowerLevel:(BluetoothHCITransmitPowerLevel*)level; @end -#endif // MAC_OS_X_VERSION_10_7 - +namespace device { namespace { -// Converts |uuid| to a IOBluetoothSDPUUID instance. -// -// |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. -IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { - DCHECK(uuid.size() == 36); - DCHECK(uuid[8] == '-'); - DCHECK(uuid[13] == '-'); - DCHECK(uuid[18] == '-'); - DCHECK(uuid[23] == '-'); - std::string numbers_only = uuid; - numbers_only.erase(23, 1); - numbers_only.erase(18, 1); - numbers_only.erase(13, 1); - numbers_only.erase(8, 1); - std::vector<uint8> uuid_bytes_vector; - base::HexStringToBytes(numbers_only, &uuid_bytes_vector); - DCHECK(uuid_bytes_vector.size() == 16); - - return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector[0] - length:uuid_bytes_vector.size()]; +// Returns the first (should be, only) UUID contained within the +// |service_class_data|. Returns an invalid (empty) UUID if none is found. +BluetoothUUID ExtractUuid(IOBluetoothSDPDataElement* service_class_data) { + NSArray* inner_elements = [service_class_data getArrayValue]; + IOBluetoothSDPUUID* sdp_uuid = nil; + for (IOBluetoothSDPDataElement* inner_element in inner_elements) { + if ([inner_element getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) { + sdp_uuid = [[inner_element getUUIDValue] getUUIDWithLength:16]; + break; + } + } + + if (!sdp_uuid) + return BluetoothUUID(); + + const uint8* uuid_bytes = reinterpret_cast<const uint8*>([sdp_uuid bytes]); + std::string uuid_str = base::HexEncode(uuid_bytes, 16); + DCHECK_EQ(uuid_str.size(), 32U); + uuid_str.insert(8, "-"); + uuid_str.insert(13, "-"); + uuid_str.insert(18, "-"); + uuid_str.insert(23, "-"); + return BluetoothUUID(uuid_str); } } // namespace -namespace device { - BluetoothDeviceMac::BluetoothDeviceMac(IOBluetoothDevice* device) - : BluetoothDevice(), device_([device retain]) { + : device_([device retain]) { } BluetoothDeviceMac::~BluetoothDeviceMac() { - [device_ release]; +} + +void BluetoothDeviceMac::AddObserver( + device::BluetoothDevice::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothDeviceMac::RemoveObserver( + device::BluetoothDevice::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); } uint32 BluetoothDeviceMac::GetBluetoothClass() const { @@ -79,7 +84,11 @@ std::string BluetoothDeviceMac::GetDeviceName() const { } std::string BluetoothDeviceMac::GetAddress() const { - return base::SysNSStringToUTF8([device_ addressString]); + return GetDeviceAddress(device_); +} + +BluetoothDevice::VendorIDSource BluetoothDeviceMac::GetVendorIDSource() const { + return VENDOR_ID_UNKNOWN; } uint16 BluetoothDeviceMac::GetVendorID() const { @@ -94,6 +103,30 @@ uint16 BluetoothDeviceMac::GetDeviceID() const { return 0; } +int BluetoothDeviceMac::GetRSSI() const { + if (![device_ isConnected]) { + NOTIMPLEMENTED(); + return kUnknownPower; + } + + int rssi = [device_ rawRSSI]; + + // The API guarantees that +127 is returned in case the RSSI is not readable: + // http://goo.gl/bpURYv + if (rssi == 127) + return kUnknownPower; + + return rssi; +} + +int BluetoothDeviceMac::GetCurrentHostTransmitPower() const { + return GetHostTransmitPower(kReadCurrentTransmitPowerLevel); +} + +int BluetoothDeviceMac::GetMaximumHostTransmitPower() const { + return GetHostTransmitPower(kReadMaximumTransmitPowerLevel); +} + bool BluetoothDeviceMac::IsPaired() const { return [device_ isPaired]; } @@ -110,37 +143,20 @@ bool BluetoothDeviceMac::IsConnecting() const { return false; } -// TODO(youngki): BluetoothServiceRecord is deprecated; implement this method -// without using BluetoothServiceRecord. -BluetoothDevice::ServiceList BluetoothDeviceMac::GetServices() const { - ServiceList service_uuids; - for (IOBluetoothSDPServiceRecord* service in [device_ services]) { - BluetoothServiceRecordMac service_record(service); - service_uuids.push_back(service_record.uuid()); +BluetoothDevice::UUIDList BluetoothDeviceMac::GetUUIDs() const { + UUIDList uuids; + for (IOBluetoothSDPServiceRecord* service_record in [device_ services]) { + IOBluetoothSDPDataElement* service_class_data = + [service_record getAttributeDataElement: + kBluetoothSDPAttributeIdentifierServiceClassIDList]; + if ([service_class_data getTypeDescriptor] == + kBluetoothSDPDataElementTypeDataElementSequence) { + BluetoothUUID uuid = ExtractUuid(service_class_data); + if (uuid.IsValid()) + uuids.push_back(uuid); + } } - return service_uuids; -} - -// NOTE(youngki): This method is deprecated; it will be removed soon. -void BluetoothDeviceMac::GetServiceRecords( - const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) { - ServiceRecordList service_record_list; - for (IOBluetoothSDPServiceRecord* service in [device_ services]) { - BluetoothServiceRecord* service_record = - new BluetoothServiceRecordMac(service); - service_record_list.push_back(service_record); - } - - callback.Run(service_record_list); -} - -// NOTE(youngki): This method is deprecated; it will be removed soon. -void BluetoothDeviceMac::ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) { - NOTIMPLEMENTED(); - callback.Run(false); + return uuids; } bool BluetoothDeviceMac::ExpectingPinCode() const { @@ -185,9 +201,8 @@ void BluetoothDeviceMac::CancelPairing() { NOTIMPLEMENTED(); } -void BluetoothDeviceMac::Disconnect( - const base::Closure& callback, - const ErrorCallback& error_callback) { +void BluetoothDeviceMac::Disconnect(const base::Closure& callback, + const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } @@ -196,40 +211,52 @@ void BluetoothDeviceMac::Forget(const ErrorCallback& error_callback) { } void BluetoothDeviceMac::ConnectToService( - const std::string& service_uuid, - const SocketCallback& callback) { - IOBluetoothSDPServiceRecord* record = - [device_ getServiceRecordForUUID:GetIOBluetoothSDPUUID(service_uuid)]; - if (record != nil) { - BluetoothServiceRecordMac service_record(record); - scoped_refptr<BluetoothSocket> socket( - BluetoothSocketMac::CreateBluetoothSocket(service_record)); - if (socket.get() != NULL) - callback.Run(socket); - } + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) { + scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket(); + socket->Connect( + device_.get(), uuid, base::Bind(callback, socket), error_callback); } -void BluetoothDeviceMac::ConnectToProfile( - device::BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) { - if (static_cast<BluetoothProfileMac*>(profile)->Connect(device_)) - callback.Run(); - else - error_callback.Run(); +void BluetoothDeviceMac::CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) { + // TODO(armansito): Implement. + error_callback.Run(ERROR_UNSUPPORTED_DEVICE); } -void BluetoothDeviceMac::SetOutOfBandPairingData( - const BluetoothOutOfBandPairingData& data, +void BluetoothDeviceMac::StartConnectionMonitor( const base::Closure& callback, const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } -void BluetoothDeviceMac::ClearOutOfBandPairingData( - const base::Closure& callback, - const ErrorCallback& error_callback) { - NOTIMPLEMENTED(); +int BluetoothDeviceMac::GetHostTransmitPower( + BluetoothHCITransmitPowerLevelType power_level_type) const { + IOBluetoothHostController* controller = + [IOBluetoothHostController defaultController]; + + // Bail if the undocumented API is unavailable on this machine. + SEL selector = @selector( + BluetoothHCIReadTransmitPowerLevel:inType:outTransmitPowerLevel:); + if (![controller respondsToSelector:selector]) + return kUnknownPower; + + BluetoothHCITransmitPowerLevel power_level; + IOReturn result = + [controller BluetoothHCIReadTransmitPowerLevel:[device_ connectionHandle] + inType:power_level_type + outTransmitPowerLevel:&power_level]; + if (result != kIOReturnSuccess) + return kUnknownPower; + + return power_level; +} + +// static +std::string BluetoothDeviceMac::GetDeviceAddress(IOBluetoothDevice* device) { + return CanonicalizeAddress(base::SysNSStringToUTF8([device addressString])); } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_device_unittest.cc b/chromium/device/bluetooth/bluetooth_device_unittest.cc new file mode 100644 index 00000000000..7fb50592536 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_device_unittest.cc @@ -0,0 +1,59 @@ +// 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 "device/bluetooth/bluetooth_device.h" + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +TEST(BluetoothDeviceTest, CanonicalizeAddressFormat_AcceptsAllValidFormats) { + // There are three valid separators (':', '-', and none). + // Case shouldn't matter. + const char* const kValidFormats[] = { + "1A:2B:3C:4D:5E:6F", + "1a:2B:3c:4D:5e:6F", + "1a:2b:3c:4d:5e:6f", + "1A-2B-3C-4D-5E-6F", + "1a-2B-3c-4D-5e-6F", + "1a-2b-3c-4d-5e-6f", + "1A2B3C4D5E6F", + "1a2B3c4D5e6F", + "1a2b3c4d5e6f", + }; + + for (size_t i = 0; i < arraysize(kValidFormats); ++i) { + SCOPED_TRACE(std::string("Input format: '") + kValidFormats[i] + "'"); + EXPECT_EQ("1A:2B:3C:4D:5E:6F", + BluetoothDevice::CanonicalizeAddress(kValidFormats[i])); + } +} + +TEST(BluetoothDeviceTest, CanonicalizeAddressFormat_RejectsInvalidFormats) { + const char* const kValidFormats[] = { + // Empty string. + "", + // Too short. + "1A:2B:3C:4D:5E", + // Too long. + "1A:2B:3C:4D:5E:6F:70", + // Missing a separator. + "1A:2B:3C:4D:5E6F", + // Mixed separators. + "1A:2B-3C:4D-5E:6F", + // Invalid characters. + "1A:2B-3C:4D-5E:6X", + // Separators in the wrong place. + "1:A2:B3:C4:D5:E6F", + }; + + for (size_t i = 0; i < arraysize(kValidFormats); ++i) { + SCOPED_TRACE(std::string("Input format: '") + kValidFormats[i] + "'"); + EXPECT_EQ(std::string(), + BluetoothDevice::CanonicalizeAddress(kValidFormats[i])); + } +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_device_win.cc b/chromium/device/bluetooth/bluetooth_device_win.cc index bfd9bc61297..47376bcdae0 100644 --- a/chromium/device/bluetooth/bluetooth_device_win.cc +++ b/chromium/device/bluetooth/bluetooth_device_win.cc @@ -9,12 +9,13 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/scoped_vector.h" +#include "base/sequenced_task_runner.h" #include "base/strings/stringprintf.h" -#include "device/bluetooth/bluetooth_out_of_band_pairing_data.h" -#include "device/bluetooth/bluetooth_profile_win.h" #include "device/bluetooth/bluetooth_service_record_win.h" +#include "device/bluetooth/bluetooth_socket_thread.h" #include "device/bluetooth/bluetooth_socket_win.h" #include "device/bluetooth/bluetooth_task_manager_win.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace { @@ -25,10 +26,18 @@ const int kSdpBytesBufferSize = 1024; namespace device { BluetoothDeviceWin::BluetoothDeviceWin( - const BluetoothTaskManagerWin::DeviceState& state) - : BluetoothDevice() { + const BluetoothTaskManagerWin::DeviceState& state, + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& net_log_source) + : BluetoothDevice(), + ui_task_runner_(ui_task_runner), + socket_thread_(socket_thread), + net_log_(net_log), + net_log_source_(net_log_source) { name_ = state.name; - address_ = state.address; + address_ = CanonicalizeAddress(state.address); bluetooth_class_ = state.bluetooth_class; visible_ = state.visible; connected_ = state.connected; @@ -42,13 +51,13 @@ BluetoothDeviceWin::BluetoothDeviceWin( std::copy((*iter)->sdp_bytes.begin(), (*iter)->sdp_bytes.end(), sdp_bytes_buffer); - BluetoothServiceRecord* service_record = new BluetoothServiceRecordWin( + BluetoothServiceRecordWin* service_record = new BluetoothServiceRecordWin( (*iter)->name, (*iter)->address, (*iter)->sdp_bytes.size(), sdp_bytes_buffer); service_record_list_.push_back(service_record); - service_uuids_.push_back(service_record->uuid()); + uuids_.push_back(service_record->uuid()); } } @@ -59,6 +68,19 @@ void BluetoothDeviceWin::SetVisible(bool visible) { visible_ = visible; } +void BluetoothDeviceWin::AddObserver( + device::BluetoothDevice::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothDeviceWin::RemoveObserver( + device::BluetoothDevice::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); +} + + uint32 BluetoothDeviceWin::GetBluetoothClass() const { return bluetooth_class_; } @@ -71,6 +93,11 @@ std::string BluetoothDeviceWin::GetAddress() const { return address_; } +BluetoothDevice::VendorIDSource +BluetoothDeviceWin::GetVendorIDSource() const { + return VENDOR_ID_UNKNOWN; +} + uint16 BluetoothDeviceWin::GetVendorID() const { return 0; } @@ -83,6 +110,21 @@ uint16 BluetoothDeviceWin::GetDeviceID() const { return 0; } +int BluetoothDeviceWin::GetRSSI() const { + NOTIMPLEMENTED(); + return kUnknownPower; +} + +int BluetoothDeviceWin::GetCurrentHostTransmitPower() const { + NOTIMPLEMENTED(); + return kUnknownPower; +} + +int BluetoothDeviceWin::GetMaximumHostTransmitPower() const { + NOTIMPLEMENTED(); + return kUnknownPower; +} + bool BluetoothDeviceWin::IsPaired() const { return paired_; } @@ -99,28 +141,8 @@ bool BluetoothDeviceWin::IsConnecting() const { return false; } -BluetoothDevice::ServiceList BluetoothDeviceWin::GetServices() const { - return service_uuids_; -} - -void BluetoothDeviceWin::GetServiceRecords( - const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) { - callback.Run(service_record_list_); -} - -void BluetoothDeviceWin::ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) { - for (ServiceRecordList::const_iterator iter = service_record_list_.begin(); - iter != service_record_list_.end(); - ++iter) { - if ((*iter)->name() == name) { - callback.Run(true); - return; - } - } - callback.Run(false); +BluetoothDevice::UUIDList BluetoothDeviceWin::GetUUIDs() const { + return uuids_; } bool BluetoothDeviceWin::ExpectingPinCode() const { @@ -176,52 +198,34 @@ void BluetoothDeviceWin::Forget(const ErrorCallback& error_callback) { } void BluetoothDeviceWin::ConnectToService( - const std::string& service_uuid, - const SocketCallback& callback) { - for (ServiceRecordList::const_iterator iter = service_record_list_.begin(); - iter != service_record_list_.end(); - ++iter) { - if ((*iter)->uuid() == service_uuid) { - // If multiple service records are found, use the first one that works. - scoped_refptr<BluetoothSocket> socket( - BluetoothSocketWin::CreateBluetoothSocket(**iter)); - if (socket.get() != NULL) { - callback.Run(socket); - return; - } - } - } + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) { + scoped_refptr<BluetoothSocketWin> socket( + BluetoothSocketWin::CreateBluetoothSocket( + ui_task_runner_, socket_thread_, NULL, net::NetLog::Source())); + socket->Connect(this, uuid, base::Bind(callback, socket), error_callback); } -void BluetoothDeviceWin::ConnectToProfile( - device::BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) { - if (static_cast<BluetoothProfileWin*>(profile)->Connect(this)) - callback.Run(); - else - error_callback.Run(); -} - -void BluetoothDeviceWin::SetOutOfBandPairingData( - const BluetoothOutOfBandPairingData& data, - const base::Closure& callback, - const ErrorCallback& error_callback) { - NOTIMPLEMENTED(); +void BluetoothDeviceWin::CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) { + // TODO(armansito): Implement. + error_callback.Run(ERROR_UNSUPPORTED_DEVICE); } -void BluetoothDeviceWin::ClearOutOfBandPairingData( +void BluetoothDeviceWin::StartConnectionMonitor( const base::Closure& callback, const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } -const BluetoothServiceRecord* BluetoothDeviceWin::GetServiceRecord( - const std::string& uuid) const { +const BluetoothServiceRecordWin* BluetoothDeviceWin::GetServiceRecord( + const device::BluetoothUUID& uuid) const { for (ServiceRecordList::const_iterator iter = service_record_list_.begin(); iter != service_record_list_.end(); ++iter) { - if ((*iter)->uuid().compare(uuid) == 0) + if ((*iter)->uuid() == uuid) return *iter; } return NULL; diff --git a/chromium/device/bluetooth/bluetooth_device_win.h b/chromium/device/bluetooth/bluetooth_device_win.h index 452f6f2b861..b403cf6dff4 100644 --- a/chromium/device/bluetooth/bluetooth_device_win.h +++ b/chromium/device/bluetooth/bluetooth_device_win.h @@ -9,37 +9,45 @@ #include <vector> #include "base/basictypes.h" +#include "base/observer_list.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_task_manager_win.h" namespace device { class BluetoothAdapterWin; -class BluetoothServiceRecord; +class BluetoothServiceRecordWin; +class BluetoothSocketThread; class BluetoothDeviceWin : public BluetoothDevice { public: explicit BluetoothDeviceWin( - const BluetoothTaskManagerWin::DeviceState& state); + const BluetoothTaskManagerWin::DeviceState& state, + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& net_log_source); virtual ~BluetoothDeviceWin(); // BluetoothDevice override + virtual void AddObserver( + device::BluetoothDevice::Observer* observer) OVERRIDE; + virtual void RemoveObserver( + device::BluetoothDevice::Observer* observer) OVERRIDE; virtual uint32 GetBluetoothClass() const OVERRIDE; virtual std::string GetAddress() const OVERRIDE; + virtual VendorIDSource GetVendorIDSource() const OVERRIDE; virtual uint16 GetVendorID() const OVERRIDE; virtual uint16 GetProductID() const OVERRIDE; virtual uint16 GetDeviceID() const OVERRIDE; + virtual int GetRSSI() const OVERRIDE; + virtual int GetCurrentHostTransmitPower() const OVERRIDE; + virtual int GetMaximumHostTransmitPower() const OVERRIDE; virtual bool IsPaired() const OVERRIDE; virtual bool IsConnected() const OVERRIDE; virtual bool IsConnectable() const OVERRIDE; virtual bool IsConnecting() const OVERRIDE; - virtual ServiceList GetServices() const OVERRIDE; - virtual void GetServiceRecords( - const ServiceRecordsCallback& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void ProvidesServiceWithName( - const std::string& name, - const ProvidesServiceCallback& callback) OVERRIDE; + virtual UUIDList GetUUIDs() const OVERRIDE; virtual bool ExpectingPinCode() const OVERRIDE; virtual bool ExpectingPasskey() const OVERRIDE; virtual bool ExpectingConfirmation() const OVERRIDE; @@ -57,21 +65,20 @@ class BluetoothDeviceWin : public BluetoothDevice { const ErrorCallback& error_callback) OVERRIDE; virtual void Forget(const ErrorCallback& error_callback) OVERRIDE; virtual void ConnectToService( - const std::string& service_uuid, - const SocketCallback& callback) OVERRIDE; - virtual void ConnectToProfile( - device::BluetoothProfile* profile, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void SetOutOfBandPairingData( - const BluetoothOutOfBandPairingData& data, - const base::Closure& callback, - const ErrorCallback& error_callback) OVERRIDE; - virtual void ClearOutOfBandPairingData( + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) OVERRIDE; + virtual void CreateGattConnection( + const GattConnectionCallback& callback, + const ConnectErrorCallback& error_callback) OVERRIDE; + virtual void StartConnectionMonitor( const base::Closure& callback, const ErrorCallback& error_callback) OVERRIDE; - const BluetoothServiceRecord* GetServiceRecord(const std::string& uuid) const; + // Used by BluetoothProfileWin to retrieve the service record for the given + // |uuid|. + const BluetoothServiceRecordWin* GetServiceRecord( + const device::BluetoothUUID& uuid) const; protected: // BluetoothDevice override @@ -84,6 +91,14 @@ class BluetoothDeviceWin : public BluetoothDevice { // discovery. void SetVisible(bool visible); + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + scoped_refptr<BluetoothSocketThread> socket_thread_; + net::NetLog* net_log_; + net::NetLog::Source net_log_source_; + + // List of observers interested in event notifications from us. + ObserverList<Observer> observers_; + // The Bluetooth class of the device, a bitmask that may be decoded using // https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm uint32 bluetooth_class_; @@ -104,7 +119,10 @@ class BluetoothDeviceWin : public BluetoothDevice { bool visible_; // The services (identified by UUIDs) that this device provides. - ServiceList service_uuids_; + UUIDList uuids_; + + // The service records retrieved from SDP. + typedef ScopedVector<BluetoothServiceRecordWin> ServiceRecordList; ServiceRecordList service_record_list_; DISALLOW_COPY_AND_ASSIGN(BluetoothDeviceWin); diff --git a/chromium/device/bluetooth/bluetooth_device_win_unittest.cc b/chromium/device/bluetooth/bluetooth_device_win_unittest.cc index 20be2ada182..48e87173118 100644 --- a/chromium/device/bluetooth/bluetooth_device_win_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_device_win_unittest.cc @@ -6,10 +6,14 @@ #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" +#include "base/test/test_simple_task_runner.h" #include "device/bluetooth/bluetooth_device_win.h" -#include "device/bluetooth/bluetooth_service_record.h" +#include "device/bluetooth/bluetooth_service_record_win.h" +#include "device/bluetooth/bluetooth_socket_thread.h" #include "device/bluetooth/bluetooth_task_manager_win.h" +#include "device/bluetooth/bluetooth_uuid.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -23,7 +27,7 @@ const char kTestAudioSdpBytes[] = "35510900000a00010001090001350319110a09000435103506190100090019350619001909" "010209000535031910020900093508350619110d090102090100250c417564696f20536f75" "726365090311090001"; -const char kTestAudioSdpUuid[] = "0000110a-0000-1000-8000-00805f9b34fb"; +const device::BluetoothUUID kTestAudioSdpUuid("110a"); const char kTestVideoSdpName[] = "Video"; const char kTestVideoSdpAddress[] = "A0:10:0A:03:02:01"; @@ -31,7 +35,7 @@ const char kTestVideoSdpBytes[] = "354b0900000a000100030900013506191112191203090004350c3503190100350519000308" "0b090005350319100209000935083506191108090100090100250d566f6963652047617465" "776179"; -const char kTestVideoSdpUuid[] = "00001112-0000-1000-8000-00805f9b34fb"; +const device::BluetoothUUID kTestVideoSdpUuid("1112"); } // namespace @@ -39,13 +43,12 @@ namespace device { class BluetoothDeviceWinTest : public testing::Test { public: - BluetoothDeviceWinTest() - : error_called_(false), - provides_service_with_name_(false) { + BluetoothDeviceWinTest() { BluetoothTaskManagerWin::DeviceState device_state; device_state.name = kDeviceName; device_state.address = kDeviceAddress; + // Add device with audio/video services. BluetoothTaskManagerWin::ServiceRecordState* audio_state = new BluetoothTaskManagerWin::ServiceRecordState(); audio_state->name = kTestAudioSdpName; @@ -60,84 +63,39 @@ class BluetoothDeviceWinTest : public testing::Test { base::HexStringToBytes(kTestVideoSdpBytes, &video_state->sdp_bytes); device_state.service_record_states.push_back(video_state); - device_.reset(new BluetoothDeviceWin(device_state)); - } - - void GetServiceRecords( - const BluetoothDevice::ServiceRecordList& service_record_list) { - service_records_ = &service_record_list; - } - - void OnError() { - error_called_ = true; - } - - void SetProvidesServiceWithName(bool provides_service_with_name) { - provides_service_with_name_ = provides_service_with_name; + scoped_refptr<base::SequencedTaskRunner> ui_task_runner( + new base::TestSimpleTaskRunner()); + scoped_refptr<BluetoothSocketThread> socket_thread( + BluetoothSocketThread::Get()); + device_.reset(new BluetoothDeviceWin(device_state, + ui_task_runner, + socket_thread, + NULL, + net::NetLog::Source())); + + // Add empty device. + device_state.service_record_states.clear(); + empty_device_.reset(new BluetoothDeviceWin(device_state, + ui_task_runner, + socket_thread, + NULL, + net::NetLog::Source())); } protected: scoped_ptr<BluetoothDevice> device_; scoped_ptr<BluetoothDevice> empty_device_; - const BluetoothDevice::ServiceRecordList* service_records_; - bool error_called_; - bool provides_service_with_name_; }; -TEST_F(BluetoothDeviceWinTest, GetServices) { - BluetoothDevice::ServiceList service_list = device_->GetServices(); - - EXPECT_EQ(2, service_list.size()); - EXPECT_STREQ(kTestAudioSdpUuid, service_list[0].c_str()); - EXPECT_STREQ(kTestVideoSdpUuid, service_list[1].c_str()); -} +TEST_F(BluetoothDeviceWinTest, GetUUIDs) { + BluetoothDevice::UUIDList uuids = device_->GetUUIDs(); -TEST_F(BluetoothDeviceWinTest, GetServiceRecords) { - device_->GetServiceRecords( - base::Bind(&BluetoothDeviceWinTest::GetServiceRecords, - base::Unretained(this)), - BluetoothDevice::ErrorCallback()); - ASSERT_TRUE(service_records_ != NULL); - EXPECT_EQ(2, service_records_->size()); - EXPECT_STREQ(kTestAudioSdpUuid, (*service_records_)[0]->uuid().c_str()); - EXPECT_STREQ(kTestVideoSdpUuid, (*service_records_)[1]->uuid().c_str()); - - BluetoothDeviceWin* device_win = - reinterpret_cast<BluetoothDeviceWin*>(device_.get()); - - const BluetoothServiceRecord* audio_device = - device_win->GetServiceRecord(kTestAudioSdpUuid); - ASSERT_TRUE(audio_device != NULL); - EXPECT_EQ((*service_records_)[0], audio_device); - - const BluetoothServiceRecord* video_device = - device_win->GetServiceRecord(kTestVideoSdpUuid); - ASSERT_TRUE(video_device != NULL); - EXPECT_EQ((*service_records_)[1], video_device); - - const BluetoothServiceRecord* invalid_device = - device_win->GetServiceRecord(kTestVideoSdpAddress); - EXPECT_TRUE(invalid_device == NULL); -} + EXPECT_EQ(2, uuids.size()); + EXPECT_EQ(kTestAudioSdpUuid, uuids[0]); + EXPECT_EQ(kTestVideoSdpUuid, uuids[1]); -TEST_F(BluetoothDeviceWinTest, ProvidesServiceWithName) { - device_->ProvidesServiceWithName( - kTestAudioSdpName, - base::Bind(&BluetoothDeviceWinTest::SetProvidesServiceWithName, - base::Unretained(this))); - EXPECT_TRUE(provides_service_with_name_); - - device_->ProvidesServiceWithName( - kTestVideoSdpName, - base::Bind(&BluetoothDeviceWinTest::SetProvidesServiceWithName, - base::Unretained(this))); - EXPECT_TRUE(provides_service_with_name_); - - device_->ProvidesServiceWithName( - "name that does not exist", - base::Bind(&BluetoothDeviceWinTest::SetProvidesServiceWithName, - base::Unretained(this))); - EXPECT_FALSE(provides_service_with_name_); + uuids = empty_device_->GetUUIDs(); + EXPECT_EQ(0, uuids.size()); } -} // namespace device
\ No newline at end of file +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_discovery_manager_mac.h b/chromium/device/bluetooth/bluetooth_discovery_manager_mac.h new file mode 100644 index 00000000000..575a033faf2 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_discovery_manager_mac.h @@ -0,0 +1,66 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_DISCOVERY_MANAGER_MAC_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_DISCOVERY_MANAGER_MAC_H_ + +#include "base/macros.h" + +@class IOBluetoothDevice; + +namespace device { + +// Class used by BluetoothAdapterMac to manage classic and LE device discovery. +// For Bluetooth Classic, this class is responsible for keeping device inquiry +// running if device discovery is initiated. +class BluetoothDiscoveryManagerMac { + public: + // Interface for being notified of events during a device discovery session. + class Observer { + public: + // Called when |manager| has found a device through classic device inquiry + // in the form of a IOBluetoothDevice. + virtual void DeviceFound(BluetoothDiscoveryManagerMac* manager, + IOBluetoothDevice* device) {} + + // Called when device discovery is no longer running, due to either a call + // to BluetoothDiscoveryManagerMac::StopDiscovery or an unexpected reason, + // such as when a user disables the controller, in which case the value of + // |unexpected| will be true. + virtual void DiscoveryStopped(BluetoothDiscoveryManagerMac* manager, + bool unexpected) {} + }; + + virtual ~BluetoothDiscoveryManagerMac(); + + // Returns true, if discovery is currently being performed. + virtual bool IsDiscovering() const = 0; + + // Initiates a discovery session. Returns true on success or if discovery + // is already running. Returns false on failure. + virtual bool StartDiscovery() = 0; + + // Stops a discovery session. Returns true on success or if discovery is + // already not running. Returns false on failure. + virtual bool StopDiscovery() = 0; + + // Creates a discovery manager for Bluetooth Classic device discovery with + // observer |observer|. Note that the life-time of |observer| should not + // end before that of the returned BluetoothDiscoveryManager, as that may + // lead to use after free errors. + static BluetoothDiscoveryManagerMac* CreateClassic(Observer* observer); + + protected: + explicit BluetoothDiscoveryManagerMac(Observer* observer); + + // Observer interested in notifications from us. + Observer* observer_; + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothDiscoveryManagerMac); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_DISCOVERY_MANAGER_MAC_H_ diff --git a/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm b/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm new file mode 100644 index 00000000000..528e22dc77b --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm @@ -0,0 +1,222 @@ +// 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 "device/bluetooth/bluetooth_discovery_manager_mac.h" + +#import <IOBluetooth/objc/IOBluetoothDevice.h> +#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h> + +#include "base/mac/scoped_nsobject.h" +#include "base/mac/sdk_forward_declarations.h" +#include "base/logging.h" + +namespace device { + +class BluetoothDiscoveryManagerMacClassic; + +} // namespace device + +// IOBluetoothDeviceInquiryDelegate implementation. +@interface BluetoothDeviceInquiryDelegate + : NSObject<IOBluetoothDeviceInquiryDelegate> { + @private + device::BluetoothDiscoveryManagerMacClassic* manager_; // weak +} + +- (id)initWithManager:(device::BluetoothDiscoveryManagerMacClassic*)manager; + +@end + +namespace device { + +// ementation of BluetoothDiscoveryManagerMac for Bluetooth classic device +// discovery, using the IOBluetooth framework. +class BluetoothDiscoveryManagerMacClassic + : public BluetoothDiscoveryManagerMac { + public: + explicit BluetoothDiscoveryManagerMacClassic(Observer* observer) + : BluetoothDiscoveryManagerMac(observer), + should_do_discovery_(false), + inquiry_running_(false), + inquiry_delegate_( + [[BluetoothDeviceInquiryDelegate alloc] initWithManager:this]), + inquiry_([[IOBluetoothDeviceInquiry alloc] + initWithDelegate:inquiry_delegate_]) {} + + virtual ~BluetoothDiscoveryManagerMacClassic() {} + + // BluetoothDiscoveryManagerMac override. + virtual bool IsDiscovering() const OVERRIDE { return should_do_discovery_; } + + // BluetoothDiscoveryManagerMac override. + virtual bool StartDiscovery() OVERRIDE { + DVLOG(1) << "Bluetooth Classic: StartDiscovery"; + DCHECK(!should_do_discovery_); + + DVLOG(1) << "Discovery requested"; + should_do_discovery_ = true; + + if (inquiry_running_) { + DVLOG(1) << "Device inquiry already running"; + return true; + } + + DVLOG(1) << "Requesting to start device inquiry"; + if ([inquiry_ start] != kIOReturnSuccess) { + DVLOG(1) << "Failed to start device inquiry"; + + // Set |should_do_discovery_| to false here. Since we're reporting an + // error, we're indicating that the adapter call StartDiscovery again + // if needed. + should_do_discovery_ = false; + return false; + } + + DVLOG(1) << "Device inquiry start was successful"; + return true; + } + + // BluetoothDiscoveryManagerMac override. + virtual bool StopDiscovery() OVERRIDE { + DVLOG(1) << "Bluetooth Classic: StopDiscovery"; + DCHECK(should_do_discovery_); + + should_do_discovery_ = false; + + if (!inquiry_running_) { + DVLOG(1) << "No device inquiry running; discovery stopped"; + return true; + } + + DVLOG(1) << "Requesting to stop device inquiry"; + IOReturn status = [inquiry_ stop]; + if (status == kIOReturnSuccess) { + DVLOG(1) << "Device inquiry stop was successful"; + return true; + } + + if (status == kIOReturnNotPermitted) { + DVLOG(1) << "Device inquiry was already stopped"; + return true; + } + + LOG(WARNING) << "Failed to stop device inquiry"; + return false; + } + + // Called by BluetoothDeviceInquiryDelegate. + void DeviceInquiryStarted(IOBluetoothDeviceInquiry* inquiry) { + DCHECK(!inquiry_running_); + + DVLOG(1) << "Device inquiry started!"; + + // If discovery was requested to stop in the mean time, stop the inquiry. + if (!should_do_discovery_) { + DVLOG(1) << "Discovery stop was requested earlier. Stopping inquiry"; + [inquiry stop]; + return; + } + + inquiry_running_ = true; + } + + void DeviceFound(IOBluetoothDeviceInquiry* inquiry, + IOBluetoothDevice* device) { + DCHECK(observer_); + observer_->DeviceFound(this, device); + } + + void DeviceInquiryComplete(IOBluetoothDeviceInquiry* inquiry, + IOReturn error, + bool aborted) { + DCHECK_EQ(inquiry_, inquiry); + DCHECK(observer_); + DVLOG(1) << "Device inquiry complete"; + inquiry_running_ = false; + + // If discovery is no longer desired, notify observers that discovery + // has stopped and return. + if (!should_do_discovery_) { + observer_->DiscoveryStopped(this, false /* unexpected */); + return; + } + + // If discovery has stopped due to an unexpected reason, notify the + // observers and return. + if (error != kIOReturnSuccess) { + DVLOG(1) << "Inquiry has stopped with an error: " << error; + should_do_discovery_ = false; + observer_->DiscoveryStopped(this, true /* unexpected */); + return; + } + + DVLOG(1) << "Restarting device inquiry"; + + if ([inquiry_ start] == kIOReturnSuccess) { + DVLOG(1) << "Device inquiry restart was successful"; + return; + } + + DVLOG(1) << "Failed to restart discovery"; + should_do_discovery_ = false; + DCHECK(observer_); + observer_->DiscoveryStopped(this, true /* unexpected */); + } + + private: + // The requested discovery state. + bool should_do_discovery_; + + // The current inquiry state. + bool inquiry_running_; + + // Objective-C objects for running and tracking device inquiry. + base::scoped_nsobject<BluetoothDeviceInquiryDelegate> inquiry_delegate_; + base::scoped_nsobject<IOBluetoothDeviceInquiry> inquiry_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothDiscoveryManagerMacClassic); +}; + +BluetoothDiscoveryManagerMac::BluetoothDiscoveryManagerMac( + Observer* observer) : observer_(observer) { + DCHECK(observer); +} + +BluetoothDiscoveryManagerMac::~BluetoothDiscoveryManagerMac() { +} + +// static +BluetoothDiscoveryManagerMac* BluetoothDiscoveryManagerMac::CreateClassic( + Observer* observer) { + return new BluetoothDiscoveryManagerMacClassic(observer); +} + +} // namespace device + +@implementation BluetoothDeviceInquiryDelegate + +- (id)initWithManager: + (device::BluetoothDiscoveryManagerMacClassic*)manager { + if ((self = [super init])) + manager_ = manager; + + return self; +} + +- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender { + manager_->DeviceInquiryStarted(sender); +} + +- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender + device:(IOBluetoothDevice*)device { + manager_->DeviceFound(sender, device); +} + +- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender + error:(IOReturn)error + aborted:(BOOL)aborted { + manager_->DeviceInquiryComplete(sender, error, aborted); +} + +@end diff --git a/chromium/device/bluetooth/bluetooth_discovery_session.cc b/chromium/device/bluetooth/bluetooth_discovery_session.cc new file mode 100644 index 00000000000..ba5f5fdba64 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_discovery_session.cc @@ -0,0 +1,65 @@ +// 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 "device/bluetooth/bluetooth_discovery_session.h" + +#include "base/bind.h" +#include "device/bluetooth/bluetooth_adapter.h" + +namespace device { + +BluetoothDiscoverySession::BluetoothDiscoverySession( + scoped_refptr<BluetoothAdapter> adapter) + : active_(true), adapter_(adapter), weak_ptr_factory_(this) { + DCHECK(adapter_.get()); +} + +BluetoothDiscoverySession::BluetoothDiscoverySession() + : active_(false), weak_ptr_factory_(this) {} + +BluetoothDiscoverySession::~BluetoothDiscoverySession() { + // |adapter_| may be NULL if this instance was initialized as a mock. + if (!adapter_.get()) { + DCHECK(!active_); + return; + } + Stop(base::Bind(&base::DoNothing), base::Bind(&base::DoNothing)); + MarkAsInactive(); +} + +bool BluetoothDiscoverySession::IsActive() const { + return active_; +} + +void BluetoothDiscoverySession::Stop( + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (!active_) { + LOG(WARNING) << "Discovery session not active. Cannot stop."; + error_callback.Run(); + return; + } + VLOG(1) << "Stopping device discovery session."; + DCHECK(adapter_.get()); + adapter_->RemoveDiscoverySession( + base::Bind(&BluetoothDiscoverySession::OnStop, + weak_ptr_factory_.GetWeakPtr(), + callback), + error_callback); +} + +void BluetoothDiscoverySession::OnStop(const base::Closure& callback) { + MarkAsInactive(); + callback.Run(); +} + +void BluetoothDiscoverySession::MarkAsInactive() { + if (!active_) + return; + active_ = false; + DCHECK(adapter_.get()); + adapter_->DiscoverySessionBecameInactive(this); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_discovery_session.h b/chromium/device/bluetooth/bluetooth_discovery_session.h new file mode 100644 index 00000000000..666538b8e06 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_discovery_session.h @@ -0,0 +1,90 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_DISCOVERY_SESSION_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_DISCOVERY_SESSION_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" + +namespace device { + +class BluetoothAdapter; + +// BluetoothDiscoverySession represents a current active or inactive device +// discovery session. Instances of this class are obtained by calling +// BluetoothAdapter::StartDiscoverySession. The Bluetooth adapter will be +// constantly searching for nearby devices, as long as at least one instance +// of an active BluetoothDiscoverySession exists. A BluetoothDiscoverySession is +// considered active, as long as the adapter is discovering AND the owner of the +// instance has not called BluetoothDiscoverySession::Stop. A +// BluetoothDiscoverySession might unexpectedly become inactive, if the adapter +// unexpectedly stops discovery. Users can implement the +// AdapterDiscoveringChanged method of the BluetoothAdapter::Observer interface +// to be notified of such a change and promptly request a new +// BluetoothDiscoverySession if their existing sessions have become inactive. +class BluetoothDiscoverySession { + public: + // The ErrorCallback is used by methods to asynchronously report errors. + typedef base::Closure ErrorCallback; + + // Destructor automatically terminates the discovery session. If this + // results in a call to the underlying system to stop device discovery + // (i.e. this instance represents the last active discovery session), + // the call may not always succeed. To be notified of such failures, + // users are highly encouraged to call BluetoothDiscoverySession::Stop, + // instead of relying on the destructor. + virtual ~BluetoothDiscoverySession(); + + // Returns true if the session is active, false otherwise. If false, the + // adapter might still be discovering as there might still be other active + // sessions; this just means that this instance no longer has a say in + // whether or not discovery should continue. In this case, the application + // should request a new BluetoothDiscoverySession to make sure that device + // discovery continues. + virtual bool IsActive() const; + + // Requests this discovery session instance to stop. If this instance is + // active, the session will stop. On success, |callback| is called and + // on error |error_callback| is called. After a successful invocation, the + // adapter may or may not stop device discovery, depending on whether or not + // other active discovery sessions are present. Users are highly encouraged + // to call this method to end a discovery session, instead of relying on the + // destructor, so that they can be notified of the result via the callback + // arguments. + virtual void Stop(const base::Closure& callback, + const ErrorCallback& error_callback); + + protected: + BluetoothDiscoverySession(); // Called by mock. + + private: + friend class BluetoothAdapter; + explicit BluetoothDiscoverySession(scoped_refptr<BluetoothAdapter> adapter); + + // Internal callback invoked when a call to Stop has succeeded. + void OnStop(const base::Closure& callback); + + // Marks this instance as inactive. Called by BluetoothAdapter to mark a + // session as inactive in the case of an unexpected change to the adapter + // discovery state. + void MarkAsInactive(); + + // Whether or not this instance represents an active discovery session. + bool active_; + + // The adapter that created this instance. + scoped_refptr<BluetoothAdapter> adapter_; + + // 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<BluetoothDiscoverySession> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothDiscoverySession); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_DISCOVERY_SESSION_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_characteristic.cc b/chromium/device/bluetooth/bluetooth_gatt_characteristic.cc new file mode 100644 index 00000000000..47182a5d9f9 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_characteristic.cc @@ -0,0 +1,27 @@ +// 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 "device/bluetooth/bluetooth_gatt_characteristic.h" + +#include "base/logging.h" + +namespace device { + +BluetoothGattCharacteristic::BluetoothGattCharacteristic() { +} + +BluetoothGattCharacteristic::~BluetoothGattCharacteristic() { +} + +// static +BluetoothGattCharacteristic* BluetoothGattCharacteristic::Create( + const BluetoothUUID& uuid, + const std::vector<uint8>& value, + Properties properties, + Permissions permissions) { + LOG(ERROR) << "Creating local GATT characteristics currently not supported."; + return NULL; +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_gatt_characteristic.h b/chromium/device/bluetooth/bluetooth_gatt_characteristic.h new file mode 100644 index 00000000000..7b952e08d65 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_characteristic.h @@ -0,0 +1,210 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_CHARACTERISTIC_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_CHARACTERISTIC_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +class BluetoothGattDescriptor; +class BluetoothGattService; +class BluetoothGattNotifySession; + +// BluetoothGattCharacteristic represents a local or remote GATT characteristic. +// A GATT characteristic is a basic data element used to construct a GATT +// service. Hence, instances of a BluetoothGattCharacteristic are associated +// with a BluetoothGattService. There are two ways in which this class is used: +// +// 1. To represent GATT characteristics that belong to a service hosted by a +// a remote device. In this case the characteristic will be constructed by +// the subsystem. +// 2. To represent GATT characteristics that belong to a locally hosted +// service. To achieve this, users can construct instances of +// BluetoothGattCharacteristic directly and add it to the desired +// BluetoothGattService instance that represents a local service. +class BluetoothGattCharacteristic { + public: + // Values representing the possible properties of a characteristic, which + // define how the characteristic can be used. Each of these properties serve + // a role as defined in the Bluetooth Specification. + // |kPropertyExtendedProperties| is a special property that, if present, + // indicates that there is a characteristic descriptor (namely the + // "Characteristic Extended Properties Descriptor" with UUID 0x2900) that + // contains additional properties pertaining to the characteristic. + // The properties "ReliableWrite| and |WriteAuxiliaries| are retrieved from + // that characteristic. + enum Property { + kPropertyNone = 0, + kPropertyBroadcast = 1 << 0, + kPropertyRead = 1 << 1, + kPropertyWriteWithoutResponse = 1 << 2, + kPropertyWrite = 1 << 3, + kPropertyNotify = 1 << 4, + kPropertyIndicate = 1 << 5, + kPropertyAuthenticatedSignedWrites = 1 << 6, + kPropertyExtendedProperties = 1 << 7, + kPropertyReliableWrite = 1 << 8, + kPropertyWritableAuxiliaries = 1 << 9 + }; + typedef uint32 Properties; + + // Values representing read, write, and encryption permissions for a + // characteristic's value. While attribute permissions for all GATT + // definitions have been set by the Bluetooth specification, characteristic + // value permissions are left up to the higher-level profile. + // + // Attribute permissions are distinct from the characteristic properties. For + // example, a characteristic may have the property |kPropertyRead| to make + // clients know that it is possible to read the characteristic value and have + // the permission |kPermissionReadEncrypted| to require a secure connection. + // It is up to the application to properly specify the permissions and + // properties for a local characteristic. + enum Permission { + kPermissionNone = 0, + kPermissionRead = 1 << 0, + kPermissionWrite = 1 << 1, + kPermissionReadEncrypted = 1 << 2, + kPermissionWriteEncrypted = 1 << 3 + }; + typedef uint32 Permissions; + + // The ErrorCallback is used by methods to asynchronously report errors. + typedef base::Closure ErrorCallback; + + // The ValueCallback is used to return the value of a remote characteristic + // upon a read request. + typedef base::Callback<void(const std::vector<uint8>&)> ValueCallback; + + // The NotifySessionCallback is used to return sessions after they have + // been successfully started. + typedef base::Callback<void(scoped_ptr<BluetoothGattNotifySession>)> + NotifySessionCallback; + + // Constructs a BluetoothGattCharacteristic that can be associated with a + // local GATT service when the adapter is in the peripheral role. To + // associate the returned characteristic with a service, add it to a local + // service by calling BluetoothGattService::AddCharacteristic. + // + // This method constructs a characteristic with UUID |uuid|, initial cached + // value |value|, properties |properties|, and permissions |permissions|. + // |value| will be cached and returned for read requests and automatically set + // for write requests by default, unless an instance of + // BluetoothGattService::Delegate has been provided to the associated + // BluetoothGattService instance, in which case the delegate will handle read + // and write requests. + // + // NOTE: Don't explicitly set |kPropertyExtendedProperties| in |properties|. + // Instead, create and add a BluetoothGattDescriptor that represents the + // "Characteristic Extended Properties" descriptor and this will automatically + // set the correspoding bit in the characteristic's properties field. If + // |properties| has |kPropertyExtendedProperties| set, it will be ignored. + static BluetoothGattCharacteristic* Create(const BluetoothUUID& uuid, + const std::vector<uint8>& value, + Properties properties, + Permissions permissions); + + // Identifier used to uniquely identify a GATT characteristic object. This is + // different from the characteristic UUID: while multiple characteristics with + // the same UUID can exist on a Bluetooth device, the identifier returned from + // this method is unique among all characteristics of a device. The contents + // of the identifier are platform specific. + virtual std::string GetIdentifier() const = 0; + + // The Bluetooth-specific UUID of the characteristic. + virtual BluetoothUUID GetUUID() const = 0; + + // Returns true, if this characteristic is hosted locally. If false, then this + // instance represents a remote GATT characteristic. + virtual bool IsLocal() const = 0; + + // Returns the value of the characteristic. For remote characteristics, this + // is the most recently cached value. For local characteristics, this is the + // most recently updated value or the value retrieved from the delegate. + virtual const std::vector<uint8>& GetValue() const = 0; + + // Returns a pointer to the GATT service this characteristic belongs to. + virtual BluetoothGattService* GetService() const = 0; + + // Returns the bitmask of characteristic properties. + virtual Properties GetProperties() const = 0; + + // Returns the bitmask of characteristic attribute permissions. + virtual Permissions GetPermissions() const = 0; + + // Returns whether or not this characteristic is currently sending value + // updates in the form of a notification or indication. + virtual bool IsNotifying() const = 0; + + // Returns the list of GATT characteristic descriptors that provide more + // information about this characteristic. + virtual std::vector<BluetoothGattDescriptor*> + GetDescriptors() const = 0; + + // Returns the GATT characteristic descriptor with identifier |identifier| if + // it belongs to this GATT characteristic. + virtual BluetoothGattDescriptor* GetDescriptor( + const std::string& identifier) const = 0; + + // Adds a characteristic descriptor to the locally hosted characteristic + // represented by this instance. This method only makes sense for local + // characteristics and won't have an effect if this instance represents a + // remote GATT service and will return false. This method takes ownership + // of |descriptor|. + virtual bool AddDescriptor(BluetoothGattDescriptor* descriptor) = 0; + + // For locally hosted characteristics, updates the characteristic's value. + // This will update the value that is visible to remote devices and send out + // any notifications and indications that have been configured. This method + // can be used in place of, and in conjunction with, + // BluetoothGattService::Delegate methods to send updates to remote devices, + // or simply to set update the cached value for read requests without having + // to implement the delegate methods. + // + // This method only makes sense for local characteristics and does nothing and + // returns false if this instance represents a remote characteristic. + virtual bool UpdateValue(const std::vector<uint8>& value) = 0; + + // Starts a notify session for the remote characteristic, if it supports + // notifications/indications. On success, the characteristic starts sending + // value notifications and |callback| is called with a session object whose + // ownership belongs to the caller. |error_callback| is called on errors. + virtual void StartNotifySession(const NotifySessionCallback& callback, + const ErrorCallback& error_callback) = 0; + + // Sends a read request to a remote characteristic to read its value. + // |callback| is called to return the read value on success and + // |error_callback| is called for failures. + virtual void ReadRemoteCharacteristic( + const ValueCallback& callback, + const ErrorCallback& error_callback) = 0; + + // Sends a write request to a remote characteristic, to modify the + // characteristic's value with the new value |new_value|. |callback| is + // called to signal success and |error_callback| for failures. This method + // only applies to remote characteristics and will fail for those that are + // locally hosted. + virtual void WriteRemoteCharacteristic( + const std::vector<uint8>& new_value, + const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + + protected: + BluetoothGattCharacteristic(); + virtual ~BluetoothGattCharacteristic(); + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothGattCharacteristic); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_CHARACTERISTIC_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc b/chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc new file mode 100644 index 00000000000..673ed5f35fb --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc @@ -0,0 +1,1346 @@ +// 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_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "chromeos/dbus/fake_bluetooth_adapter_client.h" +#include "chromeos/dbus/fake_bluetooth_agent_manager_client.h" +#include "chromeos/dbus/fake_bluetooth_device_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_descriptor_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_service_client.h" +#include "chromeos/dbus/fake_bluetooth_input_client.h" +#include "chromeos/dbus/fake_dbus_thread_manager.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_gatt_connection.h" +#include "device/bluetooth/bluetooth_gatt_descriptor.h" +#include "device/bluetooth/bluetooth_gatt_notify_session.h" +#include "device/bluetooth/bluetooth_gatt_service.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "testing/gtest/include/gtest/gtest.h" + +using device::BluetoothAdapter; +using device::BluetoothDevice; +using device::BluetoothGattCharacteristic; +using device::BluetoothGattConnection; +using device::BluetoothGattDescriptor; +using device::BluetoothGattService; +using device::BluetoothGattNotifySession; +using device::BluetoothUUID; + +namespace chromeos { + +namespace { + +const BluetoothUUID kHeartRateMeasurementUUID( + FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID); +const BluetoothUUID kBodySensorLocationUUID( + FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID); +const BluetoothUUID kHeartRateControlPointUUID( + FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID); + +// Compares GATT characteristic/descriptor values. Returns true, if the values +// are equal. +bool ValuesEqual(const std::vector<uint8>& value0, + const std::vector<uint8>& value1) { + if (value0.size() != value1.size()) + return false; + for (size_t i = 0; i < value0.size(); ++i) + if (value0[i] != value1[i]) + return false; + return true; +} + +class TestDeviceObserver : public BluetoothDevice::Observer { + public: + TestDeviceObserver(scoped_refptr<BluetoothAdapter> adapter, + BluetoothDevice* device) + : gatt_service_added_count_(0), + gatt_service_removed_count_(0), + device_address_(device->GetAddress()), + adapter_(adapter) { + device->AddObserver(this); + } + + virtual ~TestDeviceObserver() { + BluetoothDevice* device = adapter_->GetDevice(device_address_); + if (device) + device->RemoveObserver(this); + } + + // BluetoothDevice::Observer overrides. + virtual void GattServiceAdded( + BluetoothDevice* device, + BluetoothGattService* service) OVERRIDE { + ASSERT_EQ(device_address_, device->GetAddress()); + + ++gatt_service_added_count_; + last_gatt_service_id_ = service->GetIdentifier(); + last_gatt_service_uuid_ = service->GetUUID(); + + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + + EXPECT_EQ(device->GetGattService(last_gatt_service_id_), service); + + QuitMessageLoop(); + } + + virtual void GattServiceRemoved( + BluetoothDevice* device, + BluetoothGattService* service) OVERRIDE { + ASSERT_EQ(device_address_, device->GetAddress()); + + ++gatt_service_removed_count_; + last_gatt_service_id_ = service->GetIdentifier(); + last_gatt_service_uuid_ = service->GetUUID(); + + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + + // The device should return NULL for this service. + EXPECT_FALSE(device->GetGattService(last_gatt_service_id_)); + + QuitMessageLoop(); + } + + int gatt_service_added_count_; + int gatt_service_removed_count_; + std::string last_gatt_service_id_; + BluetoothUUID last_gatt_service_uuid_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + + std::string device_address_; + scoped_refptr<BluetoothAdapter> adapter_; +}; + +class TestGattServiceObserver : public BluetoothGattService::Observer { + public: + TestGattServiceObserver(scoped_refptr<BluetoothAdapter> adapter, + BluetoothDevice* device, + BluetoothGattService* service) + : gatt_service_changed_count_(0), + gatt_characteristic_added_count_(0), + gatt_characteristic_removed_count_(0), + gatt_characteristic_value_changed_count_(0), + gatt_descriptor_added_count_(0), + gatt_descriptor_removed_count_(0), + gatt_descriptor_value_changed_count_(0), + device_address_(device->GetAddress()), + gatt_service_id_(service->GetIdentifier()), + adapter_(adapter) { + service->AddObserver(this); + } + + virtual ~TestGattServiceObserver() { + // See if either the device or the service even exist. + BluetoothDevice* device = adapter_->GetDevice(device_address_); + if (!device) + return; + + BluetoothGattService* service = device->GetGattService(gatt_service_id_); + if (!service) + return; + + service->RemoveObserver(this); + } + + // BluetoothGattService::Observer overrides. + virtual void GattServiceChanged(BluetoothGattService* service) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + ++gatt_service_changed_count_; + + QuitMessageLoop(); + } + + virtual void GattCharacteristicAdded( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + + ++gatt_characteristic_added_count_; + last_gatt_characteristic_id_ = characteristic->GetIdentifier(); + last_gatt_characteristic_uuid_ = characteristic->GetUUID(); + + EXPECT_EQ(service->GetCharacteristic(last_gatt_characteristic_id_), + characteristic); + EXPECT_EQ(service, characteristic->GetService()); + + QuitMessageLoop(); + } + + virtual void GattCharacteristicRemoved( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + + ++gatt_characteristic_removed_count_; + last_gatt_characteristic_id_ = characteristic->GetIdentifier(); + last_gatt_characteristic_uuid_ = characteristic->GetUUID(); + + // The service should return NULL for this characteristic. + EXPECT_FALSE(service->GetCharacteristic(last_gatt_characteristic_id_)); + EXPECT_EQ(service, characteristic->GetService()); + + QuitMessageLoop(); + } + + virtual void GattCharacteristicValueChanged( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic, + const std::vector<uint8>& value) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + + ++gatt_characteristic_value_changed_count_; + last_gatt_characteristic_id_ = characteristic->GetIdentifier(); + last_gatt_characteristic_uuid_ = characteristic->GetUUID(); + last_changed_characteristic_value_ = value; + + EXPECT_EQ(service->GetCharacteristic(last_gatt_characteristic_id_), + characteristic); + EXPECT_EQ(service, characteristic->GetService()); + + QuitMessageLoop(); + } + + virtual void GattDescriptorAdded( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor) OVERRIDE { + ASSERT_EQ(gatt_service_id_, characteristic->GetService()->GetIdentifier()); + + ++gatt_descriptor_added_count_; + last_gatt_descriptor_id_ = descriptor->GetIdentifier(); + last_gatt_descriptor_uuid_ = descriptor->GetUUID(); + + EXPECT_EQ(characteristic->GetDescriptor(last_gatt_descriptor_id_), + descriptor); + EXPECT_EQ(characteristic, descriptor->GetCharacteristic()); + + QuitMessageLoop(); + } + + virtual void GattDescriptorRemoved( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor) OVERRIDE { + ASSERT_EQ(gatt_service_id_, characteristic->GetService()->GetIdentifier()); + + ++gatt_descriptor_removed_count_; + last_gatt_descriptor_id_ = descriptor->GetIdentifier(); + last_gatt_descriptor_uuid_ = descriptor->GetUUID(); + + // The characteristic should return NULL for this descriptor.. + EXPECT_FALSE(characteristic->GetDescriptor(last_gatt_descriptor_id_)); + EXPECT_EQ(characteristic, descriptor->GetCharacteristic()); + + QuitMessageLoop(); + } + + virtual void GattDescriptorValueChanged( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor, + const std::vector<uint8>& value) OVERRIDE { + ASSERT_EQ(gatt_service_id_, characteristic->GetService()->GetIdentifier()); + + ++gatt_descriptor_value_changed_count_; + last_gatt_descriptor_id_ = descriptor->GetIdentifier(); + last_gatt_descriptor_uuid_ = descriptor->GetUUID(); + last_changed_descriptor_value_ = value; + + EXPECT_EQ(characteristic->GetDescriptor(last_gatt_descriptor_id_), + descriptor); + EXPECT_EQ(characteristic, descriptor->GetCharacteristic()); + + QuitMessageLoop(); + } + + int gatt_service_changed_count_; + int gatt_characteristic_added_count_; + int gatt_characteristic_removed_count_; + int gatt_characteristic_value_changed_count_; + int gatt_descriptor_added_count_; + int gatt_descriptor_removed_count_; + int gatt_descriptor_value_changed_count_; + std::string last_gatt_characteristic_id_; + BluetoothUUID last_gatt_characteristic_uuid_; + std::vector<uint8> last_changed_characteristic_value_; + std::string last_gatt_descriptor_id_; + BluetoothUUID last_gatt_descriptor_uuid_; + std::vector<uint8> last_changed_descriptor_value_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + + std::string device_address_; + std::string gatt_service_id_; + scoped_refptr<BluetoothAdapter> adapter_; +}; + +} // namespace + +class BluetoothGattChromeOSTest : public testing::Test { + public: + BluetoothGattChromeOSTest() + : fake_bluetooth_gatt_service_client_(NULL), + success_callback_count_(0), + error_callback_count_(0) { + } + + virtual void SetUp() { + FakeDBusThreadManager* fake_dbus_thread_manager = new FakeDBusThreadManager; + fake_bluetooth_device_client_ = new FakeBluetoothDeviceClient; + fake_bluetooth_gatt_service_client_ = + new FakeBluetoothGattServiceClient; + fake_bluetooth_gatt_characteristic_client_ = + new FakeBluetoothGattCharacteristicClient; + fake_bluetooth_gatt_descriptor_client_ = + new FakeBluetoothGattDescriptorClient; + fake_dbus_thread_manager->SetBluetoothDeviceClient( + scoped_ptr<BluetoothDeviceClient>( + fake_bluetooth_device_client_)); + fake_dbus_thread_manager->SetBluetoothGattServiceClient( + scoped_ptr<BluetoothGattServiceClient>( + fake_bluetooth_gatt_service_client_)); + fake_dbus_thread_manager->SetBluetoothGattCharacteristicClient( + scoped_ptr<BluetoothGattCharacteristicClient>( + fake_bluetooth_gatt_characteristic_client_)); + fake_dbus_thread_manager->SetBluetoothGattDescriptorClient( + scoped_ptr<BluetoothGattDescriptorClient>( + fake_bluetooth_gatt_descriptor_client_)); + fake_dbus_thread_manager->SetBluetoothAdapterClient( + scoped_ptr<BluetoothAdapterClient>(new FakeBluetoothAdapterClient)); + fake_dbus_thread_manager->SetBluetoothInputClient( + scoped_ptr<BluetoothInputClient>(new FakeBluetoothInputClient)); + fake_dbus_thread_manager->SetBluetoothAgentManagerClient( + scoped_ptr<BluetoothAgentManagerClient>( + new FakeBluetoothAgentManagerClient)); + DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager); + + GetAdapter(); + + adapter_->SetPowered( + true, + base::Bind(&base::DoNothing), + base::Bind(&base::DoNothing)); + ASSERT_TRUE(adapter_->IsPowered()); + } + + virtual void TearDown() { + adapter_ = NULL; + update_sessions_.clear(); + gatt_conn_.reset(); + DBusThreadManager::Shutdown(); + } + + void GetAdapter() { + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&BluetoothGattChromeOSTest::AdapterCallback, + base::Unretained(this))); + ASSERT_TRUE(adapter_.get() != NULL); + ASSERT_TRUE(adapter_->IsInitialized()); + ASSERT_TRUE(adapter_->IsPresent()); + } + + void AdapterCallback(scoped_refptr<BluetoothAdapter> adapter) { + adapter_ = adapter; + } + + void SuccessCallback() { + ++success_callback_count_; + } + + void ValueCallback(const std::vector<uint8>& value) { + ++success_callback_count_; + last_read_value_ = value; + } + + void GattConnectionCallback(scoped_ptr<BluetoothGattConnection> conn) { + ++success_callback_count_; + gatt_conn_ = conn.Pass(); + } + + void NotifySessionCallback(scoped_ptr<BluetoothGattNotifySession> session) { + ++success_callback_count_; + update_sessions_.push_back(session.release()); + QuitMessageLoop(); + } + + void ErrorCallback() { + ++error_callback_count_; + } + + void DBusErrorCallback(const std::string& error_name, + const std::string& error_message) { + ++error_callback_count_; + } + + void ConnectErrorCallback(BluetoothDevice::ConnectErrorCode error) { + ++error_callback_count_; + } + + protected: + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + + base::MessageLoop message_loop_; + + FakeBluetoothDeviceClient* fake_bluetooth_device_client_; + FakeBluetoothGattServiceClient* fake_bluetooth_gatt_service_client_; + FakeBluetoothGattCharacteristicClient* + fake_bluetooth_gatt_characteristic_client_; + FakeBluetoothGattDescriptorClient* fake_bluetooth_gatt_descriptor_client_; + scoped_ptr<device::BluetoothGattConnection> gatt_conn_; + ScopedVector<BluetoothGattNotifySession> update_sessions_; + scoped_refptr<BluetoothAdapter> adapter_; + + int success_callback_count_; + int error_callback_count_; + std::vector<uint8> last_read_value_; +}; + +TEST_F(BluetoothGattChromeOSTest, GattConnection) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + ASSERT_FALSE(device->IsConnected()); + ASSERT_FALSE(gatt_conn_.get()); + ASSERT_EQ(0, success_callback_count_); + ASSERT_EQ(0, error_callback_count_); + + device->CreateGattConnection( + base::Bind(&BluetoothGattChromeOSTest::GattConnectionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_TRUE(gatt_conn_->IsConnected()); + EXPECT_EQ(FakeBluetoothDeviceClient::kLowEnergyAddress, + gatt_conn_->GetDeviceAddress()); + + gatt_conn_->Disconnect( + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(gatt_conn_->IsConnected()); + + device->CreateGattConnection( + base::Bind(&BluetoothGattChromeOSTest::GattConnectionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_TRUE(gatt_conn_->IsConnected()); + + device->Disconnect( + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(4, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_FALSE(gatt_conn_->IsConnected()); + + device->CreateGattConnection( + base::Bind(&BluetoothGattChromeOSTest::GattConnectionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(5, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + EXPECT_TRUE(gatt_conn_->IsConnected()); + + fake_bluetooth_device_client_->RemoveDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_FALSE(gatt_conn_->IsConnected()); +} + +TEST_F(BluetoothGattChromeOSTest, GattServiceAddedAndRemoved) { + // Create a fake LE device. We store the device pointer here because this is a + // test. It's unsafe to do this in production as the device might get deleted. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + EXPECT_EQ(0, observer.gatt_service_added_count_); + EXPECT_EQ(0, observer.gatt_service_removed_count_); + EXPECT_TRUE(observer.last_gatt_service_id_.empty()); + EXPECT_FALSE(observer.last_gatt_service_uuid_.IsValid()); + EXPECT_TRUE(device->GetGattServices().empty()); + + // Expose the fake Heart Rate Service. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + EXPECT_EQ(1, observer.gatt_service_added_count_); + EXPECT_EQ(0, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_EQ(1U, device->GetGattServices().size()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + EXPECT_EQ(service, device->GetGattServices()[0]); + EXPECT_EQ(service, device->GetGattService(service->GetIdentifier())); + + EXPECT_EQ(observer.last_gatt_service_uuid_, service->GetUUID()); + + // Hide the service. + observer.last_gatt_service_uuid_ = BluetoothUUID(); + observer.last_gatt_service_id_.clear(); + fake_bluetooth_gatt_service_client_->HideHeartRateService(); + + EXPECT_EQ(1, observer.gatt_service_added_count_); + EXPECT_EQ(1, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_TRUE(device->GetGattServices().empty()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + + EXPECT_EQ(NULL, device->GetGattService(observer.last_gatt_service_id_)); + + // Expose the service again. + observer.last_gatt_service_uuid_ = BluetoothUUID(); + observer.last_gatt_service_id_.clear(); + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + EXPECT_EQ(2, observer.gatt_service_added_count_); + EXPECT_EQ(1, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_EQ(1U, device->GetGattServices().size()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + + // The object |service| points to should have been deallocated. |device| + // should contain a brand new instance. + service = device->GetGattService(observer.last_gatt_service_id_); + EXPECT_EQ(service, device->GetGattServices()[0]); + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + + EXPECT_EQ(observer.last_gatt_service_uuid_, service->GetUUID()); + + // Remove the device. The observer should be notified of the removed service. + // |device| becomes invalid after this. + observer.last_gatt_service_uuid_ = BluetoothUUID(); + observer.last_gatt_service_id_.clear(); + fake_bluetooth_device_client_->RemoveDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + + EXPECT_EQ(2, observer.gatt_service_added_count_); + EXPECT_EQ(2, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + EXPECT_EQ( + NULL, adapter_->GetDevice(FakeBluetoothDeviceClient::kLowEnergyAddress)); +} + +TEST_F(BluetoothGattChromeOSTest, GattCharacteristicAddedAndRemoved) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_service_changed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + // 3 characteristics should appear. Only 1 of the characteristics sends + // value changed signals. Service changed should be fired once for + // descriptor added. + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(3U, service->GetCharacteristics().size()); + + // Hide the characteristics. 3 removed signals should be received. + fake_bluetooth_gatt_characteristic_client_->HideHeartRateCharacteristics(); + EXPECT_EQ(8, service_observer.gatt_service_changed_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Re-expose the heart rate characteristics. + fake_bluetooth_gatt_characteristic_client_->ExposeHeartRateCharacteristics( + fake_bluetooth_gatt_service_client_->GetHeartRateServicePath()); + EXPECT_EQ(12, service_observer.gatt_service_changed_count_); + EXPECT_EQ(6, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(3U, service->GetCharacteristics().size()); + + // Hide the service. All characteristics should disappear. + fake_bluetooth_gatt_service_client_->HideHeartRateService(); + EXPECT_EQ(16, service_observer.gatt_service_changed_count_); + EXPECT_EQ(6, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(6, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); +} + +TEST_F(BluetoothGattChromeOSTest, GattDescriptorAddedAndRemoved) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_service_changed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + + // Only the Heart Rate Measurement characteristic has a descriptor. + EXPECT_EQ(1, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + + BluetoothGattDescriptor* descriptor = characteristic->GetDescriptors()[0]; + EXPECT_FALSE(descriptor->IsLocal()); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + EXPECT_EQ(descriptor->GetUUID(), + service_observer.last_gatt_descriptor_uuid_); + EXPECT_EQ(descriptor->GetIdentifier(), + service_observer.last_gatt_descriptor_id_); + + // Hide the descriptor. + fake_bluetooth_gatt_descriptor_client_->HideDescriptor( + dbus::ObjectPath(descriptor->GetIdentifier())); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + EXPECT_EQ(5, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + // Expose the descriptor again. + service_observer.last_gatt_descriptor_id_.clear(); + service_observer.last_gatt_descriptor_uuid_ = BluetoothUUID(); + fake_bluetooth_gatt_descriptor_client_->ExposeDescriptor( + dbus::ObjectPath(characteristic->GetIdentifier()), + FakeBluetoothGattDescriptorClient:: + kClientCharacteristicConfigurationUUID); + EXPECT_EQ(6, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + EXPECT_EQ(2, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + descriptor = characteristic->GetDescriptors()[0]; + EXPECT_FALSE(descriptor->IsLocal()); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + EXPECT_EQ(descriptor->GetUUID(), service_observer.last_gatt_descriptor_uuid_); + EXPECT_EQ(descriptor->GetIdentifier(), + service_observer.last_gatt_descriptor_id_); +} + +TEST_F(BluetoothGattChromeOSTest, AdapterAddedAfterGattService) { + // This unit test tests that all remote GATT objects are created for D-Bus + // objects that were already exposed. + adapter_ = NULL; + ASSERT_FALSE(device::BluetoothAdapterFactory::HasSharedInstanceForTesting()); + + // Create the fake D-Bus objects. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + while (!fake_bluetooth_gatt_characteristic_client_->IsHeartRateVisible()) + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(fake_bluetooth_gatt_service_client_->IsHeartRateVisible()); + ASSERT_TRUE(fake_bluetooth_gatt_characteristic_client_->IsHeartRateVisible()); + + // Create the adapter. This should create all the GATT objects. + GetAdapter(); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + EXPECT_EQ(1U, device->GetGattServices().size()); + + BluetoothGattService* service = device->GetGattServices()[0]; + ASSERT_TRUE(service); + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + service->GetUUID()); + EXPECT_EQ(service, device->GetGattServices()[0]); + EXPECT_EQ(service, device->GetGattService(service->GetIdentifier())); + EXPECT_FALSE(service->IsLocal()); + EXPECT_EQ(3U, service->GetCharacteristics().size()); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattCharacteristicClient:: + kBodySensorLocationUUID), + characteristic->GetUUID()); + EXPECT_FALSE(characteristic->IsLocal()); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattCharacteristicClient:: + kHeartRateControlPointUUID), + characteristic->GetUUID()); + EXPECT_FALSE(characteristic->IsLocal()); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattCharacteristicClient:: + kHeartRateMeasurementUUID), + characteristic->GetUUID()); + EXPECT_FALSE(characteristic->IsLocal()); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + + BluetoothGattDescriptor* descriptor = characteristic->GetDescriptors()[0]; + ASSERT_TRUE(descriptor); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + EXPECT_FALSE(descriptor->IsLocal()); +} + +TEST_F(BluetoothGattChromeOSTest, GattCharacteristicValue) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + // Issue write request to non-writeable characteristics. + service_observer.last_gatt_characteristic_id_.clear(); + service_observer.last_gatt_characteristic_uuid_ = BluetoothUUID(); + + std::vector<uint8> write_value; + write_value.push_back(0x01); + BluetoothGattCharacteristic* characteristic = + service->GetCharacteristic(fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kHeartRateMeasurementUUID, characteristic->GetUUID()); + characteristic->WriteRemoteCharacteristic( + write_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(service_observer.last_gatt_characteristic_id_.empty()); + EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid()); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->WriteRemoteCharacteristic( + write_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(service_observer.last_gatt_characteristic_id_.empty()); + EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid()); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Issue write request to writeable characteristic. The "Body Sensor Location" + // characteristic does not send notifications and WriteValue does not result + // in a CharacteristicValueChanged event, thus no such event should be + // received. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kHeartRateControlPointUUID, characteristic->GetUUID()); + characteristic->WriteRemoteCharacteristic( + write_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(service_observer.last_gatt_characteristic_id_.empty()); + EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid()); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Issue a read request. A successful read results in a + // CharacteristicValueChanged notification. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->ReadRemoteCharacteristic( + base::Bind(&BluetoothGattChromeOSTest::ValueCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_)); +} + +TEST_F(BluetoothGattChromeOSTest, GattCharacteristicProperties) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + BluetoothGattCharacteristic *characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + EXPECT_EQ(BluetoothGattCharacteristic::kPropertyRead, + characteristic->GetProperties()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + EXPECT_EQ(BluetoothGattCharacteristic::kPropertyWrite, + characteristic->GetProperties()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + EXPECT_EQ(BluetoothGattCharacteristic::kPropertyNotify, + characteristic->GetProperties()); +} + +TEST_F(BluetoothGattChromeOSTest, GattDescriptorValue) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_service_changed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + + // Only the Heart Rate Measurement characteristic has a descriptor. + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + + BluetoothGattDescriptor* descriptor = characteristic->GetDescriptors()[0]; + EXPECT_FALSE(descriptor->IsLocal()); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + + std::vector<uint8> desc_value; + desc_value.push_back(1); + desc_value.push_back(0); + + /* The cached value will be empty until the first read request */ + EXPECT_FALSE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_TRUE(descriptor->GetValue().empty()); + + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(last_read_value_.empty()); + + // Read value. GattDescriptorValueChanged event will be sent after a + // successful read. + descriptor->ReadRemoteDescriptor( + base::Bind(&BluetoothGattChromeOSTest::ValueCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(ValuesEqual(last_read_value_, descriptor->GetValue())); + EXPECT_TRUE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_value_changed_count_); + + // Write value. Writes to this descriptor will fail. + desc_value[0] = 0x03; + descriptor->WriteRemoteDescriptor( + desc_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(ValuesEqual(last_read_value_, descriptor->GetValue())); + EXPECT_FALSE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_value_changed_count_); + + // Read new value. + descriptor->ReadRemoteDescriptor( + base::Bind(&BluetoothGattChromeOSTest::ValueCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(ValuesEqual(last_read_value_, descriptor->GetValue())); + EXPECT_FALSE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(2, service_observer.gatt_descriptor_value_changed_count_); +} + +TEST_F(BluetoothGattChromeOSTest, NotifySessions) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + // Request to start notifications. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + // The operation still hasn't completed but we should have received the first + // notification. + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(update_sessions_.empty()); + + // Send a two more requests, which should get queued. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(update_sessions_.empty()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Run the main loop. The initial call should complete. The queued call should + // succeed immediately. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(3U, update_sessions_.size()); + + // Notifications should be getting sent regularly now. + base::MessageLoop::current()->Run(); + EXPECT_GT(service_observer.gatt_characteristic_value_changed_count_, 1); + + // Stop one of the sessions. The session should become inactive but the + // characteristic should still be notifying. + BluetoothGattNotifySession* session = update_sessions_[0]; + EXPECT_TRUE(session->IsActive()); + session->Stop(base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this))); + EXPECT_EQ(4, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(session->IsActive()); + EXPECT_EQ(characteristic->GetIdentifier(), + session->GetCharacteristicIdentifier()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Delete another session. Characteristic should still be notifying. + update_sessions_.pop_back(); + EXPECT_EQ(2U, update_sessions_.size()); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_FALSE(update_sessions_[0]->IsActive()); + EXPECT_TRUE(update_sessions_[1]->IsActive()); + + // Clear the last session. + update_sessions_.clear(); + EXPECT_TRUE(update_sessions_.empty()); + EXPECT_FALSE(characteristic->IsNotifying()); + + success_callback_count_ = 0; + service_observer.gatt_characteristic_value_changed_count_ = 0; + + // Enable notifications again. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(update_sessions_.empty()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Run the message loop. Notifications should begin. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(1U, update_sessions_.size()); + EXPECT_TRUE(update_sessions_[0]->IsActive()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Check that notifications are happening. + base::MessageLoop::current()->Run(); + EXPECT_GT(service_observer.gatt_characteristic_value_changed_count_, 1); + + // Request another session. This should return immediately. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(2U, update_sessions_.size()); + EXPECT_TRUE(update_sessions_[0]->IsActive()); + EXPECT_TRUE(update_sessions_[1]->IsActive()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Hide the characteristic. The sessions should become inactive. + fake_bluetooth_gatt_characteristic_client_->HideHeartRateCharacteristics(); + EXPECT_EQ(2U, update_sessions_.size()); + EXPECT_FALSE(update_sessions_[0]->IsActive()); + EXPECT_FALSE(update_sessions_[1]->IsActive()); +} + +TEST_F(BluetoothGattChromeOSTest, NotifySessionsMadeInactive) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + // Send several requests to start notifications. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + // The operation still hasn't completed but we should have received the first + // notification. + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + // Run the main loop. The initial call should complete. The queued calls + // should succeed immediately. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(4, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_EQ(4U, update_sessions_.size()); + + for (int i = 0; i < 4; i++) + EXPECT_TRUE(update_sessions_[0]->IsActive()); + + // Stop notifications directly through the client. The sessions should get + // marked as inactive. + fake_bluetooth_gatt_characteristic_client_->StopNotify( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath(), + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + EXPECT_EQ(5, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_EQ(4U, update_sessions_.size()); + + for (int i = 0; i < 4; i++) + EXPECT_FALSE(update_sessions_[0]->IsActive()); + + // It should be possible to restart notifications and the call should reset + // the session count and make a request through the client. + update_sessions_.clear(); + success_callback_count_ = 0; + service_observer.gatt_characteristic_value_changed_count_ = 0; + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + base::MessageLoop::current()->Run(); + + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_EQ(1U, update_sessions_.size()); + EXPECT_TRUE(update_sessions_[0]->IsActive()); +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_gatt_connection.cc b/chromium/device/bluetooth/bluetooth_gatt_connection.cc new file mode 100644 index 00000000000..721391e8d1a --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_connection.cc @@ -0,0 +1,15 @@ +// 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 "device/bluetooth/bluetooth_gatt_connection.h" + +namespace device { + +BluetoothGattConnection::BluetoothGattConnection() { +} + +BluetoothGattConnection::~BluetoothGattConnection() { +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_gatt_connection.h b/chromium/device/bluetooth/bluetooth_gatt_connection.h new file mode 100644 index 00000000000..da630434879 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_connection.h @@ -0,0 +1,50 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_H_ + +#include <string> + +#include "base/callback.h" + +namespace device { + +// BluetoothGattConnection represents a GATT connection to a Bluetooth device +// that has GATT services. Instances are obtained from a BluetoothDevice, +// and the connection is kept alive as long as there is at least one +// active BluetoothGattConnection object. BluetoothGattConnection objects +// automatically update themselves, when the connection is terminated by the +// operating system (e.g. due to user action). +class BluetoothGattConnection { + public: + // Destructor automatically closes this GATT connection. If this is the last + // remaining GATT connection and this results in a call to the OS, that call + // may not always succeed. Users can make an explicit call to + // BluetoothGattConnection::Close to make sure that they are notified of + // a possible error via the callback. + virtual ~BluetoothGattConnection(); + + // Returns the Bluetooth address of the device that this connection is open + // to. + virtual std::string GetDeviceAddress() const = 0; + + // Returns true if this connection is open. + virtual bool IsConnected() = 0; + + // Disconnects this GATT connection and calls |callback| upon completion. + // After a successful invocation, the device may still remain connected due to + // other GATT connections. + virtual void Disconnect(const base::Closure& callback) = 0; + + protected: + BluetoothGattConnection(); + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothGattConnection); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_connection_chromeos.cc b/chromium/device/bluetooth/bluetooth_gatt_connection_chromeos.cc new file mode 100644 index 00000000000..94b7555170b --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_connection_chromeos.cc @@ -0,0 +1,107 @@ +// 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 "device/bluetooth/bluetooth_gatt_connection_chromeos.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" + +namespace chromeos { + +BluetoothGattConnectionChromeOS::BluetoothGattConnectionChromeOS( + scoped_refptr<device::BluetoothAdapter> adapter, + const std::string& device_address, + const dbus::ObjectPath& object_path) + : connected_(true), + adapter_(adapter), + device_address_(device_address), + object_path_(object_path) { + DCHECK(adapter_.get()); + DCHECK(!device_address_.empty()); + DCHECK(object_path_.IsValid()); + + DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this); +} + +BluetoothGattConnectionChromeOS::~BluetoothGattConnectionChromeOS() { + DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this); + Disconnect(base::Bind(&base::DoNothing)); +} + +std::string BluetoothGattConnectionChromeOS::GetDeviceAddress() const { + return device_address_; +} + +bool BluetoothGattConnectionChromeOS::IsConnected() { + // Lazily determine the activity state of the connection. If already + // marked as inactive, then return false. Otherwise, explicitly mark + // |connected_| as false if the device is removed or disconnected. We do this, + // so that if this method is called during a call to DeviceRemoved or + // DeviceChanged somewhere else, it returns the correct status. + if (!connected_) + return false; + + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()-> + GetProperties(object_path_); + if (!properties || !properties->connected.value()) + connected_ = false; + + return connected_; +} + +void BluetoothGattConnectionChromeOS::Disconnect( + const base::Closure& callback) { + if (!connected_) { + VLOG(1) << "Connection already inactive."; + callback.Run(); + return; + } + + // TODO(armansito): There isn't currently a good way to manage the ownership + // of a connection between Chrome and bluetoothd plugins/profiles. Until + // a proper reference count is kept by bluetoothd, we might unwittingly kill + // a connection that is managed by the daemon (e.g. HoG). For now, just return + // success to indicate that this BluetoothGattConnection is no longer active, + // even though the underlying connection won't actually be disconnected. This + // technically doesn't violate the contract put forth by this API. + connected_ = false; + callback.Run(); +} + +void BluetoothGattConnectionChromeOS::DeviceRemoved( + const dbus::ObjectPath& object_path) { + if (object_path != object_path_) + return; + + connected_ = false; +} + +void BluetoothGattConnectionChromeOS::DevicePropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) { + if (object_path != object_path_) + return; + + if (!connected_) + return; + + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()-> + GetProperties(object_path_); + + if (!properties) { + connected_ = false; + return; + } + + if (property_name == properties->connected.name() && + !properties->connected.value()) + connected_ = false; +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_gatt_connection_chromeos.h b/chromium/device/bluetooth/bluetooth_gatt_connection_chromeos.h new file mode 100644 index 00000000000..beade19d17b --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_connection_chromeos.h @@ -0,0 +1,66 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_CHROMEOS_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "chromeos/dbus/bluetooth_device_client.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_gatt_connection.h" + +namespace device { + +class BluetoothAdapter; + +} // namespace device + +namespace chromeos { + +// BluetoothGattConnectionChromeOS implements BluetoothGattConnection for the +// Chrome OS platform. +class BluetoothGattConnectionChromeOS + : public device::BluetoothGattConnection, + public BluetoothDeviceClient::Observer { + public: + explicit BluetoothGattConnectionChromeOS( + scoped_refptr<device::BluetoothAdapter> adapter, + const std::string& device_address, + const dbus::ObjectPath& object_path); + virtual ~BluetoothGattConnectionChromeOS(); + + // BluetoothGattConnection overrides. + virtual std::string GetDeviceAddress() const OVERRIDE; + virtual bool IsConnected() OVERRIDE; + virtual void Disconnect(const base::Closure& callback) OVERRIDE; + + private: + + // chromeos::BluetoothDeviceClient::Observer overrides. + virtual void DeviceRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void DevicePropertyChanged(const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // True, if the connection is currently active. + bool connected_; + + // The Bluetooth adapter that this connection is associated with. + scoped_refptr<device::BluetoothAdapter> adapter_; + + // Bluetooth address of the underlying device. + std::string device_address_; + + // D-Bus object path of the underlying device. This is used to filter observer + // events. + dbus::ObjectPath object_path_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothGattConnectionChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_descriptor.cc b/chromium/device/bluetooth/bluetooth_gatt_descriptor.cc new file mode 100644 index 00000000000..5d3673b5ea3 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_descriptor.cc @@ -0,0 +1,91 @@ +// 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 "device/bluetooth/bluetooth_gatt_descriptor.h" + +#include <vector> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" + +namespace device { +namespace { + +struct UUIDs { + UUIDs() : uuids_(MakeUUIDVector()) {} + + const std::vector<BluetoothUUID> uuids_; + + private: + static std::vector<BluetoothUUID> MakeUUIDVector() { + std::vector<BluetoothUUID> uuids; + static const char* const strings[] = { + "0x2900", "0x2901", "0x2902", "0x2903", "0x2904", "0x2905" + }; + + for (size_t i = 0; i < arraysize(strings); ++i) + uuids.push_back(BluetoothUUID(strings[i])); + + return uuids; + } +}; + +base::LazyInstance<const UUIDs>::Leaky g_uuids = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +const BluetoothUUID& +BluetoothGattDescriptor::CharacteristicExtendedPropertiesUuid() { + return g_uuids.Get().uuids_[0]; +} + +// static +const BluetoothUUID& +BluetoothGattDescriptor::CharacteristicUserDescriptionUuid() { + return g_uuids.Get().uuids_[1]; +} + +// static +const BluetoothUUID& +BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid() { + return g_uuids.Get().uuids_[2]; +} + +// static +const BluetoothUUID& +BluetoothGattDescriptor::ServerCharacteristicConfigurationUuid() { + return g_uuids.Get().uuids_[3]; +} + +// static +const BluetoothUUID& +BluetoothGattDescriptor::CharacteristicPresentationFormatUuid() { + return g_uuids.Get().uuids_[4]; +} + +// static +const BluetoothUUID& +BluetoothGattDescriptor::CharacteristicAggregateFormatUuid() { + return g_uuids.Get().uuids_[5]; +} + +BluetoothGattDescriptor::BluetoothGattDescriptor() { +} + +BluetoothGattDescriptor::~BluetoothGattDescriptor() { +} + +// static +BluetoothGattDescriptor* BluetoothGattDescriptor::Create( + const BluetoothUUID& uuid, + const std::vector<uint8>& value, + BluetoothGattCharacteristic::Permissions permissions) { + LOG(ERROR) << "Creating local GATT characteristic descriptors currently not " + << "supported."; + return NULL; +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_gatt_descriptor.h b/chromium/device/bluetooth/bluetooth_gatt_descriptor.h new file mode 100644 index 00000000000..e98ef29f754 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_descriptor.h @@ -0,0 +1,188 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_DESCRIPTOR_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_DESCRIPTOR_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "device/bluetooth/bluetooth_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +// BluetoothGattDescriptor represents a local or remote GATT characteristic +// descriptor. A GATT characteristic descriptor provides further information +// about a characteristic's value. They can be used to describe the +// characteristic's features or to control certain behaviors. +class BluetoothGattDescriptor { + public: + // The Bluetooth Specification declares several predefined descriptors that + // profiles can use. The following are definitions for the list of UUIDs + // and descriptions of the characteristic descriptors that they represent. + // Possible values for and further information on each descriptor can be found + // in Core v4.0, Volume 3, Part G, Section 3.3.3. All of these desciptors are + // optional and may not be present for a given characteristic. + + // The "Characteristic Extended Properties" descriptor. This defines + // additional "Characteristic Properties" which cannot fit into the allocated + // single octet property field of a characteristic. The value is a bit field + // and the two predefined bits, as per Bluetooth Core Specification v4.0, are: + // + // - Reliable Write: 0x0001 + // - Writable Auxiliaries: 0x0002 + // + static const BluetoothUUID& CharacteristicExtendedPropertiesUuid(); + + // The "Characteristic User Description" descriptor defines a UTF-8 string of + // variable size that is a user textual description of the associated + // characteristic's value. There can be only one instance of this descriptor + // per characteristic. This descriptor can be written to if the "Writable + // Auxiliaries" bit of the Characteristic Properties (via the "Characteristic + // Extended Properties" descriptor) has been set. + static const BluetoothUUID& CharacteristicUserDescriptionUuid(); + + // The "Client Characteristic Configuration" descriptor defines how the + // characteristic may be configured by a specific client. A server-side + // instance of this descriptor exists for each client that has bonded with + // the server and the value can be read and written by that client only. As + // of Core v4.0, this descriptor is used by clients to set up notifications + // and indications from a characteristic. The value is a bit field and the + // predefined bits are: + // + // - Default: 0x0000 + // - Notification: 0x0001 + // - Indication: 0x0002 + // + static const BluetoothUUID& ClientCharacteristicConfigurationUuid(); + + // The "Server Characteristic Configuration" descriptor defines how the + // characteristic may be configured for the server. There is one instance + // of this descriptor for all clients and setting the value of this descriptor + // affects its configuration for all clients. As of Core v4.0, this descriptor + // is used to set up the server to broadcast the characteristic value if + // advertising resources are available. The value is a bit field and the + // predefined bits are: + // + // - Default: 0x0000 + // - Broadcast: 0x0001 + // + static const BluetoothUUID& ServerCharacteristicConfigurationUuid(); + + // The "Characteristic Presentation Format" declaration defines the format of + // the Characteristic Value. The value is composed of 7 octets which are + // divided into groups that represent different semantic meanings. For a + // detailed description of how the value of this descriptor should be + // interpreted, refer to Core v4.0, Volume 3, Part G, Section 3.3.3.5. If more + // than one declaration of this descriptor exists for a characteristic, then a + // "Characteristic Aggregate Format" descriptor must also exist for that + // characteristic. + static const BluetoothUUID& CharacteristicPresentationFormatUuid(); + + // The "Characteristic Aggregate Format" descriptor defines the format of an + // aggragated characteristic value. In GATT's underlying protocol, ATT, each + // attribute is identified by a handle that is unique for the hosting server. + // Multiple characteristics can share the same instance(s) of a + // "Characteristic Presentation Format" descriptor. The value of the + // "Characteristic Aggregate Format" descriptor contains a list of handles + // that each refer to a "Characteristic Presentation Format" descriptor that + // is used by that characteristic. Hence, exactly one instance of this + // descriptor must exist if more than one "Characteristic Presentation Format" + // descriptors exist for a characteristic. + // + // Applications that are using the device::Bluetooth API do not have access to + // the underlying handles and shouldn't use this descriptor to determine which + // "Characteristic Presentation Format" desciptors belong to a characteristic. + // The API will construct a BluetoothGattDescriptor object for each instance + // of "Characteristic Presentation Format" descriptor per instance of + // BluetoothGattCharacteristic that represents a remote characteristic. + // Similarly for local characteristics, implementations DO NOT need to create + // an instance of BluetoothGattDescriptor for this descriptor as this will be + // handled by the subsystem. + static const BluetoothUUID& CharacteristicAggregateFormatUuid(); + + // The ErrorCallback is used by methods to asynchronously report errors. + typedef base::Closure ErrorCallback; + + // The ValueCallback is used to return the value of a remote characteristic + // descriptor upon a read request. + typedef base::Callback<void(const std::vector<uint8>&)> ValueCallback; + + // Constructs a BluetoothGattDescriptor that can be associated with a local + // GATT characteristic when the adapter is in the peripheral role. To + // associate the returned descriptor with a characteristic, add it to a local + // characteristic by calling BluetoothGattCharacteristic::AddDescriptor. + // + // This method constructs a characteristic descriptor with UUID |uuid| and the + // initial cached value |value|. |value| will be cached and returned for read + // requests and automatically modified for write requests by default, unless + // an instance of BluetoothGattService::Delegate has been provided to the + // associated BluetoothGattService instance, in which case the delegate will + // handle the read and write requests. + // + // Currently, only custom UUIDs, |kCharacteristicDescriptionUuid|, and + // |kCharacteristicPresentationFormat| are supported for locally hosted + // descriptors. This method will return NULL if |uuid| is any one of the + // unsupported predefined descriptor UUIDs. + static BluetoothGattDescriptor* Create( + const BluetoothUUID& uuid, + const std::vector<uint8>& value, + BluetoothGattCharacteristic::Permissions permissions); + + // Identifier used to uniquely identify a GATT descriptorobject. This is + // different from the descriptor UUID: while multiple descriptors with the + // same UUID can exist on a Bluetooth device, the identifier returned from + // this method is unique among all descriptors of a device. The contents of + // the identifier are platform specific. + virtual std::string GetIdentifier() const = 0; + + // The Bluetooth-specific UUID of the characteristic descriptor. + virtual BluetoothUUID GetUUID() const = 0; + + // Returns true, if this characteristic descriptor is hosted locally. If + // false, then this instance represents a remote descriptor. + virtual bool IsLocal() const = 0; + + // Returns the value of the descriptor. For remote descriptors, this is the + // most recently cached value of the remote descriptor. For local descriptors + // this is the most recently updated value or the value retrieved from the + // delegate. + virtual const std::vector<uint8>& GetValue() const = 0; + + // Returns a pointer to the GATT characteristic that this characteristic + // descriptor belongs to. + virtual BluetoothGattCharacteristic* GetCharacteristic() const = 0; + + // Returns the bitmask of characteristic descriptor attribute permissions. + virtual BluetoothGattCharacteristic::Permissions GetPermissions() const = 0; + + // Sends a read request to a remote characteristic descriptor to read its + // value. |callback| is called to return the read value on success and + // |error_callback| is called for failures. + virtual void ReadRemoteDescriptor(const ValueCallback& callback, + const ErrorCallback& error_callback) = 0; + + // Sends a write request to a remote characteristic descriptor, to modify the + // value of the descriptor with the new value |new_value|. |callback| is + // called to signal success and |error_callback| for failures. This method + // only applies to remote descriptors and will fail for those that are locally + // hosted. + virtual void WriteRemoteDescriptor( + const std::vector<uint8>& new_value, + const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + + protected: + BluetoothGattDescriptor(); + virtual ~BluetoothGattDescriptor(); + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothGattDescriptor); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_DESCRIPTOR_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_notify_session.cc b/chromium/device/bluetooth/bluetooth_gatt_notify_session.cc new file mode 100644 index 00000000000..3c9a2423880 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_notify_session.cc @@ -0,0 +1,15 @@ +// 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 "device/bluetooth/bluetooth_gatt_notify_session.h" + +namespace device { + +BluetoothGattNotifySession::BluetoothGattNotifySession() { +} + +BluetoothGattNotifySession::~BluetoothGattNotifySession() { +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_gatt_notify_session.h b/chromium/device/bluetooth/bluetooth_gatt_notify_session.h new file mode 100644 index 00000000000..9837e3a3cc3 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_notify_session.h @@ -0,0 +1,44 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_NOTIFY_SESSION_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_NOTIFY_SESSION_H_ + +#include <string> + +#include "base/callback.h" + +namespace device { + +// A BluetoothGattNotifySession represents an active session for listening +// to value updates from GATT characteristics that support notifications and/or +// indications. Instances are obtained by calling +// BluetoothGattCharacteristic::StartNotifySession. +class BluetoothGattNotifySession { + public: + // Destructor autmatically stops this session. + virtual ~BluetoothGattNotifySession(); + + // Returns the identifier of the associated characteristic. + virtual std::string GetCharacteristicIdentifier() const = 0; + + // Returns true if this session is active. + virtual bool IsActive() = 0; + + // Stops this session and calls |callback| upon completion. This won't + // necessarily stop value updates from the characteristic -- since updates + // are shared among BluetoothGattNotifySession instances -- but it will + // terminate this session. + virtual void Stop(const base::Closure& callback) = 0; + + protected: + BluetoothGattNotifySession(); + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothGattNotifySession); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_NOTIFY_SESSION_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_notify_session_chromeos.cc b/chromium/device/bluetooth/bluetooth_gatt_notify_session_chromeos.cc new file mode 100644 index 00000000000..ba7b84314e1 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_notify_session_chromeos.cc @@ -0,0 +1,132 @@ +// 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 "device/bluetooth/bluetooth_gatt_notify_session_chromeos.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_gatt_service.h" +#include "device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h" + +namespace chromeos { + +BluetoothGattNotifySessionChromeOS::BluetoothGattNotifySessionChromeOS( + scoped_refptr<device::BluetoothAdapter> adapter, + const std::string& device_address, + const std::string& service_identifier, + const std::string& characteristic_identifier, + const dbus::ObjectPath& characteristic_path) + : active_(true), + adapter_(adapter), + device_address_(device_address), + service_id_(service_identifier), + characteristic_id_(characteristic_identifier), + object_path_(characteristic_path) { + DCHECK(adapter_.get()); + DCHECK(!device_address_.empty()); + DCHECK(!service_id_.empty()); + DCHECK(!characteristic_id_.empty()); + DCHECK(object_path_.IsValid()); + + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()->AddObserver( + this); +} + +BluetoothGattNotifySessionChromeOS::~BluetoothGattNotifySessionChromeOS() { + DBusThreadManager::Get() + ->GetBluetoothGattCharacteristicClient() + ->RemoveObserver(this); + Stop(base::Bind(&base::DoNothing)); +} + +std::string BluetoothGattNotifySessionChromeOS::GetCharacteristicIdentifier() + const { + return characteristic_id_; +} + +bool BluetoothGattNotifySessionChromeOS::IsActive() { + // Determine if the session is active. If |active_| is false, then it's + // been explicitly marked, so return false. + if (!active_) + return false; + + // The fact that |active_| is true doesn't mean that the session is + // actually active, since the characteristic might have stopped sending + // notifications yet this method was called before we processed the + // observer event (e.g. because somebody else called this method in their + // BluetoothGattCharacteristicClient::Observer implementation, which was + // called before ours). Check the client to see if notifications are still + // being sent. + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get() + ->GetBluetoothGattCharacteristicClient() + ->GetProperties(object_path_); + if (!properties || !properties->notifying.value()) + active_ = false; + + return active_; +} + +void BluetoothGattNotifySessionChromeOS::Stop(const base::Closure& callback) { + if (!active_) { + VLOG(1) << "Notify session already inactive."; + callback.Run(); + return; + } + + // Mark this session as inactive no matter what. + active_ = false; + + device::BluetoothDevice* device = adapter_->GetDevice(device_address_); + if (!device) + return; + + device::BluetoothGattService* service = device->GetGattService(service_id_); + if (!service) + return; + + BluetoothRemoteGattCharacteristicChromeOS* chrc = + static_cast<BluetoothRemoteGattCharacteristicChromeOS*>( + service->GetCharacteristic(characteristic_id_)); + if (!chrc) + return; + + chrc->RemoveNotifySession(callback); +} + +void BluetoothGattNotifySessionChromeOS::GattCharacteristicRemoved( + const dbus::ObjectPath& object_path) { + if (object_path != object_path_) + return; + + active_ = false; +} + +void BluetoothGattNotifySessionChromeOS::GattCharacteristicPropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) { + if (object_path != object_path_) + return; + + if (!active_) + return; + + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get() + ->GetBluetoothGattCharacteristicClient() + ->GetProperties(object_path_); + if (!properties) { + active_ = false; + return; + } + + if (property_name == properties->notifying.name() && + !properties->notifying.value()) + active_ = false; +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_gatt_notify_session_chromeos.h b/chromium/device/bluetooth/bluetooth_gatt_notify_session_chromeos.h new file mode 100644 index 00000000000..1202fd8e7db --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_notify_session_chromeos.h @@ -0,0 +1,78 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_NOTIFY_SESSION_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_NOTIFY_SESSION_CHROMEOS_H_ + +#include <string> + +#include "base/callback.h" +#include "chromeos/dbus/bluetooth_gatt_characteristic_client.h" +#include "device/bluetooth/bluetooth_gatt_notify_session.h" + +namespace device { + +class BluetoothAdapter; + +} // namespace device + +namespace chromeos { + +class BluetoothRemoteGattCharacteristicChromeOS; + +// BluetoothGattNotifySessionChromeOS implements +// BluetoothGattNotifySession for the Chrome OS platform. +class BluetoothGattNotifySessionChromeOS + : public device::BluetoothGattNotifySession, + public BluetoothGattCharacteristicClient::Observer { + public: + virtual ~BluetoothGattNotifySessionChromeOS(); + + // BluetoothGattNotifySession overrides. + virtual std::string GetCharacteristicIdentifier() const OVERRIDE; + virtual bool IsActive() OVERRIDE; + virtual void Stop(const base::Closure& callback) OVERRIDE; + + private: + friend class BluetoothRemoteGattCharacteristicChromeOS; + + explicit BluetoothGattNotifySessionChromeOS( + scoped_refptr<device::BluetoothAdapter> adapter, + const std::string& device_address, + const std::string& service_identifier, + const std::string& characteristic_identifier, + const dbus::ObjectPath& characteristic_path); + + // BluetoothGattCharacteristicClient::Observer overrides. + virtual void GattCharacteristicRemoved( + const dbus::ObjectPath& object_path) OVERRIDE; + virtual void GattCharacteristicPropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // True, if this session is currently active. + bool active_; + + // The Bluetooth adapter that this session is associated with. + scoped_refptr<device::BluetoothAdapter> adapter_; + + // The Bluetooth address of the device hosting the characteristic. + std::string device_address_; + + // The GATT service that the characteristic belongs to. + std::string service_id_; + + // Identifier of the associated characteristic. + std::string characteristic_id_; + + // D-Bus object path of the associated characteristic. This is used to filter + // observer events. + dbus::ObjectPath object_path_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothGattNotifySessionChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_NOTIFY_SESSION_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_gatt_service.cc b/chromium/device/bluetooth/bluetooth_gatt_service.cc new file mode 100644 index 00000000000..461a63cf784 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_service.cc @@ -0,0 +1,26 @@ +// 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 "device/bluetooth/bluetooth_gatt_service.h" + +#include "base/logging.h" + +namespace device { + +BluetoothGattService::BluetoothGattService() { +} + +BluetoothGattService::~BluetoothGattService() { +} + +// static +BluetoothGattService* BluetoothGattService::Create( + const BluetoothUUID& uuid, + bool is_primary, + Delegate* delegate) { + LOG(ERROR) << "Creating local GATT services currently not supported."; + return NULL; +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_gatt_service.h b/chromium/device/bluetooth/bluetooth_gatt_service.h new file mode 100644 index 00000000000..16f4ae5f9e1 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_service.h @@ -0,0 +1,307 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_GATT_SERVICE_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_SERVICE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +class BluetoothDevice; +class BluetoothGattCharacteristic; +class BluetoothGattDescriptor; + +// BluetoothGattService represents a local or remote GATT service. A GATT +// service is hosted by a peripheral and represents a collection of data in +// the form of GATT characteristics and a set of included GATT services if this +// service is what is called "a primary service". +// +// Instances of the BluetoothGattService class are used for two functions: +// 1. To represent GATT attribute hierarchies that have been received from a +// remote Bluetooth GATT peripheral. Such BluetoothGattService instances +// are constructed and owned by a BluetoothDevice. +// +// 2. To represent a locally hosted GATT attribute hierarchy when the local +// adapter is used in the "peripheral" role. Such instances are meant to be +// constructed directly and registered. Once registered, a GATT attribute +// hierarchy will be visible to remote devices in the "central" role. +class BluetoothGattService { + public: + // The Delegate class is used to send certain events that need to be handled + // when the device is in peripheral mode. The delegate handles read and write + // requests that are issued from remote clients. + class Delegate { + public: + // Callbacks used for communicating GATT request responses. + typedef base::Callback<void(const std::vector<uint8>)> ValueCallback; + typedef base::Closure ErrorCallback; + + // Called when a remote device in the central role requests to read the + // value of the characteristic |characteristic| starting at offset |offset|. + // This method is only called if the characteristic was specified as + // readable and any authentication and authorization challanges were + // satisfied by the remote device. + // + // To respond to the request with success and return the requested value, + // the delegate must invoke |callback| with the value. Doing so will + // automatically update the value property of |characteristic|. To respond + // to the request with failure (e.g. if an invalid offset was given), + // delegates must invoke |error_callback|. If neither callback parameter is + // invoked, the request will time out and result in an error. Therefore, + // delegates MUST invoke either |callback| or |error_callback|. + virtual void OnCharacteristicReadRequest( + const BluetoothGattService* service, + const BluetoothGattCharacteristic* characteristic, + int offset, + const ValueCallback& callback, + const ErrorCallback& error_callback) = 0; + + // Called when a remote device in the central role requests to write the + // value of the characteristic |characteristic| starting at offset |offset|. + // This method is only called if the characteristic was specified as + // writeable and any authentication and authorization challanges were + // satisfied by the remote device. + // + // To respond to the request with success the delegate must invoke + // |callback| with the new value of the characteristic. Doing so will + // automatically update the value property of |characteristic|. To respond + // to the request with failure (e.g. if an invalid offset was given), + // delegates must invoke |error_callback|. If neither callback parameter is + // invoked, the request will time out and result in an error. Therefore, + // delegates MUST invoke either |callback| or |error_callback|. + virtual void OnCharacteristicWriteRequest( + const BluetoothGattService* service, + const BluetoothGattCharacteristic* characteristic, + const std::vector<uint8>& value, + int offset, + const ValueCallback& callback, + const ErrorCallback& error_callback) = 0; + + // Called when a remote device in the central role requests to read the + // value of the descriptor |descriptor| starting at offset |offset|. + // This method is only called if the characteristic was specified as + // readable and any authentication and authorization challanges were + // satisfied by the remote device. + // + // To respond to the request with success and return the requested value, + // the delegate must invoke |callback| with the value. Doing so will + // automatically update the value property of |descriptor|. To respond + // to the request with failure (e.g. if an invalid offset was given), + // delegates must invoke |error_callback|. If neither callback parameter is + // invoked, the request will time out and result in an error. Therefore, + // delegates MUST invoke either |callback| or |error_callback|. + virtual void OnDescriptorReadRequest( + const BluetoothGattService* service, + const BluetoothGattDescriptor* descriptor, + int offset, + const ValueCallback& callback, + const ErrorCallback& error_callback) = 0; + + // Called when a remote device in the central role requests to write the + // value of the descriptor |descriptor| starting at offset |offset|. + // This method is only called if the characteristic was specified as + // writeable and any authentication and authorization challanges were + // satisfied by the remote device. + // + // To respond to the request with success the delegate must invoke + // |callback| with the new value of the descriptor. Doing so will + // automatically update the value property of |descriptor|. To respond + // to the request with failure (e.g. if an invalid offset was given), + // delegates must invoke |error_callback|. If neither callback parameter is + // invoked, the request will time out and result in an error. Therefore, + // delegates MUST invoke either |callback| or |error_callback|. + virtual void OnDescriptorWriteRequest( + const BluetoothGattService* service, + const BluetoothGattDescriptor* descriptor, + const std::vector<uint8>& value, + int offset, + const ValueCallback& callback, + const ErrorCallback& error_callback) = 0; + }; + + // Interface for observing changes from a BluetoothGattService. Properties + // of remote services are received asynchronously. The Observer interface can + // be used to be notified when the initial values of a service are received + // as well as when successive changes occur during its life cycle. + class Observer { + public: + // Called when properties of the remote GATT service |service| have changed. + // This will get called for properties such as UUID, as well as for changes + // to the list of known characteristics and included services. Observers + // should read all GATT characteristic and descriptors objects and do any + // necessary set up required for a changed service. This method may be + // called several times, especially when the service is discovered for the + // first time. It will be called for each characteristic and characteristic + // descriptor that is discovered or removed. Hence this method should be + // used to check whether or not all characteristics of a service have been + // discovered that correspond to the profile implemented by the Observer. + virtual void GattServiceChanged(BluetoothGattService* service) {} + + // Called when the remote GATT characteristic |characteristic| belonging to + // GATT service |service| has been discovered. Use this to issue any initial + // read/write requests to the characteristic but don't cache the pointer as + // it may become invalid. Instead, use the specially assigned identifier + // to obtain a characteristic and cache that identifier as necessary, as it + // can be used to retrieve the characteristic from its GATT service. The + // number of characteristics with the same UUID belonging to a service + // depends on the particular profile the remote device implements, hence the + // client of a GATT based profile will usually operate on the whole set of + // characteristics and not just one. + // + // This method will always be followed by a call to GattServiceChanged, + // which can be used by observers to get all the characteristics of a + // service and perform the necessary updates. GattCharacteristicAdded exists + // mostly for convenience. + virtual void GattCharacteristicAdded( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic) {} + + // Called when a GATT characteristic |characteristic| belonging to GATT + // service |service| has been removed. This method is for convenience + // and will be followed by a call to GattServiceChanged (except when called + // after the service gets removed) which should be used for bootstrapping a + // GATT based profile. See the documentation of GattCharacteristicAdded and + // GattServiceChanged for more information. Try to obtain the service from + // its device to see whether or not the service has been removed. + virtual void GattCharacteristicRemoved( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic) {} + + // Called when the remote GATT characteristic descriptor |descriptor| + // belonging to characteristic |characteristic| has been discovered. Don't + // cache the arguments as the pointers may become invalid. Instead, use the + // specially assigned identifier to obtain a descriptor and cache that + // identifier as necessary. + // + // This method will always be followed by a call to GattServiceChanged, + // which can be used by observers to get all the characteristics of a + // service and perform the necessary updates. GattDescriptorAdded exists + // mostly for convenience. + virtual void GattDescriptorAdded( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor) {} + + // Called when a GATT characteristic descriptor |descriptor| belonging to + // characteristic |characteristic| has been removed. This method is for + // convenience and will be followed by a call to GattServiceChanged (except + // when called after the service gets removed). + virtual void GattDescriptorRemoved( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor) {} + + // Called when the value of a characteristic has changed. This might be a + // result of a read/write request to, or a notification/indication from, a + // remote GATT characteristic. + virtual void GattCharacteristicValueChanged( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic, + const std::vector<uint8>& value) {} + + // Called when the value of a characteristic descriptor has been updated. + virtual void GattDescriptorValueChanged( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor, + const std::vector<uint8>& value) {} + }; + + // The ErrorCallback is used by methods to asynchronously report errors. + typedef base::Closure ErrorCallback; + + virtual ~BluetoothGattService(); + + // Adds and removes observers for events on this GATT service. If monitoring + // multiple services, check the |service| parameter of observer methods to + // determine which service is issuing the event. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + + // Constructs a BluetoothGattService that can be locally hosted when the local + // adapter is in the peripheral role. The resulting object can then be made + // available by calling the "Register" method. This method constructs a + // service with UUID |uuid|. Whether the constructed service is primary or + // secondary is determined by |is_primary|. |delegate| is used to send certain + // peripheral role events. If |delegate| is NULL, then this service will + // employ a default behavior when responding to read and write requests based + // on the cached value of its characteristics and descriptors at a given time. + static BluetoothGattService* Create(const BluetoothUUID& uuid, + bool is_primary, + Delegate* delegate); + + // Identifier used to uniquely identify a GATT service object. This is + // different from the service UUID: while multiple services with the same UUID + // can exist on a Bluetooth device, the identifier returned from this method + // is unique among all services of a device. The contents of the identifier + // are platform specific. + virtual std::string GetIdentifier() const = 0; + + // The Bluetooth-specific UUID of the service. + virtual BluetoothUUID GetUUID() const = 0; + + // Returns true, if this service hosted locally. If false, then this service + // represents a remote GATT service. + virtual bool IsLocal() const = 0; + + // Indicates whether the type of this service is primary or secondary. A + // primary service describes the primary function of the peripheral that + // hosts it, while a secondary service only makes sense in the presence of a + // primary service. A primary service may include other primary or secondary + // services. + virtual bool IsPrimary() const = 0; + + // Returns the BluetoothDevice that this GATT service was received from, which + // also owns this service. Local services always return NULL. + virtual BluetoothDevice* GetDevice() const = 0; + + // List of characteristics that belong to this service. + virtual std::vector<BluetoothGattCharacteristic*> + GetCharacteristics() const = 0; + + // List of GATT services that are included by this service. + virtual std::vector<BluetoothGattService*> + GetIncludedServices() const = 0; + + // Returns the GATT characteristic with identifier |identifier| if it belongs + // to this GATT service. + virtual BluetoothGattCharacteristic* GetCharacteristic( + const std::string& identifier) const = 0; + + // Adds characteristics and included services to the local attribute hierarchy + // represented by this service. These methods only make sense for local + // services and won't have an effect if this instance represents a remote + // GATT service and will return false. While ownership of added + // characteristics are taken over by the service, ownership of an included + // service is not taken. + virtual bool AddCharacteristic( + BluetoothGattCharacteristic* characteristic) = 0; + virtual bool AddIncludedService(BluetoothGattService* service) = 0; + + // Registers this GATT service. Calling Register will make this service and + // all of its associated attributes available on the local adapters GATT + // database and the service UUID will be advertised to nearby devices if the + // local adapter is discoverable. Call Unregister to make this service no + // longer available. + // + // These methods only make sense for services that are local and will hence + // fail if this instance represents a remote GATT service. |callback| is + // called to denote success and |error_callback| to denote failure. + virtual void Register(const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + virtual void Unregister(const base::Closure& callback, + const ErrorCallback& error_callback) = 0; + + protected: + BluetoothGattService(); + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothGattService); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_SERVICE_H_ diff --git a/chromium/device/bluetooth/bluetooth_l2cap_channel_mac.h b/chromium/device/bluetooth/bluetooth_l2cap_channel_mac.h new file mode 100644 index 00000000000..f843608d204 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_l2cap_channel_mac.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 DEVICE_BLUETOOTH_BLUETOOTH_L2CAP_CHANNEL_MAC_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_L2CAP_CHANNEL_MAC_H_ + +#import <IOBluetooth/IOBluetooth.h> +#import <IOKit/IOReturn.h> + +#include "base/mac/scoped_nsobject.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "device/bluetooth/bluetooth_channel_mac.h" + +@class BluetoothL2capChannelDelegate; + +namespace device { + +class BluetoothL2capChannelMac : public BluetoothChannelMac { + public: + // Creates a new L2CAP channel wrapper with the given |socket| and native + // |channel|. + // NOTE: The |channel| is expected to already be retained. + BluetoothL2capChannelMac(BluetoothSocketMac* socket, + IOBluetoothL2CAPChannel* channel); + virtual ~BluetoothL2capChannelMac(); + + // Opens a new L2CAP channel with Channel ID |channel_id| to the target + // |device|. Returns the opened channel and sets |status| to kIOReturnSuccess + // if the open process was successfully started (or if an existing L2CAP + // channel was found). Otherwise, sets |status| to an error status. + static scoped_ptr<BluetoothL2capChannelMac> OpenAsync( + BluetoothSocketMac* socket, + IOBluetoothDevice* device, + BluetoothL2CAPPSM psm, + IOReturn* status); + + // BluetoothChannelMac: + virtual void SetSocket(BluetoothSocketMac* socket) OVERRIDE; + virtual IOBluetoothDevice* GetDevice() OVERRIDE; + virtual uint16_t GetOutgoingMTU() OVERRIDE; + virtual IOReturn WriteAsync(void* data, + uint16_t length, + void* refcon) OVERRIDE; + + void OnChannelOpenComplete(IOBluetoothL2CAPChannel* channel, + IOReturn status); + void OnChannelClosed(IOBluetoothL2CAPChannel* channel); + void OnChannelDataReceived(IOBluetoothL2CAPChannel* channel, + void* data, + size_t length); + void OnChannelWriteComplete(IOBluetoothL2CAPChannel* channel, + void* refcon, + IOReturn status); + + private: + // The wrapped native L2CAP channel. + base::scoped_nsobject<IOBluetoothL2CAPChannel> channel_; + + // The delegate for the native channel. + base::scoped_nsobject<BluetoothL2capChannelDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothL2capChannelMac); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_L2CAP_CHANNEL_MAC_H_ diff --git a/chromium/device/bluetooth/bluetooth_l2cap_channel_mac.mm b/chromium/device/bluetooth/bluetooth_l2cap_channel_mac.mm new file mode 100644 index 00000000000..2f38f3f89df --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_l2cap_channel_mac.mm @@ -0,0 +1,173 @@ +// 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 "device/bluetooth/bluetooth_l2cap_channel_mac.h" + +#include "base/logging.h" +#include "base/mac/sdk_forward_declarations.h" +#include "device/bluetooth/bluetooth_device_mac.h" +#include "device/bluetooth/bluetooth_socket_mac.h" + +// A simple delegate class for an open L2CAP channel that forwards methods to +// its wrapped |channel_|. +@interface BluetoothL2capChannelDelegate + : NSObject <IOBluetoothL2CAPChannelDelegate> { + @private + device::BluetoothL2capChannelMac* channel_; // weak +} + +- (id)initWithChannel:(device::BluetoothL2capChannelMac*)channel; + +@end + +@implementation BluetoothL2capChannelDelegate + +- (id)initWithChannel:(device::BluetoothL2capChannelMac*)channel { + if ((self = [super init])) + channel_ = channel; + + return self; +} + +- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)l2capChannel + status:(IOReturn)error { + channel_->OnChannelOpenComplete(l2capChannel, error); +} + +- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel + refcon:(void*)refcon + status:(IOReturn)error { + channel_->OnChannelWriteComplete(l2capChannel, refcon, error); +} + +- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel + data:(void*)dataPointer + length:(size_t)dataLength { + channel_->OnChannelDataReceived(l2capChannel, dataPointer, dataLength); +} + +- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel { + channel_->OnChannelClosed(l2capChannel); +} + +// These methods are marked as optional in the 10.8 SDK, but not in the 10.6 +// SDK. These empty implementations can be removed once we drop the 10.6 SDK. +- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel { +} +- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel { +} + +@end + +namespace device { + +BluetoothL2capChannelMac::BluetoothL2capChannelMac( + BluetoothSocketMac* socket, + IOBluetoothL2CAPChannel* channel) + : channel_(channel), + delegate_(nil) { + SetSocket(socket); +} + +BluetoothL2capChannelMac::~BluetoothL2capChannelMac() { + [channel_ setDelegate:nil]; + [channel_ closeChannel]; +} + +// static +scoped_ptr<BluetoothL2capChannelMac> BluetoothL2capChannelMac::OpenAsync( + BluetoothSocketMac* socket, + IOBluetoothDevice* device, + BluetoothL2CAPPSM psm, + IOReturn* status) { + DCHECK(socket); + scoped_ptr<BluetoothL2capChannelMac> channel( + new BluetoothL2capChannelMac(socket, nil)); + + // Retain the delegate, because IOBluetoothDevice's + // |-openL2CAPChannelAsync:withPSM:delegate:| assumes that it can take + // ownership of the delegate without calling |-retain| on it... + DCHECK(channel->delegate_); + [channel->delegate_ retain]; + IOBluetoothL2CAPChannel* l2cap_channel; + *status = [device openL2CAPChannelAsync:&l2cap_channel + withPSM:psm + delegate:channel->delegate_]; + if (*status == kIOReturnSuccess) + channel->channel_.reset([l2cap_channel retain]); + else + channel.reset(); + + return channel.Pass(); +} + +void BluetoothL2capChannelMac::SetSocket(BluetoothSocketMac* socket) { + BluetoothChannelMac::SetSocket(socket); + if (!this->socket()) + return; + + // Now that the socket is set, it's safe to associate a delegate, which can + // call back to the socket. + DCHECK(!delegate_); + delegate_.reset( + [[BluetoothL2capChannelDelegate alloc] initWithChannel:this]); + [channel_ setDelegate:delegate_]; +} + +IOBluetoothDevice* BluetoothL2capChannelMac::GetDevice() { + return [channel_ getDevice]; +} + +uint16_t BluetoothL2capChannelMac::GetOutgoingMTU() { + return [channel_ outgoingMTU]; +} + +IOReturn BluetoothL2capChannelMac::WriteAsync(void* data, + uint16_t length, + void* refcon) { + DCHECK_LE(length, GetOutgoingMTU()); + return [channel_ writeAsync:data length:length refcon:refcon]; +} + +void BluetoothL2capChannelMac::OnChannelOpenComplete( + IOBluetoothL2CAPChannel* channel, + IOReturn status) { + if (channel_) { + DCHECK_EQ(channel_, channel); + } else { + // The (potentially) asynchronous connection occurred synchronously. + // Should only be reachable from OpenAsync(). + DCHECK_EQ(status, kIOReturnSuccess); + } + + socket()->OnChannelOpenComplete( + BluetoothDeviceMac::GetDeviceAddress([channel getDevice]), status); +} + +void BluetoothL2capChannelMac::OnChannelClosed( + IOBluetoothL2CAPChannel* channel) { + DCHECK_EQ(channel_, channel); + socket()->OnChannelClosed(); +} + +void BluetoothL2capChannelMac::OnChannelDataReceived( + IOBluetoothL2CAPChannel* channel, + void* data, + size_t length) { + DCHECK_EQ(channel_, channel); + socket()->OnChannelDataReceived(data, length); +} + +void BluetoothL2capChannelMac::OnChannelWriteComplete( + IOBluetoothL2CAPChannel* channel, + void* refcon, + IOReturn status) { + // Note: We use "CHECK" below to ensure we never run into unforeseen + // occurrences of asynchronous callbacks, which could lead to data + // corruption. + CHECK_EQ(channel_, channel); + socket()->OnChannelWriteComplete(refcon, status); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_out_of_band_pairing_data.h b/chromium/device/bluetooth/bluetooth_out_of_band_pairing_data.h deleted file mode 100644 index 1b45bb0b9d2..00000000000 --- a/chromium/device/bluetooth/bluetooth_out_of_band_pairing_data.h +++ /dev/null @@ -1,27 +0,0 @@ -// 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 DEVICE_BLUETOOTH_BLUETOOTH_OUT_OF_BAND_PAIRING_DATA_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_OUT_OF_BAND_PAIRING_DATA_H_ - -#include "base/basictypes.h" - -namespace device { - -const size_t kBluetoothOutOfBandPairingDataSize = 16; - -// A simple structure representing the data required to perform Out Of Band -// Pairing. See -// http://mclean-linsky.net/joel/cv/Simple%20Pairing_WP_V10r00.pdf -struct BluetoothOutOfBandPairingData { - // Simple Pairing Hash C. - uint8 hash[kBluetoothOutOfBandPairingDataSize]; - - // Simple Pairing Randomizer R. - uint8 randomizer[kBluetoothOutOfBandPairingDataSize]; -}; - -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_OUT_OF_BAND_PAIRING_DATA_H_ diff --git a/chromium/device/bluetooth/bluetooth_pairing_chromeos.cc b/chromium/device/bluetooth/bluetooth_pairing_chromeos.cc new file mode 100644 index 00000000000..dc73f6d7f6c --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_pairing_chromeos.cc @@ -0,0 +1,273 @@ +// 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 "device/bluetooth/bluetooth_pairing_chromeos.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_chromeos.h" + +using device::BluetoothDevice; + +namespace { + +// Histogram enumerations for pairing methods. +enum UMAPairingMethod { + UMA_PAIRING_METHOD_NONE, + UMA_PAIRING_METHOD_REQUEST_PINCODE, + UMA_PAIRING_METHOD_REQUEST_PASSKEY, + UMA_PAIRING_METHOD_DISPLAY_PINCODE, + UMA_PAIRING_METHOD_DISPLAY_PASSKEY, + UMA_PAIRING_METHOD_CONFIRM_PASSKEY, + // NOTE: Add new pairing methods immediately above this line. Make sure to + // update the enum list in tools/histogram/histograms.xml accordingly. + UMA_PAIRING_METHOD_COUNT +}; + +// Number of keys that will be entered for a passkey, six digits plus the +// final enter. +const uint16 kPasskeyMaxKeysEntered = 7; + +} // namespace + +namespace chromeos { + +BluetoothPairingChromeOS::BluetoothPairingChromeOS( + BluetoothDeviceChromeOS* device, + BluetoothDevice::PairingDelegate* pairing_delegate) + : device_(device), + pairing_delegate_(pairing_delegate), + pairing_delegate_used_(false) { + VLOG(1) << "Created BluetoothPairingChromeOS for " + << device_->GetAddress(); +} + +BluetoothPairingChromeOS::~BluetoothPairingChromeOS() { + VLOG(1) << "Destroying BluetoothPairingChromeOS for " + << device_->GetAddress(); + + if (!pairing_delegate_used_) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_NONE, + UMA_PAIRING_METHOD_COUNT); + } + + if (!pincode_callback_.is_null()) { + pincode_callback_.Run( + BluetoothAgentServiceProvider::Delegate::CANCELLED, ""); + } + + if (!passkey_callback_.is_null()) { + passkey_callback_.Run( + BluetoothAgentServiceProvider::Delegate::CANCELLED, 0); + } + + if (!confirmation_callback_.is_null()) { + confirmation_callback_.Run( + BluetoothAgentServiceProvider::Delegate::CANCELLED); + } + + pairing_delegate_ = NULL; +} + +void BluetoothPairingChromeOS::RequestPinCode( + const BluetoothAgentServiceProvider::Delegate::PinCodeCallback& callback) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_REQUEST_PINCODE, + UMA_PAIRING_METHOD_COUNT); + + ResetCallbacks(); + pincode_callback_ = callback; + pairing_delegate_used_ = true; + pairing_delegate_->RequestPinCode(device_); +} + +bool BluetoothPairingChromeOS::ExpectingPinCode() const { + return !pincode_callback_.is_null(); +} + +void BluetoothPairingChromeOS::SetPinCode(const std::string& pincode) { + if (pincode_callback_.is_null()) + return; + + pincode_callback_.Run(BluetoothAgentServiceProvider::Delegate::SUCCESS, + pincode); + pincode_callback_.Reset(); + + // If this is not an outgoing connection to the device, clean up the pairing + // context since the pairing is done. The outgoing connection case is cleaned + // up in the callback for the underlying Pair() call. + if (!device_->IsConnecting()) + device_->EndPairing(); +} + +void BluetoothPairingChromeOS::DisplayPinCode(const std::string& pincode) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_DISPLAY_PINCODE, + UMA_PAIRING_METHOD_COUNT); + + ResetCallbacks(); + pairing_delegate_used_ = true; + pairing_delegate_->DisplayPinCode(device_, pincode); + + // If this is not an outgoing connection to the device, the pairing context + // needs to be cleaned up again as there's no reliable indication of + // completion of incoming pairing. + if (!device_->IsConnecting()) + device_->EndPairing(); +} + +void BluetoothPairingChromeOS::RequestPasskey( + const BluetoothAgentServiceProvider::Delegate::PasskeyCallback& callback) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_REQUEST_PASSKEY, + UMA_PAIRING_METHOD_COUNT); + + ResetCallbacks(); + passkey_callback_ = callback; + pairing_delegate_used_ = true; + pairing_delegate_->RequestPasskey(device_); +} + +bool BluetoothPairingChromeOS::ExpectingPasskey() const { + return !passkey_callback_.is_null(); +} + +void BluetoothPairingChromeOS::SetPasskey(uint32 passkey) { + if (passkey_callback_.is_null()) + return; + + passkey_callback_.Run(BluetoothAgentServiceProvider::Delegate::SUCCESS, + passkey); + passkey_callback_.Reset(); + + // If this is not an outgoing connection to the device, clean up the pairing + // context since the pairing is done. The outgoing connection case is cleaned + // up in the callback for the underlying Pair() call. + if (!device_->IsConnecting()) + device_->EndPairing(); +} + +void BluetoothPairingChromeOS::DisplayPasskey(uint32 passkey) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_DISPLAY_PASSKEY, + UMA_PAIRING_METHOD_COUNT); + + ResetCallbacks(); + pairing_delegate_used_ = true; + pairing_delegate_->DisplayPasskey(device_, passkey); + +} + +void BluetoothPairingChromeOS::KeysEntered(uint16 entered) { + pairing_delegate_used_ = true; + pairing_delegate_->KeysEntered(device_, entered); + + // If this is not an outgoing connection to the device, the pairing context + // needs to be cleaned up again as there's no reliable indication of + // completion of incoming pairing. + if (entered >= kPasskeyMaxKeysEntered && !device_->IsConnecting()) + device_->EndPairing(); +} + +void BluetoothPairingChromeOS::RequestConfirmation( + uint32 passkey, + const BluetoothAgentServiceProvider::Delegate::ConfirmationCallback& + callback) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_CONFIRM_PASSKEY, + UMA_PAIRING_METHOD_COUNT); + + ResetCallbacks(); + confirmation_callback_ = callback; + pairing_delegate_used_ = true; + pairing_delegate_->ConfirmPasskey(device_, passkey); +} + +void BluetoothPairingChromeOS::RequestAuthorization( + const BluetoothAgentServiceProvider::Delegate::ConfirmationCallback& + callback) { + UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", + UMA_PAIRING_METHOD_NONE, + UMA_PAIRING_METHOD_COUNT); + + ResetCallbacks(); + confirmation_callback_ = callback; + pairing_delegate_used_ = true; + pairing_delegate_->AuthorizePairing(device_); +} + +bool BluetoothPairingChromeOS::ExpectingConfirmation() const { + return !confirmation_callback_.is_null(); +} + +void BluetoothPairingChromeOS::ConfirmPairing() { + if (confirmation_callback_.is_null()) + return; + + confirmation_callback_.Run(BluetoothAgentServiceProvider::Delegate::SUCCESS); + confirmation_callback_.Reset(); + + // If this is not an outgoing connection to the device, clean up the pairing + // context since the pairing is done. The outgoing connection case is cleaned + // up in the callback for the underlying Pair() call. + if (!device_->IsConnecting()) + device_->EndPairing(); +} + +bool BluetoothPairingChromeOS::RejectPairing() { + return RunPairingCallbacks( + BluetoothAgentServiceProvider::Delegate::REJECTED); +} + +bool BluetoothPairingChromeOS::CancelPairing() { + return RunPairingCallbacks( + BluetoothAgentServiceProvider::Delegate::CANCELLED); +} + +BluetoothDevice::PairingDelegate* +BluetoothPairingChromeOS::GetPairingDelegate() const { + return pairing_delegate_; +} + +void BluetoothPairingChromeOS::ResetCallbacks() { + pincode_callback_.Reset(); + passkey_callback_.Reset(); + confirmation_callback_.Reset(); +} + +bool BluetoothPairingChromeOS::RunPairingCallbacks( + BluetoothAgentServiceProvider::Delegate::Status status) { + pairing_delegate_used_ = true; + + bool callback_run = false; + if (!pincode_callback_.is_null()) { + pincode_callback_.Run(status, ""); + pincode_callback_.Reset(); + callback_run = true; + } + + if (!passkey_callback_.is_null()) { + passkey_callback_.Run(status, 0); + passkey_callback_.Reset(); + callback_run = true; + } + + if (!confirmation_callback_.is_null()) { + confirmation_callback_.Run(status); + confirmation_callback_.Reset(); + callback_run = true; + } + + // If this is not an outgoing connection to the device, clean up the pairing + // context since the pairing is done. The outgoing connection case is cleaned + // up in the callback for the underlying Pair() call. + if (!device_->IsConnecting()) + device_->EndPairing(); + + return callback_run; +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_pairing_chromeos.h b/chromium/device/bluetooth/bluetooth_pairing_chromeos.h new file mode 100644 index 00000000000..ae906bc88ac --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_pairing_chromeos.h @@ -0,0 +1,146 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_PAIRING_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_PAIRING_CHROMEOS_H_ + +#include "chromeos/dbus/bluetooth_agent_service_provider.h" +#include "device/bluetooth/bluetooth_device.h" + +namespace chromeos { + +class BluetoothDeviceChromeOS; + +// The BluetoothPairingChromeOS class encapsulates the logic for an individual +// device pairing, acting as a bridge between BluetoothAdapterChromeOS which +// communicates with the underlying Controller and Host Subsystem, and +// BluetoothDeviceChromeOS which presents the pairing logic to the application. +class BluetoothPairingChromeOS { + public: + BluetoothPairingChromeOS( + BluetoothDeviceChromeOS* device, + device::BluetoothDevice::PairingDelegate* pairing_delegate); + ~BluetoothPairingChromeOS(); + + // Indicates whether the device is currently pairing and expecting a + // Passkey to be returned. + bool ExpectingPasskey() const; + + // Indicates whether the device is currently pairing and expecting + // confirmation of a displayed passkey. + bool ExpectingConfirmation() const; + + // Requests a PIN code for the current device from the current pairing + // delegate, the SetPinCode(), RejectPairing() and CancelPairing() method + // calls on this object are translated into the appropriate response to + // |callback|. + void RequestPinCode( + const BluetoothAgentServiceProvider::Delegate::PinCodeCallback& callback); + + // Indicates whether the device is currently pairing and expecting a + // PIN Code to be returned. + bool ExpectingPinCode() const; + + // Sends the PIN code |pincode| to the remote device during pairing. + // + // PIN Codes are generally required for Bluetooth 2.0 and earlier devices + // for which there is no automatic pairing or special handling. + void SetPinCode(const std::string& pincode); + + // Requests a PIN code for the current device be displayed by the current + // pairing delegate. No response is expected from the delegate. + void DisplayPinCode(const std::string& pincode); + + // Requests a Passkey for the current device from the current pairing + // delegate, the SetPasskey(), RejectPairing() and CancelPairing() method + // calls on this object are translated into the appropriate response to + // |callback|. + void RequestPasskey( + const BluetoothAgentServiceProvider::Delegate::PasskeyCallback& callback); + + // Sends the Passkey |passkey| to the remote device during pairing. + // + // Passkeys are generally required for Bluetooth 2.1 and later devices + // which cannot provide input or display on their own, and don't accept + // passkey-less pairing, and are a numeric in the range 0-999999. + void SetPasskey(uint32 passkey); + + // Requests a Passkey for the current device be displayed by the current + // pairing delegate. No response is expected from the delegate. + void DisplayPasskey(uint32 passkey); + + // Informs the current pairing delegate that |entered| keys have been + // provided to the remote device since the DisplayPasskey() call. No + // response is expected from the delegate. + void KeysEntered(uint16 entered); + + // Requests confirmation that |passkey| is displayed on the current device + // from the current pairing delegate. The ConfirmPairing(), RejectPairing() + // and CancelPairing() method calls on this object are translated into the + // appropriate response to |callback|. + void RequestConfirmation( + uint32 passkey, + const BluetoothAgentServiceProvider::Delegate::ConfirmationCallback& + callback); + + // Requests authorization that the current device be allowed to pair with + // this device from the current pairing delegate. The ConfirmPairing(), + // RejectPairing() and CancelPairing() method calls on this object are + // translated into the appropriate response to |callback|. + void RequestAuthorization( + const BluetoothAgentServiceProvider::Delegate::ConfirmationCallback& + callback); + + // Confirms to the remote device during pairing that a passkey provided by + // the ConfirmPasskey() delegate call is displayed on both devices. + void ConfirmPairing(); + + // Rejects a pairing or connection request from a remote device, returns + // false if there was no way to reject the pairing. + bool RejectPairing(); + + // Cancels a pairing or connection attempt to a remote device, returns + // false if there was no way to cancel the pairing. + bool CancelPairing(); + + // Returns the pairing delegate being used by this pairing object. + device::BluetoothDevice::PairingDelegate* GetPairingDelegate() const; + + private: + // Internal method to reset the current set of callbacks because a new + // request has arrived that supercedes them. + void ResetCallbacks(); + + // Internal method to respond to the relevant callback for a RejectPairing + // or CancelPairing call. + bool RunPairingCallbacks( + BluetoothAgentServiceProvider::Delegate::Status status); + + // The underlying BluetoothDeviceChromeOS that owns this pairing context. + BluetoothDeviceChromeOS* device_; + + // UI Pairing Delegate to make method calls on, this must live as long as + // the object capturing the PairingContext. + device::BluetoothDevice::PairingDelegate* pairing_delegate_; + + // Flag to indicate whether any pairing delegate method has been called + // during pairing. Used to determine whether we need to log the + // "no pairing interaction" metric. + bool pairing_delegate_used_; + + // During pairing these callbacks are set to those provided by method calls + // made on the BluetoothAdapterChromeOS instance by its respective + // BluetoothAgentServiceProvider instance, and are called by our own + // method calls such as SetPinCode() and SetPasskey(). + BluetoothAgentServiceProvider::Delegate::PinCodeCallback pincode_callback_; + BluetoothAgentServiceProvider::Delegate::PasskeyCallback passkey_callback_; + BluetoothAgentServiceProvider::Delegate::ConfirmationCallback + confirmation_callback_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothPairingChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_PAIRING_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_profile.cc b/chromium/device/bluetooth/bluetooth_profile.cc deleted file mode 100644 index 0d9a7572970..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile.cc +++ /dev/null @@ -1,67 +0,0 @@ -// 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.h" - -#if defined(OS_CHROMEOS) -#include "device/bluetooth/bluetooth_profile_chromeos.h" -#elif defined(OS_MACOSX) -#include "base/mac/mac_util.h" -#include "device/bluetooth/bluetooth_profile_mac.h" -#elif defined(OS_WIN) -#include "device/bluetooth/bluetooth_profile_win.h" -#endif - -#include <string> - -namespace device { - -BluetoothProfile::Options::Options() - : channel(0), - psm(0), - require_authentication(false), - require_authorization(false), - auto_connect(false), - version(0), - features(0) { -} - -BluetoothProfile::Options::~Options() { - -} - - -BluetoothProfile::BluetoothProfile() { - -} - -BluetoothProfile::~BluetoothProfile() { - -} - - -// static -void BluetoothProfile::Register(const std::string& uuid, - const Options& options, - const ProfileCallback& callback) { -#if defined(OS_CHROMEOS) - chromeos::BluetoothProfileChromeOS* profile = NULL; - profile = new chromeos::BluetoothProfileChromeOS(); - profile->Init(uuid, options, callback); -#elif defined(OS_MACOSX) - BluetoothProfile* profile = NULL; - - 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); - callback.Run(profile); -#else - callback.Run(NULL); -#endif -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_profile.h b/chromium/device/bluetooth/bluetooth_profile.h deleted file mode 100644 index 4ff2480c21d..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile.h +++ /dev/null @@ -1,109 +0,0 @@ -// 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_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_H_ - -#include <string> - -#include "base/callback.h" -#include "base/memory/ref_counted.h" - -namespace device { - -class BluetoothDevice; -class BluetoothProfileMac; -class BluetoothSocket; -class MockBluetoothProfile; - -// BluetoothProfile represents an implementation of either a client or server -// of a particular specified profile (aka service or protocol in other -// standards). -// -// Profile implementations are created by registering them through the static -// BluetoothProfile::Register() method and are always identified by a UUID -// which in any method may be specified in the short or long form. -// -// The lifecycle of BluetoothProfile instances is managed by the implementation -// but they are guaranteed to exist once provided to a Register() callback until -// the instance's Unregister() method is called, so others may hold on to -// pointers to them. -class BluetoothProfile { - public: - // Options used to register a BluetoothProfile object. - struct Options { - Options(); - ~Options(); - - // Human readable name of the Profile, e.g. "Health Device". - // Exported in the adapter's SDP or GATT tables where relevant. - std::string name; - - // RFCOMM channel used by the profile. - // Exported in the adapter's SDP or GATT tables where relevant. - uint16 channel; - - // L2CAP PSM number. - // Exported in the adapter's SDP or GATT tables where relevant. - uint16 psm; - - // Specifies whether pairing (and encryption) is required to be able to - // connect. Defaults to false. - bool require_authentication; - - // Specifies whether user authorization is required to be able to connect. - // Defaults to false. - bool require_authorization; - - // Specifies whether this profile will be automatically connected if any - // other profile of a device conects to the host. - // Defaults to false. - bool auto_connect; - - // Implemented version of the profile. - // Exported in the adapter's SDP or GATT tables where relevant. - uint16 version; - - // Implemented feature set of the profile. - // Exported in the adapter's SDP or GATT tables where relevant. - uint16 features; - }; - - // Register an implementation of the profile with UUID |uuid| and - // additional details specified in |options|. The corresponding profile - // object will be created and returned by |callback|. If the profile cannot - // be registered, NULL will be passed. - // - // This pointer is not owned by the receiver, but will not be freed unless - // its Unregister() method is called. - typedef base::Callback<void(BluetoothProfile*)> ProfileCallback; - static void Register(const std::string& uuid, - const Options& options, - const ProfileCallback& callback); - - // Unregister the profile. This deletes the profile object so, once called, - // any pointers to the profile should be discarded. - virtual void Unregister() = 0; - - // Set the connection callback for the profile to |callback|, any successful - // connection initiated by BluetoothDevice::ConnectToProfile() or incoming - // connections from devices, will have a BluetoothSocket created and passed - // to this callback. - // - // The socket will be closed when all references are released; none of the - // BluetoothProfile, or BluetoothAdapter or BluetoothDevice objects are - // guaranteed to hold a reference so this may outlive all of them. - typedef base::Callback<void( - const BluetoothDevice*, - scoped_refptr<BluetoothSocket>)> ConnectionCallback; - virtual void SetConnectionCallback(const ConnectionCallback& callback) = 0; - - protected: - BluetoothProfile(); - virtual ~BluetoothProfile(); -}; - -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_H_ diff --git a/chromium/device/bluetooth/bluetooth_profile_chromeos.cc b/chromium/device/bluetooth/bluetooth_profile_chromeos.cc deleted file mode 100644 index 31e165e8daa..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_chromeos.cc +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 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_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/message_loop.h" -#include "base/strings/string_util.h" -#include "base/task_runner_util.h" -#include "base/threading/thread_restrictions.h" -#include "base/threading/worker_pool.h" -#include "chromeos/dbus/bluetooth_profile_manager_client.h" -#include "chromeos/dbus/bluetooth_profile_service_provider.h" -#include "chromeos/dbus/dbus_thread_manager.h" -#include "dbus/bus.h" -#include "dbus/file_descriptor.h" -#include "dbus/object_path.h" -#include "device/bluetooth/bluetooth_adapter_chromeos.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/bluetooth_device.h" -#include "device/bluetooth/bluetooth_device_chromeos.h" -#include "device/bluetooth/bluetooth_profile.h" -#include "device/bluetooth/bluetooth_socket.h" -#include "device/bluetooth/bluetooth_socket_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 { - -BluetoothProfileChromeOS::BluetoothProfileChromeOS() - : weak_ptr_factory_(this) { -} - -BluetoothProfileChromeOS::~BluetoothProfileChromeOS() { - DCHECK(object_path_.value().empty()); - DCHECK(profile_.get() == NULL); -} - -void BluetoothProfileChromeOS::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; - - BluetoothProfileManagerClient::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; - base::ReplaceChars(uuid, ":-", "_", &uuid_path); - - object_path_ = dbus::ObjectPath("/org/chromium/bluetooth_profile/" + - uuid_path); - - dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); - profile_.reset(BluetoothProfileServiceProvider::Create( - system_bus, object_path_, this)); - DCHECK(profile_.get()); - - VLOG(1) << object_path_.value() << ": Register profile"; - DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> - RegisterProfile( - object_path_, - uuid, - bluetooth_options, - base::Bind(&BluetoothProfileChromeOS::OnRegisterProfile, - weak_ptr_factory_.GetWeakPtr(), - callback), - base::Bind(&BluetoothProfileChromeOS::OnRegisterProfileError, - weak_ptr_factory_.GetWeakPtr(), - callback)); -} - -void BluetoothProfileChromeOS::Unregister() { - DCHECK(!object_path_.value().empty()); - DCHECK(profile_.get()); - - profile_.reset(); - - VLOG(1) << object_path_.value() << ": Unregister profile"; - DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> - UnregisterProfile( - object_path_, - base::Bind(&BluetoothProfileChromeOS::OnUnregisterProfile, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&BluetoothProfileChromeOS::OnUnregisterProfileError, - weak_ptr_factory_.GetWeakPtr())); -} - -void BluetoothProfileChromeOS::SetConnectionCallback( - const ConnectionCallback& callback) { - connection_callback_ = callback; -} - -void BluetoothProfileChromeOS::Release() { - VLOG(1) << object_path_.value() << ": Release"; -} - -void BluetoothProfileChromeOS::NewConnection( - const dbus::ObjectPath& device_path, - scoped_ptr<dbus::FileDescriptor> fd, - const BluetoothProfileServiceProvider::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).get(), - FROM_HERE, - base::Bind(&CheckValidity, base::Passed(&fd)), - base::Bind(&BluetoothProfileChromeOS::GetAdapter, - weak_ptr_factory_.GetWeakPtr(), - device_path, - options, - callback)); -} - -void BluetoothProfileChromeOS::RequestDisconnection( - const dbus::ObjectPath& device_path, - const ConfirmationCallback& callback) { - VLOG(1) << object_path_.value() << ": Request disconnection"; - callback.Run(SUCCESS); -} - -void BluetoothProfileChromeOS::Cancel() { - VLOG(1) << object_path_.value() << ": Cancel"; -} - -void BluetoothProfileChromeOS::OnRegisterProfile( - const ProfileCallback& callback) { - VLOG(1) << object_path_.value() << ": Profile registered"; - callback.Run(this); -} - -void BluetoothProfileChromeOS::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 BluetoothProfileChromeOS::OnUnregisterProfile() { - VLOG(1) << object_path_.value() << ": Profile unregistered"; - object_path_ = dbus::ObjectPath(""); - delete this; -} - -void BluetoothProfileChromeOS::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 BluetoothProfileChromeOS::GetAdapter( - const dbus::ObjectPath& device_path, - const BluetoothProfileServiceProvider::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(&BluetoothProfileChromeOS::OnGetAdapter, - weak_ptr_factory_.GetWeakPtr(), - device_path, - options, - callback, - base::Passed(&fd))); -} - -void BluetoothProfileChromeOS::OnGetAdapter( - const dbus::ObjectPath& device_path, - const BluetoothProfileServiceProvider::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); - - BluetoothDeviceChromeOS* device = - static_cast<BluetoothAdapterChromeOS*>(adapter.get())-> - GetDeviceWithPath(device_path); - DCHECK(device); - - scoped_refptr<BluetoothSocket> socket(( - BluetoothSocketChromeOS::Create(fd.get()))); - connection_callback_.Run(device, socket); -} - -} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_profile_chromeos.h b/chromium/device/bluetooth/bluetooth_profile_chromeos.h deleted file mode 100644 index c13951e168a..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_chromeos.h +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 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_CHROMEOS_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_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/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 BluetoothProfileChromeOS class implements BluetoothProfile for the -// Chrome OS platform. -class CHROMEOS_EXPORT BluetoothProfileChromeOS - : public device::BluetoothProfile, - private BluetoothProfileServiceProvider::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; - - BluetoothProfileChromeOS(); - virtual ~BluetoothProfileChromeOS(); - - // 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); - - // BluetoothProfileServiceProvider::Delegate override. - virtual void Release() OVERRIDE; - virtual void NewConnection( - const dbus::ObjectPath& device_path, - scoped_ptr<dbus::FileDescriptor> fd, - const BluetoothProfileServiceProvider::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 BluetoothProfileServiceProvider::Delegate::Options& options, - const ConfirmationCallback& callback, - scoped_ptr<dbus::FileDescriptor> fd); - void OnGetAdapter( - const dbus::ObjectPath& device_path, - const BluetoothProfileServiceProvider::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<BluetoothProfileServiceProvider> 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<BluetoothProfileChromeOS> weak_ptr_factory_; - - DISALLOW_COPY_AND_ASSIGN(BluetoothProfileChromeOS); -}; - -} // namespace chromeos - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_profile_chromeos_unittest.cc b/chromium/device/bluetooth/bluetooth_profile_chromeos_unittest.cc deleted file mode 100644 index 1792cdac807..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_chromeos_unittest.cc +++ /dev/null @@ -1,369 +0,0 @@ -// 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/message_loop/message_loop.h" -#include "chromeos/dbus/fake_bluetooth_adapter_client.h" -#include "chromeos/dbus/fake_bluetooth_device_client.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 "chromeos/dbus/fake_dbus_thread_manager.h" -#include "device/bluetooth/bluetooth_adapter.h" -#include "device/bluetooth/bluetooth_adapter_chromeos.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/bluetooth_device.h" -#include "device/bluetooth/bluetooth_device_chromeos.h" -#include "device/bluetooth/bluetooth_profile.h" -#include "device/bluetooth/bluetooth_profile_chromeos.h" -#include "device/bluetooth/bluetooth_socket.h" -#include "device/bluetooth/bluetooth_socket_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_(base::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() { - FakeDBusThreadManager* fake_dbus_thread_manager = new FakeDBusThreadManager; - fake_bluetooth_profile_manager_client_ = - new FakeBluetoothProfileManagerClient; - fake_dbus_thread_manager->SetBluetoothProfileManagerClient( - scoped_ptr<BluetoothProfileManagerClient>( - fake_bluetooth_profile_manager_client_)); - fake_dbus_thread_manager->SetBluetoothAdapterClient( - scoped_ptr<BluetoothAdapterClient>(new FakeBluetoothAdapterClient)); - fake_dbus_thread_manager->SetBluetoothDeviceClient( - scoped_ptr<BluetoothDeviceClient>(new FakeBluetoothDeviceClient)); - fake_dbus_thread_manager->SetBluetoothInputClient( - scoped_ptr<BluetoothInputClient>(new FakeBluetoothInputClient)); - DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager); - - device::BluetoothAdapterFactory::GetAdapter( - base::Bind(&BluetoothProfileChromeOSTest::AdapterCallback, - base::Unretained(this))); - ASSERT_TRUE(adapter_.get() != 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_; - 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.get()); - 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.get(), base_buffer->size()); - success = socket->Send(write_buffer.get()); - 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.get()); - } 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.get(), base_buffer->size()); - success = socket->Send(write_buffer.get()); - 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.get()); - 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.get()); - 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.get(), base_buffer->size()); - success = socket->Send(write_buffer.get()); - 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.get()); - } 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.get(), base_buffer->size()); - success = socket->Send(write_buffer.get()); - 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.get()); - 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/chromium/device/bluetooth/bluetooth_profile_mac.h b/chromium/device/bluetooth/bluetooth_profile_mac.h deleted file mode 100644 index 0085efdeaea..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_mac.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 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_MAC_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_MAC_H_ - -#include <string> - -#include "base/basictypes.h" -#include "base/callback.h" -#include "device/bluetooth/bluetooth_profile.h" - -#ifdef __OBJC__ -@class IOBluetoothDevice; -#else -class IOBluetoothDevice; -#endif - -namespace device { - -class BluetoothProfileMac : public BluetoothProfile { - public: - // BluetoothProfile override. - virtual void Unregister() OVERRIDE; - virtual void SetConnectionCallback( - const ConnectionCallback& callback) OVERRIDE; - - // Makes an outgoing connection to |device|. - // This method runs |socket_callback_| with the socket and returns true if the - // connection is made successfully. - bool Connect(IOBluetoothDevice* device); - - private: - friend BluetoothProfile; - - BluetoothProfileMac(const std::string& uuid, const std::string& name); - virtual ~BluetoothProfileMac(); - - const std::string uuid_; - const std::string name_; - ConnectionCallback connection_callback_; -}; - -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_MAC_H_ diff --git a/chromium/device/bluetooth/bluetooth_profile_mac.mm b/chromium/device/bluetooth/bluetooth_profile_mac.mm deleted file mode 100644 index 4956651eac6..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_mac.mm +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 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_mac.h" - -#import <IOBluetooth/objc/IOBluetoothDevice.h> -#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> -#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> - -#include <string> -#include <vector> - -#include "base/basictypes.h" -#include "base/logging.h" -#include "base/memory/ref_counted.h" -#include "base/strings/string_number_conversions.h" -#include "device/bluetooth/bluetooth_device_mac.h" -#include "device/bluetooth/bluetooth_socket_mac.h" - -namespace { - -// Converts |uuid| to a IOBluetoothSDPUUID instance. -// -// |uuid| must be in the format of XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. -IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const std::string& uuid) { - DCHECK(uuid.size() == 36); - DCHECK(uuid[8] == '-'); - DCHECK(uuid[13] == '-'); - DCHECK(uuid[18] == '-'); - DCHECK(uuid[23] == '-'); - std::string numbers_only = uuid; - numbers_only.erase(23, 1); - numbers_only.erase(18, 1); - numbers_only.erase(13, 1); - numbers_only.erase(8, 1); - std::vector<uint8> uuid_bytes_vector; - base::HexStringToBytes(numbers_only, &uuid_bytes_vector); - DCHECK(uuid_bytes_vector.size() == 16); - - return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector[0] - length:uuid_bytes_vector.size()]; -} - -} // namespace - -namespace device { - -BluetoothProfileMac::BluetoothProfileMac(const std::string& uuid, - const std::string& name) - : BluetoothProfile(), uuid_(uuid), name_(name) { -} - -BluetoothProfileMac::~BluetoothProfileMac() { -} - -void BluetoothProfileMac::Unregister() { - delete this; -} - -void BluetoothProfileMac::SetConnectionCallback( - const ConnectionCallback& callback) { - connection_callback_ = callback; -} - -bool BluetoothProfileMac::Connect(IOBluetoothDevice* device) { - if (connection_callback_.is_null()) - return false; - - IOBluetoothSDPServiceRecord* record = - [device getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_)]; - if (record != nil) { - scoped_refptr<BluetoothSocket> socket( - BluetoothSocketMac::CreateBluetoothSocket(record)); - if (socket.get() != NULL) { - BluetoothDeviceMac device_mac(device); - connection_callback_.Run(&device_mac, socket); - return true; - } - } - return false; -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_profile_win.cc b/chromium/device/bluetooth/bluetooth_profile_win.cc deleted file mode 100644 index f0729bb4fb7..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_win.cc +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 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_win.h" - -#include "base/memory/ref_counted.h" -#include "device/bluetooth/bluetooth_device_win.h" -#include "device/bluetooth/bluetooth_service_record.h" -#include "device/bluetooth/bluetooth_socket_win.h" - -namespace device { - -BluetoothProfileWin::BluetoothProfileWin(const std::string& uuid, - const std::string& name) - : BluetoothProfile(), uuid_(uuid), name_(name) { -} - -BluetoothProfileWin::~BluetoothProfileWin() { -} - -void BluetoothProfileWin::Unregister() { - delete this; -} - -void BluetoothProfileWin::SetConnectionCallback( - const ConnectionCallback& callback) { - connection_callback_ = callback; -} - -bool BluetoothProfileWin::Connect(const BluetoothDeviceWin* device) { - if (connection_callback_.is_null()) - return false; - - const BluetoothServiceRecord* record = device->GetServiceRecord(uuid_); - if (record) { - scoped_refptr<BluetoothSocket> socket( - BluetoothSocketWin::CreateBluetoothSocket(*record)); - if (socket.get() != NULL) { - connection_callback_.Run(device, socket); - return true; - } - } - return false; -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_profile_win.h b/chromium/device/bluetooth/bluetooth_profile_win.h deleted file mode 100644 index a102bf5069a..00000000000 --- a/chromium/device/bluetooth/bluetooth_profile_win.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 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_WIN_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_WIN_H_ - -#include <string> - -#include "device/bluetooth/bluetooth_profile.h" - -namespace device { - -class BluetoothDeviceWin; - -class BluetoothProfileWin : public BluetoothProfile { - public: - // BluetoothProfile override. - virtual void Unregister() OVERRIDE; - virtual void SetConnectionCallback( - const ConnectionCallback& callback) OVERRIDE; - - bool Connect(const BluetoothDeviceWin* device); - - private: - friend BluetoothProfile; - - BluetoothProfileWin(const std::string& uuid, const std::string& name); - virtual ~BluetoothProfileWin(); - - const std::string uuid_; - const std::string name_; - ConnectionCallback connection_callback_; -}; - -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_PROFILE_WIN_H_ diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc new file mode 100644 index 00000000000..83231b9f651 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc @@ -0,0 +1,479 @@ +// 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 "device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h" + +#include <limits> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_gatt_notify_session_chromeos.h" +#include "device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.h" +#include "device/bluetooth/bluetooth_remote_gatt_service_chromeos.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +namespace chromeos { + +namespace { + +// Stream operator for logging vector<uint8>. +std::ostream& operator<<(std::ostream& out, const std::vector<uint8> bytes) { + out << "["; + for (std::vector<uint8>::const_iterator iter = bytes.begin(); + iter != bytes.end(); ++iter) { + out << base::StringPrintf("%02X", *iter); + } + return out << "]"; +} + +} // namespace + +BluetoothRemoteGattCharacteristicChromeOS:: + BluetoothRemoteGattCharacteristicChromeOS( + BluetoothRemoteGattServiceChromeOS* service, + const dbus::ObjectPath& object_path) + : object_path_(object_path), + service_(service), + num_notify_sessions_(0), + notify_call_pending_(false), + weak_ptr_factory_(this) { + VLOG(1) << "Creating remote GATT characteristic with identifier: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value(); + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + AddObserver(this); + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()-> + AddObserver(this); + + // Add all known GATT characteristic descriptors. + const std::vector<dbus::ObjectPath>& gatt_descs = + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()-> + GetDescriptors(); + for (std::vector<dbus::ObjectPath>::const_iterator iter = gatt_descs.begin(); + iter != gatt_descs.end(); ++iter) + GattDescriptorAdded(*iter); +} + +BluetoothRemoteGattCharacteristicChromeOS:: + ~BluetoothRemoteGattCharacteristicChromeOS() { + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()-> + RemoveObserver(this); + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + RemoveObserver(this); + + // Clean up all the descriptors. There isn't much point in notifying service + // observers for each descriptor that gets removed, so just delete them. + for (DescriptorMap::iterator iter = descriptors_.begin(); + iter != descriptors_.end(); ++iter) + delete iter->second; + + // Report an error for all pending calls to StartNotifySession. + while (!pending_start_notify_calls_.empty()) { + PendingStartNotifyCall callbacks = pending_start_notify_calls_.front(); + pending_start_notify_calls_.pop(); + callbacks.second.Run(); + } +} + +std::string BluetoothRemoteGattCharacteristicChromeOS::GetIdentifier() const { + return object_path_.value(); +} + +device::BluetoothUUID +BluetoothRemoteGattCharacteristicChromeOS::GetUUID() const { + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + GetProperties(object_path_); + DCHECK(properties); + return device::BluetoothUUID(properties->uuid.value()); +} + +bool BluetoothRemoteGattCharacteristicChromeOS::IsLocal() const { + return false; +} + +const std::vector<uint8>& +BluetoothRemoteGattCharacteristicChromeOS::GetValue() const { + return cached_value_; +} + +device::BluetoothGattService* +BluetoothRemoteGattCharacteristicChromeOS::GetService() const { + return service_; +} + +device::BluetoothGattCharacteristic::Properties +BluetoothRemoteGattCharacteristicChromeOS::GetProperties() const { + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + GetProperties(object_path_); + DCHECK(properties); + + Properties props = kPropertyNone; + const std::vector<std::string>& flags = properties->flags.value(); + for (std::vector<std::string>::const_iterator iter = flags.begin(); + iter != flags.end(); + ++iter) { + if (*iter == bluetooth_gatt_characteristic::kFlagBroadcast) + props |= kPropertyBroadcast; + if (*iter == bluetooth_gatt_characteristic::kFlagRead) + props |= kPropertyRead; + if (*iter == bluetooth_gatt_characteristic::kFlagWriteWithoutResponse) + props |= kPropertyWriteWithoutResponse; + if (*iter == bluetooth_gatt_characteristic::kFlagWrite) + props |= kPropertyWrite; + if (*iter == bluetooth_gatt_characteristic::kFlagNotify) + props |= kPropertyNotify; + if (*iter == bluetooth_gatt_characteristic::kFlagIndicate) + props |= kPropertyIndicate; + if (*iter == bluetooth_gatt_characteristic::kFlagAuthenticatedSignedWrites) + props |= kPropertyAuthenticatedSignedWrites; + if (*iter == bluetooth_gatt_characteristic::kFlagExtendedProperties) + props |= kPropertyExtendedProperties; + if (*iter == bluetooth_gatt_characteristic::kFlagReliableWrite) + props |= kPropertyReliableWrite; + if (*iter == bluetooth_gatt_characteristic::kFlagWritableAuxiliaries) + props |= kPropertyWritableAuxiliaries; + } + + return props; +} + +device::BluetoothGattCharacteristic::Permissions +BluetoothRemoteGattCharacteristicChromeOS::GetPermissions() const { + // TODO(armansito): Once BlueZ defines the permissions, return the correct + // values here. + return kPermissionNone; +} + +bool BluetoothRemoteGattCharacteristicChromeOS::IsNotifying() const { + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get() + ->GetBluetoothGattCharacteristicClient() + ->GetProperties(object_path_); + DCHECK(properties); + + return properties->notifying.value(); +} + +std::vector<device::BluetoothGattDescriptor*> +BluetoothRemoteGattCharacteristicChromeOS::GetDescriptors() const { + std::vector<device::BluetoothGattDescriptor*> descriptors; + for (DescriptorMap::const_iterator iter = descriptors_.begin(); + iter != descriptors_.end(); ++iter) + descriptors.push_back(iter->second); + return descriptors; +} + +device::BluetoothGattDescriptor* +BluetoothRemoteGattCharacteristicChromeOS::GetDescriptor( + const std::string& identifier) const { + DescriptorMap::const_iterator iter = + descriptors_.find(dbus::ObjectPath(identifier)); + if (iter == descriptors_.end()) + return NULL; + return iter->second; +} + +bool BluetoothRemoteGattCharacteristicChromeOS::AddDescriptor( + device::BluetoothGattDescriptor* descriptor) { + VLOG(1) << "Descriptors cannot be added to a remote GATT characteristic."; + return false; +} + +bool BluetoothRemoteGattCharacteristicChromeOS::UpdateValue( + const std::vector<uint8>& value) { + VLOG(1) << "Cannot update the value of a remote GATT characteristic."; + return false; +} + +void BluetoothRemoteGattCharacteristicChromeOS::ReadRemoteCharacteristic( + const ValueCallback& callback, + const ErrorCallback& error_callback) { + VLOG(1) << "Sending GATT characteristic read request to characteristic: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value() + << "."; + + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()->ReadValue( + object_path_, + base::Bind(&BluetoothRemoteGattCharacteristicChromeOS::OnValueSuccess, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothRemoteGattCharacteristicChromeOS::OnError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothRemoteGattCharacteristicChromeOS::WriteRemoteCharacteristic( + const std::vector<uint8>& new_value, + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << "Sending GATT characteristic write request to characteristic: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value() + << ", with value: " << new_value << "."; + + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()->WriteValue( + object_path_, + new_value, + callback, + base::Bind(&BluetoothRemoteGattCharacteristicChromeOS::OnError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothRemoteGattCharacteristicChromeOS::StartNotifySession( + const NotifySessionCallback& callback, + const ErrorCallback& error_callback) { + VLOG(1) << __func__; + + if (num_notify_sessions_ > 0) { + // The characteristic might have stopped notifying even though the session + // count is nonzero. This means that notifications stopped outside of our + // control and we should reset the count. If the characteristic is still + // notifying, then return success. Otherwise, reset the count and treat + // this call as if the count were 0. + if (IsNotifying()) { + // Check for overflows, though unlikely. + if (num_notify_sessions_ == std::numeric_limits<size_t>::max()) { + error_callback.Run(); + return; + } + + ++num_notify_sessions_; + DCHECK(service_); + DCHECK(service_->GetDevice()); + scoped_ptr<device::BluetoothGattNotifySession> session( + new BluetoothGattNotifySessionChromeOS( + service_->GetAdapter(), + service_->GetDevice()->GetAddress(), + service_->GetIdentifier(), + GetIdentifier(), + object_path_)); + callback.Run(session.Pass()); + return; + } + + num_notify_sessions_ = 0; + } + + // Queue the callbacks if there is a pending call to bluetoothd. + if (notify_call_pending_) { + pending_start_notify_calls_.push(std::make_pair(callback, error_callback)); + return; + } + + notify_call_pending_ = true; + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()->StartNotify( + object_path_, + base::Bind( + &BluetoothRemoteGattCharacteristicChromeOS::OnStartNotifySuccess, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothRemoteGattCharacteristicChromeOS::OnStartNotifyError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothRemoteGattCharacteristicChromeOS::RemoveNotifySession( + const base::Closure& callback) { + VLOG(1) << __func__; + + if (num_notify_sessions_ > 1) { + DCHECK(!notify_call_pending_); + --num_notify_sessions_; + callback.Run(); + return; + } + + // Notifications may have stopped outside our control. If the characteristic + // is no longer notifying, return success. + if (!IsNotifying()) { + num_notify_sessions_ = 0; + callback.Run(); + return; + } + + if (notify_call_pending_ || num_notify_sessions_ == 0) { + callback.Run(); + return; + } + + DCHECK(num_notify_sessions_ == 1); + notify_call_pending_ = true; + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()->StopNotify( + object_path_, + base::Bind( + &BluetoothRemoteGattCharacteristicChromeOS::OnStopNotifySuccess, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothRemoteGattCharacteristicChromeOS::OnStopNotifyError, + weak_ptr_factory_.GetWeakPtr(), + callback)); +} + +void BluetoothRemoteGattCharacteristicChromeOS::GattCharacteristicValueUpdated( + const dbus::ObjectPath& object_path, + const std::vector<uint8>& value) { + if (object_path != object_path_) + return; + + cached_value_ = value; + + VLOG(1) << "GATT characteristic value has changed: " << object_path.value() + << ": " << value; + DCHECK(service_); + service_->NotifyCharacteristicValueChanged(this, value); +} + +void BluetoothRemoteGattCharacteristicChromeOS::GattDescriptorAdded( + const dbus::ObjectPath& object_path) { + if (descriptors_.find(object_path) != descriptors_.end()) { + VLOG(1) << "Remote GATT characteristic descriptor already exists: " + << object_path.value(); + return; + } + + BluetoothGattDescriptorClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()-> + GetProperties(object_path); + DCHECK(properties); + if (properties->characteristic.value() != object_path_) { + VLOG(2) << "Remote GATT descriptor does not belong to this characteristic."; + return; + } + + VLOG(1) << "Adding new remote GATT descriptor for GATT characteristic: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value(); + + BluetoothRemoteGattDescriptorChromeOS* descriptor = + new BluetoothRemoteGattDescriptorChromeOS(this, object_path); + descriptors_[object_path] = descriptor; + DCHECK(descriptor->GetIdentifier() == object_path.value()); + DCHECK(descriptor->GetUUID().IsValid()); + DCHECK(service_); + + service_->NotifyDescriptorAddedOrRemoved(this, descriptor, true /* added */); + service_->NotifyServiceChanged(); +} + +void BluetoothRemoteGattCharacteristicChromeOS::GattDescriptorRemoved( + const dbus::ObjectPath& object_path) { + DescriptorMap::iterator iter = descriptors_.find(object_path); + if (iter == descriptors_.end()) { + VLOG(2) << "Unknown descriptor removed: " << object_path.value(); + return; + } + + VLOG(1) << "Removing remote GATT descriptor from characteristic: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value(); + + BluetoothRemoteGattDescriptorChromeOS* descriptor = iter->second; + DCHECK(descriptor->object_path() == object_path); + descriptors_.erase(iter); + + service_->NotifyDescriptorAddedOrRemoved(this, descriptor, false /* added */); + delete descriptor; + + DCHECK(service_); + + service_->NotifyServiceChanged(); +} + +void BluetoothRemoteGattCharacteristicChromeOS::OnValueSuccess( + const ValueCallback& callback, + const std::vector<uint8>& value) { + VLOG(1) << "Characteristic value read: " << value; + cached_value_ = value; + + DCHECK(service_); + service_->NotifyCharacteristicValueChanged(this, cached_value_); + + callback.Run(value); +} + +void BluetoothRemoteGattCharacteristicChromeOS::OnError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "Operation failed: " << error_name << ", message: " + << error_message; + error_callback.Run(); +} + +void BluetoothRemoteGattCharacteristicChromeOS::OnStartNotifySuccess( + const NotifySessionCallback& callback) { + VLOG(1) << "Started notifications from characteristic: " + << object_path_.value(); + DCHECK(num_notify_sessions_ == 0); + DCHECK(notify_call_pending_); + + ++num_notify_sessions_; + notify_call_pending_ = false; + + // Invoke the queued callbacks for this operation. + DCHECK(service_); + DCHECK(service_->GetDevice()); + scoped_ptr<device::BluetoothGattNotifySession> session( + new BluetoothGattNotifySessionChromeOS( + service_->GetAdapter(), + service_->GetDevice()->GetAddress(), + service_->GetIdentifier(), + GetIdentifier(), + object_path_)); + callback.Run(session.Pass()); + + ProcessStartNotifyQueue(); +} + +void BluetoothRemoteGattCharacteristicChromeOS::OnStartNotifyError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "Failed to start notifications from characteristic: " + << object_path_.value() << ": " << error_name << ", " + << error_message; + DCHECK(num_notify_sessions_ == 0); + DCHECK(notify_call_pending_); + + notify_call_pending_ = false; + error_callback.Run(); + + ProcessStartNotifyQueue(); +} + +void BluetoothRemoteGattCharacteristicChromeOS::OnStopNotifySuccess( + const base::Closure& callback) { + DCHECK(notify_call_pending_); + DCHECK(num_notify_sessions_ == 1); + + notify_call_pending_ = false; + --num_notify_sessions_; + callback.Run(); + + ProcessStartNotifyQueue(); +} + +void BluetoothRemoteGattCharacteristicChromeOS::OnStopNotifyError( + const base::Closure& callback, + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "Call to stop notifications failed for characteristic: " + << object_path_.value() << ": " << error_name << ", " + << error_message; + + // Since this is a best effort operation, treat this as success. + OnStopNotifySuccess(callback); +} + +void BluetoothRemoteGattCharacteristicChromeOS::ProcessStartNotifyQueue() { + while (!pending_start_notify_calls_.empty()) { + PendingStartNotifyCall callbacks = pending_start_notify_calls_.front(); + pending_start_notify_calls_.pop(); + StartNotifySession(callbacks.first, callbacks.second); + } +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h new file mode 100644 index 00000000000..26fda0b9b1f --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h @@ -0,0 +1,169 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_CHROMEOS_H_ + +#include <map> +#include <queue> +#include <string> +#include <utility> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "chromeos/dbus/bluetooth_gatt_characteristic_client.h" +#include "chromeos/dbus/bluetooth_gatt_descriptor_client.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +class BluetoothGattDescriptor; +class BluetoothGattService; + +} // namespace device + +namespace chromeos { + +class BluetoothRemoteGattDescriptorChromeOS; +class BluetoothRemoteGattServiceChromeOS; + +// The BluetoothRemoteGattCharacteristicChromeOS class implements +// BluetoothGattCharacteristic for remote GATT characteristics on the Chrome OS +// platform. +class BluetoothRemoteGattCharacteristicChromeOS + : public device::BluetoothGattCharacteristic, + public BluetoothGattCharacteristicClient::Observer, + public BluetoothGattDescriptorClient::Observer { + public: + // device::BluetoothGattCharacteristic overrides. + virtual std::string GetIdentifier() const OVERRIDE; + virtual device::BluetoothUUID GetUUID() const OVERRIDE; + virtual bool IsLocal() const OVERRIDE; + virtual const std::vector<uint8>& GetValue() const OVERRIDE; + virtual device::BluetoothGattService* GetService() const OVERRIDE; + virtual Properties GetProperties() const OVERRIDE; + virtual Permissions GetPermissions() const OVERRIDE; + virtual bool IsNotifying() const OVERRIDE; + virtual std::vector<device::BluetoothGattDescriptor*> + GetDescriptors() const OVERRIDE; + virtual device::BluetoothGattDescriptor* GetDescriptor( + const std::string& identifier) const OVERRIDE; + virtual bool AddDescriptor( + device::BluetoothGattDescriptor* descriptor) OVERRIDE; + virtual bool UpdateValue(const std::vector<uint8>& value) OVERRIDE; + virtual void ReadRemoteCharacteristic( + const ValueCallback& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void WriteRemoteCharacteristic( + const std::vector<uint8>& new_value, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void StartNotifySession(const NotifySessionCallback& callback, + const ErrorCallback& error_callback) OVERRIDE; + + // Removes one value update session and invokes |callback| on completion. This + // decrements the session reference count by 1 and if the number reaches 0, + // makes a call to the subsystem to stop notifications from this + // characteristic. + void RemoveNotifySession(const base::Closure& callback); + + // Object path of the underlying D-Bus characteristic. + const dbus::ObjectPath& object_path() const { return object_path_; } + + private: + friend class BluetoothRemoteGattServiceChromeOS; + + BluetoothRemoteGattCharacteristicChromeOS( + BluetoothRemoteGattServiceChromeOS* service, + const dbus::ObjectPath& object_path); + virtual ~BluetoothRemoteGattCharacteristicChromeOS(); + + // BluetoothGattCharacteristicClient::Observer overrides. + virtual void GattCharacteristicValueUpdated( + const dbus::ObjectPath& object_path, + const std::vector<uint8>& value) OVERRIDE; + + // BluetoothGattDescriptorClient::Observer overrides. + virtual void GattDescriptorAdded( + const dbus::ObjectPath& object_path) OVERRIDE; + virtual void GattDescriptorRemoved( + const dbus::ObjectPath& object_path) OVERRIDE; + + // Called by dbus:: on successful completion of a request to read + // the characteristic value. + void OnValueSuccess(const ValueCallback& callback, + const std::vector<uint8>& value); + + // Called by dbus:: on unsuccessful completion of a request to read or write + // the characteristic value. + void OnError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on successful completion of a request to start + // notifications. + void OnStartNotifySuccess(const NotifySessionCallback& callback); + + // Called by dbus:: on unsuccessful completion of a request to start + // notifications. + void OnStartNotifyError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on successful completion of a request to stop + // notifications. + void OnStopNotifySuccess(const base::Closure& callback); + + // Called by dbus:: on unsuccessful completion of a request to stop + // notifications. + void OnStopNotifyError(const base::Closure& callback, + const std::string& error_name, + const std::string& error_message); + + // Calls StartNotifySession for each queued request. + void ProcessStartNotifyQueue(); + + // Object path of the D-Bus characteristic object. + dbus::ObjectPath object_path_; + + // The GATT service this GATT characteristic belongs to. + BluetoothRemoteGattServiceChromeOS* service_; + + // The cached characteristic value based on the most recent read or + // notification. + std::vector<uint8> cached_value_; + + // The total number of currently active value update sessions. + size_t num_notify_sessions_; + + // Calls to StartNotifySession that are pending. This can happen during the + // first remote call to start notifications. + typedef std::pair<NotifySessionCallback, ErrorCallback> + PendingStartNotifyCall; + std::queue<PendingStartNotifyCall> pending_start_notify_calls_; + + // True, if a Start or Stop notify call to bluetoothd is currently pending. + bool notify_call_pending_; + + // Mapping from GATT descriptor object paths to descriptor objects owned by + // this characteristic. Since the Chrome OS implementation uses object paths + // as unique identifiers, we also use this mapping to return descriptors by + // identifier. + typedef std::map<dbus::ObjectPath, BluetoothRemoteGattDescriptorChromeOS*> + DescriptorMap; + DescriptorMap descriptors_; + + // 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<BluetoothRemoteGattCharacteristicChromeOS> + weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattCharacteristicChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.cc new file mode 100644 index 00000000000..8978cc55812 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.cc @@ -0,0 +1,137 @@ +// 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 "device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "chromeos/dbus/bluetooth_gatt_descriptor_client.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h" +#include "device/bluetooth/bluetooth_remote_gatt_service_chromeos.h" + +namespace chromeos { + +namespace { + +// Stream operator for logging vector<uint8>. +std::ostream& operator<<(std::ostream& out, const std::vector<uint8> bytes) { + out << "["; + for (std::vector<uint8>::const_iterator iter = bytes.begin(); + iter != bytes.end(); ++iter) { + out << base::StringPrintf("%02X", *iter); + } + return out << "]"; +} + +} // namespace + +BluetoothRemoteGattDescriptorChromeOS::BluetoothRemoteGattDescriptorChromeOS( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + const dbus::ObjectPath& object_path) + : object_path_(object_path), + characteristic_(characteristic), + weak_ptr_factory_(this) { + VLOG(1) << "Creating remote GATT descriptor with identifier: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value(); +} + +BluetoothRemoteGattDescriptorChromeOS:: + ~BluetoothRemoteGattDescriptorChromeOS() { +} + +std::string BluetoothRemoteGattDescriptorChromeOS::GetIdentifier() const { + return object_path_.value(); +} + +device::BluetoothUUID BluetoothRemoteGattDescriptorChromeOS::GetUUID() const { + BluetoothGattDescriptorClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()-> + GetProperties(object_path_); + DCHECK(properties); + return device::BluetoothUUID(properties->uuid.value()); +} + +bool BluetoothRemoteGattDescriptorChromeOS::IsLocal() const { + return false; +} + +const std::vector<uint8>& +BluetoothRemoteGattDescriptorChromeOS::GetValue() const { + return cached_value_; +} + +device::BluetoothGattCharacteristic* +BluetoothRemoteGattDescriptorChromeOS::GetCharacteristic() const { + return characteristic_; +} + +device::BluetoothGattCharacteristic::Permissions +BluetoothRemoteGattDescriptorChromeOS::GetPermissions() const { + // TODO(armansito): Once BlueZ defines the permissions, return the correct + // values here. + return device::BluetoothGattCharacteristic::kPermissionNone; +} + +void BluetoothRemoteGattDescriptorChromeOS::ReadRemoteDescriptor( + const ValueCallback& callback, + const ErrorCallback& error_callback) { + VLOG(1) << "Sending GATT characteristic descriptor read request to " + << "descriptor: " << GetIdentifier() << ", UUID: " + << GetUUID().canonical_value(); + + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()->ReadValue( + object_path_, + base::Bind(&BluetoothRemoteGattDescriptorChromeOS::OnValueSuccess, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&BluetoothRemoteGattDescriptorChromeOS::OnError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothRemoteGattDescriptorChromeOS::WriteRemoteDescriptor( + const std::vector<uint8>& new_value, + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << "Sending GATT characteristic descriptor write request to " + << "characteristic: " << GetIdentifier() << ", UUID: " + << GetUUID().canonical_value() << ", with value: " + << new_value << "."; + + DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()->WriteValue( + object_path_, + new_value, + callback, + base::Bind(&BluetoothRemoteGattDescriptorChromeOS::OnError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothRemoteGattDescriptorChromeOS::OnValueSuccess( + const ValueCallback& callback, + const std::vector<uint8>& value) { + VLOG(1) << "Descriptor value read: " << value; + cached_value_ = value; + + DCHECK(characteristic_); + BluetoothRemoteGattServiceChromeOS* service = + static_cast<BluetoothRemoteGattServiceChromeOS*>( + characteristic_->GetService()); + DCHECK(service); + service->NotifyDescriptorValueChanged(characteristic_, this, value); + callback.Run(value); +} + +void BluetoothRemoteGattDescriptorChromeOS::OnError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "Operation failed: " << error_name + << ", message: " << error_message; + error_callback.Run(); +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.h b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.h new file mode 100644 index 00000000000..d4ea1e68217 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.h @@ -0,0 +1,89 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_CHROMEOS_H_ + +#include <string> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_gatt_descriptor.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +class BluetoothGattCharacteristic; + +} // namespace device + +namespace chromeos { + +class BluetoothRemoteGattCharacteristicChromeOS; + +// The BluetoothRemoteGattDescriptorChromeOS class implements +// BluetoothGattDescriptor for remote GATT characteristic descriptors on the +// Chrome OS platform. +class BluetoothRemoteGattDescriptorChromeOS + : public device::BluetoothGattDescriptor { + public: + // device::BluetoothGattDescriptor overrides. + virtual std::string GetIdentifier() const OVERRIDE; + virtual device::BluetoothUUID GetUUID() const OVERRIDE; + virtual bool IsLocal() const OVERRIDE; + virtual const std::vector<uint8>& GetValue() const OVERRIDE; + virtual device::BluetoothGattCharacteristic* + GetCharacteristic() const OVERRIDE; + virtual device::BluetoothGattCharacteristic::Permissions + GetPermissions() const OVERRIDE; + virtual void ReadRemoteDescriptor( + const ValueCallback& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void WriteRemoteDescriptor( + const std::vector<uint8>& new_value, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + + // Object path of the underlying D-Bus characteristic. + const dbus::ObjectPath& object_path() const { return object_path_; } + + private: + friend class BluetoothRemoteGattCharacteristicChromeOS; + + BluetoothRemoteGattDescriptorChromeOS( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + const dbus::ObjectPath& object_path); + virtual ~BluetoothRemoteGattDescriptorChromeOS(); + + // Called by dbus:: on successful completion of a request to read + // the descriptor value. + void OnValueSuccess(const ValueCallback& callback, + const std::vector<uint8>& value); + + // Called by dbus:: on unsuccessful completion of a request to read or write + // the descriptor value. + void OnError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Object path of the D-Bus descriptor object. + dbus::ObjectPath object_path_; + + // The GATT characteristic this descriptor belongs to. + BluetoothRemoteGattCharacteristicChromeOS* characteristic_; + + // The cached characteristic value based on the most recent read request. + std::vector<uint8> cached_value_; + + // 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<BluetoothRemoteGattDescriptorChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattDescriptorChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc new file mode 100644 index 00000000000..70382703187 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc @@ -0,0 +1,286 @@ +// 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 "device/bluetooth/bluetooth_remote_gatt_service_chromeos.h" + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "chromeos/dbus/bluetooth_gatt_service_client.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_adapter_chromeos.h" +#include "device/bluetooth/bluetooth_device_chromeos.h" +#include "device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h" +#include "device/bluetooth/bluetooth_remote_gatt_descriptor_chromeos.h" + +namespace chromeos { + +BluetoothRemoteGattServiceChromeOS::BluetoothRemoteGattServiceChromeOS( + BluetoothAdapterChromeOS* adapter, + BluetoothDeviceChromeOS* device, + const dbus::ObjectPath& object_path) + : object_path_(object_path), + adapter_(adapter), + device_(device), + weak_ptr_factory_(this) { + VLOG(1) << "Creating remote GATT service with identifier: " + << object_path.value() << ", UUID: " << GetUUID().canonical_value(); + DCHECK(adapter_); + + DBusThreadManager::Get()->GetBluetoothGattServiceClient()->AddObserver(this); + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + AddObserver(this); + + // Add all known GATT characteristics. + const std::vector<dbus::ObjectPath>& gatt_chars = + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + GetCharacteristics(); + for (std::vector<dbus::ObjectPath>::const_iterator iter = gatt_chars.begin(); + iter != gatt_chars.end(); ++iter) + GattCharacteristicAdded(*iter); +} + +BluetoothRemoteGattServiceChromeOS::~BluetoothRemoteGattServiceChromeOS() { + DBusThreadManager::Get()->GetBluetoothGattServiceClient()-> + RemoveObserver(this); + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + RemoveObserver(this); + + // Clean up all the characteristics. Copy the characteristics list here and + // clear the original so that when we send GattCharacteristicRemoved(), + // GetCharacteristics() returns no characteristics. + CharacteristicMap characteristics = characteristics_; + characteristics_.clear(); + for (CharacteristicMap::iterator iter = characteristics.begin(); + iter != characteristics.end(); ++iter) { + FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_, + GattCharacteristicRemoved(this, iter->second)); + delete iter->second; + } +} + +void BluetoothRemoteGattServiceChromeOS::AddObserver( + device::BluetoothGattService::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothRemoteGattServiceChromeOS::RemoveObserver( + device::BluetoothGattService::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); +} + +std::string BluetoothRemoteGattServiceChromeOS::GetIdentifier() const { + return object_path_.value(); +} + +device::BluetoothUUID BluetoothRemoteGattServiceChromeOS::GetUUID() const { + BluetoothGattServiceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattServiceClient()-> + GetProperties(object_path_); + DCHECK(properties); + return device::BluetoothUUID(properties->uuid.value()); +} + +bool BluetoothRemoteGattServiceChromeOS::IsLocal() const { + return false; +} + +bool BluetoothRemoteGattServiceChromeOS::IsPrimary() const { + BluetoothGattServiceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattServiceClient()-> + GetProperties(object_path_); + DCHECK(properties); + return properties->primary.value(); +} + +device::BluetoothDevice* BluetoothRemoteGattServiceChromeOS::GetDevice() const { + return device_; +} + +std::vector<device::BluetoothGattCharacteristic*> +BluetoothRemoteGattServiceChromeOS::GetCharacteristics() const { + std::vector<device::BluetoothGattCharacteristic*> characteristics; + for (CharacteristicMap::const_iterator iter = characteristics_.begin(); + iter != characteristics_.end(); ++iter) { + characteristics.push_back(iter->second); + } + return characteristics; +} + +std::vector<device::BluetoothGattService*> +BluetoothRemoteGattServiceChromeOS::GetIncludedServices() const { + // TODO(armansito): Return the actual included services here. + return std::vector<device::BluetoothGattService*>(); +} + +device::BluetoothGattCharacteristic* +BluetoothRemoteGattServiceChromeOS::GetCharacteristic( + const std::string& identifier) const { + CharacteristicMap::const_iterator iter = + characteristics_.find(dbus::ObjectPath(identifier)); + if (iter == characteristics_.end()) + return NULL; + return iter->second; +} + +bool BluetoothRemoteGattServiceChromeOS::AddCharacteristic( + device::BluetoothGattCharacteristic* characteristic) { + VLOG(1) << "Characteristics cannot be added to a remote GATT service."; + return false; +} + +bool BluetoothRemoteGattServiceChromeOS::AddIncludedService( + device::BluetoothGattService* service) { + VLOG(1) << "Included services cannot be added to a remote GATT service."; + return false; +} + +void BluetoothRemoteGattServiceChromeOS::Register( + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << "A remote GATT service cannot be registered."; + error_callback.Run(); +} + +void BluetoothRemoteGattServiceChromeOS::Unregister( + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << "A remote GATT service cannot be unregistered."; + error_callback.Run(); +} + +scoped_refptr<device::BluetoothAdapter> +BluetoothRemoteGattServiceChromeOS::GetAdapter() const { + return adapter_; +} + +void BluetoothRemoteGattServiceChromeOS::NotifyServiceChanged() { + FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_, + GattServiceChanged(this)); +} + +void BluetoothRemoteGattServiceChromeOS::NotifyCharacteristicValueChanged( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + const std::vector<uint8>& value) { + DCHECK(characteristic->GetService() == this); + FOR_EACH_OBSERVER( + device::BluetoothGattService::Observer, + observers_, + GattCharacteristicValueChanged(this, characteristic, value)); +} + +void BluetoothRemoteGattServiceChromeOS::NotifyDescriptorAddedOrRemoved( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + BluetoothRemoteGattDescriptorChromeOS* descriptor, + bool added) { + DCHECK(characteristic->GetService() == this); + DCHECK(descriptor->GetCharacteristic() == characteristic); + if (added) { + FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_, + GattDescriptorAdded(characteristic, descriptor)); + return; + } + FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_, + GattDescriptorRemoved(characteristic, descriptor)); +} + +void BluetoothRemoteGattServiceChromeOS::NotifyDescriptorValueChanged( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + BluetoothRemoteGattDescriptorChromeOS* descriptor, + const std::vector<uint8>& value) { + DCHECK(characteristic->GetService() == this); + DCHECK(descriptor->GetCharacteristic() == characteristic); + FOR_EACH_OBSERVER( + device::BluetoothGattService::Observer, observers_, + GattDescriptorValueChanged(characteristic, descriptor, value)); +} + +void BluetoothRemoteGattServiceChromeOS::GattServicePropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name){ + if (object_path != object_path_) + return; + + VLOG(1) << "Service property changed: " << object_path.value(); + NotifyServiceChanged(); +} + +void BluetoothRemoteGattServiceChromeOS::GattCharacteristicAdded( + const dbus::ObjectPath& object_path) { + if (characteristics_.find(object_path) != characteristics_.end()) { + VLOG(1) << "Remote GATT characteristic already exists: " + << object_path.value(); + return; + } + + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + GetProperties(object_path); + DCHECK(properties); + if (properties->service.value() != object_path_) { + VLOG(2) << "Remote GATT characteristic does not belong to this service."; + return; + } + + VLOG(1) << "Adding new remote GATT characteristic for GATT service: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value(); + + BluetoothRemoteGattCharacteristicChromeOS* characteristic = + new BluetoothRemoteGattCharacteristicChromeOS(this, object_path); + characteristics_[object_path] = characteristic; + DCHECK(characteristic->GetIdentifier() == object_path.value()); + DCHECK(characteristic->GetUUID().IsValid()); + + FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_, + GattCharacteristicAdded(this, characteristic)); + NotifyServiceChanged(); +} + +void BluetoothRemoteGattServiceChromeOS::GattCharacteristicRemoved( + const dbus::ObjectPath& object_path) { + CharacteristicMap::iterator iter = characteristics_.find(object_path); + if (iter == characteristics_.end()) { + VLOG(2) << "Unknown GATT characteristic removed: " << object_path.value(); + return; + } + + VLOG(1) << "Removing remote GATT characteristic from service: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value(); + + BluetoothRemoteGattCharacteristicChromeOS* characteristic = iter->second; + DCHECK(characteristic->object_path() == object_path); + characteristics_.erase(iter); + + FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_, + GattCharacteristicRemoved(this, characteristic)); + NotifyServiceChanged(); + + delete characteristic; +} + +void BluetoothRemoteGattServiceChromeOS::GattCharacteristicPropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) { + if (characteristics_.find(object_path) == characteristics_.end()) { + VLOG(2) << "Properties of unknown characteristic changed"; + return; + } + + // We may receive a property changed event in certain cases, e.g. when the + // characteristic "Flags" property has been updated with values from the + // "Characteristic Extended Properties" descriptor. In this case, kick off + // a service changed observer event to let observers refresh the + // characteristics. + BluetoothGattCharacteristicClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothGattCharacteristicClient()-> + GetProperties(object_path); + DCHECK(properties); + if (property_name != properties->flags.name()) + return; + + NotifyServiceChanged(); +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h b/chromium/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h new file mode 100644 index 00000000000..ce481ec993d --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h @@ -0,0 +1,158 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_SERVICE_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_SERVICE_CHROMEOS_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "chromeos/dbus/bluetooth_gatt_characteristic_client.h" +#include "chromeos/dbus/bluetooth_gatt_service_client.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_gatt_service.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +class BluetoothAdapter; +class BluetoothGattCharacteristic; + +} // namespace device + +namespace chromeos { + +class BluetoothAdapterChromeOS; +class BluetoothDeviceChromeOS; +class BluetoothRemoteGattCharacteristicChromeOS; +class BluetoothRemoteGattDescriptorChromeOS; + +// The BluetoothRemoteGattServiceChromeOS class implements BluetootGattService +// for remote GATT services on the the Chrome OS platform. +class BluetoothRemoteGattServiceChromeOS + : public device::BluetoothGattService, + public BluetoothGattServiceClient::Observer, + public BluetoothGattCharacteristicClient::Observer { + public: + // device::BluetoothGattService overrides. + virtual void AddObserver( + device::BluetoothGattService::Observer* observer) OVERRIDE; + virtual void RemoveObserver( + device::BluetoothGattService::Observer* observer) OVERRIDE; + virtual std::string GetIdentifier() const OVERRIDE; + virtual device::BluetoothUUID GetUUID() const OVERRIDE; + virtual bool IsLocal() const OVERRIDE; + virtual bool IsPrimary() const OVERRIDE; + virtual device::BluetoothDevice* GetDevice() const OVERRIDE; + virtual std::vector<device::BluetoothGattCharacteristic*> + GetCharacteristics() const OVERRIDE; + virtual std::vector<device::BluetoothGattService*> + GetIncludedServices() const OVERRIDE; + virtual device::BluetoothGattCharacteristic* GetCharacteristic( + const std::string& identifier) const OVERRIDE; + virtual bool AddCharacteristic( + device::BluetoothGattCharacteristic* characteristic) OVERRIDE; + virtual bool AddIncludedService( + device::BluetoothGattService* service) OVERRIDE; + virtual void Register(const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void Unregister(const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + + // Object path of the underlying service. + const dbus::ObjectPath& object_path() const { return object_path_; } + + // Returns the adapter associated with this service. + scoped_refptr<device::BluetoothAdapter> GetAdapter() const; + + // Notifies its observers that the GATT service has changed. This is mainly + // used by BluetoothRemoteGattCharacteristicChromeOS instances to notify + // service observers when characteristic descriptors get added and removed. + void NotifyServiceChanged(); + + // Notifies its observers that the value of a characteristic has changed. + // Called by BluetoothRemoteGattCharacteristicChromeOS instances to notify + // service observers when their cached value is updated after a successful + // read request or when a "ValueUpdated" signal is received. + void NotifyCharacteristicValueChanged( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + const std::vector<uint8>& value); + + // Notifies its observers that a descriptor |descriptor| belonging to + // characteristic |characteristic| has been added or removed. This is used + // by BluetoothRemoteGattCharacteristicChromeOS instances to notify service + // observers when characteristic descriptors get added and removed. If |added| + // is true, an "Added" event will be sent. Otherwise, a "Removed" event will + // be sent. + void NotifyDescriptorAddedOrRemoved( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + BluetoothRemoteGattDescriptorChromeOS* descriptor, + bool added); + + // Notifies its observers that the value of a descriptor has changed. Called + // by BluetoothRemoteGattDescriptorChromeOS instances to notify service + // observers when their cached value gets updated after a read request. + void NotifyDescriptorValueChanged( + BluetoothRemoteGattCharacteristicChromeOS* characteristic, + BluetoothRemoteGattDescriptorChromeOS* descriptor, + const std::vector<uint8>& value); + + private: + friend class BluetoothDeviceChromeOS; + + BluetoothRemoteGattServiceChromeOS(BluetoothAdapterChromeOS* adapter, + BluetoothDeviceChromeOS* device, + const dbus::ObjectPath& object_path); + virtual ~BluetoothRemoteGattServiceChromeOS(); + + // BluetoothGattServiceClient::Observer override. + virtual void GattServicePropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // BluetoothGattCharacteristicClient::Observer override. + virtual void GattCharacteristicAdded( + const dbus::ObjectPath& object_path) OVERRIDE; + virtual void GattCharacteristicRemoved( + const dbus::ObjectPath& object_path) OVERRIDE; + virtual void GattCharacteristicPropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // Object path of the GATT service. + dbus::ObjectPath object_path_; + + // List of observers interested in event notifications from us. + ObserverList<device::BluetoothGattService::Observer> observers_; + + // The adapter associated with this service. It's ok to store a raw pointer + // here since |adapter_| indirectly owns this instance. + BluetoothAdapterChromeOS* adapter_; + + // The device this GATT service belongs to. It's ok to store a raw pointer + // here since |device_| owns this instance. + BluetoothDeviceChromeOS* device_; + + // Mapping from GATT characteristic object paths to characteristic objects. + // owned by this service. Since the Chrome OS implementation uses object + // paths as unique identifiers, we also use this mapping to return + // characteristics by identifier. + typedef std::map<dbus::ObjectPath, BluetoothRemoteGattCharacteristicChromeOS*> + CharacteristicMap; + CharacteristicMap characteristics_; + + // 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<BluetoothRemoteGattServiceChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattServiceChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_SERVICE_CHROMEOS_H_ diff --git a/chromium/device/bluetooth/bluetooth_rfcomm_channel_mac.h b/chromium/device/bluetooth/bluetooth_rfcomm_channel_mac.h new file mode 100644 index 00000000000..cc64010b1d9 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_rfcomm_channel_mac.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 DEVICE_BLUETOOTH_BLUETOOTH_RFCOMM_CHANNEL_MAC_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_RFCOMM_CHANNEL_MAC_H_ + +#import <IOBluetooth/IOBluetooth.h> +#import <IOKit/IOReturn.h> + +#include "base/mac/scoped_nsobject.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "device/bluetooth/bluetooth_channel_mac.h" + +@class BluetoothRfcommChannelDelegate; + +namespace device { + +class BluetoothRfcommChannelMac : public BluetoothChannelMac { + public: + // Creates a new RFCOMM channel wrapper with the given |socket| and native + // |channel|. + // NOTE: The |channel| is expected to already be retained. + BluetoothRfcommChannelMac(BluetoothSocketMac* socket, + IOBluetoothRFCOMMChannel* channel); + virtual ~BluetoothRfcommChannelMac(); + + // Opens a new RFCOMM channel with Channel ID |channel_id| to the target + // |device|. Returns the opened channel and sets |status| to kIOReturnSuccess + // if the open process was successfully started (or if an existing RFCOMM + // channel was found). Otherwise, sets |status| to an error status. + static scoped_ptr<BluetoothRfcommChannelMac> OpenAsync( + BluetoothSocketMac* socket, + IOBluetoothDevice* device, + BluetoothRFCOMMChannelID channel_id, + IOReturn* status); + + // BluetoothChannelMac: + virtual void SetSocket(BluetoothSocketMac* socket) OVERRIDE; + virtual IOBluetoothDevice* GetDevice() OVERRIDE; + virtual uint16_t GetOutgoingMTU() OVERRIDE; + virtual IOReturn WriteAsync(void* data, + uint16_t length, + void* refcon) OVERRIDE; + + void OnChannelOpenComplete(IOBluetoothRFCOMMChannel* channel, + IOReturn status); + void OnChannelClosed(IOBluetoothRFCOMMChannel* channel); + void OnChannelDataReceived(IOBluetoothRFCOMMChannel* channel, + void* data, + size_t length); + void OnChannelWriteComplete(IOBluetoothRFCOMMChannel* channel, + void* refcon, + IOReturn status); + + private: + // The wrapped native RFCOMM channel. + base::scoped_nsobject<IOBluetoothRFCOMMChannel> channel_; + + // The delegate for the native channel. + base::scoped_nsobject<BluetoothRfcommChannelDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothRfcommChannelMac); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_RFCOMM_CHANNEL_MAC_H_ diff --git a/chromium/device/bluetooth/bluetooth_rfcomm_channel_mac.mm b/chromium/device/bluetooth/bluetooth_rfcomm_channel_mac.mm new file mode 100644 index 00000000000..693a2965857 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_rfcomm_channel_mac.mm @@ -0,0 +1,168 @@ +// 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 "device/bluetooth/bluetooth_rfcomm_channel_mac.h" + +#include "base/logging.h" +#include "device/bluetooth/bluetooth_device_mac.h" +#include "device/bluetooth/bluetooth_socket_mac.h" + +// A simple delegate class for an open RFCOMM channel that forwards methods to +// its wrapped |channel_|. +@interface BluetoothRfcommChannelDelegate + : NSObject <IOBluetoothRFCOMMChannelDelegate> { + @private + device::BluetoothRfcommChannelMac* channel_; // weak +} + +- (id)initWithChannel:(device::BluetoothRfcommChannelMac*)channel; + +@end + +@implementation BluetoothRfcommChannelDelegate + +- (id)initWithChannel:(device::BluetoothRfcommChannelMac*)channel { + if ((self = [super init])) + channel_ = channel; + + return self; +} + +- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel + status:(IOReturn)error { + channel_->OnChannelOpenComplete(rfcommChannel, error); +} + +- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel + refcon:(void*)refcon + status:(IOReturn)error { + channel_->OnChannelWriteComplete(rfcommChannel, refcon, error); +} + +- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel + data:(void*)dataPointer + length:(size_t)dataLength { + channel_->OnChannelDataReceived(rfcommChannel, dataPointer, dataLength); +} + +- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel { + channel_->OnChannelClosed(rfcommChannel); +} + +@end + +namespace device { + +BluetoothRfcommChannelMac::BluetoothRfcommChannelMac( + BluetoothSocketMac* socket, + IOBluetoothRFCOMMChannel* channel) + : channel_(channel), + delegate_(nil) { + SetSocket(socket); +} + +BluetoothRfcommChannelMac::~BluetoothRfcommChannelMac() { + [channel_ setDelegate:nil]; + [channel_ closeChannel]; +} + +// static +scoped_ptr<BluetoothRfcommChannelMac> BluetoothRfcommChannelMac::OpenAsync( + BluetoothSocketMac* socket, + IOBluetoothDevice* device, + BluetoothRFCOMMChannelID channel_id, + IOReturn* status) { + DCHECK(socket); + scoped_ptr<BluetoothRfcommChannelMac> channel( + new BluetoothRfcommChannelMac(socket, nil)); + + // Retain the delegate, because IOBluetoothDevice's + // |-openRFCOMMChannelAsync:withChannelID:delegate:| assumes that it can take + // ownership of the delegate without calling |-retain| on it... + DCHECK(channel->delegate_); + [channel->delegate_ retain]; + IOBluetoothRFCOMMChannel* rfcomm_channel; + *status = [device openRFCOMMChannelAsync:&rfcomm_channel + withChannelID:channel_id + delegate:channel->delegate_]; + if (*status == kIOReturnSuccess) { + // Note: No need to retain the |rfcomm_channel| -- the returned channel is + // already retained. + channel->channel_.reset(rfcomm_channel); + } else { + channel.reset(); + } + + return channel.Pass(); +} + +void BluetoothRfcommChannelMac::SetSocket(BluetoothSocketMac* socket) { + BluetoothChannelMac::SetSocket(socket); + if (!this->socket()) + return; + + // Now that the socket is set, it's safe to associate a delegate, which can + // call back to the socket. + DCHECK(!delegate_); + delegate_.reset( + [[BluetoothRfcommChannelDelegate alloc] initWithChannel:this]); + [channel_ setDelegate:delegate_]; +} + +IOBluetoothDevice* BluetoothRfcommChannelMac::GetDevice() { + return [channel_ getDevice]; +} + +uint16_t BluetoothRfcommChannelMac::GetOutgoingMTU() { + return [channel_ getMTU]; +} + +IOReturn BluetoothRfcommChannelMac::WriteAsync(void* data, + uint16_t length, + void* refcon) { + DCHECK_LE(length, GetOutgoingMTU()); + return [channel_ writeAsync:data length:length refcon:refcon]; +} + +void BluetoothRfcommChannelMac::OnChannelOpenComplete( + IOBluetoothRFCOMMChannel* channel, + IOReturn status) { + if (channel_) { + DCHECK_EQ(channel_, channel); + } else { + // The (potentially) asynchronous connection occurred synchronously. + // Should only be reachable from OpenAsync(). + DCHECK_EQ(status, kIOReturnSuccess); + } + + socket()->OnChannelOpenComplete( + BluetoothDeviceMac::GetDeviceAddress([channel getDevice]), status); +} + +void BluetoothRfcommChannelMac::OnChannelClosed( + IOBluetoothRFCOMMChannel* channel) { + DCHECK_EQ(channel_, channel); + socket()->OnChannelClosed(); +} + +void BluetoothRfcommChannelMac::OnChannelDataReceived( + IOBluetoothRFCOMMChannel* channel, + void* data, + size_t length) { + DCHECK_EQ(channel_, channel); + socket()->OnChannelDataReceived(data, length); +} + +void BluetoothRfcommChannelMac::OnChannelWriteComplete( + IOBluetoothRFCOMMChannel* channel, + void* refcon, + IOReturn status) { + // Note: We use "CHECK" below to ensure we never run into unforeseen + // occurrences of asynchronous callbacks, which could lead to data + // corruption. + CHECK_EQ(channel_, channel); + socket()->OnChannelWriteComplete(refcon, status); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_service_record.cc b/chromium/device/bluetooth/bluetooth_service_record.cc deleted file mode 100644 index aa973735f74..00000000000 --- a/chromium/device/bluetooth/bluetooth_service_record.cc +++ /dev/null @@ -1,15 +0,0 @@ -// 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 "device/bluetooth/bluetooth_service_record.h" - -namespace device { - -BluetoothServiceRecord::BluetoothServiceRecord() { -} - -BluetoothServiceRecord::~BluetoothServiceRecord() { -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_service_record.h b/chromium/device/bluetooth/bluetooth_service_record.h deleted file mode 100644 index eebdb6ad376..00000000000 --- a/chromium/device/bluetooth/bluetooth_service_record.h +++ /dev/null @@ -1,70 +0,0 @@ -// 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 DEVICE_BLUETOOTH_BLUETOOTH_SERVICE_RECORD_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_SERVICE_RECORD_H_ - -#include <string> - -#include "base/basictypes.h" - -namespace device { - -// BluetoothServiceRecord represents an SDP service record. -// -// This implementation is currently incomplete: it only supports those fields -// that have been necessary so far. -class BluetoothServiceRecord { - public: - virtual ~BluetoothServiceRecord(); - - // The human-readable name of this service. - const std::string& name() const { return name_; } - - // The address of the BluetoothDevice providing this service. - const std::string& address() const { return address_; } - - // The UUID of the service. This field may be empty if no UUID was - // specified in the service record. - const std::string& uuid() const { return uuid_; } - - // Indicates if this service supports HID. - bool SupportsHid() const { return supports_hid_; } - - // For HID services, returns the HIDReconnectInitiate attribute. For non-HID - // or unknown services defaults to true. - bool hid_reconnect_initiate() const { return hid_reconnect_initiate_; } - - // For HID services, returns the HIDNormallyConnectable attribute. For non-HID - // or unknown services defaults to true. - bool hid_normally_connectable() const { return hid_normally_connectable_; } - - // Indicates if this service supports RFCOMM communication. - bool SupportsRfcomm() const { return supports_rfcomm_; } - - // The RFCOMM channel to use, if this service supports RFCOMM communication. - // The return value is undefined if SupportsRfcomm() returns false. - uint8 rfcomm_channel() const { return rfcomm_channel_; } - - protected: - BluetoothServiceRecord(); - - std::string address_; - std::string name_; - std::string uuid_; - - bool supports_hid_; - bool hid_reconnect_initiate_; - bool hid_normally_connectable_; - - bool supports_rfcomm_; - uint8 rfcomm_channel_; - - private: - DISALLOW_COPY_AND_ASSIGN(BluetoothServiceRecord); -}; - -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_SERVICE_RECORD_H_ diff --git a/chromium/device/bluetooth/bluetooth_service_record_mac.h b/chromium/device/bluetooth/bluetooth_service_record_mac.h deleted file mode 100644 index 13eb56bf7b7..00000000000 --- a/chromium/device/bluetooth/bluetooth_service_record_mac.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 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_SERVICE_RECORD_MAC_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_SERVICE_RECORD_MAC_H_ - -#include "device/bluetooth/bluetooth_service_record.h" - -#ifdef __OBJC__ -@class IOBluetoothDevice; -@class IOBluetoothSDPServiceRecord; -#else -class IOBluetoothDevice; -class IOBluetoothSDPServiceRecord; -#endif - -namespace device { - -class BluetoothServiceRecordMac : public BluetoothServiceRecord { - public: - explicit BluetoothServiceRecordMac(IOBluetoothSDPServiceRecord* record); - virtual ~BluetoothServiceRecordMac(); - - IOBluetoothDevice* GetIOBluetoothDevice() const { - return device_; - } - - private: - IOBluetoothDevice* device_; - - DISALLOW_COPY_AND_ASSIGN(BluetoothServiceRecordMac); -}; - -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_SERVICE_RECORD_MAC_H_ diff --git a/chromium/device/bluetooth/bluetooth_service_record_mac.mm b/chromium/device/bluetooth/bluetooth_service_record_mac.mm deleted file mode 100644 index 306d5e5ec39..00000000000 --- a/chromium/device/bluetooth/bluetooth_service_record_mac.mm +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 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_service_record_mac.h" - -#include <IOBluetooth/Bluetooth.h> -#import <IOBluetooth/IOBluetoothUtilities.h> -#import <IOBluetooth/objc/IOBluetoothDevice.h> -#import <IOBluetooth/objc/IOBluetoothSDPDataElement.h> -#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> -#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> - -#include <string> - -#include "base/basictypes.h" -#include "base/strings/stringprintf.h" -#include "base/strings/sys_string_conversions.h" - -namespace { - -void ExtractUuid(IOBluetoothSDPDataElement* service_class_data, - std::string* uuid) { - NSArray* inner_elements = [service_class_data getArrayValue]; - IOBluetoothSDPUUID* sdp_uuid = nil; - for (IOBluetoothSDPDataElement* inner_element in inner_elements) { - if ([inner_element getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) { - sdp_uuid = [[inner_element getUUIDValue] getUUIDWithLength:16]; - break; - } - } - if (sdp_uuid != nil) { - const uint8* uuid_bytes = reinterpret_cast<const uint8*>([sdp_uuid bytes]); - *uuid = base::StringPrintf( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - uuid_bytes[0], uuid_bytes[1], uuid_bytes[2], uuid_bytes[3], - uuid_bytes[4], uuid_bytes[5], uuid_bytes[6], uuid_bytes[7], - uuid_bytes[8], uuid_bytes[9], uuid_bytes[10], uuid_bytes[11], - uuid_bytes[12], uuid_bytes[13], uuid_bytes[14], uuid_bytes[15]); - } -} - -} // namespace - -namespace device { - -BluetoothServiceRecordMac::BluetoothServiceRecordMac( - IOBluetoothSDPServiceRecord* record) - : BluetoothServiceRecord() { - name_ = base::SysNSStringToUTF8([record getServiceName]); - device_ = [[record device] retain]; - address_ = base::SysNSStringToUTF8(IOBluetoothNSStringFromDeviceAddress( - [device_ getAddress])); - - // TODO(youngki): Extract these values from |record|. - supports_hid_ = false; - hid_reconnect_initiate_ = false; - hid_normally_connectable_ = false; - - supports_rfcomm_ = - [record getRFCOMMChannelID:&rfcomm_channel_] == kIOReturnSuccess; - - const BluetoothSDPServiceAttributeID service_class_id = 1; - - IOBluetoothSDPDataElement* service_class_data = - [record getAttributeDataElement:service_class_id]; - if ([service_class_data getTypeDescriptor] == - kBluetoothSDPDataElementTypeDataElementSequence) { - ExtractUuid(service_class_data, &uuid_); - } -} - -BluetoothServiceRecordMac::~BluetoothServiceRecordMac() { - [device_ release]; -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_service_record_mac_unittest.mm b/chromium/device/bluetooth/bluetooth_service_record_mac_unittest.mm deleted file mode 100644 index 22e4d1a3cfc..00000000000 --- a/chromium/device/bluetooth/bluetooth_service_record_mac_unittest.mm +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 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_service_record_mac.h" - -#import <IOBluetooth/objc/IOBluetoothSDPDataElement.h> -#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> -#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> - -#include <string> - -#include "base/basictypes.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/sys_string_conversions.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -const BluetoothSDPServiceAttributeID kServiceClassIDAttributeId = 0x0001; -const BluetoothSDPServiceAttributeID kProtocolDescriptorListAttributeId = - 0x0004; -const BluetoothSDPServiceAttributeID kServiceNameAttributeId = 0x0100; - -const uint8 kRfcommChannel = 0x0c; -const char kServiceName[] = "Headset Audio Gateway"; - -const char kExpectedRfcommUuid[] = "01234567-89ab-cdef-0123-456789abcdef"; -const char kExpectedSerialUuid[] = "00001101-0000-1000-8000-00805f9b34fb"; - -const int kMaxUuidSize = 16; - -} // namespace - -namespace device { - -class BluetoothServiceRecordMacTest : public testing::Test { - public: - - IOBluetoothSDPUUID* ConvertUuid(const char* uuid_hex_char, size_t uuid_size) { - std::vector<uint8> uuid_bytes_vector; - uint8 uuid_buffer[kMaxUuidSize]; - base::HexStringToBytes(uuid_hex_char, &uuid_bytes_vector); - std::copy(uuid_bytes_vector.begin(), - uuid_bytes_vector.end(), - uuid_buffer); - return [IOBluetoothSDPUUID uuidWithBytes:uuid_buffer length:uuid_size]; - } - - IOBluetoothSDPDataElement* GetServiceClassId(IOBluetoothSDPUUID* uuid) { - IOBluetoothSDPDataElement* uuid_element = - [IOBluetoothSDPDataElement withElementValue:uuid]; - return [IOBluetoothSDPDataElement - withElementValue:[NSArray arrayWithObject:uuid_element]]; - } - - IOBluetoothSDPDataElement* GetProtocolDescriptorList(bool supports_rfcomm) { - NSMutableArray* protocol_descriptor_list_sequence = [NSMutableArray array]; - - const uint8 l2cap_uuid_bytes[] = { 0x01, 0x00 }; - - IOBluetoothSDPUUID* l2cap_uuid = - [IOBluetoothSDPUUID uuidWithBytes:l2cap_uuid_bytes length:2]; - [protocol_descriptor_list_sequence - addObject:[NSArray arrayWithObject:l2cap_uuid]]; - - if (supports_rfcomm) { - const uint8 rfcomm_uuid_bytes[] = { 0x00, 0x03 }; - IOBluetoothSDPUUID* rfcomm_uuid = - [IOBluetoothSDPUUID uuidWithBytes:rfcomm_uuid_bytes length:2]; - NSNumber* rfcomm_channel = - [NSNumber numberWithUnsignedChar:kRfcommChannel]; - [protocol_descriptor_list_sequence - addObject:[NSArray - arrayWithObjects:rfcomm_uuid, rfcomm_channel, nil]]; - } - - return [IOBluetoothSDPDataElement - withElementValue:protocol_descriptor_list_sequence]; - } - - IOBluetoothSDPDataElement* GetServiceName(const std::string& service_name) { - return [IOBluetoothSDPDataElement - withElementValue:base::SysUTF8ToNSString(service_name)]; - } - - IOBluetoothSDPServiceRecord* GetServiceRecord( - IOBluetoothSDPUUID* uuid, bool supports_rfcomm) { - NSMutableDictionary* service_attrs = [NSMutableDictionary dictionary]; - - if (uuid != nil) { - [service_attrs - setObject:GetServiceClassId(uuid) - forKey:[NSNumber numberWithInt:kServiceClassIDAttributeId]]; - } - [service_attrs - setObject:GetProtocolDescriptorList(supports_rfcomm) - forKey:[NSNumber numberWithInt:kProtocolDescriptorListAttributeId]]; - [service_attrs - setObject:GetServiceName(kServiceName) - forKey:[NSNumber numberWithInt:kServiceNameAttributeId]]; - - return [IOBluetoothSDPServiceRecord withServiceDictionary:service_attrs - device:nil]; - } -}; - -TEST_F(BluetoothServiceRecordMacTest, RfcommService) { - const char rfcomm_uuid_bytes[] = "0123456789abcdef0123456789abcdef"; - IOBluetoothSDPUUID* rfcomm_uuid = - ConvertUuid(rfcomm_uuid_bytes, sizeof(rfcomm_uuid_bytes) / 2); - - BluetoothServiceRecordMac record(GetServiceRecord(rfcomm_uuid, true)); - EXPECT_EQ(kServiceName, record.name()); - EXPECT_TRUE(record.SupportsRfcomm()); - EXPECT_EQ(kRfcommChannel, record.rfcomm_channel()); - EXPECT_EQ(kExpectedRfcommUuid, record.uuid()); -} - -TEST_F(BluetoothServiceRecordMacTest, ShortUuid) { - const char short_uuid_bytes[] = "1101"; - IOBluetoothSDPUUID* short_uuid = - ConvertUuid(short_uuid_bytes, sizeof(short_uuid_bytes) / 2); - - BluetoothServiceRecordMac record(GetServiceRecord(short_uuid, false)); - EXPECT_EQ(kExpectedSerialUuid, record.uuid()); -} - -TEST_F(BluetoothServiceRecordMacTest, MediumUuid) { - const char medium_uuid_bytes[] = "00001101"; - IOBluetoothSDPUUID* medium_uuid = - ConvertUuid(medium_uuid_bytes, sizeof(medium_uuid_bytes) / 2); - - BluetoothServiceRecordMac record(GetServiceRecord(medium_uuid, false)); - EXPECT_EQ(kExpectedSerialUuid, record.uuid()); -} - -TEST_F(BluetoothServiceRecordMacTest, UpperCaseUuid) { - const char upper_case_uuid_bytes[] = "0123456789ABCDEF0123456789ABCDEF"; - IOBluetoothSDPUUID* upper_case_uuid = - ConvertUuid(upper_case_uuid_bytes, sizeof(upper_case_uuid_bytes) / 2); - - BluetoothServiceRecordMac record(GetServiceRecord(upper_case_uuid, false)); - EXPECT_EQ(kExpectedRfcommUuid, record.uuid()); -} - -TEST_F(BluetoothServiceRecordMacTest, InvalidUuid) { - BluetoothServiceRecordMac record(GetServiceRecord(nil, false)); - EXPECT_EQ("", record.uuid()); -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_service_record_win.cc b/chromium/device/bluetooth/bluetooth_service_record_win.cc index e0ba750f297..48f87232ab4 100644 --- a/chromium/device/bluetooth/bluetooth_service_record_win.cc +++ b/chromium/device/bluetooth/bluetooth_service_record_win.cc @@ -10,7 +10,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "device/bluetooth/bluetooth_init_win.h" -#include "device/bluetooth/bluetooth_utils.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace { @@ -61,7 +61,8 @@ void ExtractChannels(const SDP_ELEMENT_DATA& protocol_descriptor_list_data, } } -void ExtractUuid(const SDP_ELEMENT_DATA& uuid_data, std::string* uuid) { +void ExtractUuid(const SDP_ELEMENT_DATA& uuid_data, + device::BluetoothUUID* uuid) { HBLUETOOTH_CONTAINER_ELEMENT inner_uuid_element = NULL; SDP_ELEMENT_DATA inner_uuid_data; if (AdvanceToSdpType(uuid_data, @@ -71,13 +72,13 @@ void ExtractUuid(const SDP_ELEMENT_DATA& uuid_data, std::string* uuid) { if (inner_uuid_data.specificType == SDP_ST_UUID16) { std::string uuid_hex = base::StringPrintf("%04x", inner_uuid_data.data.uuid16); - *uuid = device::bluetooth_utils::CanonicalUuid(uuid_hex); + *uuid = device::BluetoothUUID(uuid_hex); } else if (inner_uuid_data.specificType == SDP_ST_UUID32) { std::string uuid_hex = base::StringPrintf("%08x", inner_uuid_data.data.uuid32); - *uuid = device::bluetooth_utils::CanonicalUuid(uuid_hex); + *uuid = device::BluetoothUUID(uuid_hex); } else if (inner_uuid_data.specificType == SDP_ST_UUID128) { - *uuid = base::StringPrintf( + *uuid = device::BluetoothUUID(base::StringPrintf( "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", inner_uuid_data.data.uuid128.Data1, inner_uuid_data.data.uuid128.Data2, @@ -89,9 +90,9 @@ void ExtractUuid(const SDP_ELEMENT_DATA& uuid_data, std::string* uuid) { inner_uuid_data.data.uuid128.Data4[4], inner_uuid_data.data.uuid128.Data4[5], inner_uuid_data.data.uuid128.Data4[6], - inner_uuid_data.data.uuid128.Data4[7]); + inner_uuid_data.data.uuid128.Data4[7])); } else { - uuid->clear(); + *uuid = device::BluetoothUUID(); } } } @@ -143,8 +144,8 @@ BluetoothServiceRecordWin::BluetoothServiceRecordWin( blob_size, kUuidId, &uuid_data)) { - ExtractUuid(uuid_data, &uuid_); + ExtractUuid(uuid_data, &uuid_); } } -} // namespace device
\ No newline at end of file +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_service_record_win.h b/chromium/device/bluetooth/bluetooth_service_record_win.h index b9f03312ba8..5f2b8f87f71 100644 --- a/chromium/device/bluetooth/bluetooth_service_record_win.h +++ b/chromium/device/bluetooth/bluetooth_service_record_win.h @@ -9,23 +9,44 @@ #include "base/basictypes.h" #include "device/bluetooth/bluetooth_init_win.h" -#include "device/bluetooth/bluetooth_service_record.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace device { -class BluetoothServiceRecordWin : public BluetoothServiceRecord { +class BluetoothServiceRecordWin { public: BluetoothServiceRecordWin(const std::string& name, const std::string& address, uint64 blob_size, uint8* blob_data); - BTH_ADDR bth_addr() const { - return bth_addr_; - } + BTH_ADDR bth_addr() const { return bth_addr_; } + + // The human-readable name of this service. + const std::string& name() const { return name_; } + + // The address of the BluetoothDevice providing this service. + const std::string& address() const { return address_; } + + // The UUID of the service. This field may be empty if no UUID was + // specified in the service record. + const BluetoothUUID& uuid() const { return uuid_; } + + // Indicates if this service supports RFCOMM communication. + bool SupportsRfcomm() const { return supports_rfcomm_; } + + // The RFCOMM channel to use, if this service supports RFCOMM communication. + // The return value is undefined if SupportsRfcomm() returns false. + uint8 rfcomm_channel() const { return rfcomm_channel_; } private: BTH_ADDR bth_addr_; + std::string address_; + std::string name_; + BluetoothUUID uuid_; + + bool supports_rfcomm_; + uint8 rfcomm_channel_; DISALLOW_COPY_AND_ASSIGN(BluetoothServiceRecordWin); }; diff --git a/chromium/device/bluetooth/bluetooth_service_record_win_unittest.cc b/chromium/device/bluetooth/bluetooth_service_record_win_unittest.cc index 804b6200304..c9d44c6ba00 100644 --- a/chromium/device/bluetooth/bluetooth_service_record_win_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_service_record_win_unittest.cc @@ -5,6 +5,7 @@ #include "base/basictypes.h" #include "base/strings/string_number_conversions.h" #include "device/bluetooth/bluetooth_service_record_win.h" +#include "device/bluetooth/bluetooth_uuid.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -14,14 +15,14 @@ const char kTestNoRfcommSdpBytes[] = "010209000535031910020900093508350619110d090102090100250c417564696f20536f75" "726365090311090001"; const int kTestNoRfcommSdpBytesSize = sizeof(kTestNoRfcommSdpBytes) / 2; -const char kTestNoRfcommSdpUuid[] = "0000110a-0000-1000-8000-00805f9b34fb"; +const device::BluetoothUUID kTestNoRfcommSdpUuid("110a"); const char kTestRfcommSdpBytes[] = "354b0900000a000100030900013506191112191203090004350c3503190100350519000308" "0b090005350319100209000935083506191108090100090100250d566f6963652047617465" "776179"; const int kTestRfcommSdpBytesSize = sizeof(kTestRfcommSdpBytes) / 2; -const char kTestRfcommSdpUuid[] = "00001112-0000-1000-8000-00805f9b34fb"; +const device::BluetoothUUID kTestRfcommSdpUuid("1112"); const int kTestRfcommChannel = 11; } // namespace @@ -46,7 +47,7 @@ TEST_F(BluetoothServiceRecordWinTest, NoRfcommSdp) { "01:02:03:0A:10:A0", kTestNoRfcommSdpBytesSize, sdp_bytes_array); - EXPECT_STREQ(kTestNoRfcommSdpUuid, service_record.uuid().c_str()); + EXPECT_EQ(kTestNoRfcommSdpUuid, service_record.uuid()); EXPECT_FALSE(service_record.SupportsRfcomm()); } @@ -58,7 +59,7 @@ TEST_F(BluetoothServiceRecordWinTest, RfcommSdp) { "01:02:03:0A:10:A0", kTestRfcommSdpBytesSize, sdp_bytes_array); - EXPECT_STREQ(kTestRfcommSdpUuid, service_record.uuid().c_str()); + EXPECT_EQ(kTestRfcommSdpUuid, service_record.uuid()); EXPECT_TRUE(service_record.SupportsRfcomm()); EXPECT_EQ(kTestRfcommChannel, service_record.rfcomm_channel()); } @@ -73,4 +74,4 @@ TEST_F(BluetoothServiceRecordWinTest, BthAddr) { EXPECT_EQ(1108152553632, service_record.bth_addr()); } -} // namespace device
\ No newline at end of file +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket.cc b/chromium/device/bluetooth/bluetooth_socket.cc new file mode 100644 index 00000000000..26981ab382d --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_socket.cc @@ -0,0 +1,11 @@ +// 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 "device/bluetooth/bluetooth_socket.h" + +namespace device { + +BluetoothSocket::~BluetoothSocket() {} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket.h b/chromium/device/bluetooth/bluetooth_socket.h index 6ae349e9ac9..1c7e50facb3 100644 --- a/chromium/device/bluetooth/bluetooth_socket.h +++ b/chromium/device/bluetooth/bluetooth_socket.h @@ -7,38 +7,77 @@ #include <string> +#include "base/callback.h" #include "base/memory/ref_counted.h" namespace net { - -class DrainableIOBuffer; -class GrowableIOBuffer; - +class IOBuffer; } // namespace net namespace device { +class BluetoothDevice; +class BluetoothUUID; + // BluetoothSocket represents a socket to a specific service on a // BluetoothDevice. BluetoothSocket objects are ref counted and may outlive // both the BluetoothDevice and BluetoothAdapter that were involved in their -// creation. -class BluetoothSocket : public base::RefCounted<BluetoothSocket> { +// creation. In terms of threading, platform specific implementations may +// differ slightly, but platform independent consumers must guarantee calling +// various instance methods on the same thread as the thread used at +// construction time -- platform specific implementation are resonsible for +// marshalling calls to a different thread if required. +class BluetoothSocket : public base::RefCountedThreadSafe<BluetoothSocket> { public: - // Receives data from the socket and stores it in |buffer|. It returns whether - // the reception has been successful. If it fails, the caller can get the - // error message through |GetLastErrorMessage()|. - virtual bool Receive(net::GrowableIOBuffer* buffer) = 0; + enum ErrorReason { kSystemError, 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 BluetoothDevice* device, + scoped_refptr<BluetoothSocket> socket)> + AcceptCompletionCallback; + typedef base::Callback<void(const std::string& error_message)> + ErrorCompletionCallback; + typedef base::Callback<void(ErrorReason, const std::string& error_message)> + ReceiveErrorCompletionCallback; + + // Destroys resources associated with the socket. After calling this method, + // it is illegal to call any method on this socket instance (except for the + // desctrutor via Release). + virtual void Close() = 0; + + // Gracefully disconnects the socket and calls |callback| upon completion. + // There is no failure case, as this is a best effort operation. + virtual void Disconnect(const base::Closure& success_callback) = 0; + + // Receives data from the socket and calls |success_callback| when data is + // available. |buffer_size| specifies the maximum number of bytes that can be + // received. If an error occurs, calls |error_callback| with a reason and an + // error message. + virtual void Receive( + int buffer_size, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback) = 0; - // Sends |buffer| to the socket. It returns whether the sending has been - // successful. If it fails, the caller can get the error message through - // |GetLastErrorMessage()|. - virtual bool Send(net::DrainableIOBuffer* buffer) = 0; + // Sends |buffer| to the socket and calls |success_callback| when data has + // been successfully sent. |buffer_size| is the number of bytes contained in + // |buffer|. If an error occurs, calls |error_callback| with an error message. + virtual void Send(scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) = 0; - virtual std::string GetLastErrorMessage() const = 0; + // Accepts a pending client connection from the socket and calls + // |success_callback| on completion, passing a new BluetoothSocket instance + // for the new client. If an error occurs, calls |error_callback| with a + // reason and an error message. + virtual void Accept(const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) = 0; protected: - friend class base::RefCounted<BluetoothSocket>; - virtual ~BluetoothSocket() {} + friend class base::RefCountedThreadSafe<BluetoothSocket>; + virtual ~BluetoothSocket(); }; } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket_chromeos.cc b/chromium/device/bluetooth/bluetooth_socket_chromeos.cc index 3a881d12813..9e6ba35f524 100644 --- a/chromium/device/bluetooth/bluetooth_socket_chromeos.cc +++ b/chromium/device/bluetooth/bluetooth_socket_chromeos.cc @@ -4,164 +4,586 @@ #include "device/bluetooth/bluetooth_socket_chromeos.h" -#include <errno.h> -#include <poll.h> -#include <unistd.h> -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/socket.h> - +#include <queue> #include <string> +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" #include "base/logging.h" +#include "base/memory/linked_ptr.h" #include "base/memory/ref_counted.h" -#include "base/posix/eintr_wrapper.h" -#include "base/safe_strerror_posix.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_util.h" +#include "base/task_runner_util.h" #include "base/threading/thread_restrictions.h" +#include "base/threading/worker_pool.h" +#include "chromeos/dbus/bluetooth_device_client.h" +#include "chromeos/dbus/bluetooth_profile_manager_client.h" +#include "chromeos/dbus/bluetooth_profile_service_provider.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "dbus/bus.h" #include "dbus/file_descriptor.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_adapter_chromeos.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_chromeos.h" #include "device/bluetooth/bluetooth_socket.h" -#include "net/base/io_buffer.h" +#include "device/bluetooth/bluetooth_socket_net.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::BluetoothAdapter; +using device::BluetoothDevice; +using device::BluetoothSocketThread; +using device::BluetoothUUID; + +namespace { + +const char kAcceptFailed[] = "Failed to accept connection."; +const char kInvalidUUID[] = "Invalid UUID"; +const char kSocketNotListening[] = "Socket is not listening."; + +} // namespace namespace chromeos { -BluetoothSocketChromeOS::BluetoothSocketChromeOS(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; +// static +scoped_refptr<BluetoothSocketChromeOS> +BluetoothSocketChromeOS::CreateBluetoothSocket( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + + return make_scoped_refptr( + new BluetoothSocketChromeOS( + ui_task_runner, socket_thread, net_log, source)); +} + +BluetoothSocketChromeOS::AcceptRequest::AcceptRequest() {} + +BluetoothSocketChromeOS::AcceptRequest::~AcceptRequest() {} + +BluetoothSocketChromeOS::ConnectionRequest::ConnectionRequest() + : accepting(false), + cancelled(false) {} + +BluetoothSocketChromeOS::ConnectionRequest::~ConnectionRequest() {} + +BluetoothSocketChromeOS::BluetoothSocketChromeOS( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source) + : BluetoothSocketNet(ui_task_runner, socket_thread, net_log, source) { +} + +BluetoothSocketChromeOS::~BluetoothSocketChromeOS() { + DCHECK(object_path_.value().empty()); + DCHECK(profile_.get() == NULL); + + if (adapter_.get()) { + adapter_->RemoveObserver(this); + adapter_ = NULL; + } +} + +void BluetoothSocketChromeOS::Connect( + const BluetoothDeviceChromeOS* device, + const BluetoothUUID& uuid, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(object_path_.value().empty()); + DCHECK(!profile_.get()); + + if (!uuid.IsValid()) { + error_callback.Run(kInvalidUUID); + return; + } + + device_address_ = device->GetAddress(); + device_path_ = device->object_path(); + uuid_ = uuid; + options_.reset(new BluetoothProfileManagerClient::Options()); + + RegisterProfile(success_callback, error_callback); +} + +void BluetoothSocketChromeOS::Listen( + scoped_refptr<BluetoothAdapter> adapter, + SocketType socket_type, + const BluetoothUUID& uuid, + int psm_or_channel, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(object_path_.value().empty()); + DCHECK(!profile_.get()); + + if (!uuid.IsValid()) { + error_callback.Run(kInvalidUUID); + return; + } + + adapter_ = adapter; + adapter_->AddObserver(this); + + uuid_ = uuid; + options_.reset(new BluetoothProfileManagerClient::Options()); + + switch (socket_type) { + case kRfcomm: + options_->channel.reset(new uint16( + psm_or_channel == BluetoothAdapter::kChannelAuto + ? 0 : psm_or_channel)); + break; + case kL2cap: + options_->psm.reset(new uint16( + psm_or_channel == BluetoothAdapter::kPsmAuto + ? 0 : psm_or_channel)); + break; + default: + NOTREACHED(); } - if (optval == SOCK_DGRAM || optval == SOCK_SEQPACKET) { - socket_type_ = L2CAP; + RegisterProfile(success_callback, error_callback); +} + +void BluetoothSocketChromeOS::Close() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + if (profile_) + UnregisterProfile(); + + if (!device_path_.value().empty()) { + BluetoothSocketNet::Close(); } else { - socket_type_ = RFCOMM; + DoCloseListening(); } } -BluetoothSocketChromeOS::~BluetoothSocketChromeOS() { - close(fd_); +void BluetoothSocketChromeOS::Disconnect(const base::Closure& callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + if (profile_) + UnregisterProfile(); + + if (!device_path_.value().empty()) { + BluetoothSocketNet::Disconnect(callback); + } else { + DoCloseListening(); + callback.Run(); + } } -bool BluetoothSocketChromeOS::Receive(net::GrowableIOBuffer *buffer) { - base::ThreadRestrictions::AssertIOAllowed(); +void BluetoothSocketChromeOS::Accept( + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); - 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; - } + if (!device_path_.value().empty()) { + error_callback.Run(kSocketNotListening); + return; + } - // 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; - } - } + // Only one pending accept at a time + if (accept_request_.get()) { + error_callback.Run(net::ErrorToString(net::ERR_IO_PENDING)); + return; + } - buffer->SetCapacity(count); + accept_request_.reset(new AcceptRequest); + accept_request_->success_callback = success_callback; + accept_request_->error_callback = error_callback; + + if (connection_request_queue_.size() >= 1) { + AcceptConnectionRequest(); + } +} + +void BluetoothSocketChromeOS::RegisterProfile( + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(object_path_.value().empty()); + DCHECK(!profile_.get()); + + // The object path is relatively meaningless, but has to be unique, so for + // connecting profiles use a combination of the device address and profile + // UUID. + std::string device_address_path, uuid_path; + base::ReplaceChars(device_address_, ":-", "_", &device_address_path); + base::ReplaceChars(uuid_.canonical_value(), ":-", "_", &uuid_path); + if (!device_address_path.empty()) { + object_path_ = dbus::ObjectPath("/org/chromium/bluetooth_profile/" + + device_address_path + "/" + uuid_path); } 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; + object_path_ = dbus::ObjectPath("/org/chromium/bluetooth_profile/" + + uuid_path); + } + + // Create the service provider for the profile object. + dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); + profile_.reset(BluetoothProfileServiceProvider::Create( + system_bus, object_path_, this)); + DCHECK(profile_.get()); + + // Before reaching out to the Bluetooth Daemon to register a listening socket, + // make sure it's actually running. If not, report success and carry on; + // the profile will be registered when the daemon becomes available. + if (adapter_ && !adapter_->IsPresent()) { + VLOG(1) << object_path_.value() << ": Delaying profile registration."; + success_callback.Run(); + return; + } + + VLOG(1) << object_path_.value() << ": Registering profile."; + DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> + RegisterProfile( + object_path_, + uuid_.canonical_value(), + *options_, + base::Bind(&BluetoothSocketChromeOS::OnRegisterProfile, + this, + success_callback, + error_callback), + base::Bind(&BluetoothSocketChromeOS::OnRegisterProfileError, + this, + error_callback)); +} + +void BluetoothSocketChromeOS::OnRegisterProfile( + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + if (!device_path_.value().empty()) { + VLOG(1) << object_path_.value() << ": Profile registered, connecting to " + << device_path_.value(); + + DBusThreadManager::Get()->GetBluetoothDeviceClient()-> + ConnectProfile( + device_path_, + uuid_.canonical_value(), + base::Bind( + &BluetoothSocketChromeOS::OnConnectProfile, + this, + success_callback), + base::Bind( + &BluetoothSocketChromeOS::OnConnectProfileError, + this, + error_callback)); + } else { + VLOG(1) << object_path_.value() << ": Profile registered."; + success_callback.Run(); + } +} + +void BluetoothSocketChromeOS::OnRegisterProfileError( + const ErrorCompletionCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + LOG(WARNING) << object_path_.value() << ": Failed to register profile: " + << error_name << ": " << error_message; + error_callback.Run(error_message); +} + +void BluetoothSocketChromeOS::OnConnectProfile( + const base::Closure& success_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + VLOG(1) << object_path_.value() << ": Profile connected."; + UnregisterProfile(); + success_callback.Run(); +} + +void BluetoothSocketChromeOS::OnConnectProfileError( + const ErrorCompletionCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + LOG(WARNING) << object_path_.value() << ": Failed to connect profile: " + << error_name << ": " << error_message; + UnregisterProfile(); + error_callback.Run(error_message); +} + +void BluetoothSocketChromeOS::AdapterPresentChanged(BluetoothAdapter* adapter, + bool present) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(!object_path_.value().empty()); + DCHECK(profile_.get()); + + if (!present) + return; + + VLOG(1) << object_path_.value() << ": Re-register profile."; + DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> + RegisterProfile( + object_path_, + uuid_.canonical_value(), + *options_, + base::Bind(&BluetoothSocketChromeOS::OnInternalRegisterProfile, + this), + base::Bind(&BluetoothSocketChromeOS::OnInternalRegisterProfileError, + this)); +} + +void BluetoothSocketChromeOS::OnInternalRegisterProfile() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + VLOG(1) << object_path_.value() << ": Profile re-registered"; +} + +void BluetoothSocketChromeOS::OnInternalRegisterProfileError( + const std::string& error_name, + const std::string& error_message) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + // It's okay if the profile already exists, it means we registered it on + // initialization. + if (error_name == bluetooth_profile_manager::kErrorAlreadyExists) + return; + + LOG(WARNING) << object_path_.value() << ": Failed to re-register profile: " + << error_name << ": " << error_message; +} + +void BluetoothSocketChromeOS::Released() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + VLOG(1) << object_path_.value() << ": Release"; +} + +void BluetoothSocketChromeOS::NewConnection( + const dbus::ObjectPath& device_path, + scoped_ptr<dbus::FileDescriptor> fd, + const BluetoothProfileServiceProvider::Delegate::Options& options, + const ConfirmationCallback& callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + VLOG(1) << object_path_.value() << ": New connection from device: " + << device_path.value(); + + if (!device_path_.value().empty()) { + DCHECK(device_path_ == device_path); + + socket_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind( + &BluetoothSocketChromeOS::DoNewConnection, + this, + device_path_, + base::Passed(&fd), + options, + callback)); + } else { + linked_ptr<ConnectionRequest> request(new ConnectionRequest()); + request->device_path = device_path; + request->fd = fd.Pass(); + request->options = options; + request->callback = callback; + + connection_request_queue_.push(request); + VLOG(1) << object_path_.value() << ": Connection is now pending."; + if (accept_request_) { + AcceptConnectionRequest(); } } +} - if (bytes_read == 0 && socket_type_ == RFCOMM) { - // TODO(keybuk, youngki): Agree a common way to flag disconnected. - error_message_ = "Disconnected"; - return false; +void BluetoothSocketChromeOS::RequestDisconnection( + const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + VLOG(1) << object_path_.value() << ": Request disconnection"; + callback.Run(SUCCESS); +} + +void BluetoothSocketChromeOS::Cancel() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + VLOG(1) << object_path_.value() << ": Cancel"; + + if (!connection_request_queue_.size()) + return; + + // If the front request is being accepted mark it as cancelled, otherwise + // just pop it from the queue. + linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); + if (!request->accepting) { + request->cancelled = true; + } else { + connection_request_queue_.pop(); } +} + +void BluetoothSocketChromeOS::AcceptConnectionRequest() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(accept_request_.get()); + DCHECK(connection_request_queue_.size() >= 1); - return true; + VLOG(1) << object_path_.value() << ": Accepting pending connection."; + + linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); + request->accepting = true; + + BluetoothDeviceChromeOS* device = + static_cast<BluetoothAdapterChromeOS*>(adapter_.get())-> + GetDeviceWithPath(request->device_path); + DCHECK(device); + + scoped_refptr<BluetoothSocketChromeOS> client_socket = + BluetoothSocketChromeOS::CreateBluetoothSocket( + ui_task_runner(), + socket_thread(), + net_log(), + source()); + + client_socket->device_address_ = device->GetAddress(); + client_socket->device_path_ = request->device_path; + client_socket->uuid_ = uuid_; + + socket_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind( + &BluetoothSocketChromeOS::DoNewConnection, + client_socket, + request->device_path, + base::Passed(&request->fd), + request->options, + base::Bind(&BluetoothSocketChromeOS::OnNewConnection, + this, + client_socket, + request->callback))); } -bool BluetoothSocketChromeOS::Send(net::DrainableIOBuffer *buffer) { +void BluetoothSocketChromeOS::DoNewConnection( + const dbus::ObjectPath& device_path, + scoped_ptr<dbus::FileDescriptor> fd, + const BluetoothProfileServiceProvider::Delegate::Options& options, + const ConfirmationCallback& callback) { + DCHECK(socket_thread()->task_runner()->RunsTasksOnCurrentThread()); base::ThreadRestrictions::AssertIOAllowed(); + fd->CheckValidity(); - 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; - } + VLOG(1) << object_path_.value() << ": Validity check complete."; + if (!fd->is_valid()) { + LOG(WARNING) << object_path_.value() << " :" << fd->value() + << ": Invalid file descriptor received from Bluetooth Daemon."; + ui_task_runner()->PostTask(FROM_HERE, + base::Bind(callback, REJECTED));; + return; + } + + if (tcp_socket()) { + LOG(WARNING) << object_path_.value() << ": Already connected"; + ui_task_runner()->PostTask(FROM_HERE, + base::Bind(callback, REJECTED));; + return; + } + + ResetTCPSocket(); + + // Note: We don't have a meaningful |IPEndPoint|, but that is ok since the + // TCPSocket implementation does not actually require one. + int net_result = tcp_socket()->AdoptConnectedSocket(fd->value(), + net::IPEndPoint()); + if (net_result != net::OK) { + LOG(WARNING) << object_path_.value() << ": Error adopting socket: " + << std::string(net::ErrorToString(net_result)); + ui_task_runner()->PostTask(FROM_HERE, + base::Bind(callback, REJECTED));; + return; + } + + VLOG(2) << object_path_.value() << ": Taking descriptor, confirming success."; + fd->TakeValue(); + ui_task_runner()->PostTask(FROM_HERE, + base::Bind(callback, SUCCESS));; +} + +void BluetoothSocketChromeOS::OnNewConnection( + scoped_refptr<BluetoothSocket> socket, + const ConfirmationCallback& callback, + Status status) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(accept_request_.get()); + DCHECK(connection_request_queue_.size() >= 1); + + linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); + if (status == SUCCESS && !request->cancelled) { + BluetoothDeviceChromeOS* device = + static_cast<BluetoothAdapterChromeOS*>(adapter_.get())-> + GetDeviceWithPath(request->device_path); + DCHECK(device); + + accept_request_->success_callback.Run(device, socket); + } else { + accept_request_->error_callback.Run(kAcceptFailed); } - return true; + accept_request_.reset(NULL); + connection_request_queue_.pop(); + + callback.Run(status); } -std::string BluetoothSocketChromeOS::GetLastErrorMessage() const { - return error_message_; +void BluetoothSocketChromeOS::DoCloseListening() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + if (accept_request_) { + accept_request_->error_callback.Run( + net::ErrorToString(net::ERR_CONNECTION_CLOSED)); + accept_request_.reset(NULL); + } + + while (connection_request_queue_.size() > 0) { + linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); + request->callback.Run(REJECTED); + connection_request_queue_.pop(); + } } -// static -scoped_refptr<device::BluetoothSocket> BluetoothSocketChromeOS::Create( - dbus::FileDescriptor* fd) { - DCHECK(fd->is_valid()); +void BluetoothSocketChromeOS::UnregisterProfile() { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(!object_path_.value().empty()); + DCHECK(profile_.get()); + + VLOG(1) << object_path_.value() << ": Unregister profile"; + DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> + UnregisterProfile( + object_path_, + base::Bind(&BluetoothSocketChromeOS::OnUnregisterProfile, + this, + object_path_), + base::Bind(&BluetoothSocketChromeOS::OnUnregisterProfileError, + this, + object_path_)); + + profile_.reset(); + object_path_ = dbus::ObjectPath(""); +} + +void BluetoothSocketChromeOS::OnUnregisterProfile( + const dbus::ObjectPath& object_path) { + VLOG(1) << object_path.value() << ": Profile unregistered"; +} + +void BluetoothSocketChromeOS::OnUnregisterProfileError( + const dbus::ObjectPath& object_path, + const std::string& error_name, + const std::string& error_message) { + // It's okay if the profile doesn't exist, it means we haven't registered it + // yet. + if (error_name == bluetooth_profile_manager::kErrorDoesNotExist) + return; - BluetoothSocketChromeOS* bluetooth_socket = - new BluetoothSocketChromeOS(fd->TakeValue());; - return scoped_refptr<BluetoothSocketChromeOS>(bluetooth_socket); + LOG(WARNING) << object_path_.value() << ": Failed to unregister profile: " + << error_name << ": " << error_message; } } // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_socket_chromeos.h b/chromium/device/bluetooth/bluetooth_socket_chromeos.h index 1c52c679f0e..43f512666eb 100644 --- a/chromium/device/bluetooth/bluetooth_socket_chromeos.h +++ b/chromium/device/bluetooth/bluetooth_socket_chromeos.h @@ -5,65 +5,200 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_CHROMEOS_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_CHROMEOS_H_ +#include <queue> #include <string> -#include "base/basictypes.h" -#include "base/memory/ref_counted.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chromeos/chromeos_export.h" +#include "chromeos/dbus/bluetooth_profile_manager_client.h" +#include "chromeos/dbus/bluetooth_profile_service_provider.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_net.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace dbus { - class FileDescriptor; - } // namespace dbus -namespace net { - -class DrainableIOBuffer; -class GrowableIOBuffer; - -} // namespace net - namespace chromeos { +class BluetoothDeviceChromeOS; + // The BluetoothSocketChromeOS class implements BluetoothSocket for the // Chrome OS platform. class CHROMEOS_EXPORT BluetoothSocketChromeOS - : public device::BluetoothSocket { + : public device::BluetoothSocketNet, + public device::BluetoothAdapter::Observer, + public BluetoothProfileServiceProvider::Delegate { 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); + static scoped_refptr<BluetoothSocketChromeOS> CreateBluetoothSocket( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<device::BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source); + + // Connects this socket to the service on |device| published as UUID |uuid|, + // the underlying protocol and PSM or Channel is obtained through service + // discovery. On a successful connection the socket properties will be updated + // and |success_callback| called. On failure |error_callback| will be called + // with a message explaining the cause of the failure. + virtual void Connect(const BluetoothDeviceChromeOS* device, + const device::BluetoothUUID& uuid, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // Listens using this socket using a service published on |adapter|. The + // service is either RFCOMM or L2CAP depending on |socket_type| and published + // as UUID |uuid|. The |psm_or_channel| argument is interpreted according to + // |socket_type|. |success_callback| will be called if the service is + // successfully registered, |error_callback| on failure with a message + // explaining the cause. + enum SocketType { kRfcomm, kL2cap }; + virtual void Listen(scoped_refptr<device::BluetoothAdapter> adapter, + SocketType socket_type, + const device::BluetoothUUID& uuid, + int psm_or_channel, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // BluetoothSocket: + virtual void Close() OVERRIDE; + virtual void Disconnect(const base::Closure& callback) OVERRIDE; + virtual void Accept(const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) OVERRIDE; + + // Returns the object path of the socket. + const dbus::ObjectPath& object_path() const { return object_path_; } protected: virtual ~BluetoothSocketChromeOS(); private: - BluetoothSocketChromeOS(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 + BluetoothSocketChromeOS( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<device::BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source); + + // Register the underlying profile client object with the Bluetooth Daemon. + void RegisterProfile(const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + void OnRegisterProfile(const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + void OnRegisterProfileError(const ErrorCompletionCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the ConnectProfile() method. + void OnConnectProfile(const base::Closure& success_callback); + void OnConnectProfileError(const ErrorCompletionCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // BluetoothAdapter::Observer: + virtual void AdapterPresentChanged(device::BluetoothAdapter* adapter, + bool present) OVERRIDE; + + // Called by dbus:: on completion of the RegisterProfile() method call + // triggered as a result of the adapter becoming present again. + void OnInternalRegisterProfile(); + void OnInternalRegisterProfileError(const std::string& error_name, + const std::string& error_message); + + // BluetoothProfileServiceProvider::Delegate: + virtual void Released() OVERRIDE; + virtual void NewConnection( + const dbus::ObjectPath& device_path, + scoped_ptr<dbus::FileDescriptor> fd, + const BluetoothProfileServiceProvider::Delegate::Options& options, + const ConfirmationCallback& callback) OVERRIDE; + virtual void RequestDisconnection( + const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + + // Method run to accept a single incoming connection. + void AcceptConnectionRequest(); + + // Method run on the socket thread to validate the file descriptor of a new + // connection and set up the underlying net::TCPSocket() for it. + void DoNewConnection( + const dbus::ObjectPath& device_path, + scoped_ptr<dbus::FileDescriptor> fd, + const BluetoothProfileServiceProvider::Delegate::Options& options, + const ConfirmationCallback& callback); + + // Method run on the UI thread after a new connection has been accepted and + // a socket allocated in |socket|. Takes care of calling the Accept() + // callback and |callback| with the right arguments based on |status|. + void OnNewConnection(scoped_refptr<BluetoothSocket> socket, + const ConfirmationCallback& callback, + Status status); + + // Method run on the socket thread with a valid file descriptor |fd|, once + // complete calls |callback| on the UI thread with an appropriate argument + // indicating success or failure. + void DoConnect(scoped_ptr<dbus::FileDescriptor> fd, + const ConfirmationCallback& callback); + + // Method run to clean-up a listening socket. + void DoCloseListening(); + + // Unregister the underlying profile client object from the Bluetooth Daemon. + void UnregisterProfile(); + void OnUnregisterProfile(const dbus::ObjectPath& object_path); + void OnUnregisterProfileError(const dbus::ObjectPath& object_path, + const std::string& error_name, + const std::string& error_message); + + // Adapter the profile is registered against; this is only present when the + // socket is listening. + scoped_refptr<device::BluetoothAdapter> adapter_; + + // Address and D-Bus object path of the device being connected to, empty and + // ignored if the socket is listening. + std::string device_address_; + dbus::ObjectPath device_path_; + + // UUID of the profile being connected to, or listening on. + device::BluetoothUUID uuid_; + + // Copy of the profile options used for registering the profile. + scoped_ptr<BluetoothProfileManagerClient::Options> options_; + + // 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<BluetoothProfileServiceProvider> profile_; + + // Pending request to an Accept() call. + struct AcceptRequest { + AcceptRequest(); + ~AcceptRequest(); + + AcceptCompletionCallback success_callback; + ErrorCompletionCallback error_callback; }; - - // 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_; + scoped_ptr<AcceptRequest> accept_request_; + + // Queue of incoming connection requests. + struct ConnectionRequest { + ConnectionRequest(); + ~ConnectionRequest(); + + dbus::ObjectPath device_path; + scoped_ptr<dbus::FileDescriptor> fd; + BluetoothProfileServiceProvider::Delegate::Options options; + ConfirmationCallback callback; + bool accepting; + bool cancelled; + }; + std::queue<linked_ptr<ConnectionRequest> > connection_request_queue_; DISALLOW_COPY_AND_ASSIGN(BluetoothSocketChromeOS); }; diff --git a/chromium/device/bluetooth/bluetooth_socket_chromeos_unittest.cc b/chromium/device/bluetooth/bluetooth_socket_chromeos_unittest.cc new file mode 100644 index 00000000000..7c868371c47 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_socket_chromeos_unittest.cc @@ -0,0 +1,513 @@ +// 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/bind.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "chromeos/dbus/fake_bluetooth_adapter_client.h" +#include "chromeos/dbus/fake_bluetooth_agent_manager_client.h" +#include "chromeos/dbus/fake_bluetooth_device_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_service_client.h" +#include "chromeos/dbus/fake_bluetooth_input_client.h" +#include "chromeos/dbus/fake_bluetooth_profile_manager_client.h" +#include "chromeos/dbus/fake_dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_adapter_chromeos.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_chromeos.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_chromeos.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest.h" + +using device::BluetoothAdapter; +using device::BluetoothDevice; +using device::BluetoothSocket; +using device::BluetoothSocketThread; +using device::BluetoothUUID; + +namespace { + +void DoNothingDBusErrorCallback(const std::string& error_name, + const std::string& error_message) {} + +} // namespace + +namespace chromeos { + +class BluetoothSocketChromeOSTest : public testing::Test { + public: + BluetoothSocketChromeOSTest() + : success_callback_count_(0), + error_callback_count_(0), + last_bytes_sent_(0), + last_bytes_received_(0), + last_reason_(BluetoothSocket::kSystemError) {} + + virtual void SetUp() OVERRIDE { + scoped_ptr<FakeDBusThreadManager> fake_dbus_thread_manager( + new FakeDBusThreadManager); + + fake_dbus_thread_manager->SetBluetoothAdapterClient( + scoped_ptr<BluetoothAdapterClient>(new FakeBluetoothAdapterClient)); + fake_dbus_thread_manager->SetBluetoothAgentManagerClient( + scoped_ptr<BluetoothAgentManagerClient>( + new FakeBluetoothAgentManagerClient)); + fake_dbus_thread_manager->SetBluetoothDeviceClient( + scoped_ptr<BluetoothDeviceClient>(new FakeBluetoothDeviceClient)); + fake_dbus_thread_manager->SetBluetoothGattServiceClient( + scoped_ptr<BluetoothGattServiceClient>( + new FakeBluetoothGattServiceClient)); + fake_dbus_thread_manager->SetBluetoothInputClient( + scoped_ptr<BluetoothInputClient>(new FakeBluetoothInputClient)); + fake_dbus_thread_manager->SetBluetoothProfileManagerClient( + scoped_ptr<BluetoothProfileManagerClient>( + new FakeBluetoothProfileManagerClient)); + + DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager.release()); + BluetoothSocketThread::Get(); + + // Grab a pointer to the adapter. + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&BluetoothSocketChromeOSTest::AdapterCallback, + base::Unretained(this))); + ASSERT_TRUE(adapter_.get() != NULL); + ASSERT_TRUE(adapter_->IsInitialized()); + ASSERT_TRUE(adapter_->IsPresent()); + + // Turn on the adapter. + adapter_->SetPowered( + true, + base::Bind(&base::DoNothing), + base::Bind(&base::DoNothing)); + ASSERT_TRUE(adapter_->IsPowered()); + } + + virtual void TearDown() OVERRIDE { + adapter_ = NULL; + BluetoothSocketThread::CleanupForTesting(); + DBusThreadManager::Shutdown(); + } + + void AdapterCallback(scoped_refptr<BluetoothAdapter> adapter) { + adapter_ = adapter; + } + + void SuccessCallback() { + ++success_callback_count_; + message_loop_.Quit(); + } + + void ErrorCallback(const std::string& message) { + ++error_callback_count_; + last_message_ = message; + + message_loop_.Quit(); + } + + void ConnectToServiceSuccessCallback(scoped_refptr<BluetoothSocket> socket) { + ++success_callback_count_; + last_socket_ = socket; + + message_loop_.Quit(); + } + + void SendSuccessCallback(int bytes_sent) { + ++success_callback_count_; + last_bytes_sent_ = bytes_sent; + + message_loop_.Quit(); + } + + void ReceiveSuccessCallback(int bytes_received, + scoped_refptr<net::IOBuffer> io_buffer) { + ++success_callback_count_; + last_bytes_received_ = bytes_received; + last_io_buffer_ = io_buffer; + + message_loop_.Quit(); + } + + void ReceiveErrorCallback(BluetoothSocket::ErrorReason reason, + const std::string& error_message) { + ++error_callback_count_; + last_reason_ = reason; + last_message_ = error_message; + + message_loop_.Quit(); + } + + void CreateServiceSuccessCallback(scoped_refptr<BluetoothSocket> socket) { + ++success_callback_count_; + last_socket_ = socket; + } + + void AcceptSuccessCallback(const BluetoothDevice* device, + scoped_refptr<BluetoothSocket> socket) { + ++success_callback_count_; + last_device_ = device; + last_socket_ = socket; + + message_loop_.Quit(); + } + + void ImmediateSuccessCallback() { + ++success_callback_count_; + } + + protected: + base::MessageLoop message_loop_; + + scoped_refptr<BluetoothAdapter> adapter_; + + unsigned int success_callback_count_; + unsigned int error_callback_count_; + + std::string last_message_; + scoped_refptr<BluetoothSocket> last_socket_; + int last_bytes_sent_; + int last_bytes_received_; + scoped_refptr<net::IOBuffer> last_io_buffer_; + BluetoothSocket::ErrorReason last_reason_; + const BluetoothDevice* last_device_; +}; + +TEST_F(BluetoothSocketChromeOSTest, Connect) { + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + + device->ConnectToService( + BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid), + base::Bind(&BluetoothSocketChromeOSTest::ConnectToServiceSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take ownership of the socket for the remainder of the test. + scoped_refptr<BluetoothSocket> socket = last_socket_; + last_socket_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Send data to the socket, expect all of the data to be sent. + scoped_refptr<net::StringIOBuffer> write_buffer( + new net::StringIOBuffer("test")); + + socket->Send(write_buffer.get(), write_buffer->size(), + base::Bind(&BluetoothSocketChromeOSTest::SendSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_EQ(last_bytes_sent_, write_buffer->size()); + + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Receive data from the socket, and fetch the buffer from the callback; since + // the fake is an echo server, we expect to receive what we wrote. + socket->Receive( + 4096, + base::Bind(&BluetoothSocketChromeOSTest::ReceiveSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ReceiveErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_EQ(4, last_bytes_received_); + EXPECT_TRUE(last_io_buffer_.get() != NULL); + + // Take ownership of the received buffer. + scoped_refptr<net::IOBuffer> read_buffer = last_io_buffer_; + last_io_buffer_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + std::string data = std::string(read_buffer->data(), last_bytes_received_); + EXPECT_EQ("test", data); + + read_buffer = NULL; + + // Receive data again; the socket will have been closed, this should cause a + // disconnected error to be returned via the error callback. + socket->Receive( + 4096, + base::Bind(&BluetoothSocketChromeOSTest::ReceiveSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ReceiveErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + + EXPECT_EQ(0U, success_callback_count_); + EXPECT_EQ(1U, error_callback_count_); + EXPECT_EQ(BluetoothSocket::kDisconnected, last_reason_); + EXPECT_EQ(net::ErrorToString(net::OK), last_message_); + + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Send data again; since the socket is closed we should get a system error + // equivalent to the connection reset error. + write_buffer = new net::StringIOBuffer("second test"); + + socket->Send(write_buffer.get(), write_buffer->size(), + base::Bind(&BluetoothSocketChromeOSTest::SendSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + message_loop_.Run(); + + EXPECT_EQ(0U, success_callback_count_); + EXPECT_EQ(1U, error_callback_count_); + EXPECT_EQ(net::ErrorToString(net::ERR_CONNECTION_RESET), last_message_); + + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Close our end of the socket. + socket->Disconnect(base::Bind(&BluetoothSocketChromeOSTest::SuccessCallback, + base::Unretained(this))); + + message_loop_.Run(); + EXPECT_EQ(1U, success_callback_count_); +} + +TEST_F(BluetoothSocketChromeOSTest, Listen) { + adapter_->CreateRfcommService( + BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid), + BluetoothAdapter::kChannelAuto, + base::Bind(&BluetoothSocketChromeOSTest::CreateServiceSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take ownership of the socket for the remainder of the test. + scoped_refptr<BluetoothSocket> server_socket = last_socket_; + last_socket_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Simulate an incoming connection by just calling the ConnectProfile method + // of the underlying fake device client (from the BlueZ point of view, + // outgoing and incoming look the same). + // + // This is done before the Accept() call to simulate a pending call at the + // point that Accept() is called. + FakeBluetoothDeviceClient* fake_bluetooth_device_client = + static_cast<FakeBluetoothDeviceClient*>( + DBusThreadManager::Get()->GetBluetoothDeviceClient()); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + fake_bluetooth_device_client->ConnectProfile( + static_cast<BluetoothDeviceChromeOS*>(device)->object_path(), + FakeBluetoothProfileManagerClient::kRfcommUuid, + base::Bind(&base::DoNothing), + base::Bind(&DoNothingDBusErrorCallback)); + + server_socket->Accept( + base::Bind(&BluetoothSocketChromeOSTest::AcceptSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take ownership of the client socket for the remainder of the test. + scoped_refptr<BluetoothSocket> client_socket = last_socket_; + last_socket_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Close our end of the client socket. + client_socket->Disconnect( + base::Bind(&BluetoothSocketChromeOSTest::SuccessCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + client_socket = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Run a second connection test, this time calling Accept() before the + // incoming connection comes in. + server_socket->Accept( + base::Bind(&BluetoothSocketChromeOSTest::AcceptSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + + fake_bluetooth_device_client->ConnectProfile( + static_cast<BluetoothDeviceChromeOS*>(device)->object_path(), + FakeBluetoothProfileManagerClient::kRfcommUuid, + base::Bind(&base::DoNothing), + base::Bind(&DoNothingDBusErrorCallback)); + + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take ownership of the client socket for the remainder of the test. + client_socket = last_socket_; + last_socket_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Close our end of the client socket. + client_socket->Disconnect( + base::Bind(&BluetoothSocketChromeOSTest::SuccessCallback, + base::Unretained(this))); + + message_loop_.Run(); + + EXPECT_EQ(1U, success_callback_count_); + client_socket = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Now close the server socket. + server_socket->Disconnect( + base::Bind(&BluetoothSocketChromeOSTest::ImmediateSuccessCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, success_callback_count_); +} + +TEST_F(BluetoothSocketChromeOSTest, ListenBeforeAdapterStart) { + // Start off with an invisible adapter, register the profile, then make + // the adapter visible. + FakeBluetoothAdapterClient* fake_bluetooth_adapter_client = + static_cast<FakeBluetoothAdapterClient*>( + DBusThreadManager::Get()->GetBluetoothAdapterClient()); + fake_bluetooth_adapter_client->SetVisible(false); + + adapter_->CreateRfcommService( + BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid), + BluetoothAdapter::kChannelAuto, + base::Bind(&BluetoothSocketChromeOSTest::CreateServiceSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take ownership of the socket for the remainder of the test. + scoped_refptr<BluetoothSocket> socket = last_socket_; + last_socket_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // But there shouldn't be a profile registered yet. + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client = + static_cast<FakeBluetoothProfileManagerClient*>( + DBusThreadManager::Get()->GetBluetoothProfileManagerClient()); + FakeBluetoothProfileServiceProvider* profile_service_provider = + fake_bluetooth_profile_manager_client->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kRfcommUuid); + EXPECT_TRUE(profile_service_provider == NULL); + + // Make the adapter visible. This should register a profile. + fake_bluetooth_adapter_client->SetVisible(true); + + profile_service_provider = + fake_bluetooth_profile_manager_client->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kRfcommUuid); + EXPECT_TRUE(profile_service_provider != NULL); + + // Cleanup the socket. + socket->Disconnect( + base::Bind(&BluetoothSocketChromeOSTest::ImmediateSuccessCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, success_callback_count_); +} + +TEST_F(BluetoothSocketChromeOSTest, ListenAcrossAdapterRestart) { + // The fake adapter starts off visible by default. + FakeBluetoothAdapterClient* fake_bluetooth_adapter_client = + static_cast<FakeBluetoothAdapterClient*>( + DBusThreadManager::Get()->GetBluetoothAdapterClient()); + + adapter_->CreateRfcommService( + BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid), + BluetoothAdapter::kChannelAuto, + base::Bind(&BluetoothSocketChromeOSTest::CreateServiceSuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, success_callback_count_); + EXPECT_EQ(0U, error_callback_count_); + EXPECT_TRUE(last_socket_.get() != NULL); + + // Take ownership of the socket for the remainder of the test. + scoped_refptr<BluetoothSocket> socket = last_socket_; + last_socket_ = NULL; + success_callback_count_ = 0; + error_callback_count_ = 0; + + // Make sure the profile was registered with the daemon. + FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client = + static_cast<FakeBluetoothProfileManagerClient*>( + DBusThreadManager::Get()->GetBluetoothProfileManagerClient()); + FakeBluetoothProfileServiceProvider* profile_service_provider = + fake_bluetooth_profile_manager_client->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kRfcommUuid); + EXPECT_TRUE(profile_service_provider != NULL); + + // Make the adapter invisible, and fiddle with the profile fake to unregister + // the profile since this doesn't happen automatically. + fake_bluetooth_adapter_client->SetVisible(false); + fake_bluetooth_profile_manager_client->UnregisterProfile( + static_cast<BluetoothSocketChromeOS*>(socket.get())->object_path(), + base::Bind(&base::DoNothing), + base::Bind(&DoNothingDBusErrorCallback)); + + // Then make the adapter visible again. This should re-register the profile. + fake_bluetooth_adapter_client->SetVisible(true); + + profile_service_provider = + fake_bluetooth_profile_manager_client->GetProfileServiceProvider( + FakeBluetoothProfileManagerClient::kRfcommUuid); + EXPECT_TRUE(profile_service_provider != NULL); + + // Cleanup the socket. + socket->Disconnect( + base::Bind(&BluetoothSocketChromeOSTest::ImmediateSuccessCallback, + base::Unretained(this))); + + EXPECT_EQ(1U, success_callback_count_); +} + +} // namespace chromeos diff --git a/chromium/device/bluetooth/bluetooth_socket_mac.h b/chromium/device/bluetooth/bluetooth_socket_mac.h index 4d7bfb7e018..bbb107d8a38 100644 --- a/chromium/device/bluetooth/bluetooth_socket_mac.h +++ b/chromium/device/bluetooth/bluetooth_socket_mac.h @@ -5,65 +5,193 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_MAC_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_MAC_H_ +#include <queue> #include <string> +#import <IOBluetooth/IOBluetooth.h> +#import <IOKit/IOReturn.h> + +#include "base/mac/scoped_nsobject.h" +#include "base/memory/linked_ptr.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" #include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_uuid.h" -#ifdef __OBJC__ -@class BluetoothRFCOMMChannelDelegate; -@class IOBluetoothRFCOMMChannel; -@class IOBluetoothSDPServiceRecord; -#else -class BluetoothRFCOMMChannelDelegate; -class IOBluetoothRFCOMMChannel; -class IOBluetoothSDPServiceRecord; -#endif +@class BluetoothRfcommConnectionListener; +@class BluetoothL2capConnectionListener; namespace net { - -class DrainableIOBuffer; -class GrowableIOBuffer; - -} // namespace net +class IOBuffer; +class IOBufferWithSize; +} namespace device { -class BluetoothServiceRecord; +class BluetoothAdapterMac; +class BluetoothChannelMac; -// This class is an implementation of BluetoothSocket class for OSX platform. +// Implements the BluetoothSocket class for the Mac OS X platform. class BluetoothSocketMac : public BluetoothSocket { public: - // TODO(youngki): This method is deprecated; remove this method when - // BluetoothServiceRecord is removed. - static scoped_refptr<BluetoothSocket> CreateBluetoothSocket( - const BluetoothServiceRecord& service_record); + static scoped_refptr<BluetoothSocketMac> CreateSocket(); + + // Connects this socket to the service on |device| published as UUID |uuid|. + // The underlying protocol and PSM or Channel is obtained through service + // discovery. On a successful connection, the socket properties will be + // updated and |success_callback| called. On failure, |error_callback| will be + // called with a message explaining the cause of failure. + void Connect(IOBluetoothDevice* device, + const BluetoothUUID& uuid, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // Listens for incoming RFCOMM connections using this socket: Publishes an + // RFCOMM service on the |adapter| as UUID |uuid| with Channel |channel_id|. + // |success_callback| will be called if the service is successfully + // registered, |error_callback| on failure with a message explaining the + // cause. + void ListenUsingRfcomm(scoped_refptr<BluetoothAdapterMac> adapter, + const BluetoothUUID& uuid, + int channel_id, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // Listens for incoming L2CAP connections using this socket: Publishes an + // L2CAP service on the |adapter| as UUID |uuid| with PSM |psm|. + // |success_callback| will be called if the service is successfully + // registered, |error_callback| on failure with a message explaining the + // cause. + void ListenUsingL2cap(scoped_refptr<BluetoothAdapterMac> adapter, + const BluetoothUUID& uuid, + int psm, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // BluetoothSocket: + virtual void Close() OVERRIDE; + virtual void Disconnect(const base::Closure& callback) OVERRIDE; + virtual void Receive( + int /* buffer_size */, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback) OVERRIDE; + virtual void Send(scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) OVERRIDE; + virtual void Accept(const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) OVERRIDE; + + // Callback that is invoked when the OS completes an SDP query. + // |status| is the returned status from the SDP query, |device| is the + // IOBluetoothDevice for which the query was made. The remaining + // parameters are those from |Connect()|. + void OnSDPQueryComplete( + IOReturn status, + IOBluetoothDevice* device, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // Called by BluetoothRfcommConnectionListener and + // BluetoothL2capConnectionListener. + void OnChannelOpened(scoped_ptr<BluetoothChannelMac> channel); + + // Called by |channel_|. + // Note: OnChannelOpenComplete might be called before the |channel_| is set. + void OnChannelOpenComplete(const std::string& device_address, + IOReturn status); + void OnChannelClosed(); + void OnChannelDataReceived(void* data, size_t length); + void OnChannelWriteComplete(void* refcon, IOReturn status); + + private: + struct AcceptRequest { + AcceptRequest(); + ~AcceptRequest(); + + AcceptCompletionCallback success_callback; + ErrorCompletionCallback error_callback; + }; + + struct SendRequest { + SendRequest(); + ~SendRequest(); + int buffer_size; + SendCompletionCallback success_callback; + ErrorCompletionCallback error_callback; + IOReturn status; + int active_async_writes; + bool error_signaled; + }; + + struct ReceiveCallbacks { + ReceiveCallbacks(); + ~ReceiveCallbacks(); + ReceiveCompletionCallback success_callback; + ReceiveErrorCompletionCallback error_callback; + }; + + struct ConnectCallbacks { + ConnectCallbacks(); + ~ConnectCallbacks(); + base::Closure success_callback; + ErrorCompletionCallback error_callback; + }; + + BluetoothSocketMac(); + virtual ~BluetoothSocketMac(); - static scoped_refptr<BluetoothSocket> CreateBluetoothSocket( - IOBluetoothSDPServiceRecord* record); + // Accepts a single incoming connection. + void AcceptConnectionRequest(); - // BluetoothSocket override - virtual bool Receive(net::GrowableIOBuffer* buffer) OVERRIDE; - virtual bool Send(net::DrainableIOBuffer* buffer) OVERRIDE; - virtual std::string GetLastErrorMessage() const OVERRIDE; + void ReleaseChannel(); + void ReleaseListener(); - // called by BluetoothRFCOMMChannelDelegate. - void OnDataReceived(IOBluetoothRFCOMMChannel* rfcomm_channel, - void* data, - size_t length); + bool is_connecting() const { return connect_callbacks_; } - protected: - virtual ~BluetoothSocketMac(); + // Used to verify that all methods are called on the same thread. + base::ThreadChecker thread_checker_; - private: - explicit BluetoothSocketMac(IOBluetoothRFCOMMChannel* rfcomm_channel); + // Adapter the socket is registered against. This is only present when the + // socket is listening. + scoped_refptr<BluetoothAdapterMac> adapter_; + + // UUID of the profile being connected to, or that the socket is listening on. + device::BluetoothUUID uuid_; + + // Simple helpers that register for OS notifications and forward them to + // |this| profile. + base::scoped_nsobject<BluetoothRfcommConnectionListener> + rfcomm_connection_listener_; + base::scoped_nsobject<BluetoothL2capConnectionListener> + l2cap_connection_listener_; + + // A handle to the service record registered in the system SDP server. + // Used to eventually unregister the service. + BluetoothSDPServiceRecordHandle service_record_handle_; + + // The channel used to issue commands. + scoped_ptr<BluetoothChannelMac> channel_; + + // Connection callbacks -- when a pending async connection is active. + scoped_ptr<ConnectCallbacks> connect_callbacks_; + + // Packets received while there is no pending "receive" callback. + std::queue<scoped_refptr<net::IOBufferWithSize> > receive_queue_; + + // Receive callbacks -- when a receive call is active. + scoped_ptr<ReceiveCallbacks> receive_callbacks_; + + // Send queue -- one entry per pending send operation. + std::queue<linked_ptr<SendRequest>> send_queue_; - void ResetIncomingDataBuffer(); + // The pending request to an Accept() call, or null if there is no pending + // request. + scoped_ptr<AcceptRequest> accept_request_; - IOBluetoothRFCOMMChannel* rfcomm_channel_; - BluetoothRFCOMMChannelDelegate* delegate_; - scoped_refptr<net::GrowableIOBuffer> incoming_data_buffer_; - std::string error_message_; + // Queue of incoming connections. + std::queue<linked_ptr<BluetoothChannelMac>> accept_queue_; DISALLOW_COPY_AND_ASSIGN(BluetoothSocketMac); }; diff --git a/chromium/device/bluetooth/bluetooth_socket_mac.mm b/chromium/device/bluetooth/bluetooth_socket_mac.mm index 3751441f521..e428ba5a7f0 100644 --- a/chromium/device/bluetooth/bluetooth_socket_mac.mm +++ b/chromium/device/bluetooth/bluetooth_socket_mac.mm @@ -4,179 +4,935 @@ #include "device/bluetooth/bluetooth_socket_mac.h" -#import <IOBluetooth/objc/IOBluetoothDevice.h> -#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h> -#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> +#import <IOBluetooth/IOBluetooth.h> #include <limits> +#include <sstream> #include <string> #include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/mac/scoped_cftyperef.h" #include "base/memory/ref_counted.h" +#include "base/numerics/safe_conversions.h" #include "base/strings/stringprintf.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/sys_string_conversions.h" -#include "device/bluetooth/bluetooth_service_record.h" -#include "device/bluetooth/bluetooth_service_record_mac.h" +#include "base/threading/thread_restrictions.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_adapter_mac.h" +#include "device/bluetooth/bluetooth_channel_mac.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_mac.h" +#include "device/bluetooth/bluetooth_l2cap_channel_mac.h" +#include "device/bluetooth/bluetooth_rfcomm_channel_mac.h" #include "net/base/io_buffer.h" +#include "net/base/net_errors.h" // Replicate specific 10.7 SDK declarations for building with prior SDKs. #if !defined(MAC_OS_X_VERSION_10_7) || \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 @interface IOBluetoothDevice (LionSDKDeclarations) -- (NSString*)addressString; +- (IOReturn)performSDPQuery:(id)target uuids:(NSArray*)uuids; @end #endif // MAC_OS_X_VERSION_10_7 -@interface BluetoothRFCOMMChannelDelegate - : NSObject <IOBluetoothRFCOMMChannelDelegate> { +using device::BluetoothSocket; + +// A simple helper class that forwards SDP query completed notifications to its +// wrapped |socket_|. +@interface SDPQueryListener : NSObject { + @private + // The socket that registered for notifications. + scoped_refptr<device::BluetoothSocketMac> socket_; + + // Callbacks associated with the request that triggered this SDP query. + base::Closure success_callback_; + BluetoothSocket::ErrorCompletionCallback error_callback_; + + // The device being queried. + IOBluetoothDevice* device_; // weak +} + +- (id)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket + device:(IOBluetoothDevice*)device + success_callback:(base::Closure)success_callback + error_callback:(BluetoothSocket::ErrorCompletionCallback)error_callback; +- (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status; + +@end + +@implementation SDPQueryListener + +- (id)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket + device:(IOBluetoothDevice*)device + success_callback:(base::Closure)success_callback + error_callback:(BluetoothSocket::ErrorCompletionCallback)error_callback { + if ((self = [super init])) { + socket_ = socket; + device_ = device; + success_callback_ = success_callback; + error_callback_ = error_callback; + } + + return self; +} + +- (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status { + DCHECK_EQ(device, device_); + socket_->OnSDPQueryComplete( + status, device, success_callback_, error_callback_); +} + +@end + +// A simple helper class that forwards RFCOMM channel opened notifications to +// its wrapped |socket_|. +@interface BluetoothRfcommConnectionListener : NSObject { + @private + // The socket that owns |self|. + device::BluetoothSocketMac* socket_; // weak + + // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel + // creation notifications. + IOBluetoothUserNotification* rfcommNewChannelNotification_; // weak +} + +- (id)initWithSocket:(device::BluetoothSocketMac*)socket + channelID:(BluetoothRFCOMMChannelID)channelID; +- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification + channel:(IOBluetoothRFCOMMChannel*)rfcommChannel; + +@end + +@implementation BluetoothRfcommConnectionListener + +- (id)initWithSocket:(device::BluetoothSocketMac*)socket + channelID:(BluetoothRFCOMMChannelID)channelID { + if ((self = [super init])) { + socket_ = socket; + + SEL selector = @selector(rfcommChannelOpened:channel:); + const auto kIncomingDirection = + kIOBluetoothUserNotificationChannelDirectionIncoming; + rfcommNewChannelNotification_ = + [IOBluetoothRFCOMMChannel + registerForChannelOpenNotifications:self + selector:selector + withChannelID:channelID + direction:kIncomingDirection]; + } + + return self; +} + +- (void)dealloc { + [rfcommNewChannelNotification_ unregister]; + [super dealloc]; +} + +- (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification + channel:(IOBluetoothRFCOMMChannel*)rfcommChannel { + if (notification != rfcommNewChannelNotification_) { + // This case is reachable if there are pre-existing RFCOMM channels open at + // the time that the listener is created. In that case, each existing + // channel calls into this method with a different notification than the one + // this class registered with. Ignore those; this class is only interested + // in channels that have opened since it registered for notifications. + return; + } + + socket_->OnChannelOpened(scoped_ptr<device::BluetoothChannelMac>( + new device::BluetoothRfcommChannelMac(NULL, [rfcommChannel retain]))); +} + +@end + +// A simple helper class that forwards L2CAP channel opened notifications to +// its wrapped |socket_|. +@interface BluetoothL2capConnectionListener : NSObject { @private + // The socket that owns |self|. device::BluetoothSocketMac* socket_; // weak + + // The OS mechanism used to subscribe to and unsubscribe from L2CAP channel + // creation notifications. + IOBluetoothUserNotification* l2capNewChannelNotification_; // weak } -- (id)initWithSocket:(device::BluetoothSocketMac*)socket; +- (id)initWithSocket:(device::BluetoothSocketMac*)socket + psm:(BluetoothL2CAPPSM)psm; +- (void)l2capChannelOpened:(IOBluetoothUserNotification*)notification + channel:(IOBluetoothL2CAPChannel*)l2capChannel; @end -@implementation BluetoothRFCOMMChannelDelegate +@implementation BluetoothL2capConnectionListener -- (id)initWithSocket:(device::BluetoothSocketMac*)socket { - if ((self = [super init])) +- (id)initWithSocket:(device::BluetoothSocketMac*)socket + psm:(BluetoothL2CAPPSM)psm { + if ((self = [super init])) { socket_ = socket; + SEL selector = @selector(l2capChannelOpened:channel:); + const auto kIncomingDirection = + kIOBluetoothUserNotificationChannelDirectionIncoming; + l2capNewChannelNotification_ = + [IOBluetoothL2CAPChannel + registerForChannelOpenNotifications:self + selector:selector + withPSM:psm + direction:kIncomingDirection]; + } + return self; } -- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel - data:(void*)dataPointer - length:(size_t)dataLength { - socket_->OnDataReceived(rfcommChannel, dataPointer, dataLength); +- (void)dealloc { + [l2capNewChannelNotification_ unregister]; + [super dealloc]; +} + +- (void)l2capChannelOpened:(IOBluetoothUserNotification*)notification + channel:(IOBluetoothL2CAPChannel*)l2capChannel { + if (notification != l2capNewChannelNotification_) { + // This case is reachable if there are pre-existing L2CAP channels open at + // the time that the listener is created. In that case, each existing + // channel calls into this method with a different notification than the one + // this class registered with. Ignore those; this class is only interested + // in channels that have opened since it registered for notifications. + return; + } + + socket_->OnChannelOpened(scoped_ptr<device::BluetoothChannelMac>( + new device::BluetoothL2capChannelMac(NULL, [l2capChannel retain]))); } @end namespace device { +namespace { -BluetoothSocketMac::BluetoothSocketMac(IOBluetoothRFCOMMChannel* rfcomm_channel) - : rfcomm_channel_(rfcomm_channel), - delegate_([[BluetoothRFCOMMChannelDelegate alloc] initWithSocket:this]) { - [rfcomm_channel_ setDelegate:delegate_]; - ResetIncomingDataBuffer(); +// It's safe to use 0 to represent an unregistered service, as implied by the +// documentation at [ http://goo.gl/YRtCkF ]. +const BluetoothSDPServiceRecordHandle kInvalidServiceRecordHandle = 0; + +const char kInvalidOrUsedChannel[] = "Invalid channel or already in use"; +const char kInvalidOrUsedPsm[] = "Invalid PSM or already in use"; +const char kProfileNotFound[] = "Profile not found"; +const char kSDPQueryFailed[] = "SDP query failed"; +const char kSocketConnecting[] = "The socket is currently connecting"; +const char kSocketAlreadyConnected[] = "The socket is already connected"; +const char kSocketNotConnected[] = "The socket is not connected"; +const char kReceivePending[] = "A Receive operation is pending"; + +template <class T> +void empty_queue(std::queue<T>& queue) { + std::queue<T> empty; + std::swap(queue, empty); } -BluetoothSocketMac::~BluetoothSocketMac() { - [rfcomm_channel_ setDelegate:nil]; - [rfcomm_channel_ closeChannel]; - [rfcomm_channel_ release]; - [delegate_ release]; +// Converts |uuid| to a IOBluetoothSDPUUID instance. +IOBluetoothSDPUUID* GetIOBluetoothSDPUUID(const BluetoothUUID& uuid) { + // The canonical UUID format is XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. + const std::string uuid_str = uuid.canonical_value(); + DCHECK_EQ(uuid_str.size(), 36U); + DCHECK_EQ(uuid_str[8], '-'); + DCHECK_EQ(uuid_str[13], '-'); + DCHECK_EQ(uuid_str[18], '-'); + DCHECK_EQ(uuid_str[23], '-'); + std::string numbers_only = uuid_str; + numbers_only.erase(23, 1); + numbers_only.erase(18, 1); + numbers_only.erase(13, 1); + numbers_only.erase(8, 1); + std::vector<uint8> uuid_bytes_vector; + base::HexStringToBytes(numbers_only, &uuid_bytes_vector); + DCHECK_EQ(uuid_bytes_vector.size(), 16U); + + return [IOBluetoothSDPUUID uuidWithBytes:&uuid_bytes_vector.front() + length:uuid_bytes_vector.size()]; } -// static -scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket( - const BluetoothServiceRecord& service_record) { - BluetoothSocketMac* bluetooth_socket = NULL; - if (service_record.SupportsRfcomm()) { - const BluetoothServiceRecordMac* service_record_mac = - static_cast<const BluetoothServiceRecordMac*>(&service_record); - IOBluetoothDevice* device = service_record_mac->GetIOBluetoothDevice(); - IOBluetoothRFCOMMChannel* rfcomm_channel; - IOReturn status = - [device openRFCOMMChannelAsync:&rfcomm_channel - withChannelID:service_record.rfcomm_channel() - delegate:nil]; - if (status == kIOReturnSuccess) { - bluetooth_socket = new BluetoothSocketMac(rfcomm_channel); - } else { - LOG(ERROR) << "Failed to connect bluetooth socket (" - << service_record.address() << "): (" << status << ")"; - } +// Converts the given |integer| to a string. +NSString* IntToNSString(int integer) { + return [[NSNumber numberWithInt:integer] stringValue]; +} + +// Returns a dictionary containing the Bluetooth service definition +// corresponding to the provided |uuid| and |protocol_definition|. +NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid, + NSArray* protocol_definition) { + NSMutableDictionary* service_definition = [NSMutableDictionary dictionary]; + + // TODO(isherman): The service's language is currently hardcoded to English. + // The language should ideally be specified in the chrome.bluetooth API + // instead. + // TODO(isherman): Pass in the service name to this function. + const int kEnglishLanguageBase = 100; + const int kServiceNameKey = + kEnglishLanguageBase + kBluetoothSDPAttributeIdentifierServiceName; + NSString* service_name = base::SysUTF8ToNSString(uuid.canonical_value()); + [service_definition setObject:service_name + forKey:IntToNSString(kServiceNameKey)]; + + const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList; + NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid)]; + [service_definition setObject:uuids forKey:IntToNSString(kUUIDsKey)]; + + const int kProtocolDefinitionsKey = + kBluetoothSDPAttributeIdentifierProtocolDescriptorList; + [service_definition setObject:protocol_definition + forKey:IntToNSString(kProtocolDefinitionsKey)]; + + return service_definition; +} + +// Returns a dictionary containing the Bluetooth RFCOMM service definition +// corresponding to the provided |uuid| and |channel_id|. +NSDictionary* BuildRfcommServiceDefinition(const BluetoothUUID& uuid, + int channel_id) { + NSArray* rfcomm_protocol_definition = + @[ + @[ + [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP] + ], + @[ + [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM], + @{ + @"DataElementType": @1, // Unsigned integer. + @"DataElementSize": @1, // 1 byte. + @"DataElementValue": [NSNumber numberWithInt:channel_id] + } + ] + ]; + return BuildServiceDefinition(uuid, rfcomm_protocol_definition); +} + +// Returns a dictionary containing the Bluetooth L2CAP service definition +// corresponding to the provided |uuid| and |psm|. +NSDictionary* BuildL2capServiceDefinition(const BluetoothUUID& uuid, + int psm) { + NSArray* l2cap_protocol_definition = + @[ + @[ + [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP], + @{ + @"DataElementType": @1, // Unsigned integer. + @"DataElementSize": @2, // 2 bytes. + @"DataElementValue": [NSNumber numberWithInt:psm] + } + ] + ]; + return BuildServiceDefinition(uuid, l2cap_protocol_definition); +} + +// Registers a Bluetooth service with the specified |service_definition| in the +// system SDP server. Returns a handle to the registered service on success. If +// the service could not be registered, or if |verify_service_callback| +// indicates that the to-be-registered service is not configured correctly, +// returns |kInvalidServiceRecordHandle|. +BluetoothSDPServiceRecordHandle RegisterService( + NSDictionary* service_definition, + const base::Callback<bool(IOBluetoothSDPServiceRecord*)>& + verify_service_callback) { + // Attempt to register the service. + IOBluetoothSDPServiceRecordRef service_record_ref; + IOReturn result = + IOBluetoothAddServiceDict((CFDictionaryRef)service_definition, + &service_record_ref); + if (result != kIOReturnSuccess) + return kInvalidServiceRecordHandle; + // Transfer ownership to a scoped object, to simplify memory management. + base::ScopedCFTypeRef<IOBluetoothSDPServiceRecordRef> + scoped_service_record_ref(service_record_ref); + + // Extract the service record handle. + BluetoothSDPServiceRecordHandle service_record_handle; + IOBluetoothSDPServiceRecord* service_record = + [IOBluetoothSDPServiceRecord withSDPServiceRecordRef:service_record_ref]; + result = [service_record getServiceRecordHandle:&service_record_handle]; + if (result != kIOReturnSuccess) + return kInvalidServiceRecordHandle; + + // Verify that the registered service was configured correctly. If not, + // withdraw the service. + if (!verify_service_callback.Run(service_record)) { + IOBluetoothRemoveServiceWithRecordHandle(service_record_handle); + return kInvalidServiceRecordHandle; } - // TODO(youngki): add support for L2CAP sockets as well. - return scoped_refptr<BluetoothSocketMac>(bluetooth_socket); + return service_record_handle; } +// Returns true iff the |requested_channel_id| was registered in the RFCOMM +// |service_record|. If it was, also updates |registered_channel_id| with the +// registered value, as the requested id may have been left unspecified. +bool VerifyRfcommService(int requested_channel_id, + BluetoothRFCOMMChannelID* registered_channel_id, + IOBluetoothSDPServiceRecord* service_record) { + // Test whether the requested channel id was available. + // TODO(isherman): The OS doesn't seem to actually pick a random channel if we + // pass in |kChannelAuto|. + BluetoothRFCOMMChannelID rfcomm_channel_id; + IOReturn result = [service_record getRFCOMMChannelID:&rfcomm_channel_id]; + if (result != kIOReturnSuccess || + (requested_channel_id != BluetoothAdapter::kChannelAuto && + rfcomm_channel_id != requested_channel_id)) { + return false; + } + + *registered_channel_id = rfcomm_channel_id; + return true; +} + +// Registers an RFCOMM service with the specified |uuid| and |channel_id| in the +// system SDP server. Returns a handle to the registered service and updates +// |registered_channel_id| to the actual channel id, or returns +// |kInvalidServiceRecordHandle| if the service could not be registered. +BluetoothSDPServiceRecordHandle RegisterRfcommService( + const BluetoothUUID& uuid, + int channel_id, + BluetoothRFCOMMChannelID* registered_channel_id) { + return RegisterService( + BuildRfcommServiceDefinition(uuid, channel_id), + base::Bind(&VerifyRfcommService, channel_id, registered_channel_id)); +} + +// Returns true iff the |requested_psm| was registered in the L2CAP +// |service_record|. If it was, also updates |registered_psm| with the +// registered value, as the requested PSM may have been left unspecified. +bool VerifyL2capService(int requested_psm, + BluetoothL2CAPPSM* registered_psm, + IOBluetoothSDPServiceRecord* service_record) { + // Test whether the requested PSM was available. + // TODO(isherman): The OS doesn't seem to actually pick a random PSM if we + // pass in |kPsmAuto|. + BluetoothL2CAPPSM l2cap_psm; + IOReturn result = [service_record getL2CAPPSM:&l2cap_psm]; + if (result != kIOReturnSuccess || + (requested_psm != BluetoothAdapter::kPsmAuto && + l2cap_psm != requested_psm)) { + return false; + } + + *registered_psm = l2cap_psm; + return true; +} + +// Registers an L2CAP service with the specified |uuid| and |psm| in the system +// SDP server. Returns a handle to the registered service and updates +// |registered_psm| to the actual PSM, or returns |kInvalidServiceRecordHandle| +// if the service could not be registered. +BluetoothSDPServiceRecordHandle RegisterL2capService( + const BluetoothUUID& uuid, + int psm, + BluetoothL2CAPPSM* registered_psm) { + return RegisterService(BuildL2capServiceDefinition(uuid, psm), + base::Bind(&VerifyL2capService, psm, registered_psm)); +} + +} // namespace + // static -scoped_refptr<BluetoothSocket> BluetoothSocketMac::CreateBluetoothSocket( - IOBluetoothSDPServiceRecord* record) { - BluetoothSocketMac* bluetooth_socket = NULL; - uint8 rfcomm_channel_id; - if ([record getRFCOMMChannelID:&rfcomm_channel_id] == kIOReturnSuccess) { - IOBluetoothDevice* device = [record device]; - IOBluetoothRFCOMMChannel* rfcomm_channel; - IOReturn status = - [device openRFCOMMChannelAsync:&rfcomm_channel - withChannelID:rfcomm_channel_id - delegate:nil]; - if (status == kIOReturnSuccess) { - bluetooth_socket = new BluetoothSocketMac(rfcomm_channel); - } else { - LOG(ERROR) << "Failed to connect bluetooth socket (" - << base::SysNSStringToUTF8([device addressString]) << "): (" << status - << ")"; +scoped_refptr<BluetoothSocketMac> BluetoothSocketMac::CreateSocket() { + return make_scoped_refptr(new BluetoothSocketMac()); +} + +void BluetoothSocketMac::Connect( + IOBluetoothDevice* device, + const BluetoothUUID& uuid, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + uuid_ = uuid; + + // Perform an SDP query on the |device| to refresh the cache, in case the + // services that the |device| advertises have changed since the previous + // query. + DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " " + << uuid_.canonical_value() << ": Sending SDP query."; + SDPQueryListener* listener = + [[SDPQueryListener alloc] initWithSocket:this + device:device + success_callback:success_callback + error_callback:error_callback]; + [device performSDPQuery:[listener autorelease] + uuids:@[GetIOBluetoothSDPUUID(uuid_)]]; +} + +void BluetoothSocketMac::ListenUsingRfcomm( + scoped_refptr<BluetoothAdapterMac> adapter, + const BluetoothUUID& uuid, + int channel_id, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + adapter_ = adapter; + uuid_ = uuid; + + DVLOG(1) << uuid_.canonical_value() << ": Registering RFCOMM service."; + BluetoothRFCOMMChannelID registered_channel_id; + service_record_handle_ = + RegisterRfcommService(uuid, channel_id, ®istered_channel_id); + if (service_record_handle_ == kInvalidServiceRecordHandle) { + error_callback.Run(kInvalidOrUsedChannel); + return; + } + + rfcomm_connection_listener_.reset( + [[BluetoothRfcommConnectionListener alloc] + initWithSocket:this + channelID:registered_channel_id]); + + success_callback.Run(); +} + +void BluetoothSocketMac::ListenUsingL2cap( + scoped_refptr<BluetoothAdapterMac> adapter, + const BluetoothUUID& uuid, + int psm, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + adapter_ = adapter; + uuid_ = uuid; + + DVLOG(1) << uuid_.canonical_value() << ": Registering L2CAP service."; + BluetoothL2CAPPSM registered_psm; + service_record_handle_ = RegisterL2capService(uuid, psm, ®istered_psm); + if (service_record_handle_ == kInvalidServiceRecordHandle) { + error_callback.Run(kInvalidOrUsedPsm); + return; + } + + l2cap_connection_listener_.reset( + [[BluetoothL2capConnectionListener alloc] initWithSocket:this + psm:registered_psm]); + + success_callback.Run(); +} + +void BluetoothSocketMac::OnSDPQueryComplete( + IOReturn status, + IOBluetoothDevice* device, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " " + << uuid_.canonical_value() << ": SDP query complete."; + + if (status != kIOReturnSuccess) { + error_callback.Run(kSDPQueryFailed); + return; + } + + IOBluetoothSDPServiceRecord* record = [device + getServiceRecordForUUID:GetIOBluetoothSDPUUID(uuid_)]; + if (record == nil) { + error_callback.Run(kProfileNotFound); + return; + } + + if (is_connecting()) { + error_callback.Run(kSocketConnecting); + return; + } + + if (channel_) { + error_callback.Run(kSocketAlreadyConnected); + return; + } + + // Since RFCOMM is built on top of L2CAP, a service record with both should + // always be treated as RFCOMM. + BluetoothRFCOMMChannelID rfcomm_channel_id = BluetoothAdapter::kChannelAuto; + BluetoothL2CAPPSM l2cap_psm = BluetoothAdapter::kPsmAuto; + status = [record getRFCOMMChannelID:&rfcomm_channel_id]; + if (status != kIOReturnSuccess) { + status = [record getL2CAPPSM:&l2cap_psm]; + if (status != kIOReturnSuccess) { + error_callback.Run(kProfileNotFound); + return; } } - // TODO(youngki): Add support for L2CAP sockets as well. + if (rfcomm_channel_id != BluetoothAdapter::kChannelAuto) { + DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " " + << uuid_.canonical_value() << ": Opening RFCOMM channel: " + << rfcomm_channel_id; + } else { + DCHECK_NE(l2cap_psm, BluetoothAdapter::kPsmAuto); + DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " " + << uuid_.canonical_value() << ": Opening L2CAP channel: " + << l2cap_psm; + } + + // Note: It's important to set the connect callbacks *prior* to opening the + // channel, as opening the channel can synchronously call into + // OnChannelOpenComplete(). + connect_callbacks_.reset(new ConnectCallbacks()); + connect_callbacks_->success_callback = success_callback; + connect_callbacks_->error_callback = error_callback; + + if (rfcomm_channel_id != BluetoothAdapter::kChannelAuto) { + channel_ = BluetoothRfcommChannelMac::OpenAsync( + this, device, rfcomm_channel_id, &status); + } else { + DCHECK_NE(l2cap_psm, BluetoothAdapter::kPsmAuto); + channel_ = + BluetoothL2capChannelMac::OpenAsync(this, device, l2cap_psm, &status); + } + if (status != kIOReturnSuccess) { + ReleaseChannel(); + std::stringstream error; + error << "Failed to connect bluetooth socket (" + << BluetoothDeviceMac::GetDeviceAddress(device) << "): (" << status + << ")"; + error_callback.Run(error.str()); + return; + } + + DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " " + << uuid_.canonical_value() + << ": channel opening in background."; +} + +void BluetoothSocketMac::OnChannelOpened( + scoped_ptr<BluetoothChannelMac> channel) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << uuid_.canonical_value() << ": Incoming channel pending."; + + accept_queue_.push(linked_ptr<BluetoothChannelMac>(channel.release())); + if (accept_request_) + AcceptConnectionRequest(); + + // TODO(isherman): Test whether these TODOs are still relevant. + // TODO(isherman): Currently, both the profile and the socket remain alive + // even after the app that requested them is closed. That's not great, as a + // misbehaving app could saturate all of the system's RFCOMM channels, and + // then they would not be freed until the user restarts Chrome. + // http://crbug.com/367316 + // TODO(isherman): Likewise, the socket currently remains alive even if the + // underlying rfcomm_channel is closed, e.g. via the client disconnecting, or + // the user closing the Bluetooth connection via the system menu. This + // functions essentially as a minor memory leak. + // http://crbug.com/367319 +} + +void BluetoothSocketMac::OnChannelOpenComplete( + const std::string& device_address, + IOReturn status) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(is_connecting()); + + DVLOG(1) << device_address << " " << uuid_.canonical_value() + << ": channel open complete."; + + scoped_ptr<ConnectCallbacks> temp = connect_callbacks_.Pass(); + if (status != kIOReturnSuccess) { + ReleaseChannel(); + std::stringstream error; + error << "Failed to connect bluetooth socket (" << device_address << "): (" + << status << ")"; + temp->error_callback.Run(error.str()); + return; + } + + temp->success_callback.Run(); +} + +void BluetoothSocketMac::Close() { + DCHECK(thread_checker_.CalledOnValidThread()); - return scoped_refptr<BluetoothSocketMac>(bluetooth_socket); + if (channel_) + ReleaseChannel(); + else if (service_record_handle_ != kInvalidServiceRecordHandle) + ReleaseListener(); } -bool BluetoothSocketMac::Receive(net::GrowableIOBuffer* buffer) { - CHECK(buffer->offset() == 0); - int length = incoming_data_buffer_->offset(); - if (length > 0) { - buffer->SetCapacity(length); - memcpy(buffer->data(), incoming_data_buffer_->StartOfBuffer(), length); - buffer->set_offset(length); +void BluetoothSocketMac::Disconnect(const base::Closure& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + Close(); + callback.Run(); +} - ResetIncomingDataBuffer(); +void BluetoothSocketMac::Receive( + int /* buffer_size */, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (is_connecting()) { + error_callback.Run(BluetoothSocket::kSystemError, kSocketConnecting); + return; } - return true; + + if (!channel_) { + error_callback.Run(BluetoothSocket::kDisconnected, kSocketNotConnected); + return; + } + + // Only one pending read at a time + if (receive_callbacks_) { + error_callback.Run(BluetoothSocket::kIOPending, kReceivePending); + return; + } + + // If there is at least one packet, consume it and succeed right away. + if (!receive_queue_.empty()) { + scoped_refptr<net::IOBufferWithSize> buffer = receive_queue_.front(); + receive_queue_.pop(); + success_callback.Run(buffer->size(), buffer); + return; + } + + // Set the receive callback to use when data is received. + receive_callbacks_.reset(new ReceiveCallbacks()); + receive_callbacks_->success_callback = success_callback; + receive_callbacks_->error_callback = error_callback; } -bool BluetoothSocketMac::Send(net::DrainableIOBuffer* buffer) { - int bytes_written = buffer->BytesRemaining(); - IOReturn status = [rfcomm_channel_ writeAsync:buffer->data() - length:bytes_written - refcon:nil]; +void BluetoothSocketMac::OnChannelDataReceived(void* data, size_t length) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!is_connecting()); + + int data_size = base::checked_cast<int>(length); + scoped_refptr<net::IOBufferWithSize> buffer( + new net::IOBufferWithSize(data_size)); + memcpy(buffer->data(), data, buffer->size()); + + // If there is a pending read callback, call it now. + if (receive_callbacks_) { + scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass(); + temp->success_callback.Run(buffer->size(), buffer); + return; + } + + // Otherwise, enqueue the buffer for later use + receive_queue_.push(buffer); +} + +void BluetoothSocketMac::Send(scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (is_connecting()) { + error_callback.Run(kSocketConnecting); + return; + } + + if (!channel_) { + error_callback.Run(kSocketNotConnected); + return; + } + + // Create and enqueue request in preparation of async writes. + linked_ptr<SendRequest> request(new SendRequest()); + request->buffer_size = buffer_size; + request->success_callback = success_callback; + request->error_callback = error_callback; + send_queue_.push(request); + + // |writeAsync| accepts buffers of max. mtu bytes per call, so we need to emit + // multiple write operations if buffer_size > mtu. + uint16_t mtu = channel_->GetOutgoingMTU(); + scoped_refptr<net::DrainableIOBuffer> send_buffer( + new net::DrainableIOBuffer(buffer, buffer_size)); + while (send_buffer->BytesRemaining() > 0) { + int byte_count = send_buffer->BytesRemaining(); + if (byte_count > mtu) + byte_count = mtu; + IOReturn status = + channel_->WriteAsync(send_buffer->data(), byte_count, request.get()); + + if (status != kIOReturnSuccess) { + std::stringstream error; + error << "Failed to connect bluetooth socket (" + << channel_->GetDeviceAddress() << "): (" << status << ")"; + // Remember the first error only + if (request->status == kIOReturnSuccess) + request->status = status; + request->error_signaled = true; + request->error_callback.Run(error.str()); + // We may have failed to issue any write operation. In that case, there + // will be no corresponding completion callback for this particular + // request, so we must forget about it now. + if (request->active_async_writes == 0) { + send_queue_.pop(); + } + return; + } + + request->active_async_writes++; + send_buffer->DidConsume(byte_count); + } +} + +void BluetoothSocketMac::OnChannelWriteComplete(void* refcon, IOReturn status) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Note: We use "CHECK" below to ensure we never run into unforeseen + // occurrences of asynchronous callbacks, which could lead to data + // corruption. + CHECK_EQ(static_cast<SendRequest*>(refcon), send_queue_.front().get()); + + // Keep a local linked_ptr to avoid releasing the request too early if we end + // up removing it from the queue. + linked_ptr<SendRequest> request = send_queue_.front(); + + // Remember the first error only if (status != kIOReturnSuccess) { - error_message_ = base::StringPrintf( - "Failed to send data. IOReturn code: %u", status); - return false; + if (request->status == kIOReturnSuccess) + request->status = status; } - buffer->DidConsume(bytes_written); - return true; + // Figure out if we are done with this async request + request->active_async_writes--; + if (request->active_async_writes > 0) + return; + + // If this was the last active async write for this request, remove it from + // the queue and call the appropriate callback associated to the request. + send_queue_.pop(); + if (request->status != kIOReturnSuccess) { + if (!request->error_signaled) { + std::stringstream error; + error << "Failed to connect bluetooth socket (" + << channel_->GetDeviceAddress() << "): (" << status << ")"; + request->error_signaled = true; + request->error_callback.Run(error.str()); + } + } else { + request->success_callback.Run(request->buffer_size); + } } -std::string BluetoothSocketMac::GetLastErrorMessage() const { - return error_message_; +void BluetoothSocketMac::OnChannelClosed() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (receive_callbacks_) { + scoped_ptr<ReceiveCallbacks> temp = receive_callbacks_.Pass(); + temp->error_callback.Run(BluetoothSocket::kDisconnected, + kSocketNotConnected); + } + + ReleaseChannel(); } -void BluetoothSocketMac::OnDataReceived( - IOBluetoothRFCOMMChannel* rfcomm_channel, void* data, size_t length) { - DCHECK(rfcomm_channel_ == rfcomm_channel); - CHECK_LT(length, static_cast<size_t>(std::numeric_limits<int>::max())); - int data_size = static_cast<int>(length); - if (incoming_data_buffer_->RemainingCapacity() < data_size) { - int additional_capacity = - std::max(data_size, incoming_data_buffer_->capacity()); - CHECK_LT( - additional_capacity, - std::numeric_limits<int>::max() - incoming_data_buffer_->capacity()); - incoming_data_buffer_->SetCapacity( - incoming_data_buffer_->capacity() + additional_capacity); +void BluetoothSocketMac::Accept( + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Allow only one pending accept at a time. + if (accept_request_) { + error_callback.Run(net::ErrorToString(net::ERR_IO_PENDING)); + return; } - memcpy(incoming_data_buffer_->data(), data, data_size); - incoming_data_buffer_->set_offset( - incoming_data_buffer_->offset() + data_size); + + accept_request_.reset(new AcceptRequest); + accept_request_->success_callback = success_callback; + accept_request_->error_callback = error_callback; + + if (accept_queue_.size() >= 1) + AcceptConnectionRequest(); } -void BluetoothSocketMac::ResetIncomingDataBuffer() { - incoming_data_buffer_ = new net::GrowableIOBuffer(); - incoming_data_buffer_->SetCapacity(1024); +void BluetoothSocketMac::AcceptConnectionRequest() { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << uuid_.canonical_value() << ": Accepting pending connection."; + + linked_ptr<BluetoothChannelMac> channel = accept_queue_.front(); + accept_queue_.pop(); + + adapter_->DeviceConnected(channel->GetDevice()); + BluetoothDevice* device = adapter_->GetDevice(channel->GetDeviceAddress()); + DCHECK(device); + + scoped_refptr<BluetoothSocketMac> client_socket = + BluetoothSocketMac::CreateSocket(); + + client_socket->uuid_ = uuid_; + client_socket->channel_.reset(channel.release()); + + // Associating the socket can synchronously call into OnChannelOpenComplete(). + // Make sure to first set the new socket to be connecting and hook it up to + // run the accept callback with the device object. + client_socket->connect_callbacks_.reset(new ConnectCallbacks()); + client_socket->connect_callbacks_->success_callback = + base::Bind(accept_request_->success_callback, device, client_socket); + client_socket->connect_callbacks_->error_callback = + accept_request_->error_callback; + accept_request_.reset(); + + // Now it's safe to associate the socket with the channel. + client_socket->channel_->SetSocket(client_socket.get()); + + DVLOG(1) << uuid_.canonical_value() << ": Accept complete."; +} + +BluetoothSocketMac::AcceptRequest::AcceptRequest() {} + +BluetoothSocketMac::AcceptRequest::~AcceptRequest() {} + +BluetoothSocketMac::SendRequest::SendRequest() + : status(kIOReturnSuccess), active_async_writes(0), error_signaled(false) {} + +BluetoothSocketMac::SendRequest::~SendRequest() {} + +BluetoothSocketMac::ReceiveCallbacks::ReceiveCallbacks() {} + +BluetoothSocketMac::ReceiveCallbacks::~ReceiveCallbacks() {} + +BluetoothSocketMac::ConnectCallbacks::ConnectCallbacks() {} + +BluetoothSocketMac::ConnectCallbacks::~ConnectCallbacks() {} + +BluetoothSocketMac::BluetoothSocketMac() + : service_record_handle_(kInvalidServiceRecordHandle) { +} + +BluetoothSocketMac::~BluetoothSocketMac() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!channel_); + DCHECK(!rfcomm_connection_listener_); +} + +void BluetoothSocketMac::ReleaseChannel() { + DCHECK(thread_checker_.CalledOnValidThread()); + channel_.reset(); + + // Closing the channel above prevents the callback delegate from being called + // so it is now safe to release all callback state. + connect_callbacks_.reset(); + receive_callbacks_.reset(); + empty_queue(receive_queue_); + empty_queue(send_queue_); +} + +void BluetoothSocketMac::ReleaseListener() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_NE(service_record_handle_, kInvalidServiceRecordHandle); + + IOBluetoothRemoveServiceWithRecordHandle(service_record_handle_); + rfcomm_connection_listener_.reset(); + l2cap_connection_listener_.reset(); + + // Destroying the listener above prevents the callback delegate from being + // called so it is now safe to release all callback state. + accept_request_.reset(); + empty_queue(accept_queue_); } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket_net.cc b/chromium/device/bluetooth/bluetooth_socket_net.cc new file mode 100644 index 00000000000..8007d77dca3 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_socket_net.cc @@ -0,0 +1,312 @@ +// 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 "device/bluetooth/bluetooth_socket_net.h" + +#include <queue> +#include <string> + +#include "base/logging.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_restrictions.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace { + +const char kSocketNotConnected[] = "Socket is not connected."; + +static void DeactivateSocket( + const scoped_refptr<device::BluetoothSocketThread>& socket_thread) { + socket_thread->OnSocketDeactivate(); +} + +} // namespace + +namespace device { + +BluetoothSocketNet::WriteRequest::WriteRequest() + : buffer_size(0) {} + +BluetoothSocketNet::WriteRequest::~WriteRequest() {} + +BluetoothSocketNet::BluetoothSocketNet( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source) + : ui_task_runner_(ui_task_runner), + socket_thread_(socket_thread), + net_log_(net_log), + source_(source) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + socket_thread_->OnSocketActivate(); +} + +BluetoothSocketNet::~BluetoothSocketNet() { + DCHECK(tcp_socket_.get() == NULL); + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(&DeactivateSocket, socket_thread_)); +} + +void BluetoothSocketNet::Close() { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + socket_thread_->task_runner()->PostTask( + FROM_HERE, base::Bind(&BluetoothSocketNet::DoClose, this)); +} + +void BluetoothSocketNet::Disconnect( + const base::Closure& success_callback) { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + socket_thread_->task_runner()->PostTask( + FROM_HERE, + base::Bind( + &BluetoothSocketNet::DoDisconnect, + this, + base::Bind(&BluetoothSocketNet::PostSuccess, + this, + success_callback))); +} + +void BluetoothSocketNet::Receive( + int buffer_size, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + socket_thread_->task_runner()->PostTask( + FROM_HERE, + base::Bind( + &BluetoothSocketNet::DoReceive, + this, + buffer_size, + base::Bind(&BluetoothSocketNet::PostReceiveCompletion, + this, + success_callback), + base::Bind(&BluetoothSocketNet::PostReceiveErrorCompletion, + this, + error_callback))); +} + +void BluetoothSocketNet::Send( + scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + socket_thread_->task_runner()->PostTask( + FROM_HERE, + base::Bind( + &BluetoothSocketNet::DoSend, + this, + buffer, + buffer_size, + base::Bind(&BluetoothSocketNet::PostSendCompletion, + this, + success_callback), + base::Bind(&BluetoothSocketNet::PostErrorCompletion, + this, + error_callback))); +} + +void BluetoothSocketNet::ResetData() { +} + +void BluetoothSocketNet::ResetTCPSocket() { + tcp_socket_.reset(new net::TCPSocket(net_log_, source_)); +} + +void BluetoothSocketNet::SetTCPSocket(scoped_ptr<net::TCPSocket> tcp_socket) { + tcp_socket_ = tcp_socket.Pass(); +} + +void BluetoothSocketNet::PostSuccess(const base::Closure& callback) { + ui_task_runner_->PostTask(FROM_HERE, callback); +} + +void BluetoothSocketNet::PostErrorCompletion( + const ErrorCompletionCallback& callback, + const std::string& error) { + ui_task_runner_->PostTask(FROM_HERE, base::Bind(callback, error)); +} + +void BluetoothSocketNet::DoClose() { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + if (tcp_socket_) { + tcp_socket_->Close(); + tcp_socket_.reset(NULL); + } + + // Note: Closing |tcp_socket_| above released all potential pending + // Send/Receive operations, so we can no safely release the state associated + // to those pending operations. + read_buffer_ = NULL; + std::queue<linked_ptr<WriteRequest> > empty; + std::swap(write_queue_, empty); + + ResetData(); +} + +void BluetoothSocketNet::DoDisconnect(const base::Closure& callback) { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + DoClose(); + callback.Run(); +} + +void BluetoothSocketNet::DoReceive( + int buffer_size, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback) { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + if (!tcp_socket_) { + error_callback.Run(BluetoothSocket::kDisconnected, kSocketNotConnected); + return; + } + + // Only one pending read at a time + if (read_buffer_.get()) { + error_callback.Run(BluetoothSocket::kIOPending, + net::ErrorToString(net::ERR_IO_PENDING)); + return; + } + + scoped_refptr<net::IOBufferWithSize> buffer( + new net::IOBufferWithSize(buffer_size)); + int read_result = + tcp_socket_->Read(buffer.get(), + buffer->size(), + base::Bind(&BluetoothSocketNet::OnSocketReadComplete, + this, + success_callback, + error_callback)); + + read_buffer_ = buffer; + if (read_result != net::ERR_IO_PENDING) + OnSocketReadComplete(success_callback, error_callback, read_result); +} + +void BluetoothSocketNet::OnSocketReadComplete( + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback, + int read_result) { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + scoped_refptr<net::IOBufferWithSize> buffer; + buffer.swap(read_buffer_); + if (read_result > 0) { + success_callback.Run(read_result, buffer); + } else if (read_result == net::OK || + read_result == net::ERR_CONNECTION_CLOSED || + read_result == net::ERR_CONNECTION_RESET) { + error_callback.Run(BluetoothSocket::kDisconnected, + net::ErrorToString(read_result)); + } else { + error_callback.Run(BluetoothSocket::kSystemError, + net::ErrorToString(read_result)); + } +} + +void BluetoothSocketNet::DoSend( + scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + if (!tcp_socket_) { + error_callback.Run(kSocketNotConnected); + return; + } + + linked_ptr<WriteRequest> request(new WriteRequest()); + request->buffer = buffer; + request->buffer_size = buffer_size; + request->success_callback = success_callback; + request->error_callback = error_callback; + + write_queue_.push(request); + if (write_queue_.size() == 1) { + SendFrontWriteRequest(); + } +} + +void BluetoothSocketNet::SendFrontWriteRequest() { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + if (!tcp_socket_) + return; + + if (write_queue_.size() == 0) + return; + + linked_ptr<WriteRequest> request = write_queue_.front(); + net::CompletionCallback callback = + base::Bind(&BluetoothSocketNet::OnSocketWriteComplete, + this, + request->success_callback, + request->error_callback); + int send_result = + tcp_socket_->Write(request->buffer, request->buffer_size, callback); + if (send_result != net::ERR_IO_PENDING) { + callback.Run(send_result); + } +} + +void BluetoothSocketNet::OnSocketWriteComplete( + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback, + int send_result) { + DCHECK(socket_thread_->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + write_queue_.pop(); + + if (send_result >= net::OK) { + success_callback.Run(send_result); + } else { + error_callback.Run(net::ErrorToString(send_result)); + } + + // Don't call directly to avoid potentail large recursion. + socket_thread_->task_runner()->PostNonNestableTask( + FROM_HERE, + base::Bind(&BluetoothSocketNet::SendFrontWriteRequest, this)); +} + +void BluetoothSocketNet::PostReceiveCompletion( + const ReceiveCompletionCallback& callback, + int io_buffer_size, + scoped_refptr<net::IOBuffer> io_buffer) { + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, io_buffer_size, io_buffer)); +} + +void BluetoothSocketNet::PostReceiveErrorCompletion( + const ReceiveErrorCompletionCallback& callback, + ErrorReason reason, + const std::string& error_message) { + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, reason, error_message)); +} + +void BluetoothSocketNet::PostSendCompletion( + const SendCompletionCallback& callback, + int bytes_written) { + ui_task_runner_->PostTask(FROM_HERE, base::Bind(callback, bytes_written)); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket_net.h b/chromium/device/bluetooth/bluetooth_socket_net.h new file mode 100644 index 00000000000..b16e2359cf1 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_socket_net.h @@ -0,0 +1,131 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_NET_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_NET_H_ + +#include <queue> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_socket_thread.h" +#include "net/base/net_log.h" +#include "net/socket/tcp_socket.h" + +namespace net { +class IOBuffer; +class IOBufferWithSize; +} // namespace net + +namespace device { + +// This class is a base-class for implementations of BluetoothSocket that can +// use net::TCPSocket. All public methods (including the factory method) must +// be called on the UI thread, while underlying socket operations are +// performed on a separate thread. +class BluetoothSocketNet : public BluetoothSocket { + public: + // BluetoothSocket: + virtual void Close() OVERRIDE; + virtual void Disconnect(const base::Closure& callback) OVERRIDE; + virtual void Receive(int buffer_size, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback) + OVERRIDE; + virtual void Send(scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) OVERRIDE; + + protected: + BluetoothSocketNet(scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source); + virtual ~BluetoothSocketNet(); + + // Resets locally held data after a socket is closed. Default implementation + // does nothing, subclasses may override. + virtual void ResetData(); + + // Methods for subclasses to obtain the members. + scoped_refptr<base::SequencedTaskRunner> ui_task_runner() const { + return ui_task_runner_; + } + + scoped_refptr<BluetoothSocketThread> socket_thread() const { + return socket_thread_; + } + + net::NetLog* net_log() const { return net_log_; } + const net::NetLog::Source& source() const { return source_; } + + net::TCPSocket* tcp_socket() { return tcp_socket_.get(); } + + void ResetTCPSocket(); + void SetTCPSocket(scoped_ptr<net::TCPSocket> tcp_socket); + + void PostSuccess(const base::Closure& callback); + void PostErrorCompletion(const ErrorCompletionCallback& callback, + const std::string& error); + + private: + struct WriteRequest { + WriteRequest(); + ~WriteRequest(); + + scoped_refptr<net::IOBuffer> buffer; + int buffer_size; + SendCompletionCallback success_callback; + ErrorCompletionCallback error_callback; + }; + + void DoClose(); + void DoDisconnect(const base::Closure& callback); + void DoReceive(int buffer_size, + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback); + void OnSocketReadComplete( + const ReceiveCompletionCallback& success_callback, + const ReceiveErrorCompletionCallback& error_callback, + int read_result); + void DoSend(scoped_refptr<net::IOBuffer> buffer, + int buffer_size, + const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback); + void SendFrontWriteRequest(); + void OnSocketWriteComplete(const SendCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback, + int send_result); + + void PostReceiveCompletion(const ReceiveCompletionCallback& callback, + int io_buffer_size, + scoped_refptr<net::IOBuffer> io_buffer); + void PostReceiveErrorCompletion( + const ReceiveErrorCompletionCallback& callback, + ErrorReason reason, + const std::string& error_message); + void PostSendCompletion(const SendCompletionCallback& callback, + int bytes_written); + + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + scoped_refptr<BluetoothSocketThread> socket_thread_; + net::NetLog* net_log_; + const net::NetLog::Source source_; + + scoped_ptr<net::TCPSocket> tcp_socket_; + scoped_refptr<net::IOBufferWithSize> read_buffer_; + std::queue<linked_ptr<WriteRequest> > write_queue_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothSocketNet); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_NET_H_ diff --git a/chromium/device/bluetooth/bluetooth_socket_thread.cc b/chromium/device/bluetooth/bluetooth_socket_thread.cc new file mode 100644 index 00000000000..1f3b97bf98b --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_socket_thread.cc @@ -0,0 +1,78 @@ +// 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 "device/bluetooth/bluetooth_socket_thread.h" + +#include "base/lazy_instance.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread.h" + +namespace device { + +base::LazyInstance<scoped_refptr<BluetoothSocketThread> > g_instance = + LAZY_INSTANCE_INITIALIZER; + +// static +scoped_refptr<BluetoothSocketThread> BluetoothSocketThread::Get() { + if (!g_instance.Get().get()) { + g_instance.Get() = new BluetoothSocketThread(); + } + return g_instance.Get(); +} + +// static +void BluetoothSocketThread::CleanupForTesting() { + DCHECK(g_instance.Get()); + g_instance.Get() = NULL; +} + +BluetoothSocketThread::BluetoothSocketThread() + : active_socket_count_(0) {} + +BluetoothSocketThread::~BluetoothSocketThread() { + if (thread_) { + thread_->Stop(); + thread_.reset(NULL); + task_runner_ = NULL; + } +} + +void BluetoothSocketThread::OnSocketActivate() { + DCHECK(thread_checker_.CalledOnValidThread()); + active_socket_count_++; + EnsureStarted(); +} + +void BluetoothSocketThread::OnSocketDeactivate() { + DCHECK(thread_checker_.CalledOnValidThread()); + active_socket_count_--; + if (active_socket_count_ == 0 && thread_) { + thread_->Stop(); + thread_.reset(NULL); + task_runner_ = NULL; + } +} + +void BluetoothSocketThread::EnsureStarted() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (thread_) + return; + + base::Thread::Options thread_options; + thread_options.message_loop_type = base::MessageLoop::TYPE_IO; + thread_.reset(new base::Thread("BluetoothSocketThread")); + thread_->StartWithOptions(thread_options); + task_runner_ = thread_->message_loop_proxy(); +} + +scoped_refptr<base::SequencedTaskRunner> BluetoothSocketThread::task_runner() + const { + DCHECK(active_socket_count_ > 0); + DCHECK(thread_); + DCHECK(task_runner_); + + return task_runner_; +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket_thread.h b/chromium/device/bluetooth/bluetooth_socket_thread.h new file mode 100644 index 00000000000..02de8204351 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_socket_thread.h @@ -0,0 +1,50 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_THREAD_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_THREAD_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" + +namespace base { +class SequencedTaskRunner; +class Thread; +} // namespace base + +namespace device { + +// Thread abstraction used by |BluetoothSocketChromeOS| and |BluetoothSocketWin| +// to perform IO operations on the underlying platform sockets. An instance of +// this class can be shared by many active sockets. +class BluetoothSocketThread + : public base::RefCountedThreadSafe<BluetoothSocketThread> { + public: + static scoped_refptr<BluetoothSocketThread> Get(); + static void CleanupForTesting(); + + void OnSocketActivate(); + void OnSocketDeactivate(); + + scoped_refptr<base::SequencedTaskRunner> task_runner() const; + + private: + friend class base::RefCountedThreadSafe<BluetoothSocketThread>; + BluetoothSocketThread(); + virtual ~BluetoothSocketThread(); + + void EnsureStarted(); + + base::ThreadChecker thread_checker_; + int active_socket_count_; + scoped_ptr<base::Thread> thread_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothSocketThread); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_SOCKET_THREAD_H_ diff --git a/chromium/device/bluetooth/bluetooth_socket_win.cc b/chromium/device/bluetooth/bluetooth_socket_win.cc index a684d14cffd..a46f4b5ec40 100644 --- a/chromium/device/bluetooth/bluetooth_socket_win.cc +++ b/chromium/device/bluetooth/bluetooth_socket_win.cc @@ -4,112 +4,396 @@ #include "device/bluetooth/bluetooth_socket_win.h" +#include <objbase.h> + #include <string> #include "base/logging.h" #include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "device/bluetooth/bluetooth_device_win.h" #include "device/bluetooth/bluetooth_init_win.h" #include "device/bluetooth/bluetooth_service_record_win.h" +#include "device/bluetooth/bluetooth_socket_thread.h" #include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" #include "net/base/winsock_init.h" namespace { -std::string FormatErrorMessage(DWORD error_code) { - TCHAR error_msg[1024]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, - 0, - error_code, - 0, - error_msg, - 1024, - NULL); - return base::SysWideToUTF8(error_msg); +const char kL2CAPNotSupported[] = "Bluetooth L2CAP protocal is not supported"; +const char kSocketAlreadyConnected[] = "Socket is already connected."; +const char kInvalidRfcommPort[] = "Invalid RFCCOMM port."; +const char kFailedToCreateSocket[] = "Failed to create socket."; +const char kFailedToBindSocket[] = "Failed to bind socket."; +const char kFailedToListenOnSocket[] = "Failed to listen on socket."; +const char kFailedToGetSockNameForSocket[] = "Failed to getsockname."; +const char kFailedToAccept[] = "Failed to accept."; +const char kInvalidUUID[] = "Invalid UUID"; +const char kWsaSetServiceError[] = "WSASetService error."; + +std::string IPEndPointToBluetoothAddress(const net::IPEndPoint& end_point) { + if (end_point.address().size() != net::kBluetoothAddressSize) + return std::string(); + // The address is copied from BTH_ADDR field of SOCKADDR_BTH, which is a + // 64-bit ULONGLONG that stores Bluetooth address in little-endian. Print in + // reverse order to preserve the correct ordering. + return base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X", + end_point.address()[5], + end_point.address()[4], + end_point.address()[3], + end_point.address()[2], + end_point.address()[1], + end_point.address()[0]); } } // namespace namespace device { -BluetoothSocketWin::BluetoothSocketWin(SOCKET fd) : fd_(fd) { +struct BluetoothSocketWin::ServiceRegData { + ServiceRegData() { + ZeroMemory(&address, sizeof(address)); + ZeroMemory(&address_info, sizeof(address_info)); + ZeroMemory(&uuid, sizeof(uuid)); + ZeroMemory(&service, sizeof(service)); + } + + SOCKADDR_BTH address; + CSADDR_INFO address_info; + GUID uuid; + base::string16 name; + WSAQUERYSET service; +}; + +// static +scoped_refptr<BluetoothSocketWin> +BluetoothSocketWin::CreateBluetoothSocket( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<device::BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + + return make_scoped_refptr( + new BluetoothSocketWin(ui_task_runner, socket_thread, net_log, source)); +} + +BluetoothSocketWin::BluetoothSocketWin( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source) + : BluetoothSocketNet(ui_task_runner, socket_thread, net_log, source), + supports_rfcomm_(false), + rfcomm_channel_(-1), + bth_addr_(BTH_ADDR_NULL) { } BluetoothSocketWin::~BluetoothSocketWin() { - closesocket(fd_); } -// static -scoped_refptr<BluetoothSocket> BluetoothSocketWin::CreateBluetoothSocket( - const BluetoothServiceRecord& service_record) { - BluetoothSocketWin* bluetooth_socket = NULL; - if (service_record.SupportsRfcomm()) { - net::EnsureWinsockInit(); - SOCKET socket_fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); - SOCKADDR_BTH sa; - ZeroMemory(&sa, sizeof(sa)); - sa.addressFamily = AF_BTH; - sa.port = service_record.rfcomm_channel(); - const BluetoothServiceRecordWin* service_record_win = - static_cast<const BluetoothServiceRecordWin*>(&service_record); - sa.btAddr = service_record_win->bth_addr(); - - int status = connect(socket_fd, - reinterpret_cast<SOCKADDR *>(&sa), - sizeof(sa)); - DWORD error_code = WSAGetLastError(); - if (status == 0 || error_code == WSAEINPROGRESS) { - bluetooth_socket = - new BluetoothSocketWin(socket_fd); - } else { - LOG(ERROR) << "Failed to connect bluetooth socket " - << "(" << service_record.address() << "): " - << "(" << error_code << ")" << FormatErrorMessage(error_code); - closesocket(socket_fd); +void BluetoothSocketWin::Connect( + const BluetoothDeviceWin* device, + const BluetoothUUID& uuid, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(device); + + if (!uuid.IsValid()) { + error_callback.Run(kInvalidUUID); + return; + } + + const BluetoothServiceRecordWin* service_record_win = + device->GetServiceRecord(uuid); + if (!service_record_win) { + error_callback.Run(kInvalidUUID); + return; + } + + device_address_ = service_record_win->address(); + if (service_record_win->SupportsRfcomm()) { + supports_rfcomm_ = true; + rfcomm_channel_ = service_record_win->rfcomm_channel(); + bth_addr_ = service_record_win->bth_addr(); + } + + socket_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind( + &BluetoothSocketWin::DoConnect, + this, + base::Bind(&BluetoothSocketWin::PostSuccess, this, success_callback), + base::Bind( + &BluetoothSocketWin::PostErrorCompletion, this, error_callback))); +} + +void BluetoothSocketWin::Listen(scoped_refptr<BluetoothAdapter> adapter, + const BluetoothUUID& uuid, + int rfcomm_channel, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + adapter_ = adapter; + + socket_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&BluetoothSocketWin::DoListen, + this, + uuid, + rfcomm_channel, + success_callback, + error_callback)); +} + +void BluetoothSocketWin::ResetData() { + if (service_reg_data_) { + if (WSASetService(&service_reg_data_->service,RNRSERVICE_DELETE, 0) == + SOCKET_ERROR) { + LOG(WARNING) << "Failed to unregister service."; } + service_reg_data_.reset(); } - // TODO(youngki) add support for L2CAP sockets as well. +} - return scoped_refptr<BluetoothSocketWin>(bluetooth_socket); +void BluetoothSocketWin::Accept( + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + socket_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&BluetoothSocketWin::DoAccept, + this, + success_callback, + error_callback)); } -bool BluetoothSocketWin::Receive(net::GrowableIOBuffer* buffer) { - buffer->SetCapacity(1024); - int bytes_read; - do { - if (buffer->RemainingCapacity() == 0) - buffer->SetCapacity(buffer->capacity() * 2); - bytes_read = recv(fd_, buffer->data(), buffer->RemainingCapacity(), 0); - if (bytes_read > 0) - buffer->set_offset(buffer->offset() + bytes_read); - } while (bytes_read > 0); +void BluetoothSocketWin::DoConnect( + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(socket_thread()->task_runner()->RunsTasksOnCurrentThread()); + base::ThreadRestrictions::AssertIOAllowed(); + + if (tcp_socket()) { + error_callback.Run(kSocketAlreadyConnected); + return; + } + if (!supports_rfcomm_) { + // TODO(youngki) add support for L2CAP sockets as well. + error_callback.Run(kL2CAPNotSupported); + return; + } + + ResetTCPSocket(); + net::EnsureWinsockInit(); + SOCKET socket_fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); + SOCKADDR_BTH sa; + ZeroMemory(&sa, sizeof(sa)); + sa.addressFamily = AF_BTH; + sa.port = rfcomm_channel_; + sa.btAddr = bth_addr_; + + // TODO(rpaquay): Condider making this call non-blocking. + int status = connect(socket_fd, reinterpret_cast<SOCKADDR*>(&sa), sizeof(sa)); DWORD error_code = WSAGetLastError(); - if (bytes_read < 0 && error_code != WSAEWOULDBLOCK) { - error_message_ = FormatErrorMessage(error_code); - return false; + if (!(status == 0 || error_code == WSAEINPROGRESS)) { + LOG(ERROR) << "Failed to connect bluetooth socket " + << "(" << device_address_ << "): " + << logging::SystemErrorCodeToString(error_code); + error_callback.Run("Error connecting to socket: " + + logging::SystemErrorCodeToString(error_code)); + closesocket(socket_fd); + return; + } + + // Note: We don't have a meaningful |IPEndPoint|, but that is ok since the + // TCPSocket implementation does not actually require one. + int net_result = + tcp_socket()->AdoptConnectedSocket(socket_fd, net::IPEndPoint()); + if (net_result != net::OK) { + error_callback.Run("Error connecting to socket: " + + std::string(net::ErrorToString(net_result))); + closesocket(socket_fd); + return; } - return true; + + success_callback.Run(); } -bool BluetoothSocketWin::Send(net::DrainableIOBuffer* buffer) { - int bytes_written; - do { - bytes_written = send(fd_, buffer->data(), buffer->BytesRemaining(), 0); - if (bytes_written > 0) - buffer->DidConsume(bytes_written); - } while (buffer->BytesRemaining() > 0 && bytes_written > 0); +void BluetoothSocketWin::DoListen( + const BluetoothUUID& uuid, + int rfcomm_channel, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(socket_thread()->task_runner()->RunsTasksOnCurrentThread()); + DCHECK(!tcp_socket() && !service_reg_data_); - DWORD error_code = WSAGetLastError(); - if (bytes_written < 0 && error_code != WSAEWOULDBLOCK) { - error_message_ = FormatErrorMessage(error_code); - return false; + // The valid range is 0-30. 0 means BT_PORT_ANY and 1-30 are the + // valid RFCOMM port numbers of SOCKADDR_BTH. + if (rfcomm_channel < 0 || rfcomm_channel > 30) { + LOG(WARNING) << "Failed to start service: " + << "Invalid RFCCOMM port " << rfcomm_channel + << ", uuid=" << uuid.value(); + PostErrorCompletion(error_callback, kInvalidRfcommPort); + return; + } + + SOCKET socket_fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); + if (socket_fd == INVALID_SOCKET) { + LOG(WARNING) << "Failed to start service: create socket, " + << "winsock err=" << WSAGetLastError(); + PostErrorCompletion(error_callback, kFailedToCreateSocket); + return; + } + + // Note that |socket_fd| belongs to a non-TCP address family (i.e. AF_BTH), + // TCPSocket methods that involve address could not be called. So bind() + // is called on |socket_fd| directly. + scoped_ptr<net::TCPSocket> scoped_socket( + new net::TCPSocket(NULL, net::NetLog::Source())); + scoped_socket->AdoptListenSocket(socket_fd); + + SOCKADDR_BTH sa; + struct sockaddr* sock_addr = reinterpret_cast<struct sockaddr*>(&sa); + int sock_addr_len = sizeof(sa); + ZeroMemory(&sa, sock_addr_len); + sa.addressFamily = AF_BTH; + sa.port = rfcomm_channel ? rfcomm_channel : BT_PORT_ANY; + if (bind(socket_fd, sock_addr, sock_addr_len) < 0) { + LOG(WARNING) << "Failed to start service: create socket, " + << "winsock err=" << WSAGetLastError(); + PostErrorCompletion(error_callback, kFailedToBindSocket); + return; + } + + const int kListenBacklog = 5; + if (scoped_socket->Listen(kListenBacklog) < 0) { + LOG(WARNING) << "Failed to start service: Listen" + << "winsock err=" << WSAGetLastError(); + PostErrorCompletion(error_callback, kFailedToListenOnSocket); + return; + } + + scoped_ptr<ServiceRegData> reg_data(new ServiceRegData); + reg_data->name = base::UTF8ToUTF16(uuid.canonical_value()); + + if (getsockname(socket_fd, sock_addr, &sock_addr_len)) { + LOG(WARNING) << "Failed to start service: getsockname, " + << "winsock err=" << WSAGetLastError(); + PostErrorCompletion(error_callback, kFailedToGetSockNameForSocket); + return; + } + reg_data->address = sa; + + reg_data->address_info.LocalAddr.iSockaddrLength = sizeof(sa); + reg_data->address_info.LocalAddr.lpSockaddr = + reinterpret_cast<struct sockaddr*>(®_data->address); + reg_data->address_info.iSocketType = SOCK_STREAM; + reg_data->address_info.iProtocol = BTHPROTO_RFCOMM; + + base::string16 cannonical_uuid = L"{" + base::ASCIIToUTF16( + uuid.canonical_value()) + L"}"; + if (!SUCCEEDED(CLSIDFromString(cannonical_uuid.c_str(), ®_data->uuid))) { + LOG(WARNING) << "Failed to start service: " + << ", invalid uuid=" << cannonical_uuid; + PostErrorCompletion(error_callback, kInvalidUUID); + return; + } + + reg_data->service.dwSize = sizeof(WSAQUERYSET); + reg_data->service.lpszServiceInstanceName = + const_cast<LPWSTR>(reg_data->name.c_str()); + reg_data->service.lpServiceClassId = ®_data->uuid; + reg_data->service.dwNameSpace = NS_BTH; + reg_data->service.dwNumberOfCsAddrs = 1; + reg_data->service.lpcsaBuffer = ®_data->address_info; + + if (WSASetService(®_data->service, + RNRSERVICE_REGISTER, 0) == SOCKET_ERROR) { + LOG(WARNING) << "Failed to register profile: WSASetService" + << "winsock err=" << WSAGetLastError(); + PostErrorCompletion(error_callback, kWsaSetServiceError); + return; + } + + SetTCPSocket(scoped_socket.Pass()); + service_reg_data_ = reg_data.Pass(); + + PostSuccess(success_callback); +} + +void BluetoothSocketWin::DoAccept( + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(socket_thread()->task_runner()->RunsTasksOnCurrentThread()); + int result = tcp_socket()->Accept( + &accept_socket_, + &accept_address_, + base::Bind(&BluetoothSocketWin::OnAcceptOnSocketThread, + this, + success_callback, + error_callback)); + if (result != net::OK && result != net::ERR_IO_PENDING) { + LOG(WARNING) << "Failed to accept, net err=" << result; + PostErrorCompletion(error_callback, kFailedToAccept); } - return true; } -std::string BluetoothSocketWin::GetLastErrorMessage() const { - return error_message_; +void BluetoothSocketWin::OnAcceptOnSocketThread( + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback, + int accept_result) { + DCHECK(socket_thread()->task_runner()->RunsTasksOnCurrentThread()); + if (accept_result != net::OK) { + LOG(WARNING) << "OnAccept error, net err=" << accept_result; + PostErrorCompletion(error_callback, kFailedToAccept); + return; + } + + ui_task_runner()->PostTask( + FROM_HERE, + base::Bind(&BluetoothSocketWin::OnAcceptOnUI, + this, + base::Passed(&accept_socket_), + accept_address_, + success_callback, + error_callback)); +} + +void BluetoothSocketWin::OnAcceptOnUI( + scoped_ptr<net::TCPSocket> accept_socket, + const net::IPEndPoint& peer_address, + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) { + DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); + + const std::string peer_device_address = + IPEndPointToBluetoothAddress(peer_address); + const BluetoothDevice* peer_device = adapter_->GetDevice(peer_device_address); + if (!peer_device) { + LOG(WARNING) << "OnAccept failed with unknown device, addr=" + << peer_device_address; + error_callback.Run(kFailedToAccept); + return; + } + + scoped_refptr<BluetoothSocketWin> peer_socket = CreateBluetoothSocket( + ui_task_runner(), + socket_thread(), + net_log(), + source()); + peer_socket->SetTCPSocket(accept_socket.Pass()); + success_callback.Run(peer_device, peer_socket); } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_socket_win.h b/chromium/device/bluetooth/bluetooth_socket_win.h index 41713e71309..22ec412a1f4 100644 --- a/chromium/device/bluetooth/bluetooth_socket_win.h +++ b/chromium/device/bluetooth/bluetooth_socket_win.h @@ -10,39 +10,91 @@ #include <string> #include "base/memory/ref_counted.h" +#include "device/bluetooth/bluetooth_service_record_win.h" #include "device/bluetooth/bluetooth_socket.h" - -namespace net { - -class DrainableIOBuffer; -class GrowableIOBuffer; - -} // namespace net +#include "device/bluetooth/bluetooth_socket_net.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "net/base/ip_endpoint.h" +#include "net/socket/tcp_socket.h" namespace device { -class BluetoothServiceRecord; +class BluetoothAdapter; +class BluetoothDeviceWin; -// This class is an implementation of BluetoothSocket class for Windows -// platform. -class BluetoothSocketWin : public BluetoothSocket { +// The BluetoothSocketWin class implements BluetoothSocket for the Microsoft +// Windows platform. +class BluetoothSocketWin : public BluetoothSocketNet { public: - static scoped_refptr<BluetoothSocket> CreateBluetoothSocket( - const BluetoothServiceRecord& service_record); - - // BluetoothSocket override - virtual bool Receive(net::GrowableIOBuffer* buffer) OVERRIDE; - virtual bool Send(net::DrainableIOBuffer* buffer) OVERRIDE; - virtual std::string GetLastErrorMessage() const OVERRIDE; + static scoped_refptr<BluetoothSocketWin> CreateBluetoothSocket( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source); + + // Connect to the peer device and calls |success_callback| when the + // connection has been established successfully. If an error occurs, calls + // |error_callback| with a system error message. + void Connect(const BluetoothDeviceWin* device, + const BluetoothUUID& uuid, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // Listens using this socket using an RFCOMM service published as UUID |uuid| + // with Channel |channel|. |success_callback| will be called if the service + // is successfully registered, |error_callback| on failure with a message + // explaining the cause. + void Listen(scoped_refptr<BluetoothAdapter> adapter, + const BluetoothUUID& uuid, + int rfcomm_channel, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + + // BluetoothSocketNet: + void ResetData(); + + // BluetoothSocket: + virtual void Accept(const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback) OVERRIDE; protected: virtual ~BluetoothSocketWin(); private: - explicit BluetoothSocketWin(SOCKET fd); - - const SOCKET fd_; - std::string error_message_; + struct ServiceRegData; + + BluetoothSocketWin(scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + scoped_refptr<BluetoothSocketThread> socket_thread, + net::NetLog* net_log, + const net::NetLog::Source& source); + + + void DoConnect(const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + void DoListen(const BluetoothUUID& uuid, + int rfcomm_channel, + const base::Closure& success_callback, + const ErrorCompletionCallback& error_callback); + void DoAccept(const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback); + void OnAcceptOnSocketThread(const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback, + int accept_result); + void OnAcceptOnUI(scoped_ptr<net::TCPSocket> accept_socket, + const net::IPEndPoint& peer_address, + const AcceptCompletionCallback& success_callback, + const ErrorCompletionCallback& error_callback); + + std::string device_address_; + bool supports_rfcomm_; + uint8 rfcomm_channel_; + BTH_ADDR bth_addr_; + + // Data members below are only used when listening. + scoped_refptr<device::BluetoothAdapter> adapter_; + scoped_ptr<ServiceRegData> service_reg_data_; + scoped_ptr<net::TCPSocket> accept_socket_; + net::IPEndPoint accept_address_; DISALLOW_COPY_AND_ASSIGN(BluetoothSocketWin); }; diff --git a/chromium/device/bluetooth/bluetooth_strings.grd b/chromium/device/bluetooth/bluetooth_strings.grd index 7e10b6282ab..9d3eec4a66c 100644 --- a/chromium/device/bluetooth/bluetooth_strings.grd +++ b/chromium/device/bluetooth/bluetooth_strings.grd @@ -12,34 +12,34 @@ This file contains the strings for bluetooth. </output> <output filename="device_bluetooth_strings_am.pak" type="data_package" lang="am" /> <output filename="device_bluetooth_strings_ar.pak" type="data_package" lang="ar" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_ast.pak" type="data_package" lang="ast" /> </if> <output filename="device_bluetooth_strings_bg.pak" type="data_package" lang="bg" /> <output filename="device_bluetooth_strings_bn.pak" type="data_package" lang="bn" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_bs.pak" type="data_package" lang="bs" /> </if> <output filename="device_bluetooth_strings_ca.pak" type="data_package" lang="ca" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_ca@valencia.pak" type="data_package" lang="ca@valencia" /> </if> <output filename="device_bluetooth_strings_cs.pak" type="data_package" lang="cs" /> <output filename="device_bluetooth_strings_da.pak" type="data_package" lang="da" /> <output filename="device_bluetooth_strings_de.pak" type="data_package" lang="de" /> <output filename="device_bluetooth_strings_el.pak" type="data_package" lang="el" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_en-AU.pak" type="data_package" lang="en-AU" /> </if> <output filename="device_bluetooth_strings_en-GB.pak" type="data_package" lang="en-GB" /> <output filename="device_bluetooth_strings_en-US.pak" type="data_package" lang="en" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_eo.pak" type="data_package" lang="eo" /> </if> <output filename="device_bluetooth_strings_es.pak" type="data_package" lang="es" /> <output filename="device_bluetooth_strings_es-419.pak" type="data_package" lang="es-419" /> <output filename="device_bluetooth_strings_et.pak" type="data_package" lang="et" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_eu.pak" type="data_package" lang="eu" /> </if> <output filename="device_bluetooth_strings_fa.pak" type="data_package" lang="fa" /> @@ -47,7 +47,7 @@ This file contains the strings for bluetooth. <output filename="device_bluetooth_strings_fi.pak" type="data_package" lang="fi" /> <output filename="device_bluetooth_strings_fil.pak" type="data_package" lang="fil" /> <output filename="device_bluetooth_strings_fr.pak" type="data_package" lang="fr" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_gl.pak" type="data_package" lang="gl" /> </if> <output filename="device_bluetooth_strings_gu.pak" type="data_package" lang="gu" /> @@ -55,19 +55,19 @@ This file contains the strings for bluetooth. <output filename="device_bluetooth_strings_hi.pak" type="data_package" lang="hi" /> <output filename="device_bluetooth_strings_hr.pak" type="data_package" lang="hr" /> <output filename="device_bluetooth_strings_hu.pak" type="data_package" lang="hu" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_hy.pak" type="data_package" lang="hy" /> <output filename="device_bluetooth_strings_ia.pak" type="data_package" lang="ia" /> </if> <output filename="device_bluetooth_strings_id.pak" type="data_package" lang="id" /> <output filename="device_bluetooth_strings_it.pak" type="data_package" lang="it" /> <output filename="device_bluetooth_strings_ja.pak" type="data_package" lang="ja" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_ka.pak" type="data_package" lang="ka" /> </if> <output filename="device_bluetooth_strings_kn.pak" type="data_package" lang="kn" /> <output filename="device_bluetooth_strings_ko.pak" type="data_package" lang="ko" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_ku.pak" type="data_package" lang="ku" /> <output filename="device_bluetooth_strings_kw.pak" type="data_package" lang="kw" /> </if> @@ -94,7 +94,7 @@ This file contains the strings for bluetooth. <output filename="device_bluetooth_strings_te.pak" type="data_package" lang="te" /> <output filename="device_bluetooth_strings_th.pak" type="data_package" lang="th" /> <output filename="device_bluetooth_strings_tr.pak" type="data_package" lang="tr" /> - <if expr="pp_ifdef('use_third_party_translations')"> + <if expr="use_third_party_translations"> <output filename="device_bluetooth_strings_ug.pak" type="data_package" lang="ug" /> </if> <output filename="device_bluetooth_strings_uk.pak" type="data_package" lang="uk" /> diff --git a/chromium/device/bluetooth/bluetooth_task_manager_win.cc b/chromium/device/bluetooth/bluetooth_task_manager_win.cc index 3347f0f1753..7d8da089d6e 100644 --- a/chromium/device/bluetooth/bluetooth_task_manager_win.cc +++ b/chromium/device/bluetooth/bluetooth_task_manager_win.cc @@ -30,6 +30,8 @@ const int kMaxNumDeviceAddressChar = 127; const int kServiceDiscoveryResultBufferSize = 5000; const int kMaxDeviceDiscoveryTimeout = 48; +typedef device::BluetoothTaskManagerWin::ServiceRecordState ServiceRecordState; + // Populates bluetooth adapter state using adapter_handle. void GetAdapterState(HANDLE adapter_handle, device::BluetoothTaskManagerWin::AdapterState* state) { @@ -71,6 +73,49 @@ void GetDeviceState(const BLUETOOTH_DEVICE_INFO& device_info, state->authenticated = !!device_info.fAuthenticated; } +void DiscoverDeviceServices( + const std::string& device_address, + const GUID& protocol_uuid, + ScopedVector<ServiceRecordState>* service_record_states) { + // Bluetooth and WSAQUERYSET for Service Inquiry. See http://goo.gl/2v9pyt. + WSAQUERYSET sdp_query; + ZeroMemory(&sdp_query, sizeof(sdp_query)); + sdp_query.dwSize = sizeof(sdp_query); + GUID protocol = protocol_uuid; + sdp_query.lpServiceClassId = &protocol; + sdp_query.dwNameSpace = NS_BTH; + wchar_t device_address_context[kMaxNumDeviceAddressChar]; + std::size_t length = base::SysUTF8ToWide("(" + device_address + ")").copy( + device_address_context, kMaxNumDeviceAddressChar); + device_address_context[length] = NULL; + sdp_query.lpszContext = device_address_context; + HANDLE sdp_handle; + if (ERROR_SUCCESS != + WSALookupServiceBegin(&sdp_query, LUP_RETURN_ALL, &sdp_handle)) { + return; + } + char sdp_buffer[kServiceDiscoveryResultBufferSize]; + LPWSAQUERYSET sdp_result_data = reinterpret_cast<LPWSAQUERYSET>(sdp_buffer); + while (true) { + DWORD sdp_buffer_size = sizeof(sdp_buffer); + if (ERROR_SUCCESS != + WSALookupServiceNext( + sdp_handle, LUP_RETURN_ALL, &sdp_buffer_size, sdp_result_data)) { + break; + } + ServiceRecordState* service_record_state = new ServiceRecordState(); + service_record_state->name = + base::SysWideToUTF8(sdp_result_data->lpszServiceInstanceName); + service_record_state->address = device_address; + for (uint64 i = 0; i < sdp_result_data->lpBlob->cbSize; i++) { + service_record_state->sdp_bytes.push_back( + sdp_result_data->lpBlob->pBlobData[i]); + } + service_record_states->push_back(service_record_state); + } + WSALookupServiceEnd(sdp_handle); +} + } // namespace namespace device { @@ -78,6 +123,28 @@ namespace device { // static const int BluetoothTaskManagerWin::kPollIntervalMs = 500; +BluetoothTaskManagerWin::AdapterState::AdapterState() : powered(false) { +} + +BluetoothTaskManagerWin::AdapterState::~AdapterState() { +} + +BluetoothTaskManagerWin::ServiceRecordState::ServiceRecordState() { +} + +BluetoothTaskManagerWin::ServiceRecordState::~ServiceRecordState() { +} + +BluetoothTaskManagerWin::DeviceState::DeviceState() + : bluetooth_class(0), + visible(false), + connected(false), + authenticated(false) { +} + +BluetoothTaskManagerWin::DeviceState::~DeviceState() { +} + BluetoothTaskManagerWin::BluetoothTaskManagerWin( scoped_refptr<base::SequencedTaskRunner> ui_task_runner) : ui_task_runner_(ui_task_runner), @@ -375,41 +442,9 @@ void BluetoothTaskManagerWin::DiscoverServices( const std::string device_address = (*iter)->address; ScopedVector<ServiceRecordState>* service_record_states = &(*iter)->service_record_states; - WSAQUERYSET sdp_query; - ZeroMemory(&sdp_query, sizeof(sdp_query)); - sdp_query.dwSize = sizeof(sdp_query); - GUID protocol = L2CAP_PROTOCOL_UUID; - sdp_query.lpServiceClassId = &protocol; - sdp_query.dwNameSpace = NS_BTH; - wchar_t device_address_context[kMaxNumDeviceAddressChar]; - std::size_t length = - base::SysUTF8ToWide("(" + device_address + ")").copy( - device_address_context, kMaxNumDeviceAddressChar); - device_address_context[length] = NULL; - sdp_query.lpszContext = device_address_context; - HANDLE sdp_handle; - if (ERROR_SUCCESS != - WSALookupServiceBegin(&sdp_query, LUP_RETURN_ALL, &sdp_handle)) { - return; - } - char sdp_buffer[kServiceDiscoveryResultBufferSize]; - LPWSAQUERYSET sdp_result_data = reinterpret_cast<LPWSAQUERYSET>(sdp_buffer); - DWORD sdp_buffer_size = sizeof(sdp_buffer); - while (ERROR_SUCCESS == WSALookupServiceNext(sdp_handle, - LUP_RETURN_ALL, - &sdp_buffer_size, - sdp_result_data)) { - ServiceRecordState* service_record_state = new ServiceRecordState(); - service_record_state->name = - base::SysWideToUTF8(sdp_result_data->lpszServiceInstanceName); - service_record_state->address = device_address; - for (uint64 i = 0; i < sdp_result_data->lpBlob->cbSize; i++) { - service_record_state->sdp_bytes.push_back( - sdp_result_data->lpBlob->pBlobData[i]); - } - service_record_states->push_back(service_record_state); - } - WSALookupServiceEnd(sdp_handle); + + DiscoverDeviceServices( + device_address, L2CAP_PROTOCOL_UUID, service_record_states); } } diff --git a/chromium/device/bluetooth/bluetooth_task_manager_win.h b/chromium/device/bluetooth/bluetooth_task_manager_win.h index 2c787d66ec4..991e95c34a9 100644 --- a/chromium/device/bluetooth/bluetooth_task_manager_win.h +++ b/chromium/device/bluetooth/bluetooth_task_manager_win.h @@ -35,18 +35,24 @@ class BluetoothTaskManagerWin : public base::RefCountedThreadSafe<BluetoothTaskManagerWin> { public: struct AdapterState { + AdapterState(); + ~AdapterState(); std::string name; std::string address; bool powered; }; struct ServiceRecordState { + ServiceRecordState(); + ~ServiceRecordState(); std::string name; std::string address; std::vector<uint8> sdp_bytes; }; struct DeviceState { + DeviceState(); + ~DeviceState(); std::string name; std::string address; uint32 bluetooth_class; diff --git a/chromium/device/bluetooth/bluetooth_utils.cc b/chromium/device/bluetooth/bluetooth_utils.cc deleted file mode 100644 index b1f9f7816eb..00000000000 --- a/chromium/device/bluetooth/bluetooth_utils.cc +++ /dev/null @@ -1,59 +0,0 @@ -// 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 "device/bluetooth/bluetooth_utils.h" - -#include <vector> - -#include "base/basictypes.h" -#include "base/logging.h" -#include "base/strings/string_util.h" - -namespace { -static const char* kCommonUuidPostfix = "-0000-1000-8000-00805f9b34fb"; -static const char* kCommonUuidPrefix = "0000"; -static const int kUuidSize = 36; -} // namespace - -namespace device { -namespace bluetooth_utils { - -std::string CanonicalUuid(std::string uuid) { - if (uuid.empty()) - return std::string(); - - if (uuid.size() < 11 && uuid.find("0x") == 0) - uuid = uuid.substr(2); - - if (!(uuid.size() == 4 || uuid.size() == 8 || uuid.size() == 36)) - return std::string(); - - if (uuid.size() == 4 || uuid.size() == 8) { - for (size_t i = 0; i < uuid.size(); ++i) { - if (!IsHexDigit(uuid[i])) - return std::string(); - } - - if (uuid.size() == 4) - return kCommonUuidPrefix + uuid + kCommonUuidPostfix; - - return uuid + kCommonUuidPostfix; - } - - std::string uuid_result(uuid); - for (int i = 0; i < kUuidSize; ++i) { - if (i == 8 || i == 13 || i == 18 || i == 23) { - if (uuid[i] != '-') - return std::string(); - } else { - if (!IsHexDigit(uuid[i])) - return std::string(); - uuid_result[i] = tolower(uuid[i]); - } - } - return uuid_result; -} - -} // namespace bluetooth_utils -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_utils.h b/chromium/device/bluetooth/bluetooth_utils.h deleted file mode 100644 index 099516013c4..00000000000 --- a/chromium/device/bluetooth/bluetooth_utils.h +++ /dev/null @@ -1,31 +0,0 @@ -// 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 DEVICE_BLUETOOTH_BLUETOOTH_UTILS_H_ -#define DEVICE_BLUETOOTH_BLUETOOTH_UTILS_H_ - -#include <string> - -#include "base/basictypes.h" - -namespace device { -namespace bluetooth_utils { - -// Takes a 4, 8 or 36 character UUID, validates it and returns it in 36 -// character format with all hex digits lower case. If |uuid| is invalid, the -// empty string is returned. -// -// Valid inputs are: -// XXXX -// 0xXXXX -// XXXXXXXX -// 0xXXXXXXXX -// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -std::string CanonicalUuid(std::string uuid); - -} // namespace bluetooth_utils -} // namespace device - -#endif // DEVICE_BLUETOOTH_BLUETOOTH_UTILS_H_ - diff --git a/chromium/device/bluetooth/bluetooth_utils_unittest.cc b/chromium/device/bluetooth/bluetooth_utils_unittest.cc deleted file mode 100644 index 46d5f34a848..00000000000 --- a/chromium/device/bluetooth/bluetooth_utils_unittest.cc +++ /dev/null @@ -1,53 +0,0 @@ -// 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 "base/basictypes.h" -#include "device/bluetooth/bluetooth_utils.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace device { - -TEST(BluetoothUtilsTest, CanonicalUuid) { - // Does nothing for an already canonical UUID - EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb", - bluetooth_utils::CanonicalUuid("00001101-0000-1000-8000-00805f9b34fb")); - - // Rejects misformatted - EXPECT_EQ("", bluetooth_utils::CanonicalUuid("1101a")); - EXPECT_EQ("", bluetooth_utils::CanonicalUuid("Z101")); - EXPECT_EQ("", bluetooth_utils::CanonicalUuid("0000-1101")); - EXPECT_EQ("", bluetooth_utils::CanonicalUuid("0000Z101")); - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("0001101-0000-1000-8000-00805f9b34fb")); - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("Z0001101-0000-1000-8000-00805f9b34fb")); - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("00001101 0000-1000-8000-00805f9b34fb")); - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("00001101-0000:1000-8000-00805f9b34fb")); - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("00001101-0000-1000;8000-00805f9b34fb")); - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("00001101-0000-1000-8000000805f9b34fb")); - - // Lower case - EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb", - bluetooth_utils::CanonicalUuid("00001101-0000-1000-8000-00805F9B34FB")); - - // Short to full - EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb", - bluetooth_utils::CanonicalUuid("1101")); - EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb", - bluetooth_utils::CanonicalUuid("0x1101")); - EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb", - bluetooth_utils::CanonicalUuid("00001101")); - EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb", - bluetooth_utils::CanonicalUuid("0x00001101")); - - // No 0x prefix on 36 character - EXPECT_EQ("", - bluetooth_utils::CanonicalUuid("0x00001101-0000-1000-8000-00805f9b34fb")); -} - -} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_uuid.cc b/chromium/device/bluetooth/bluetooth_uuid.cc new file mode 100644 index 00000000000..714d1c5c340 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_uuid.cc @@ -0,0 +1,95 @@ +// 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 "device/bluetooth/bluetooth_uuid.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace device { + +namespace { + +const char* kCommonUuidPostfix = "-0000-1000-8000-00805f9b34fb"; +const char* kCommonUuidPrefix = "0000"; + +// Returns the canonical, 128-bit canonical, and the format of the UUID +// in |canonical|, |canonical_128|, and |format| based on |uuid|. +void GetCanonicalUuid(std::string uuid, + std::string* canonical, + std::string* canonical_128, + BluetoothUUID::Format* format) { + // Initialize the values for the failure case. + canonical->clear(); + canonical_128->clear(); + *format = BluetoothUUID::kFormatInvalid; + + if (uuid.empty()) + return; + + if (uuid.size() < 11 && uuid.find("0x") == 0) + uuid = uuid.substr(2); + + if (!(uuid.size() == 4 || uuid.size() == 8 || uuid.size() == 36)) + return; + + for (size_t i = 0; i < uuid.size(); ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (uuid[i] != '-') + return; + } else { + if (!IsHexDigit(uuid[i])) + return; + uuid[i] = base::ToLowerASCII(uuid[i]); + } + } + + canonical->assign(uuid); + if (uuid.size() == 4) { + canonical_128->assign(kCommonUuidPrefix + uuid + kCommonUuidPostfix); + *format = BluetoothUUID::kFormat16Bit; + } else if (uuid.size() == 8) { + canonical_128->assign(uuid + kCommonUuidPostfix); + *format = BluetoothUUID::kFormat32Bit; + } else { + canonical_128->assign(uuid); + *format = BluetoothUUID::kFormat128Bit; + } +} + +} // namespace + + +BluetoothUUID::BluetoothUUID(const std::string& uuid) { + GetCanonicalUuid(uuid, &value_, &canonical_value_, &format_); +} + +BluetoothUUID::BluetoothUUID() : format_(kFormatInvalid) { +} + +BluetoothUUID::~BluetoothUUID() { +} + +bool BluetoothUUID::IsValid() const { + return format_ != kFormatInvalid; +} + +bool BluetoothUUID::operator<(const BluetoothUUID& uuid) const { + return canonical_value_ < uuid.canonical_value_; +} + +bool BluetoothUUID::operator==(const BluetoothUUID& uuid) const { + return canonical_value_ == uuid.canonical_value_; +} + +bool BluetoothUUID::operator!=(const BluetoothUUID& uuid) const { + return canonical_value_ != uuid.canonical_value_; +} + +void PrintTo(const BluetoothUUID& uuid, std::ostream* out) { + *out << uuid.canonical_value(); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_uuid.h b/chromium/device/bluetooth/bluetooth_uuid.h new file mode 100644 index 00000000000..5c2fc8a3b9c --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_uuid.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 DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ + +#include <string> + +namespace device { + +// Opaque wrapper around a Bluetooth UUID. Instances of UUID represent the +// 128-bit universally unique identifiers (UUIDs) of profiles and attributes +// used in Bluetooth based communication, such as a peripheral's services, +// characteristics, and characteristic descriptors. An instance are +// constructed using a string representing 16, 32, or 128 bit UUID formats. +class BluetoothUUID { + public: + // Possible representation formats used during construction. + enum Format { + kFormatInvalid, + kFormat16Bit, + kFormat32Bit, + kFormat128Bit + }; + + // Single argument constructor. |uuid| can be a 16, 32, or 128 bit UUID + // represented as a 4, 8, or 36 character string with the following + // formats: + // xxxx + // 0xxxxx + // xxxxxxxx + // 0xxxxxxxxx + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // + // 16 and 32 bit UUIDs will be internally converted to a 128 bit UUID using + // the base UUID defined in the Bluetooth specification, hence custom UUIDs + // should be provided in the 128-bit format. If |uuid| is in an unsupported + // format, the result might be invalid. Use IsValid to check for validity + // after construction. + explicit BluetoothUUID(const std::string& uuid); + + // Default constructor does nothing. Since BluetoothUUID is copyable, this + // constructor is useful for initializing member variables and assigning a + // value to them later. The default constructor will initialize an invalid + // UUID by definition and the string accessors will return an empty string. + BluetoothUUID(); + virtual ~BluetoothUUID(); + + // Returns true, if the UUID is in a valid canonical format. + bool IsValid() const; + + // Returns the representation format of the UUID. This reflects the format + // that was provided during construction. + Format format() const { return format_; } + + // Returns the value of the UUID as a string. The representation format is + // based on what was passed in during construction. For the supported sizes, + // this representation can have the following formats: + // - 16 bit: xxxx + // - 32 bit: xxxxxxxx + // - 128 bit: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // where x is a lowercase hex digit. + const std::string& value() const { return value_; } + + // Returns the underlying 128-bit value as a string in the following format: + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // where x is a lowercase hex digit. + const std::string& canonical_value() const { return canonical_value_; } + + // Permit sufficient comparison to allow a UUID to be used as a key in a + // std::map. + bool operator<(const BluetoothUUID& uuid) const; + + // Equality operators. + bool operator==(const BluetoothUUID& uuid) const; + bool operator!=(const BluetoothUUID& uuid) const; + + private: + // String representation of the UUID that was used during construction. For + // the supported sizes, this representation can have the following formats: + // - 16 bit: xxxx + // - 32 bit: xxxxxxxx + // - 128 bit: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + Format format_; + std::string value_; + + // The 128-bit string representation of the UUID. + std::string canonical_value_; +}; + +// This is required by gtest to print a readable output on test failures. +void PrintTo(const BluetoothUUID& uuid, std::ostream* out); + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ diff --git a/chromium/device/bluetooth/bluetooth_uuid_unittest.cc b/chromium/device/bluetooth/bluetooth_uuid_unittest.cc new file mode 100644 index 00000000000..c59ab1c934e --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_uuid_unittest.cc @@ -0,0 +1,107 @@ +// 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/macros.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +TEST(BluetoothUUIDTest, BluetoothUUID) { + const char kValid128Bit0[] = "12345678-1234-5678-9abc-def123456789"; + const char kValid128Bit1[] = "00001101-0000-1000-8000-00805f9b34fb"; + const char kInvalid36Char0[] = "1234567-1234-5678-9abc-def123456789"; + const char kInvalid36Char1[] = "0x00001101-0000-1000-8000-00805f9b34fb"; + const char kInvalid4Char[] = "Z101"; + const char kValid16Bit[] = "0x1101"; + const char kValid32Bit[] = "00001101"; + + // Valid 128-bit custom UUID. + BluetoothUUID uuid0(kValid128Bit0); + EXPECT_TRUE(uuid0.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormat128Bit, uuid0.format()); + EXPECT_EQ(uuid0.value(), uuid0.canonical_value()); + + // Valid 128-bit UUID. + BluetoothUUID uuid1(kValid128Bit1); + EXPECT_TRUE(uuid1.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormat128Bit, uuid1.format()); + EXPECT_EQ(uuid1.value(), uuid1.canonical_value()); + + EXPECT_NE(uuid0, uuid1); + + // Invalid 128-bit UUID. + BluetoothUUID uuid2(kInvalid36Char0); + EXPECT_FALSE(uuid2.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormatInvalid, uuid2.format()); + EXPECT_TRUE(uuid2.value().empty()); + EXPECT_TRUE(uuid2.canonical_value().empty()); + + // Invalid 128-bit UUID. + BluetoothUUID uuid3(kInvalid36Char1); + EXPECT_FALSE(uuid3.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormatInvalid, uuid3.format()); + EXPECT_TRUE(uuid3.value().empty()); + EXPECT_TRUE(uuid3.canonical_value().empty()); + + // Invalid 16-bit UUID. + BluetoothUUID uuid4(kInvalid4Char); + EXPECT_FALSE(uuid4.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormatInvalid, uuid4.format()); + EXPECT_TRUE(uuid4.value().empty()); + EXPECT_TRUE(uuid4.canonical_value().empty()); + + // Valid 16-bit UUID. + BluetoothUUID uuid5(kValid16Bit); + EXPECT_TRUE(uuid5.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormat16Bit, uuid5.format()); + EXPECT_NE(uuid5.value(), uuid5.canonical_value()); + EXPECT_EQ("1101", uuid5.value()); + EXPECT_EQ(kValid128Bit1, uuid5.canonical_value()); + + // Valid 32-bit UUID. + BluetoothUUID uuid6(kValid32Bit); + EXPECT_TRUE(uuid6.IsValid()); + EXPECT_EQ(BluetoothUUID::kFormat32Bit, uuid6.format()); + EXPECT_NE(uuid6.value(), uuid6.canonical_value()); + EXPECT_EQ("00001101", uuid6.value()); + EXPECT_EQ(kValid128Bit1, uuid6.canonical_value()); + + // uuid5, uuid6, and uuid1 are equivalent. + EXPECT_EQ(uuid5, uuid6); + EXPECT_EQ(uuid1, uuid5); + EXPECT_EQ(uuid1, uuid6); +} + +// Verify that UUIDs are parsed case-insensitively +TEST(BluetoothUUIDTest, BluetoothUUID_CaseInsensitive) { + const char k16Bit[] = "1abc"; + const char k32Bit[] = "00001abc"; + const char k128Bit[] = "00001abc-0000-1000-8000-00805f9b34fb"; + + struct TestCase { + const std::string input_uuid; + const std::string expected_value; + } test_cases[] = { + { "1abc", k16Bit }, + { "1ABC", k16Bit }, + { "1aBc", k16Bit }, + { "00001abc", k32Bit }, + { "00001ABC", k32Bit }, + { "00001aBc", k32Bit }, + { "00001abc-0000-1000-8000-00805f9b34fb", k128Bit }, + { "00001ABC-0000-1000-8000-00805F9B34FB", k128Bit }, + { "00001aBc-0000-1000-8000-00805F9b34fB", k128Bit }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + SCOPED_TRACE("Input UUID: " + test_cases[i].input_uuid); + BluetoothUUID uuid(test_cases[i].input_uuid); + EXPECT_TRUE(uuid.IsValid()); + EXPECT_EQ(test_cases[i].expected_value, uuid.value()); + EXPECT_EQ(k128Bit, uuid.canonical_value()); + } +} + +} // namespace device diff --git a/chromium/device/device_tests.gyp b/chromium/device/device_tests.gyp index e0564b5260c..577b28d3731 100644 --- a/chromium/device/device_tests.gyp +++ b/chromium/device/device_tests.gyp @@ -19,20 +19,27 @@ 'bluetooth/bluetooth.gyp:device_bluetooth_mocks', 'nfc/nfc.gyp:device_nfc', 'usb/usb.gyp:device_usb', + 'hid/hid.gyp:device_hid', ], 'sources': [ 'bluetooth/bluetooth_adapter_mac_unittest.mm', + 'bluetooth/bluetooth_adapter_unittest.cc', 'bluetooth/bluetooth_adapter_win_unittest.cc', + 'bluetooth/bluetooth_device_unittest.cc', 'bluetooth/bluetooth_device_win_unittest.cc', 'bluetooth/bluetooth_chromeos_unittest.cc', - 'bluetooth/bluetooth_profile_chromeos_unittest.cc', - 'bluetooth/bluetooth_service_record_mac_unittest.mm', + 'bluetooth/bluetooth_gatt_chromeos_unittest.cc', 'bluetooth/bluetooth_service_record_win_unittest.cc', + 'bluetooth/bluetooth_socket_chromeos_unittest.cc', 'bluetooth/bluetooth_task_manager_win_unittest.cc', - 'bluetooth/bluetooth_utils_unittest.cc', + 'bluetooth/bluetooth_uuid_unittest.cc', 'nfc/nfc_chromeos_unittest.cc', 'nfc/nfc_ndef_record_unittest.cc', 'usb/usb_ids_unittest.cc', + 'hid/hid_connection_unittest.cc', + 'hid/hid_report_descriptor_unittest.cc', + 'hid/hid_service_unittest.cc', + 'hid/input_service_linux_unittest.cc', ], 'conditions': [ ['chromeos==1', { @@ -52,13 +59,23 @@ }], ['os_posix == 1 and OS != "mac" and OS != "android" and OS != "ios"', { 'conditions': [ - ['linux_use_tcmalloc == 1', { + ['use_allocator!="none"', { 'dependencies': [ '../base/allocator/allocator.gyp:allocator', ], }], ], }], + ['OS=="linux" and use_udev==0', { + # Udev is the only Linux implementation. If we're compiling without + # Udev, disable these unittests. + 'dependencies!': [ + 'hid/hid.gyp:device_hid', + ], + 'sources/': [ + ['exclude', '^hid/'], + ], + }], ], }, ], diff --git a/chromium/device/hid/DEPS b/chromium/device/hid/DEPS new file mode 100644 index 00000000000..6a2f02e2180 --- /dev/null +++ b/chromium/device/hid/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net/base", +] diff --git a/chromium/device/hid/device_monitor_linux.cc b/chromium/device/hid/device_monitor_linux.cc new file mode 100644 index 00000000000..8d112c127a1 --- /dev/null +++ b/chromium/device/hid/device_monitor_linux.cc @@ -0,0 +1,150 @@ +// 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 "device/hid/device_monitor_linux.h" + +#include <libudev.h> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace device { + +namespace { + +const char kUdevName[] = "udev"; +const char kUdevActionAdd[] = "add"; +const char kUdevActionRemove[] = "remove"; + +// The instance will be reset when message loop destroys. +base::LazyInstance<scoped_ptr<DeviceMonitorLinux> >::Leaky + g_device_monitor_linux_ptr = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +DeviceMonitorLinux::DeviceMonitorLinux() : monitor_fd_(-1) { + base::ThreadRestrictions::AssertIOAllowed(); + base::MessageLoop::current()->AddDestructionObserver(this); + + udev_.reset(udev_new()); + if (!udev_) { + LOG(ERROR) << "Failed to create udev."; + return; + } + monitor_.reset(udev_monitor_new_from_netlink(udev_.get(), kUdevName)); + if (!monitor_) { + LOG(ERROR) << "Failed to create udev monitor."; + return; + } + + int ret = udev_monitor_enable_receiving(monitor_.get()); + if (ret != 0) { + LOG(ERROR) << "Failed to start udev monitoring."; + return; + } + + monitor_fd_ = udev_monitor_get_fd(monitor_.get()); + if (monitor_fd_ <= 0) { + LOG(ERROR) << "Failed to start udev monitoring."; + return; + } + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + monitor_fd_, + true, + base::MessageLoopForIO::WATCH_READ, + &monitor_watcher_, + this)) { + return; + } +} + +// static +DeviceMonitorLinux* DeviceMonitorLinux::GetInstance() { + if (!HasInstance()) + g_device_monitor_linux_ptr.Get().reset(new DeviceMonitorLinux()); + return g_device_monitor_linux_ptr.Get().get(); +} + +// static +bool DeviceMonitorLinux::HasInstance() { + return g_device_monitor_linux_ptr.Get().get(); +} + +void DeviceMonitorLinux::AddObserver(Observer* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (observer) + observers_.AddObserver(observer); +} + +void DeviceMonitorLinux::RemoveObserver(Observer* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (observer) + observers_.RemoveObserver(observer); +} + +ScopedUdevDevicePtr DeviceMonitorLinux::GetDeviceFromPath( + const std::string& path) { + DCHECK(thread_checker_.CalledOnValidThread()); + ScopedUdevDevicePtr device( + udev_device_new_from_syspath(udev_.get(), path.c_str())); + return device.Pass(); +} + +void DeviceMonitorLinux::Enumerate(const EnumerateCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get())); + + if (!enumerate) { + LOG(ERROR) << "Failed to enumerate devices."; + return; + } + + if (udev_enumerate_scan_devices(enumerate.get()) != 0) { + LOG(ERROR) << "Failed to enumerate devices."; + return; + } + + // This list is managed by |enumerate|. + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); + for (udev_list_entry* i = devices; i != NULL; + i = udev_list_entry_get_next(i)) { + ScopedUdevDevicePtr device( + udev_device_new_from_syspath(udev_.get(), udev_list_entry_get_name(i))); + if (device) + callback.Run(device.get()); + } +} + +void DeviceMonitorLinux::WillDestroyCurrentMessageLoop() { + DCHECK(thread_checker_.CalledOnValidThread()); + g_device_monitor_linux_ptr.Get().reset(NULL); +} + +void DeviceMonitorLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(monitor_fd_, fd); + + ScopedUdevDevicePtr device(udev_monitor_receive_device(monitor_.get())); + if (!device) + return; + + std::string action(udev_device_get_action(device.get())); + if (action == kUdevActionAdd) + FOR_EACH_OBSERVER(Observer, observers_, OnDeviceAdded(device.get())); + else if (action == kUdevActionRemove) + FOR_EACH_OBSERVER(Observer, observers_, OnDeviceRemoved(device.get())); +} + +void DeviceMonitorLinux::OnFileCanWriteWithoutBlocking(int fd) {} + +DeviceMonitorLinux::~DeviceMonitorLinux() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::MessageLoop::current()->RemoveDestructionObserver(this); + monitor_watcher_.StopWatchingFileDescriptor(); + close(monitor_fd_); +} + +} // namespace device diff --git a/chromium/device/hid/device_monitor_linux.h b/chromium/device/hid/device_monitor_linux.h new file mode 100644 index 00000000000..2f219dc9e24 --- /dev/null +++ b/chromium/device/hid/device_monitor_linux.h @@ -0,0 +1,75 @@ +// 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 DEVICE_HID_DEVICE_MONITOR_LINUX_H_ +#define DEVICE_HID_DEVICE_MONITOR_LINUX_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_pump_libevent.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "device/udev_linux/udev.h" + +struct udev_device; + +namespace device { + +// This class listends for notifications from libudev about +// connected/disconnected devices. This class is *NOT* thread-safe and +// all methods must be accessed from the FILE thread. +class DeviceMonitorLinux : public base::MessageLoop::DestructionObserver, + public base::MessagePumpLibevent::Watcher { + public: + typedef base::Callback<void(udev_device* device)> EnumerateCallback; + + class Observer { + public: + virtual ~Observer() {} + virtual void OnDeviceAdded(udev_device* device) = 0; + virtual void OnDeviceRemoved(udev_device* device) = 0; + }; + + DeviceMonitorLinux(); + + static DeviceMonitorLinux* GetInstance(); + static bool HasInstance(); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + ScopedUdevDevicePtr GetDeviceFromPath(const std::string& path); + void Enumerate(const EnumerateCallback& callback); + + // Implements base::MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + // Implements base::MessagePumpLibevent::Watcher + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + friend struct base::DefaultDeleter<DeviceMonitorLinux>; + + virtual ~DeviceMonitorLinux(); + + ScopedUdevPtr udev_; + ScopedUdevMonitorPtr monitor_; + int monitor_fd_; + base::MessagePumpLibevent::FileDescriptorWatcher monitor_watcher_; + + ObserverList<Observer> observers_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(DeviceMonitorLinux); +}; + +} // namespace device + +#endif // DEVICE_HID_DEVICE_MONITOR_LINUX_H_ diff --git a/chromium/device/hid/hid.gyp b/chromium/device/hid/hid.gyp new file mode 100644 index 00000000000..c12845a0fa4 --- /dev/null +++ b/chromium/device/hid/hid.gyp @@ -0,0 +1,67 @@ +# Copyright 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'device_hid', + 'type': 'static_library', + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'device_monitor_linux.cc', + 'device_monitor_linux.h', + 'hid_connection.cc', + 'hid_connection.h', + 'hid_connection_linux.cc', + 'hid_connection_linux.h', + 'hid_connection_mac.cc', + 'hid_connection_mac.h', + 'hid_connection_win.cc', + 'hid_connection_win.h', + 'hid_device_info.cc', + 'hid_device_info.h', + 'hid_report_descriptor.cc', + 'hid_report_descriptor.h', + 'hid_report_descriptor_item.cc', + 'hid_report_descriptor_item.h', + 'hid_service.cc', + 'hid_service.h', + 'hid_service_linux.cc', + 'hid_service_linux.h', + 'hid_service_mac.cc', + 'hid_service_mac.h', + 'hid_service_win.cc', + 'hid_service_win.h', + 'hid_usage_and_page.cc', + 'hid_usage_and_page.h', + 'hid_utils_mac.cc', + 'hid_utils_mac.h', + 'input_service_linux.cc', + 'input_service_linux.h', + ], + 'conditions': [ + ['use_udev==1', { + 'dependencies': [ + '../udev_linux/udev.gyp:udev_linux', + ], + }, { # use_udev==0 + # The Linux implementation is based on Udev. + 'sources!': [ + 'device_monitor_linux.cc', + 'device_monitor_linux.h', + 'hid_service_linux.cc', + 'hid_service_linux.h', + 'input_service_linux.cc', + 'input_service_linux.h', + ], + }], + ], + }, + ], +} diff --git a/chromium/device/hid/hid_connection.cc b/chromium/device/hid/hid_connection.cc new file mode 100644 index 00000000000..c134bf2702e --- /dev/null +++ b/chromium/device/hid/hid_connection.cc @@ -0,0 +1,22 @@ +// Copyright (c) 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 "device/hid/hid_connection.h" + +namespace device { + +PendingHidReport::PendingHidReport() {} + +PendingHidReport::~PendingHidReport() {} + +PendingHidRead::PendingHidRead() {} + +PendingHidRead::~PendingHidRead() {} + +HidConnection::HidConnection(const HidDeviceInfo& device_info) + : device_info_(device_info) {} + +HidConnection::~HidConnection() {} + +} // namespace device diff --git a/chromium/device/hid/hid_connection.h b/chromium/device/hid/hid_connection.h new file mode 100644 index 00000000000..5c08fbc5bbd --- /dev/null +++ b/chromium/device/hid/hid_connection.h @@ -0,0 +1,65 @@ +// 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 DEVICE_HID_HID_CONNECTION_H_ +#define DEVICE_HID_HID_CONNECTION_H_ + +#include <stdint.h> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "device/hid/hid_device_info.h" +#include "net/base/io_buffer.h" + +namespace device { + +class HidConnection : public base::RefCountedThreadSafe<HidConnection> { + public: + typedef base::Callback<void(bool success, size_t size)> IOCallback; + + virtual void Read(scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) = 0; + virtual void Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) = 0; + virtual void GetFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) = 0; + virtual void SendFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) = 0; + + const HidDeviceInfo& device_info() const { return device_info_; } + + protected: + friend class base::RefCountedThreadSafe<HidConnection>; + friend struct HidDeviceInfo; + + explicit HidConnection(const HidDeviceInfo& device_info); + virtual ~HidConnection(); + + private: + const HidDeviceInfo device_info_; + + DISALLOW_COPY_AND_ASSIGN(HidConnection); +}; + +struct PendingHidReport { + PendingHidReport(); + ~PendingHidReport(); + + scoped_refptr<net::IOBufferWithSize> buffer; +}; + +struct PendingHidRead { + PendingHidRead(); + ~PendingHidRead(); + + scoped_refptr<net::IOBufferWithSize> buffer; + HidConnection::IOCallback callback; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_H_ diff --git a/chromium/device/hid/hid_connection_linux.cc b/chromium/device/hid/hid_connection_linux.cc new file mode 100644 index 00000000000..425d88fa35c --- /dev/null +++ b/chromium/device/hid/hid_connection_linux.cc @@ -0,0 +1,210 @@ +// Copyright (c) 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 "device/hid/hid_connection_linux.h" + +#include <errno.h> +#include <fcntl.h> +#include <libudev.h> +#include <linux/hidraw.h> +#include <sys/ioctl.h> + +#include <string> + +#include "base/files/file_path.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread_restrictions.h" +#include "base/tuple.h" +#include "device/hid/hid_service.h" + +// These are already defined in newer versions of linux/hidraw.h. +#ifndef HIDIOCSFEATURE +#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len) +#endif +#ifndef HIDIOCGFEATURE +#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len) +#endif + +namespace device { + +namespace { + +// Copies a buffer into a new one with a report ID byte inserted at the front. +scoped_refptr<net::IOBufferWithSize> CopyBufferWithReportId( + scoped_refptr<net::IOBufferWithSize> buffer, + uint8_t report_id) { + scoped_refptr<net::IOBufferWithSize> new_buffer( + new net::IOBufferWithSize(buffer->size() + 1)); + new_buffer->data()[0] = report_id; + memcpy(new_buffer->data() + 1, buffer->data(), buffer->size()); + return new_buffer; +} + +} // namespace + +HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info, + std::string dev_node) + : HidConnection(device_info) { + DCHECK(thread_checker_.CalledOnValidThread()); + + int flags = base::File::FLAG_OPEN | + base::File::FLAG_READ | + base::File::FLAG_WRITE; + + base::File device_file(base::FilePath(dev_node), flags); + if (!device_file.IsValid()) { + base::File::Error file_error = device_file.error_details(); + + if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) { + VLOG(1) << "Access denied opening device read-write, trying read-only."; + + flags = base::File::FLAG_OPEN | base::File::FLAG_READ; + + device_file = base::File(base::FilePath(dev_node), flags); + } + } + if (!device_file.IsValid()) { + LOG(ERROR) << "Failed to open '" << dev_node << "': " + << base::File::ErrorToString(device_file.error_details()); + return; + } + + if (fcntl(device_file.GetPlatformFile(), F_SETFL, + fcntl(device_file.GetPlatformFile(), F_GETFL) | O_NONBLOCK)) { + PLOG(ERROR) << "Failed to set non-blocking flag to device file."; + return; + } + device_file_ = device_file.Pass(); + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + device_file_.GetPlatformFile(), + true, + base::MessageLoopForIO::WATCH_READ_WRITE, + &device_file_watcher_, + this)) { + LOG(ERROR) << "Failed to start watching device file."; + } +} + +HidConnectionLinux::~HidConnectionLinux() { + DCHECK(thread_checker_.CalledOnValidThread()); + Disconnect(); +} + +void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(fd, device_file_.GetPlatformFile()); + + uint8 buffer[1024] = {0}; + int bytes_read = + HANDLE_EINTR(read(device_file_.GetPlatformFile(), buffer, 1024)); + if (bytes_read < 0) { + if (errno == EAGAIN) { + return; + } + Disconnect(); + return; + } + + PendingHidReport report; + report.buffer = new net::IOBufferWithSize(bytes_read); + memcpy(report.buffer->data(), buffer, bytes_read); + pending_reports_.push(report); + ProcessReadQueue(); +} + +void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {} + +void HidConnectionLinux::Disconnect() { + DCHECK(thread_checker_.CalledOnValidThread()); + device_file_watcher_.StopWatchingFileDescriptor(); + device_file_.Close(); + while (!pending_reads_.empty()) { + PendingHidRead pending_read = pending_reads_.front(); + pending_reads_.pop(); + pending_read.callback.Run(false, 0); + } +} + +void HidConnectionLinux::Read(scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + PendingHidRead pending_read; + pending_read.buffer = buffer; + pending_read.callback = callback; + pending_reads_.push(pending_read); + ProcessReadQueue(); +} + +void HidConnectionLinux::Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + // If report ID is non-zero, insert it into a new copy of the buffer. + if (report_id != 0) + buffer = CopyBufferWithReportId(buffer, report_id); + int bytes_written = HANDLE_EINTR( + write(device_file_.GetPlatformFile(), buffer->data(), buffer->size())); + if (bytes_written < 0) { + Disconnect(); + callback.Run(false, 0); + } else { + callback.Run(true, bytes_written); + } +} + +void HidConnectionLinux::GetFeatureReport( + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (buffer->size() == 0) { + callback.Run(false, 0); + return; + } + + // The first byte of the destination buffer is the report ID being requested. + buffer->data()[0] = report_id; + int result = ioctl(device_file_.GetPlatformFile(), + HIDIOCGFEATURE(buffer->size()), + buffer->data()); + if (result < 0) + callback.Run(false, 0); + else + callback.Run(true, result); +} + +void HidConnectionLinux::SendFeatureReport( + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (report_id != 0) + buffer = CopyBufferWithReportId(buffer, report_id); + int result = ioctl(device_file_.GetPlatformFile(), + HIDIOCSFEATURE(buffer->size()), + buffer->data()); + if (result < 0) + callback.Run(false, 0); + else + callback.Run(true, result); +} + +void HidConnectionLinux::ProcessReadQueue() { + while (pending_reads_.size() && pending_reports_.size()) { + PendingHidRead read = pending_reads_.front(); + pending_reads_.pop(); + PendingHidReport report = pending_reports_.front(); + if (report.buffer->size() > read.buffer->size()) { + read.callback.Run(false, report.buffer->size()); + } else { + memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size()); + pending_reports_.pop(); + read.callback.Run(true, report.buffer->size()); + } + } +} + +} // namespace device diff --git a/chromium/device/hid/hid_connection_linux.h b/chromium/device/hid/hid_connection_linux.h new file mode 100644 index 00000000000..108ad881a95 --- /dev/null +++ b/chromium/device/hid/hid_connection_linux.h @@ -0,0 +1,59 @@ +// Copyright (c) 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 DEVICE_HID_HID_CONNECTION_LINUX_H_ +#define DEVICE_HID_HID_CONNECTION_LINUX_H_ + +#include <queue> + +#include "base/files/file.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_pump_libevent.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_device_info.h" + +namespace device { + +class HidConnectionLinux : public HidConnection, + public base::MessagePumpLibevent::Watcher { + public: + HidConnectionLinux(HidDeviceInfo device_info, std::string dev_node); + + virtual void Read(scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void GetFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void SendFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + + // Implements base::MessagePumpLibevent::Watcher + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + friend class base::RefCountedThreadSafe<HidConnectionLinux>; + virtual ~HidConnectionLinux(); + + void ProcessReadQueue(); + void Disconnect(); + + base::File device_file_; + base::MessagePumpLibevent::FileDescriptorWatcher device_file_watcher_; + + std::queue<PendingHidReport> pending_reports_; + std::queue<PendingHidRead> pending_reads_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(HidConnectionLinux); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_LINUX_H_ diff --git a/chromium/device/hid/hid_connection_mac.cc b/chromium/device/hid/hid_connection_mac.cc new file mode 100644 index 00000000000..ce17df4cc9b --- /dev/null +++ b/chromium/device/hid/hid_connection_mac.cc @@ -0,0 +1,176 @@ +// Copyright (c) 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 "device/hid/hid_connection_mac.h" + +#include "base/bind.h" +#include "base/mac/foundation_util.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_restrictions.h" +#include "device/hid/hid_connection_mac.h" + +namespace device { + +HidConnectionMac::HidConnectionMac(HidDeviceInfo device_info) + : HidConnection(device_info), + device_(device_info.device_id, base::scoped_policy::RETAIN) { + DCHECK(thread_checker_.CalledOnValidThread()); + + message_loop_ = base::MessageLoopProxy::current(); + + DCHECK(device_.get()); + inbound_buffer_.reset((uint8_t*)malloc(device_info.input_report_size)); + IOHIDDeviceRegisterInputReportCallback(device_.get(), + inbound_buffer_.get(), + device_info.input_report_size, + &HidConnectionMac::InputReportCallback, + this); + IOHIDDeviceOpen(device_, kIOHIDOptionsTypeNone); +} + +HidConnectionMac::~HidConnectionMac() { + DCHECK(thread_checker_.CalledOnValidThread()); + + while (!pending_reads_.empty()) { + pending_reads_.front().callback.Run(false, 0); + pending_reads_.pop(); + } + + IOHIDDeviceClose(device_, kIOHIDOptionsTypeNone); +} + +void HidConnectionMac::Read(scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!device_) { + callback.Run(false, 0); + return; + } + PendingHidRead read; + read.buffer = buffer; + read.callback = callback; + pending_reads_.push(read); + ProcessReadQueue(); +} + +void HidConnectionMac::Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + WriteReport(kIOHIDReportTypeOutput, report_id, buffer, callback); +} + +void HidConnectionMac::GetFeatureReport( + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (device_info().feature_report_size == 0) { + callback.Run(false, 0); + return; + } + + if (buffer->size() < device_info().feature_report_size) { + callback.Run(false, 0); + return; + } + + uint8_t* feature_report_buffer = reinterpret_cast<uint8_t*>(buffer->data()); + CFIndex feature_report_size = device_info().feature_report_size; + IOReturn result = IOHIDDeviceGetReport(device_, + kIOHIDReportTypeFeature, + report_id, + feature_report_buffer, + &feature_report_size); + if (result == kIOReturnSuccess) + callback.Run(true, feature_report_size); + else + callback.Run(false, 0); +} + +void HidConnectionMac::SendFeatureReport( + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + WriteReport(kIOHIDReportTypeFeature, report_id, buffer, callback); +} + +void HidConnectionMac::InputReportCallback(void* context, + IOReturn result, + void* sender, + IOHIDReportType type, + uint32_t report_id, + uint8_t* report_bytes, + CFIndex report_length) { + HidConnectionMac* connection = static_cast<HidConnectionMac*>(context); + // report_id is already contained in report_bytes + scoped_refptr<net::IOBufferWithSize> buffer; + buffer = new net::IOBufferWithSize(report_length); + memcpy(buffer->data(), report_bytes, report_length); + + connection->message_loop_->PostTask( + FROM_HERE, + base::Bind( + &HidConnectionMac::ProcessInputReport, connection, type, buffer)); +} + +void HidConnectionMac::ProcessReadQueue() { + DCHECK(thread_checker_.CalledOnValidThread()); + while (pending_reads_.size() && pending_reports_.size()) { + PendingHidRead read = pending_reads_.front(); + pending_reads_.pop(); + PendingHidReport report = pending_reports_.front(); + if (read.buffer->size() < report.buffer->size()) { + read.callback.Run(false, report.buffer->size()); + } else { + memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size()); + pending_reports_.pop(); + read.callback.Run(true, report.buffer->size()); + } + } +} + +void HidConnectionMac::ProcessInputReport( + IOHIDReportType type, + scoped_refptr<net::IOBufferWithSize> buffer) { + DCHECK(thread_checker_.CalledOnValidThread()); + PendingHidReport report; + report.buffer = buffer; + pending_reports_.push(report); + ProcessReadQueue(); +} + +void HidConnectionMac::WriteReport(IOHIDReportType type, + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!device_) { + callback.Run(false, 0); + return; + } + scoped_refptr<net::IOBufferWithSize> output_buffer; + if (report_id != 0) { + output_buffer = new net::IOBufferWithSize(buffer->size() + 1); + output_buffer->data()[0] = static_cast<uint8_t>(report_id); + memcpy(output_buffer->data() + 1, buffer->data(), buffer->size()); + } else { + output_buffer = new net::IOBufferWithSize(buffer->size()); + memcpy(output_buffer->data(), buffer->data(), buffer->size()); + } + IOReturn res = + IOHIDDeviceSetReport(device_.get(), + type, + report_id, + reinterpret_cast<uint8_t*>(output_buffer->data()), + output_buffer->size()); + if (res != kIOReturnSuccess) { + callback.Run(false, 0); + } else { + callback.Run(true, output_buffer->size()); + } +} + +} // namespace device diff --git a/chromium/device/hid/hid_connection_mac.h b/chromium/device/hid/hid_connection_mac.h new file mode 100644 index 00000000000..c307fb6f878 --- /dev/null +++ b/chromium/device/hid/hid_connection_mac.h @@ -0,0 +1,81 @@ +// Copyright (c) 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 DEVICE_HID_HID_CONNECTION_MAC_H_ +#define DEVICE_HID_HID_CONNECTION_MAC_H_ + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> + +#include <queue> + +#include "base/mac/foundation_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_device_info.h" + +namespace base { +class MessageLoopProxy; +} + +namespace net { +class IOBuffer; +} + +namespace device { + +class HidConnectionMac : public HidConnection { + public: + explicit HidConnectionMac(HidDeviceInfo device_info); + + virtual void Read(scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void GetFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void SendFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + + private: + virtual ~HidConnectionMac(); + + static void InputReportCallback(void* context, + IOReturn result, + void* sender, + IOHIDReportType type, + uint32_t report_id, + uint8_t* report_bytes, + CFIndex report_length); + void ProcessReadQueue(); + void ProcessInputReport(IOHIDReportType type, + scoped_refptr<net::IOBufferWithSize> buffer); + + void WriteReport(IOHIDReportType type, + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback); + + scoped_refptr<base::MessageLoopProxy> message_loop_; + + base::ScopedCFTypeRef<IOHIDDeviceRef> device_; + scoped_ptr<uint8_t, base::FreeDeleter> inbound_buffer_; + + std::queue<PendingHidReport> pending_reports_; + std::queue<PendingHidRead> pending_reads_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(HidConnectionMac); +}; + + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_MAC_H_ diff --git a/chromium/device/hid/hid_connection_unittest.cc b/chromium/device/hid/hid_connection_unittest.cc new file mode 100644 index 00000000000..27254817715 --- /dev/null +++ b/chromium/device/hid/hid_connection_unittest.cc @@ -0,0 +1,128 @@ +// Copyright (c) 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 <vector> + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_service.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +namespace { + +using net::IOBufferWithSize; + +const int kUSBLUFADemoVID = 0x03eb; +const int kUSBLUFADemoPID = 0x204f; +const uint64_t kReport = 0x0903a65d030f8ec9ULL; + +int g_read_times = 0; +void Read(scoped_refptr<HidConnection> conn); + +void OnRead(scoped_refptr<HidConnection> conn, + scoped_refptr<IOBufferWithSize> buffer, + bool success, + size_t bytes) { + EXPECT_TRUE(success); + if (success) { + g_read_times++; + EXPECT_EQ(8U, bytes); + if (bytes == 8) { + uint64_t* data = reinterpret_cast<uint64_t*>(buffer->data()); + EXPECT_EQ(kReport, *data); + } else { + base::MessageLoop::current()->Quit(); + } + } else { + LOG(ERROR) << "~"; + g_read_times++; + } + + if (g_read_times < 3){ + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(Read, conn)); + } else { + base::MessageLoop::current()->Quit(); + } +} + +void Read(scoped_refptr<HidConnection> conn) { + scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(8)); + conn->Read(buffer, base::Bind(OnRead, conn, buffer)); +} + +void OnWriteNormal(bool success, + size_t bytes) { + ASSERT_TRUE(success); + base::MessageLoop::current()->Quit(); +} + +void WriteNormal(scoped_refptr<HidConnection> conn) { + scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(8)); + *(int64_t*)buffer->data() = kReport; + + conn->Write(0, buffer, base::Bind(OnWriteNormal)); +} + +} // namespace + +class HidConnectionTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + message_loop_.reset(new base::MessageLoopForIO()); + service_.reset(HidService::CreateInstance()); + ASSERT_TRUE(service_); + + std::vector<HidDeviceInfo> devices; + service_->GetDevices(&devices); + device_id_ = kInvalidHidDeviceId; + for (std::vector<HidDeviceInfo>::iterator it = devices.begin(); + it != devices.end(); + ++it) { + if (it->vendor_id == kUSBLUFADemoVID && + it->product_id == kUSBLUFADemoPID) { + device_id_ = it->device_id; + return; + } + } + } + + virtual void TearDown() OVERRIDE { + service_.reset(NULL); + message_loop_.reset(NULL); + } + + HidDeviceId device_id_; + scoped_ptr<base::MessageLoopForIO> message_loop_; + scoped_ptr<HidService> service_; +}; + +TEST_F(HidConnectionTest, Create) { + scoped_refptr<HidConnection> connection = service_->Connect(device_id_); + ASSERT_TRUE(connection || device_id_ == kInvalidHidDeviceId); +} + +TEST_F(HidConnectionTest, Read) { + scoped_refptr<HidConnection> connection = service_->Connect(device_id_); + if (connection) { + message_loop_->PostTask(FROM_HERE, base::Bind(Read, connection)); + message_loop_->Run(); + } +} + +TEST_F(HidConnectionTest, Write) { + scoped_refptr<HidConnection> connection = service_->Connect(device_id_); + + if (connection) { + message_loop_->PostTask(FROM_HERE, base::Bind(WriteNormal, connection)); + message_loop_->Run(); + } +} + +} // namespace device diff --git a/chromium/device/hid/hid_connection_win.cc b/chromium/device/hid/hid_connection_win.cc new file mode 100644 index 00000000000..17448f07182 --- /dev/null +++ b/chromium/device/hid/hid_connection_win.cc @@ -0,0 +1,303 @@ +// Copyright (c) 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 "device/hid/hid_connection_win.h" + +#include <cstring> + +#include "base/files/file.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/object_watcher.h" +#include "base/win/scoped_handle.h" +#include "device/hid/hid_service.h" +#include "device/hid/hid_service_win.h" + +#define INITGUID + +#include <windows.h> +#include <hidclass.h> + +extern "C" { +#include <hidsdi.h> +} + +#include <setupapi.h> +#include <winioctl.h> + +namespace device { + +struct PendingHidTransfer : public base::RefCounted<PendingHidTransfer>, + public base::win::ObjectWatcher::Delegate, + public base::MessageLoop::DestructionObserver { + PendingHidTransfer(scoped_refptr<HidConnectionWin> connection, + scoped_refptr<net::IOBufferWithSize> target_buffer, + scoped_refptr<net::IOBufferWithSize> receive_buffer, + HidConnection::IOCallback callback); + + void TakeResultFromWindowsAPI(BOOL result); + + OVERLAPPED* GetOverlapped() { return &overlapped_; } + + // Implements base::win::ObjectWatcher::Delegate. + virtual void OnObjectSignaled(HANDLE object) OVERRIDE; + + // Implements base::MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + scoped_refptr<HidConnectionWin> connection_; + scoped_refptr<net::IOBufferWithSize> target_buffer_; + scoped_refptr<net::IOBufferWithSize> receive_buffer_; + HidConnection::IOCallback callback_; + OVERLAPPED overlapped_; + base::win::ScopedHandle event_; + base::win::ObjectWatcher watcher_; + + private: + friend class base::RefCounted<PendingHidTransfer>; + + virtual ~PendingHidTransfer(); + + DISALLOW_COPY_AND_ASSIGN(PendingHidTransfer); +}; + +PendingHidTransfer::PendingHidTransfer( + scoped_refptr<HidConnectionWin> connection, + scoped_refptr<net::IOBufferWithSize> target_buffer, + scoped_refptr<net::IOBufferWithSize> receive_buffer, + HidConnection::IOCallback callback) + : connection_(connection), + target_buffer_(target_buffer), + receive_buffer_(receive_buffer), + callback_(callback), + event_(CreateEvent(NULL, FALSE, FALSE, NULL)) { + memset(&overlapped_, 0, sizeof(OVERLAPPED)); + overlapped_.hEvent = event_.Get(); +} + +PendingHidTransfer::~PendingHidTransfer() { + base::MessageLoop::current()->RemoveDestructionObserver(this); +} + +void PendingHidTransfer::TakeResultFromWindowsAPI(BOOL result) { + if (result || GetLastError() != ERROR_IO_PENDING) { + connection_->OnTransferFinished(this); + } else { + base::MessageLoop::current()->AddDestructionObserver(this); + AddRef(); + watcher_.StartWatching(event_.Get(), this); + } +} + +void PendingHidTransfer::OnObjectSignaled(HANDLE event_handle) { + connection_->OnTransferFinished(this); + Release(); +} + +void PendingHidTransfer::WillDestroyCurrentMessageLoop() { + watcher_.StopWatching(); + connection_->OnTransferCanceled(this); +} + +HidConnectionWin::HidConnectionWin(const HidDeviceInfo& device_info) + : HidConnection(device_info) { + DCHECK(thread_checker_.CalledOnValidThread()); + file_.Set(CreateFileA(device_info.device_id.c_str(), + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL)); + + if (!file_.IsValid() && + GetLastError() == base::File::FILE_ERROR_ACCESS_DENIED) { + file_.Set(CreateFileA(device_info.device_id.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL)); + } +} + +bool HidConnectionWin::available() const { + return file_.IsValid(); +} + +HidConnectionWin::~HidConnectionWin() { + DCHECK(thread_checker_.CalledOnValidThread()); + CancelIo(file_.Get()); +} + +void HidConnectionWin::Read(scoped_refptr<net::IOBufferWithSize> buffer, + const HidConnection::IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (device_info().input_report_size == 0) { + // The device does not support input reports. + callback.Run(false, 0); + return; + } + + // This fairly awkward logic is correct: If Windows does not expect a device + // to supply a report ID in its input reports, it requires the buffer to be + // 1 byte larger than what the device actually sends. + int receive_buffer_size = device_info().input_report_size; + int expected_buffer_size = receive_buffer_size; + if (!device_info().has_report_id) + expected_buffer_size -= 1; + + if (buffer->size() < expected_buffer_size) { + callback.Run(false, 0); + return; + } + + scoped_refptr<net::IOBufferWithSize> receive_buffer(buffer); + if (receive_buffer_size != expected_buffer_size) + receive_buffer = new net::IOBufferWithSize(receive_buffer_size); + scoped_refptr<PendingHidTransfer> transfer( + new PendingHidTransfer(this, buffer, receive_buffer, callback)); + transfers_.insert(transfer); + transfer->TakeResultFromWindowsAPI( + ReadFile(file_.Get(), + receive_buffer->data(), + static_cast<DWORD>(receive_buffer->size()), + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const HidConnection::IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (device_info().output_report_size == 0) { + // The device does not support output reports. + callback.Run(false, 0); + return; + } + + // The Windows API always wants either a report ID (if supported) or + // zero at the front of every output report. + scoped_refptr<net::IOBufferWithSize> output_buffer(buffer); + output_buffer = new net::IOBufferWithSize(buffer->size() + 1); + output_buffer->data()[0] = report_id; + memcpy(output_buffer->data() + 1, buffer->data(), buffer->size()); + + scoped_refptr<PendingHidTransfer> transfer( + new PendingHidTransfer(this, output_buffer, NULL, callback)); + transfers_.insert(transfer); + transfer->TakeResultFromWindowsAPI( + WriteFile(file_.Get(), + output_buffer->data(), + static_cast<DWORD>(output_buffer->size()), + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::GetFeatureReport( + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (device_info().feature_report_size == 0) { + // The device does not support feature reports. + callback.Run(false, 0); + return; + } + + int receive_buffer_size = device_info().feature_report_size; + int expected_buffer_size = receive_buffer_size; + if (!device_info().has_report_id) + expected_buffer_size -= 1; + if (buffer->size() < expected_buffer_size) { + callback.Run(false, 0); + return; + } + + scoped_refptr<net::IOBufferWithSize> receive_buffer(buffer); + if (receive_buffer_size != expected_buffer_size) + receive_buffer = new net::IOBufferWithSize(receive_buffer_size); + + // The first byte of the destination buffer is the report ID being requested. + receive_buffer->data()[0] = report_id; + scoped_refptr<PendingHidTransfer> transfer( + new PendingHidTransfer(this, buffer, receive_buffer, callback)); + transfers_.insert(transfer); + transfer->TakeResultFromWindowsAPI( + DeviceIoControl(file_.Get(), + IOCTL_HID_GET_FEATURE, + NULL, + 0, + receive_buffer->data(), + static_cast<DWORD>(receive_buffer->size()), + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::SendFeatureReport( + uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (device_info().feature_report_size == 0) { + // The device does not support feature reports. + callback.Run(false, 0); + return; + } + + // The Windows API always wants either a report ID (if supported) or + // zero at the front of every output report. + scoped_refptr<net::IOBufferWithSize> output_buffer(buffer); + output_buffer = new net::IOBufferWithSize(buffer->size() + 1); + output_buffer->data()[0] = report_id; + memcpy(output_buffer->data() + 1, buffer->data(), buffer->size()); + + scoped_refptr<PendingHidTransfer> transfer( + new PendingHidTransfer(this, output_buffer, NULL, callback)); + transfer->TakeResultFromWindowsAPI( + DeviceIoControl(file_.Get(), + IOCTL_HID_SET_FEATURE, + output_buffer->data(), + static_cast<DWORD>(output_buffer->size()), + NULL, + 0, + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::OnTransferFinished( + scoped_refptr<PendingHidTransfer> transfer) { + DWORD bytes_transferred; + transfers_.erase(transfer); + if (GetOverlappedResult( + file_, transfer->GetOverlapped(), &bytes_transferred, FALSE)) { + if (bytes_transferred == 0) + transfer->callback_.Run(true, 0); + // If this is an input transfer and the receive buffer is not the same as + // the target buffer, we need to copy the receive buffer into the target + // buffer, discarding the first byte. This is because the target buffer's + // owner is not expecting a report ID but Windows will always provide one. + if (transfer->receive_buffer_ && + transfer->receive_buffer_ != transfer->target_buffer_) { + // Move one byte forward. + --bytes_transferred; + memcpy(transfer->target_buffer_->data(), + transfer->receive_buffer_->data() + 1, + bytes_transferred); + } + transfer->callback_.Run(true, bytes_transferred); + } else { + transfer->callback_.Run(false, 0); + } +} + +void HidConnectionWin::OnTransferCanceled( + scoped_refptr<PendingHidTransfer> transfer) { + transfers_.erase(transfer); + transfer->callback_.Run(false, 0); +} + +} // namespace device diff --git a/chromium/device/hid/hid_connection_win.h b/chromium/device/hid/hid_connection_win.h new file mode 100644 index 00000000000..263897a7eef --- /dev/null +++ b/chromium/device/hid/hid_connection_win.h @@ -0,0 +1,57 @@ +// Copyright (c) 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 DEVICE_HID_HID_CONNECTION_WIN_H_ +#define DEVICE_HID_HID_CONNECTION_WIN_H_ + +#include <windows.h> + +#include <set> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_checker.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_device_info.h" + +namespace device { + +struct PendingHidTransfer; + +class HidConnectionWin : public HidConnection { + public: + explicit HidConnectionWin(const HidDeviceInfo& device_info); + + bool available() const; + + virtual void Read(scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void Write(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void GetFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + virtual void SendFeatureReport(uint8_t report_id, + scoped_refptr<net::IOBufferWithSize> buffer, + const IOCallback& callback) OVERRIDE; + + void OnTransferFinished(scoped_refptr<PendingHidTransfer> transfer); + void OnTransferCanceled(scoped_refptr<PendingHidTransfer> transfer); + + private: + ~HidConnectionWin(); + + base::win::ScopedHandle file_; + std::set<scoped_refptr<PendingHidTransfer> > transfers_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(HidConnectionWin); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_WIN_H_ diff --git a/chromium/device/hid/hid_device_info.cc b/chromium/device/hid/hid_device_info.cc new file mode 100644 index 00000000000..89be442b90a --- /dev/null +++ b/chromium/device/hid/hid_device_info.cc @@ -0,0 +1,25 @@ +// 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 "device/hid/hid_device_info.h" + +namespace device { + +#if !defined(OS_MACOSX) +const char kInvalidHidDeviceId[] = ""; +#endif + +HidDeviceInfo::HidDeviceInfo() + : device_id(kInvalidHidDeviceId), + bus_type(kHIDBusTypeUSB), + vendor_id(0), + product_id(0), + input_report_size(0), + output_report_size(0), + feature_report_size(0), + has_report_id(false) {} + +HidDeviceInfo::~HidDeviceInfo() {} + +} // namespace device diff --git a/chromium/device/hid/hid_device_info.h b/chromium/device/hid/hid_device_info.h new file mode 100644 index 00000000000..1b143c22535 --- /dev/null +++ b/chromium/device/hid/hid_device_info.h @@ -0,0 +1,55 @@ +// 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 DEVICE_HID_HID_DEVICE_INFO_H_ +#define DEVICE_HID_HID_DEVICE_INFO_H_ + +#include <string> +#include <vector> + +#include "build/build_config.h" +#include "device/hid/hid_usage_and_page.h" + +#if defined(OS_MACOSX) +#include <IOKit/hid/IOHIDDevice.h> +#endif + +namespace device { + +enum HidBusType { + kHIDBusTypeUSB = 0, + kHIDBusTypeBluetooth = 1, +}; + +#if defined(OS_MACOSX) +typedef IOHIDDeviceRef HidDeviceId; +const HidDeviceId kInvalidHidDeviceId = NULL; +#else +typedef std::string HidDeviceId; +extern const char kInvalidHidDeviceId[]; +#endif + +struct HidDeviceInfo { + HidDeviceInfo(); + ~HidDeviceInfo(); + + HidDeviceId device_id; + + HidBusType bus_type; + uint16_t vendor_id; + uint16_t product_id; + + int input_report_size; + int output_report_size; + int feature_report_size; + std::vector<HidUsageAndPage> usages; + bool has_report_id; + + std::string product_name; + std::string serial_number; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_DEVICE_INFO_H_ diff --git a/chromium/device/hid/hid_report_descriptor.cc b/chromium/device/hid/hid_report_descriptor.cc new file mode 100644 index 00000000000..f2cb0f4901f --- /dev/null +++ b/chromium/device/hid/hid_report_descriptor.cc @@ -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. + +#include "device/hid/hid_report_descriptor.h" + +#include "base/stl_util.h" + +namespace device { + +HidReportDescriptor::HidReportDescriptor(const uint8_t* bytes, size_t size) { + size_t header_index = 0; + HidReportDescriptorItem* item = NULL; + while (header_index < size) { + item = new HidReportDescriptorItem(&bytes[header_index], item); + items_.push_back(linked_ptr<HidReportDescriptorItem>(item)); + header_index += item->GetSize(); + } +} + +HidReportDescriptor::~HidReportDescriptor() {} + +void HidReportDescriptor::GetTopLevelCollections( + std::vector<HidUsageAndPage>* topLevelCollections) { + DCHECK(topLevelCollections); + STLClearObject(topLevelCollections); + + for (std::vector<linked_ptr<HidReportDescriptorItem> >::const_iterator + items_iter = items().begin(); + items_iter != items().end(); + ++items_iter) { + linked_ptr<HidReportDescriptorItem> item = *items_iter; + + bool isTopLevelCollection = + item->tag() == HidReportDescriptorItem::kTagCollection && + item->parent() == NULL; + + if (isTopLevelCollection) { + uint16_t collection_usage = 0; + HidUsageAndPage::Page collection_usage_page = + HidUsageAndPage::kPageUndefined; + + HidReportDescriptorItem* usage = item->previous(); + if (usage && usage->tag() == HidReportDescriptorItem::kTagUsage) { + collection_usage = usage->GetShortData(); + } + + HidReportDescriptorItem* usage_page = usage->previous(); + if (usage_page && + usage_page->tag() == HidReportDescriptorItem::kTagUsagePage) { + collection_usage_page = + (HidUsageAndPage::Page)usage_page->GetShortData(); + } + + topLevelCollections->push_back( + HidUsageAndPage(collection_usage, collection_usage_page)); + } + } +} + +} // namespace device diff --git a/chromium/device/hid/hid_report_descriptor.h b/chromium/device/hid/hid_report_descriptor.h new file mode 100644 index 00000000000..fa67fa43c29 --- /dev/null +++ b/chromium/device/hid/hid_report_descriptor.h @@ -0,0 +1,38 @@ +// 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 DEVICE_HID_HID_REPORT_DESCRIPTOR_H_ +#define DEVICE_HID_HID_REPORT_DESCRIPTOR_H_ + +#include <vector> + +#include "base/memory/linked_ptr.h" +#include "device/hid/hid_report_descriptor_item.h" +#include "device/hid/hid_usage_and_page.h" + +namespace device { + +// HID report descriptor. +// See section 6.2.2 of HID specifications (v1.11). +class HidReportDescriptor { + + public: + HidReportDescriptor(const uint8_t* bytes, size_t size); + ~HidReportDescriptor(); + + const std::vector<linked_ptr<HidReportDescriptorItem> >& items() const { + return items_; + } + + // Returns HID usages of top-level collections present in the descriptor. + void GetTopLevelCollections( + std::vector<HidUsageAndPage>* topLevelCollections); + + private: + std::vector<linked_ptr<HidReportDescriptorItem> > items_; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_REPORT_DESCRIPTOR_H_ diff --git a/chromium/device/hid/hid_report_descriptor_item.cc b/chromium/device/hid/hid_report_descriptor_item.cc new file mode 100644 index 00000000000..bdd03ce0ebd --- /dev/null +++ b/chromium/device/hid/hid_report_descriptor_item.cc @@ -0,0 +1,112 @@ +// 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 "device/hid/hid_report_descriptor_item.h" + +#include <stdlib.h> + +#include "base/logging.h" +#include "device/hid/hid_usage_and_page.h" + +namespace device { + +namespace { + +struct Header { + uint8_t size : 2; + uint8_t type : 2; + uint8_t tag : 4; +}; + +} // namespace + +HidReportDescriptorItem::HidReportDescriptorItem( + const uint8_t* bytes, + HidReportDescriptorItem* previous) + : previous_(previous), next_(NULL), parent_(NULL), shortData_(0) { + Header* header = (Header*)&bytes[0]; + tag_ = (Tag)(header->tag << 2 | header->type); + + if (IsLong()) { + // In a long item, payload size is the second byte. + payload_size_ = bytes[1]; + } else { + payload_size_ = header->size; + DCHECK(payload_size_ <= sizeof(shortData_)); + memcpy(&shortData_, &bytes[GetHeaderSize()], payload_size()); + } + + if (previous) { + DCHECK(!previous->next_); + previous->next_ = this; + switch (previous->tag()) { + case kTagCollection: + parent_ = previous; + break; + default: + break; + } + if (!parent_) { + switch (tag()) { + case kTagEndCollection: + if (previous->parent()) { + parent_ = previous->parent()->parent(); + } + break; + default: + parent_ = previous->parent(); + break; + } + } + } +} + +size_t HidReportDescriptorItem::GetDepth() const { + HidReportDescriptorItem* parent_item = parent(); + if (parent_item) + return parent_item->GetDepth() + 1; + return 0; +} + +bool HidReportDescriptorItem::IsLong() const { return tag() == kTagLong; } + +size_t HidReportDescriptorItem::GetHeaderSize() const { + return IsLong() ? 3 : 1; +} + +size_t HidReportDescriptorItem::GetSize() const { + return GetHeaderSize() + payload_size(); +} + +uint32_t HidReportDescriptorItem::GetShortData() const { + DCHECK(!IsLong()); + return shortData_; +} + +HidReportDescriptorItem::CollectionType +HidReportDescriptorItem::GetCollectionTypeFromValue(uint32_t value) { + switch (value) { + case 0x00: + return kCollectionTypePhysical; + case 0x01: + return kCollectionTypePhysical; + case 0x02: + return kCollectionTypePhysical; + case 0x03: + return kCollectionTypePhysical; + case 0x04: + return kCollectionTypePhysical; + case 0x05: + return kCollectionTypePhysical; + case 0x06: + return kCollectionTypePhysical; + default: + break; + } + if (0x80 < value && value < 0xFF) + return kCollectionTypeVendor; + return kCollectionTypeReserved; +} + +} // namespace device diff --git a/chromium/device/hid/hid_report_descriptor_item.h b/chromium/device/hid/hid_report_descriptor_item.h new file mode 100644 index 00000000000..d3392330709 --- /dev/null +++ b/chromium/device/hid/hid_report_descriptor_item.h @@ -0,0 +1,183 @@ +// 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 DEVICE_HID_HID_REPORT_DESCRIPTOR_ITEM_H_ +#define DEVICE_HID_HID_REPORT_DESCRIPTOR_ITEM_H_ + +#include "base/basictypes.h" + +namespace device { + +// An element of a HID report descriptor. +class HidReportDescriptorItem { + private: + friend class HidReportDescriptor; + + enum Type { + kTypeMain = 0, + kTypeGlobal = 1, + kTypeLocal = 2, + kTypeReserved = 3 + }; + + enum MainTag { + kMainTagDefault = 0x00, // 0000 + kMainTagInput = 0x08, // 1000 + kMainTagOutput = 0x09, // 1001 + kMainTagFeature = 0x0B, // 1011 + kMainTagCollection = 0x0A, // 1010 + kMainTagEndCollection = 0x0C // 1100 + }; + + enum GlobalTag { + kGlobalTagUsagePage = 0x00, // 0000 + kGlobalTagLogicalMinimum = 0x01, // 0001 + kGlobalTagLogicalMaximum = 0x02, // 0010 + kGlobalTagPhysicalMinimum = 0x03, // 0011 + kGlobalTagPhysicalMaximum = 0x04, // 0100 + kGlobalTagUnitExponent = 0x05, // 0101 + kGlobalTagUnit = 0x06, // 0110 + kGlobalTagReportSize = 0x07, // 0111 + kGlobalTagReportId = 0x08, // 1000 + kGlobalTagReportCount = 0x09, // 1001 + kGlobalTagPush = 0x0A, // 1010 + kGlobalTagPop = 0x0B // 1011 + }; + + enum LocalTag { + kLocalTagUsage = 0x00, // 0000 + kLocalTagUsageMinimum = 0x01, // 0001 + kLocalTagUsageMaximum = 0x02, // 0010 + kLocalTagDesignatorIndex = 0x03, // 0011 + kLocalTagDesignatorMinimum = 0x04, // 0100 + kLocalTagDesignatorMaximum = 0x05, // 0101 + kLocalTagStringIndex = 0x07, // 0111 + kLocalTagStringMinimum = 0x08, // 1000 + kLocalTagStringMaximum = 0x09, // 1001 + kLocalTagDelimiter = 0x0A // 1010 + }; + + enum ReservedTag { + kReservedTagLong = 0xF // 1111 + }; + + public: + enum Tag { + kTagDefault = kMainTagDefault << 2 | kTypeMain, + kTagInput = kMainTagInput << 2 | kTypeMain, + kTagOutput = kMainTagOutput << 2 | kTypeMain, + kTagFeature = kMainTagFeature << 2 | kTypeMain, + kTagCollection = kMainTagCollection << 2 | kTypeMain, + kTagEndCollection = kMainTagEndCollection << 2 | kTypeMain, + kTagUsagePage = kGlobalTagUsagePage << 2 | kTypeGlobal, + kTagLogicalMinimum = kGlobalTagLogicalMinimum << 2 | kTypeGlobal, + kTagLogicalMaximum = kGlobalTagLogicalMaximum << 2 | kTypeGlobal, + kTagPhysicalMinimum = kGlobalTagPhysicalMinimum << 2 | kTypeGlobal, + kTagPhysicalMaximum = kGlobalTagPhysicalMaximum << 2 | kTypeGlobal, + kTagUnitExponent = kGlobalTagUnitExponent << 2 | kTypeGlobal, + kTagUnit = kGlobalTagUnit << 2 | kTypeGlobal, + kTagReportSize = kGlobalTagReportSize << 2 | kTypeGlobal, + kTagReportId = kGlobalTagReportId << 2 | kTypeGlobal, + kTagReportCount = kGlobalTagReportCount << 2 | kTypeGlobal, + kTagPush = kGlobalTagPush << 2 | kTypeGlobal, + kTagPop = kGlobalTagPop << 2 | kTypeGlobal, + kTagUsage = kLocalTagUsage << 2 | kTypeLocal, + kTagUsageMinimum = kLocalTagUsageMinimum << 2 | kTypeLocal, + kTagUsageMaximum = kLocalTagUsageMaximum << 2 | kTypeLocal, + kTagDesignatorIndex = kLocalTagDesignatorIndex << 2 | kTypeLocal, + kTagDesignatorMinimum = kLocalTagDesignatorMinimum << 2 | kTypeLocal, + kTagDesignatorMaximum = kLocalTagDesignatorMaximum << 2 | kTypeLocal, + kTagStringIndex = kLocalTagStringIndex << 2 | kTypeLocal, + kTagStringMinimum = kLocalTagStringMinimum << 2 | kTypeLocal, + kTagStringMaximum = kLocalTagStringMaximum << 2 | kTypeLocal, + kTagDelimiter = kLocalTagDelimiter << 2 | kTypeLocal, + kTagLong = kReservedTagLong << 2 | kTypeReserved + }; + + // HID Input/Output/Feature report information. + // Can be retrieved from GetShortData() + // when item.tag() == HidReportDescriptorItem::kTagInput + // or HidReportDescriptorItem::kTagOutput + // or HidReportDescriptorItem::kTagFeature + struct ReportInfo { + uint8_t data_or_constant : 1; + uint8_t array_or_variable : 1; + uint8_t absolute_or_relative : 1; + uint8_t wrap : 1; + uint8_t linear : 1; + uint8_t preferred : 1; + uint8_t null : 1; + uint8_t reserved_1 : 1; + uint8_t bit_field_or_buffer : 1; + uint8_t reserved_2 : 1; + }; + + // HID collection type. + // Can be retrieved from GetShortData() + // when item.tag() == HidReportDescriptorItem::kTagCollection + enum CollectionType { + kCollectionTypePhysical, + kCollectionTypeApplication, + kCollectionTypeLogical, + kCollectionTypeReport, + kCollectionTypeNamedArray, + kCollectionTypeUsageSwitch, + kCollectionTypeUsageModifier, + kCollectionTypeReserved, + kCollectionTypeVendor + }; + + private: + HidReportDescriptorItem(const uint8_t* bytes, + HidReportDescriptorItem* previous); + + public: + ~HidReportDescriptorItem() {} + + // Previous element in report descriptor. + // Owned by descriptor instance. + HidReportDescriptorItem* previous() const { + return previous_; + }; + // Next element in report descriptor. + // Owned by descriptor instance. + HidReportDescriptorItem* next() const { + return next_; + }; + // Parent element in report descriptor. + // Owned by descriptor instance. + // Can be NULL. + HidReportDescriptorItem* parent() const { + return parent_; + }; + // Level in Parent-Children relationship tree. + // 0 for top-level items (parent()==NULL). + // 1 if parent() is top-level. + // 2 if parent() has a top-level parent. Etc. + size_t GetDepth() const; + Tag tag() const { return tag_; } + // Returns true for a long item, false otherwise. + bool IsLong() const; + // Raw data of a short item. + // Not valid for a long item. + uint32_t GetShortData() const; + + static CollectionType GetCollectionTypeFromValue(uint32_t value); + + private: + size_t GetHeaderSize() const; + size_t payload_size() const { return payload_size_; } + size_t GetSize() const; + + HidReportDescriptorItem* previous_; + HidReportDescriptorItem* next_; + HidReportDescriptorItem* parent_; + Tag tag_; + uint32_t shortData_; + size_t payload_size_; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_REPORT_DESCRIPTOR_ITEM_H_ diff --git a/chromium/device/hid/hid_report_descriptor_unittest.cc b/chromium/device/hid/hid_report_descriptor_unittest.cc new file mode 100644 index 00000000000..0d258891081 --- /dev/null +++ b/chromium/device/hid/hid_report_descriptor_unittest.cc @@ -0,0 +1,613 @@ +// Copyright (c) 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 <sstream> + +#include "device/hid/hid_report_descriptor.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace testing; + +namespace device { + +namespace { + +std::ostream& operator<<(std::ostream& os, + const HidUsageAndPage::Page& usage_page) { + switch (usage_page) { + case HidUsageAndPage::kPageUndefined: + os << "Undefined"; + break; + case HidUsageAndPage::kPageGenericDesktop: + os << "Generic Desktop"; + break; + case HidUsageAndPage::kPageSimulation: + os << "Simulation"; + break; + case HidUsageAndPage::kPageVirtualReality: + os << "Virtual Reality"; + break; + case HidUsageAndPage::kPageSport: + os << "Sport"; + break; + case HidUsageAndPage::kPageGame: + os << "Game"; + break; + case HidUsageAndPage::kPageKeyboard: + os << "Keyboard"; + break; + case HidUsageAndPage::kPageLed: + os << "Led"; + break; + case HidUsageAndPage::kPageButton: + os << "Button"; + break; + case HidUsageAndPage::kPageOrdinal: + os << "Ordinal"; + break; + case HidUsageAndPage::kPageTelephony: + os << "Telephony"; + break; + case HidUsageAndPage::kPageConsumer: + os << "Consumer"; + break; + case HidUsageAndPage::kPageDigitizer: + os << "Digitizer"; + break; + case HidUsageAndPage::kPagePidPage: + os << "Pid Page"; + break; + case HidUsageAndPage::kPageUnicode: + os << "Unicode"; + break; + case HidUsageAndPage::kPageAlphanumericDisplay: + os << "Alphanumeric Display"; + break; + case HidUsageAndPage::kPageMedicalInstruments: + os << "Medical Instruments"; + break; + case HidUsageAndPage::kPageMonitor0: + os << "Monitor 0"; + break; + case HidUsageAndPage::kPageMonitor1: + os << "Monitor 1"; + break; + case HidUsageAndPage::kPageMonitor2: + os << "Monitor 2"; + break; + case HidUsageAndPage::kPageMonitor3: + os << "Monitor 3"; + break; + case HidUsageAndPage::kPagePower0: + os << "Power 0"; + break; + case HidUsageAndPage::kPagePower1: + os << "Power 1"; + break; + case HidUsageAndPage::kPagePower2: + os << "Power 2"; + break; + case HidUsageAndPage::kPagePower3: + os << "Power 3"; + break; + case HidUsageAndPage::kPageBarCodeScanner: + os << "Bar Code Scanner"; + break; + case HidUsageAndPage::kPageScale: + os << "Scale"; + break; + case HidUsageAndPage::kPageMagneticStripeReader: + os << "Magnetic Stripe Reader"; + break; + case HidUsageAndPage::kPageReservedPointOfSale: + os << "Reserved Point Of Sale"; + break; + case HidUsageAndPage::kPageCameraControl: + os << "Camera Control"; + break; + case HidUsageAndPage::kPageArcade: + os << "Arcade"; + break; + case HidUsageAndPage::kPageVendor: + os << "Vendor"; + break; + case HidUsageAndPage::kPageMediaCenter: + os << "Media Center"; + break; + default: + NOTREACHED(); + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, + const HidUsageAndPage& usage_and_page) { + os << "Usage Page: " << usage_and_page.usage_page << ", Usage: " + << "0x" << std::hex << std::uppercase << usage_and_page.usage; + return os; +} + +std::ostream& operator<<(std::ostream& os, + const HidReportDescriptorItem::Tag& tag) { + switch (tag) { + case HidReportDescriptorItem::kTagDefault: + os << "Default"; + break; + case HidReportDescriptorItem::kTagInput: + os << "Input"; + break; + case HidReportDescriptorItem::kTagOutput: + os << "Output"; + break; + case HidReportDescriptorItem::kTagFeature: + os << "Feature"; + break; + case HidReportDescriptorItem::kTagCollection: + os << "Collection"; + break; + case HidReportDescriptorItem::kTagEndCollection: + os << "End Collection"; + break; + case HidReportDescriptorItem::kTagUsagePage: + os << "Usage Page"; + break; + case HidReportDescriptorItem::kTagLogicalMinimum: + os << "Logical Minimum"; + break; + case HidReportDescriptorItem::kTagLogicalMaximum: + os << "Logical Maximum"; + break; + case HidReportDescriptorItem::kTagPhysicalMinimum: + os << "Physical Minimum"; + break; + case HidReportDescriptorItem::kTagPhysicalMaximum: + os << "Physical Maximum"; + break; + case HidReportDescriptorItem::kTagUnitExponent: + os << "Unit Exponent"; + break; + case HidReportDescriptorItem::kTagUnit: + os << "Unit"; + break; + case HidReportDescriptorItem::kTagReportSize: + os << "Report Size"; + break; + case HidReportDescriptorItem::kTagReportId: + os << "Report ID"; + break; + case HidReportDescriptorItem::kTagReportCount: + os << "Report Count"; + break; + case HidReportDescriptorItem::kTagPush: + os << "Push"; + break; + case HidReportDescriptorItem::kTagPop: + os << "Pop"; + break; + case HidReportDescriptorItem::kTagUsage: + os << "Usage"; + break; + case HidReportDescriptorItem::kTagUsageMinimum: + os << "Usage Minimum"; + break; + case HidReportDescriptorItem::kTagUsageMaximum: + os << "Usage Maximum"; + break; + case HidReportDescriptorItem::kTagDesignatorIndex: + os << "Designator Index"; + break; + case HidReportDescriptorItem::kTagDesignatorMinimum: + os << "Designator Minimum"; + break; + case HidReportDescriptorItem::kTagDesignatorMaximum: + os << "Designator Maximum"; + break; + case HidReportDescriptorItem::kTagStringIndex: + os << "String Index"; + break; + case HidReportDescriptorItem::kTagStringMinimum: + os << "String Minimum"; + break; + case HidReportDescriptorItem::kTagStringMaximum: + os << "String Maximum"; + break; + case HidReportDescriptorItem::kTagDelimiter: + os << "Delimeter"; + break; + case HidReportDescriptorItem::kTagLong: + os << "Long"; + break; + default: + NOTREACHED(); + break; + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, + const HidReportDescriptorItem::ReportInfo& data) { + if (data.data_or_constant) + os << "Con"; + else + os << "Dat"; + if (data.array_or_variable) + os << "|Arr"; + else + os << "|Var"; + if (data.absolute_or_relative) + os << "|Abs"; + else + os << "|Rel"; + if (data.wrap) + os << "|Wrp"; + else + os << "|NoWrp"; + if (data.linear) + os << "|NoLin"; + else + os << "|Lin"; + if (data.preferred) + os << "|NoPrf"; + else + os << "|Prf"; + if (data.null) + os << "|Null"; + else + os << "|NoNull"; + if (data.bit_field_or_buffer) + os << "|Buff"; + else + os << "|BitF"; + return os; +} + +std::ostream& operator<<(std::ostream& os, + const HidReportDescriptorItem::CollectionType& type) { + switch (type) { + case HidReportDescriptorItem::kCollectionTypePhysical: + os << "Physical"; + break; + case HidReportDescriptorItem::kCollectionTypeApplication: + os << "Application"; + break; + case HidReportDescriptorItem::kCollectionTypeLogical: + os << "Logical"; + break; + case HidReportDescriptorItem::kCollectionTypeReport: + os << "Report"; + break; + case HidReportDescriptorItem::kCollectionTypeNamedArray: + os << "Named Array"; + break; + case HidReportDescriptorItem::kCollectionTypeUsageSwitch: + os << "Usage Switch"; + break; + case HidReportDescriptorItem::kCollectionTypeUsageModifier: + os << "Usage Modifier"; + break; + case HidReportDescriptorItem::kCollectionTypeReserved: + os << "Reserved"; + break; + case HidReportDescriptorItem::kCollectionTypeVendor: + os << "Vendor"; + break; + default: + NOTREACHED(); + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, + const HidReportDescriptorItem& item) { + HidReportDescriptorItem::Tag item_tag = item.tag(); + uint32_t data = item.GetShortData(); + + std::ostringstream sstr; + sstr << item_tag; + sstr << " ("; + + long pos = sstr.tellp(); + switch (item_tag) { + case HidReportDescriptorItem::kTagDefault: + case HidReportDescriptorItem::kTagEndCollection: + case HidReportDescriptorItem::kTagPush: + case HidReportDescriptorItem::kTagPop: + case HidReportDescriptorItem::kTagLong: + break; + + case HidReportDescriptorItem::kTagCollection: + sstr << HidReportDescriptorItem::GetCollectionTypeFromValue(data); + break; + + case HidReportDescriptorItem::kTagInput: + case HidReportDescriptorItem::kTagOutput: + case HidReportDescriptorItem::kTagFeature: + sstr << (HidReportDescriptorItem::ReportInfo&)data; + break; + + case HidReportDescriptorItem::kTagUsagePage: + sstr << (HidUsageAndPage::Page)data; + break; + + case HidReportDescriptorItem::kTagUsage: + case HidReportDescriptorItem::kTagReportId: + sstr << "0x" << std::hex << std::uppercase << data; + break; + + default: + sstr << data; + break; + } + if (pos == sstr.tellp()) { + std::string str = sstr.str(); + str.erase(str.end() - 2, str.end()); + os << str; + } else { + os << sstr.str() << ")"; + } + + return os; +} + +const char kIndentStep[] = " "; + +std::ostream& operator<<(std::ostream& os, + const HidReportDescriptor& descriptor) { + for (std::vector<linked_ptr<HidReportDescriptorItem> >::const_iterator + items_iter = descriptor.items().begin(); + items_iter != descriptor.items().end(); + ++items_iter) { + linked_ptr<HidReportDescriptorItem> item = *items_iter; + size_t indentLevel = item->GetDepth(); + for (size_t i = 0; i < indentLevel; i++) + os << kIndentStep; + os << *item.get() << std::endl; + } + return os; +} + +// See 'E.6 Report Descriptor (Keyboard)' +// in HID specifications (v1.11) +const uint8_t kKeyboard[] = { + 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, + 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, + 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05, 0x75, 0x01, 0x05, + 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, + 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, + 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0xC0}; + +// See 'E.10 Report Descriptor (Mouse)' +// in HID specifications (v1.11) +const uint8_t kMouse[] = {0x05, 0x01, 0x09, 0x02, 0xA1, 0x01, 0x09, 0x01, 0xA1, + 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, 0x15, 0x00, + 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, + 0x01, 0x75, 0x05, 0x81, 0x01, 0x05, 0x01, 0x09, 0x30, + 0x09, 0x31, 0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, + 0x02, 0x81, 0x06, 0xC0, 0xC0}; + +const uint8_t kLogitechUnifyingReceiver[] = { + 0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x85, 0x10, 0x75, 0x08, + 0x95, 0x06, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x09, 0x01, 0x81, 0x00, + 0x09, 0x01, 0x91, 0x00, 0xC0, 0x06, 0x00, 0xFF, 0x09, 0x02, 0xA1, + 0x01, 0x85, 0x11, 0x75, 0x08, 0x95, 0x13, 0x15, 0x00, 0x26, 0xFF, + 0x00, 0x09, 0x02, 0x81, 0x00, 0x09, 0x02, 0x91, 0x00, 0xC0, 0x06, + 0x00, 0xFF, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x20, 0x75, 0x08, 0x95, + 0x0E, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x09, 0x41, 0x81, 0x00, 0x09, + 0x41, 0x91, 0x00, 0x85, 0x21, 0x95, 0x1F, 0x15, 0x00, 0x26, 0xFF, + 0x00, 0x09, 0x42, 0x81, 0x00, 0x09, 0x42, 0x91, 0x00, 0xC0}; + +} // namespace + +class HidReportDescriptorTest : public testing::Test { + + protected: + virtual void SetUp() OVERRIDE { descriptor_ = NULL; } + + virtual void TearDown() OVERRIDE { + if (descriptor_) { + delete descriptor_; + } + } + + public: + void ParseDescriptor(const std::string& expected, + const uint8_t* bytes, + size_t size) { + descriptor_ = new HidReportDescriptor(bytes, size); + + std::stringstream actual; + actual << *descriptor_; + + std::cout << "HID report descriptor:" << std::endl; + std::cout << actual.str(); + + // TODO(jracle@logitech.com): refactor string comparison in favor of + // testing individual fields. + ASSERT_EQ(expected, actual.str()); + } + + void GetTopLevelCollections(const std::vector<HidUsageAndPage>& expected, + const uint8_t* bytes, + size_t size) { + descriptor_ = new HidReportDescriptor(bytes, size); + + std::vector<HidUsageAndPage> actual; + descriptor_->GetTopLevelCollections(&actual); + + std::cout << "HID top-level collections:" << std::endl; + for (std::vector<HidUsageAndPage>::const_iterator iter = actual.begin(); + iter != actual.end(); + ++iter) { + std::cout << *iter << std::endl; + } + + ASSERT_THAT(actual, ContainerEq(expected)); + } + + private: + HidReportDescriptor* descriptor_; +}; + +TEST_F(HidReportDescriptorTest, ParseDescriptor_Keyboard) { + const char expected[] = { + "Usage Page (Generic Desktop)\n" + "Usage (0x6)\n" + "Collection (Physical)\n" + " Usage Page (Keyboard)\n" + " Usage Minimum (224)\n" + " Usage Maximum (231)\n" + " Logical Minimum (0)\n" + " Logical Maximum (1)\n" + " Report Size (1)\n" + " Report Count (8)\n" + " Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Report Count (1)\n" + " Report Size (8)\n" + " Input (Con|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Report Count (5)\n" + " Report Size (1)\n" + " Usage Page (Led)\n" + " Usage Minimum (1)\n" + " Usage Maximum (5)\n" + " Output (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Report Count (1)\n" + " Report Size (3)\n" + " Output (Con|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Report Count (6)\n" + " Report Size (8)\n" + " Logical Minimum (0)\n" + " Logical Maximum (101)\n" + " Usage Page (Keyboard)\n" + " Usage Minimum (0)\n" + " Usage Maximum (101)\n" + " Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + "End Collection\n"}; + + ParseDescriptor(std::string(expected), kKeyboard, sizeof(kKeyboard)); +} + +TEST_F(HidReportDescriptorTest, TopLevelCollections_Keyboard) { + HidUsageAndPage expected[] = { + HidUsageAndPage(0x06, HidUsageAndPage::kPageGenericDesktop)}; + + GetTopLevelCollections(std::vector<HidUsageAndPage>( + expected, expected + ARRAYSIZE_UNSAFE(expected)), + kKeyboard, + sizeof(kKeyboard)); +} + +TEST_F(HidReportDescriptorTest, ParseDescriptor_Mouse) { + const char expected[] = { + "Usage Page (Generic Desktop)\n" + "Usage (0x2)\n" + "Collection (Physical)\n" + " Usage (0x1)\n" + " Collection (Physical)\n" + " Usage Page (Button)\n" + " Usage Minimum (1)\n" + " Usage Maximum (3)\n" + " Logical Minimum (0)\n" + " Logical Maximum (1)\n" + " Report Count (3)\n" + " Report Size (1)\n" + " Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Report Count (1)\n" + " Report Size (5)\n" + " Input (Con|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Usage Page (Generic Desktop)\n" + " Usage (0x30)\n" + " Usage (0x31)\n" + " Logical Minimum (129)\n" + " Logical Maximum (127)\n" + " Report Size (8)\n" + " Report Count (2)\n" + " Input (Dat|Arr|Abs|NoWrp|Lin|Prf|NoNull|BitF)\n" + " End Collection\n" + "End Collection\n"}; + + ParseDescriptor(std::string(expected), kMouse, sizeof(kMouse)); +} + +TEST_F(HidReportDescriptorTest, TopLevelCollections_Mouse) { + HidUsageAndPage expected[] = { + HidUsageAndPage(0x02, HidUsageAndPage::kPageGenericDesktop)}; + + GetTopLevelCollections(std::vector<HidUsageAndPage>( + expected, expected + ARRAYSIZE_UNSAFE(expected)), + kMouse, + sizeof(kMouse)); +} + +TEST_F(HidReportDescriptorTest, ParseDescriptor_LogitechUnifyingReceiver) { + const char expected[] = { + "Usage Page (Vendor)\n" + "Usage (0x1)\n" + "Collection (Physical)\n" + " Report ID (0x10)\n" + " Report Size (8)\n" + " Report Count (6)\n" + " Logical Minimum (0)\n" + " Logical Maximum (255)\n" + " Usage (0x1)\n" + " Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Usage (0x1)\n" + " Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + "End Collection\n" + "Usage Page (Vendor)\n" + "Usage (0x2)\n" + "Collection (Physical)\n" + " Report ID (0x11)\n" + " Report Size (8)\n" + " Report Count (19)\n" + " Logical Minimum (0)\n" + " Logical Maximum (255)\n" + " Usage (0x2)\n" + " Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Usage (0x2)\n" + " Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + "End Collection\n" + "Usage Page (Vendor)\n" + "Usage (0x4)\n" + "Collection (Physical)\n" + " Report ID (0x20)\n" + " Report Size (8)\n" + " Report Count (14)\n" + " Logical Minimum (0)\n" + " Logical Maximum (255)\n" + " Usage (0x41)\n" + " Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Usage (0x41)\n" + " Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Report ID (0x21)\n" + " Report Count (31)\n" + " Logical Minimum (0)\n" + " Logical Maximum (255)\n" + " Usage (0x42)\n" + " Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + " Usage (0x42)\n" + " Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)\n" + "End Collection\n"}; + + ParseDescriptor(std::string(expected), + kLogitechUnifyingReceiver, + sizeof(kLogitechUnifyingReceiver)); +} + +TEST_F(HidReportDescriptorTest, TopLevelCollections_LogitechUnifyingReceiver) { + HidUsageAndPage expected[] = { + HidUsageAndPage(0x01, HidUsageAndPage::kPageVendor), + HidUsageAndPage(0x02, HidUsageAndPage::kPageVendor), + HidUsageAndPage(0x04, HidUsageAndPage::kPageVendor), }; + + GetTopLevelCollections(std::vector<HidUsageAndPage>( + expected, expected + ARRAYSIZE_UNSAFE(expected)), + kLogitechUnifyingReceiver, + sizeof(kLogitechUnifyingReceiver)); +} + +} // namespace device diff --git a/chromium/device/hid/hid_service.cc b/chromium/device/hid/hid_service.cc new file mode 100644 index 00000000000..4d24b0093f4 --- /dev/null +++ b/chromium/device/hid/hid_service.cc @@ -0,0 +1,99 @@ +// 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 "device/hid/hid_service.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/stl_util.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_LINUX) && defined(USE_UDEV) +#include "device/hid/hid_service_linux.h" +#elif defined(OS_MACOSX) +#include "device/hid/hid_service_mac.h" +#else +#include "device/hid/hid_service_win.h" +#endif + +namespace device { + +namespace { + +// The instance will be reset when message loop destroys. +base::LazyInstance<scoped_ptr<HidService> >::Leaky g_hid_service_ptr = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +void HidService::GetDevices(std::vector<HidDeviceInfo>* devices) { + DCHECK(thread_checker_.CalledOnValidThread()); + STLClearObject(devices); + for (DeviceMap::iterator it = devices_.begin(); + it != devices_.end(); + ++it) { + devices->push_back(it->second); + } +} + +// Fills in the device info struct of the given device_id. +bool HidService::GetDeviceInfo(const HidDeviceId& device_id, + HidDeviceInfo* info) const { + DeviceMap::const_iterator it = devices_.find(device_id); + if (it == devices_.end()) + return false; + *info = it->second; + return true; +} + +void HidService::WillDestroyCurrentMessageLoop() { + DCHECK(thread_checker_.CalledOnValidThread()); + g_hid_service_ptr.Get().reset(NULL); +} + +HidService::HidService() { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(thread_checker_.CalledOnValidThread()); + base::MessageLoop::current()->AddDestructionObserver(this); +} + +HidService::~HidService() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::MessageLoop::current()->RemoveDestructionObserver(this); +} + +HidService* HidService::CreateInstance() { +#if defined(OS_LINUX) && defined(USE_UDEV) + return new HidServiceLinux(); +#elif defined(OS_MACOSX) + return new HidServiceMac(); +#elif defined(OS_WIN) + return new HidServiceWin(); +#else + return NULL; +#endif +} + +void HidService::AddDevice(const HidDeviceInfo& info) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!ContainsKey(devices_, info.device_id)) { + devices_[info.device_id] = info; + } +} + +void HidService::RemoveDevice(const HidDeviceId& device_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + DeviceMap::iterator it = devices_.find(device_id); + if (it != devices_.end()) + devices_.erase(it); +} + +HidService* HidService::GetInstance() { + if (!g_hid_service_ptr.Get().get()) + g_hid_service_ptr.Get().reset(CreateInstance()); + return g_hid_service_ptr.Get().get(); +} + +} // namespace device diff --git a/chromium/device/hid/hid_service.h b/chromium/device/hid/hid_service.h new file mode 100644 index 00000000000..ee0ebb7acbb --- /dev/null +++ b/chromium/device/hid/hid_service.h @@ -0,0 +1,64 @@ +// 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 DEVICE_HID_HID_SERVICE_H_ +#define DEVICE_HID_HID_SERVICE_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_checker.h" +#include "device/hid/hid_device_info.h" + +namespace device { + +class HidConnection; + +class HidService : public base::MessageLoop::DestructionObserver { + public: + // Must be called on FILE thread. + static HidService* GetInstance(); + + // Enumerates and returns a list of device identifiers. + virtual void GetDevices(std::vector<HidDeviceInfo>* devices); + + // Fills in a DeviceInfo struct with info for the given device_id. + // Returns |true| if successful or |false| if |device_id| is invalid. + bool GetDeviceInfo(const HidDeviceId& device_id, HidDeviceInfo* info) const; + + virtual scoped_refptr<HidConnection> Connect( + const HidDeviceId& device_id) = 0; + + // Implements base::MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + protected: + friend struct base::DefaultDeleter<HidService>; + friend class HidConnectionTest; + + typedef std::map<HidDeviceId, HidDeviceInfo> DeviceMap; + + HidService(); + virtual ~HidService(); + + static HidService* CreateInstance(); + + void AddDevice(const HidDeviceInfo& info); + void RemoveDevice(const HidDeviceId& device_id); + + base::ThreadChecker thread_checker_; + + private: + DeviceMap devices_; + + DISALLOW_COPY_AND_ASSIGN(HidService); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_H_ diff --git a/chromium/device/hid/hid_service_linux.cc b/chromium/device/hid/hid_service_linux.cc new file mode 100644 index 00000000000..5257dcd874a --- /dev/null +++ b/chromium/device/hid/hid_service_linux.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 <linux/hidraw.h> +#include <sys/ioctl.h> + +#include <stdint.h> + +#include <string> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/platform_file.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "device/hid/hid_connection_linux.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_report_descriptor.h" +#include "device/hid/hid_service_linux.h" +#include "device/udev_linux/udev.h" + +namespace device { + +namespace { + +const char kHIDSubSystem[] = "hid"; +const char kHidrawSubsystem[] = "hidraw"; +const char kHIDID[] = "HID_ID"; +const char kHIDName[] = "HID_NAME"; +const char kHIDUnique[] = "HID_UNIQ"; + +} // namespace + +HidServiceLinux::HidServiceLinux() { + DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance(); + monitor->AddObserver(this); + monitor->Enumerate( + base::Bind(&HidServiceLinux::OnDeviceAdded, base::Unretained(this))); +} + +scoped_refptr<HidConnection> HidServiceLinux::Connect( + const HidDeviceId& device_id) { + HidDeviceInfo device_info; + if (!GetDeviceInfo(device_id, &device_info)) + return NULL; + + ScopedUdevDevicePtr device = + DeviceMonitorLinux::GetInstance()->GetDeviceFromPath( + device_info.device_id); + + if (device) { + std::string dev_node; + if (!FindHidrawDevNode(device.get(), &dev_node)) { + LOG(ERROR) << "Cannot open HID device as hidraw device."; + return NULL; + } + return new HidConnectionLinux(device_info, dev_node); + } + + return NULL; +} + +HidServiceLinux::~HidServiceLinux() { + if (DeviceMonitorLinux::HasInstance()) + DeviceMonitorLinux::GetInstance()->RemoveObserver(this); +} + +void HidServiceLinux::OnDeviceAdded(udev_device* device) { + if (!device) + return; + + const char* device_path = udev_device_get_syspath(device); + if (!device_path) + return; + const char* subsystem = udev_device_get_subsystem(device); + if (!subsystem || strcmp(subsystem, kHIDSubSystem) != 0) + return; + + HidDeviceInfo device_info; + device_info.device_id = device_path; + + uint32_t int_property = 0; + const char* str_property = NULL; + + const char* hid_id = udev_device_get_property_value(device, kHIDID); + if (!hid_id) + return; + + std::vector<std::string> parts; + base::SplitString(hid_id, ':', &parts); + if (parts.size() != 3) { + return; + } + + if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) { + device_info.vendor_id = int_property; + } + + if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) { + device_info.product_id = int_property; + } + + str_property = udev_device_get_property_value(device, kHIDUnique); + if (str_property != NULL) + device_info.serial_number = str_property; + + str_property = udev_device_get_property_value(device, kHIDName); + if (str_property != NULL) + device_info.product_name = str_property; + + std::string dev_node; + if (!FindHidrawDevNode(device, &dev_node)) { + LOG(ERROR) << "Cannot find device node for HID device."; + return; + } + + int flags = base::File::FLAG_OPEN | base::File::FLAG_READ; + + base::File device_file(base::FilePath(dev_node), flags); + if (!device_file.IsValid()) { + LOG(ERROR) << "Cannot open '" << dev_node << "': " + << base::File::ErrorToString(device_file.error_details()); + return; + } + + int desc_size = 0; + int res = ioctl(device_file.GetPlatformFile(), HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + LOG(ERROR) << "HIDIOCGRDESCSIZE failed."; + device_file.Close(); + return; + } + + hidraw_report_descriptor rpt_desc; + rpt_desc.size = desc_size; + + res = ioctl(device_file.GetPlatformFile(), HIDIOCGRDESC, &rpt_desc); + if (res < 0) { + LOG(ERROR) << "HIDIOCGRDESC failed."; + device_file.Close(); + return; + } + + device_file.Close(); + + HidReportDescriptor report_descriptor(rpt_desc.value, rpt_desc.size); + report_descriptor.GetTopLevelCollections(&device_info.usages); + + AddDevice(device_info); +} + +void HidServiceLinux::OnDeviceRemoved(udev_device* device) { + const char* device_path = udev_device_get_syspath(device);; + if (device_path) + RemoveDevice(device_path); +} + +bool HidServiceLinux::FindHidrawDevNode(udev_device* parent, + std::string* result) { + udev* udev = udev_device_get_udev(parent); + if (!udev) { + return false; + } + ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev)); + if (!enumerate) { + return false; + } + if (udev_enumerate_add_match_subsystem(enumerate.get(), kHidrawSubsystem)) { + return false; + } + if (udev_enumerate_scan_devices(enumerate.get())) { + return false; + } + std::string parent_path(udev_device_get_devpath(parent)); + if (parent_path.length() == 0 || *parent_path.rbegin() != '/') + parent_path += '/'; + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); + for (udev_list_entry* i = devices; i != NULL; + i = udev_list_entry_get_next(i)) { + ScopedUdevDevicePtr hid_dev( + udev_device_new_from_syspath(udev, udev_list_entry_get_name(i))); + const char* raw_path = udev_device_get_devnode(hid_dev.get()); + std::string device_path = udev_device_get_devpath(hid_dev.get()); + if (raw_path && + !device_path.compare(0, parent_path.length(), parent_path)) { + std::string sub_path = device_path.substr(parent_path.length()); + if (sub_path.substr(0, sizeof(kHidrawSubsystem) - 1) == + kHidrawSubsystem) { + *result = raw_path; + return true; + } + } + } + + return false; +} + +} // namespace device diff --git a/chromium/device/hid/hid_service_linux.h b/chromium/device/hid/hid_service_linux.h new file mode 100644 index 00000000000..8d5b115cb9e --- /dev/null +++ b/chromium/device/hid/hid_service_linux.h @@ -0,0 +1,42 @@ +// 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 DEVICE_HID_HID_SERVICE_LINUX_H_ +#define DEVICE_HID_HID_SERVICE_LINUX_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "device/hid/device_monitor_linux.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service.h" + +struct udev_device; + +namespace device { + +class HidConnection; + +class HidServiceLinux : public HidService, + public DeviceMonitorLinux::Observer { + public: + HidServiceLinux(); + + virtual scoped_refptr<HidConnection> Connect(const HidDeviceId& device_id) + OVERRIDE; + + // Implements DeviceMonitorLinux::Observer: + virtual void OnDeviceAdded(udev_device* device) OVERRIDE; + virtual void OnDeviceRemoved(udev_device* device) OVERRIDE; + + private: + virtual ~HidServiceLinux(); + + static bool FindHidrawDevNode(udev_device* parent, std::string* result); + + DISALLOW_COPY_AND_ASSIGN(HidServiceLinux); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_LINUX_H_ diff --git a/chromium/device/hid/hid_service_mac.cc b/chromium/device/hid/hid_service_mac.cc new file mode 100644 index 00000000000..ed85ec2d5cc --- /dev/null +++ b/chromium/device/hid/hid_service_mac.cc @@ -0,0 +1,192 @@ +// 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 "device/hid/hid_service_mac.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "device/hid/hid_connection_mac.h" +#include "device/hid/hid_utils_mac.h" + +namespace device { + +class HidServiceMac; + +namespace { + +typedef std::vector<IOHIDDeviceRef> HidDeviceList; + +HidServiceMac* HidServiceFromContext(void* context) { + return static_cast<HidServiceMac*>(context); +} + +// Callback for CFSetApplyFunction as used by EnumerateHidDevices. +void HidEnumerationBackInserter(const void* value, void* context) { + HidDeviceList* devices = static_cast<HidDeviceList*>(context); + const IOHIDDeviceRef device = + static_cast<IOHIDDeviceRef>(const_cast<void*>(value)); + devices->push_back(device); +} + +void EnumerateHidDevices(IOHIDManagerRef hid_manager, + HidDeviceList* device_list) { + DCHECK(device_list->size() == 0); + // Note that our ownership of each copied device is implied. + base::ScopedCFTypeRef<CFSetRef> devices(IOHIDManagerCopyDevices(hid_manager)); + if (devices) + CFSetApplyFunction(devices, HidEnumerationBackInserter, device_list); +} + +} // namespace + +HidServiceMac::HidServiceMac() { + DCHECK(thread_checker_.CalledOnValidThread()); + message_loop_ = base::MessageLoopProxy::current(); + DCHECK(message_loop_); + hid_manager_.reset(IOHIDManagerCreate(NULL, 0)); + if (!hid_manager_) { + LOG(ERROR) << "Failed to initialize HidManager"; + return; + } + DCHECK(CFGetTypeID(hid_manager_) == IOHIDManagerGetTypeID()); + IOHIDManagerOpen(hid_manager_, kIOHIDOptionsTypeNone); + IOHIDManagerSetDeviceMatching(hid_manager_, NULL); + + // Enumerate all the currently known devices. + Enumerate(); + + // Register for plug/unplug notifications. + StartWatchingDevices(); +} + +HidServiceMac::~HidServiceMac() { + StopWatchingDevices(); +} + +void HidServiceMac::StartWatchingDevices() { + DCHECK(thread_checker_.CalledOnValidThread()); + IOHIDManagerRegisterDeviceMatchingCallback( + hid_manager_, &AddDeviceCallback, this); + IOHIDManagerRegisterDeviceRemovalCallback( + hid_manager_, &RemoveDeviceCallback, this); + IOHIDManagerScheduleWithRunLoop( + hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode); +} + +void HidServiceMac::StopWatchingDevices() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!hid_manager_) + return; + IOHIDManagerUnscheduleFromRunLoop( + hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + IOHIDManagerClose(hid_manager_, kIOHIDOptionsTypeNone); +} + +void HidServiceMac::AddDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef hid_device) { + DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent()); + // Claim ownership of the device. + CFRetain(hid_device); + HidServiceMac* service = HidServiceFromContext(context); + service->message_loop_->PostTask(FROM_HERE, + base::Bind(&HidServiceMac::PlatformAddDevice, + base::Unretained(service), + base::Unretained(hid_device))); +} + +void HidServiceMac::RemoveDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef hid_device) { + DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent()); + HidServiceMac* service = HidServiceFromContext(context); + service->message_loop_->PostTask( + FROM_HERE, + base::Bind(&HidServiceMac::PlatformRemoveDevice, + base::Unretained(service), + base::Unretained(hid_device))); +} + +void HidServiceMac::Enumerate() { + DCHECK(thread_checker_.CalledOnValidThread()); + HidDeviceList devices; + EnumerateHidDevices(hid_manager_, &devices); + for (HidDeviceList::const_iterator iter = devices.begin(); + iter != devices.end(); + ++iter) { + IOHIDDeviceRef hid_device = *iter; + PlatformAddDevice(hid_device); + } +} + +void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef hid_device) { + // Note that our ownership of hid_device is implied if calling this method. + // It is balanced in PlatformRemoveDevice. + DCHECK(thread_checker_.CalledOnValidThread()); + + HidDeviceInfo device_info; + device_info.device_id = hid_device; + device_info.vendor_id = + GetHidIntProperty(hid_device, CFSTR(kIOHIDVendorIDKey)); + device_info.product_id = + GetHidIntProperty(hid_device, CFSTR(kIOHIDProductIDKey)); + device_info.input_report_size = + GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey)); + device_info.output_report_size = + GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey)); + device_info.feature_report_size = + GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey)); + CFTypeRef deviceUsagePairsRaw = + IOHIDDeviceGetProperty(hid_device, CFSTR(kIOHIDDeviceUsagePairsKey)); + CFArrayRef deviceUsagePairs = + base::mac::CFCast<CFArrayRef>(deviceUsagePairsRaw); + CFIndex deviceUsagePairsCount = CFArrayGetCount(deviceUsagePairs); + for (CFIndex i = 0; i < deviceUsagePairsCount; i++) { + CFDictionaryRef deviceUsagePair = base::mac::CFCast<CFDictionaryRef>( + CFArrayGetValueAtIndex(deviceUsagePairs, i)); + CFNumberRef usage_raw = base::mac::CFCast<CFNumberRef>( + CFDictionaryGetValue(deviceUsagePair, CFSTR(kIOHIDDeviceUsageKey))); + uint16_t usage; + CFNumberGetValue(usage_raw, kCFNumberSInt32Type, &usage); + CFNumberRef page_raw = base::mac::CFCast<CFNumberRef>( + CFDictionaryGetValue(deviceUsagePair, CFSTR(kIOHIDDeviceUsagePageKey))); + HidUsageAndPage::Page page; + CFNumberGetValue(page_raw, kCFNumberSInt32Type, &page); + device_info.usages.push_back(HidUsageAndPage(usage, page)); + } + device_info.product_name = + GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey)); + device_info.serial_number = + GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey)); + AddDevice(device_info); +} + +void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef hid_device) { + DCHECK(thread_checker_.CalledOnValidThread()); + RemoveDevice(hid_device); + CFRelease(hid_device); +} + +scoped_refptr<HidConnection> HidServiceMac::Connect( + const HidDeviceId& device_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + HidDeviceInfo device_info; + if (!GetDeviceInfo(device_id, &device_info)) + return NULL; + return scoped_refptr<HidConnection>(new HidConnectionMac(device_info)); +} + +} // namespace device diff --git a/chromium/device/hid/hid_service_mac.h b/chromium/device/hid/hid_service_mac.h new file mode 100644 index 00000000000..7c4323412cc --- /dev/null +++ b/chromium/device/hid/hid_service_mac.h @@ -0,0 +1,64 @@ +// 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 DEVICE_HID_HID_SERVICE_MAC_H_ +#define DEVICE_HID_HID_SERVICE_MAC_H_ + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> + +#include <string> + +#include "base/mac/foundation_util.h" +#include "base/memory/ref_counted.h" +#include "device/hid/hid_service.h" + +namespace base { +class MessageLoopProxy; +} + +namespace device { + +class HidConnection; + +class HidServiceMac : public HidService { + public: + HidServiceMac(); + + virtual scoped_refptr<HidConnection> Connect(const HidDeviceId& device_id) + OVERRIDE; + + private: + virtual ~HidServiceMac(); + + void StartWatchingDevices(); + void StopWatchingDevices(); + + // Device changing callbacks. + static void AddDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef hid_device); + static void RemoveDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef hid_device); + + void Enumerate(); + + void PlatformAddDevice(IOHIDDeviceRef hid_device); + void PlatformRemoveDevice(IOHIDDeviceRef hid_device); + + // Platform HID Manager + base::ScopedCFTypeRef<IOHIDManagerRef> hid_manager_; + + // The message loop for the thread on which this service was created. + scoped_refptr<base::MessageLoopProxy> message_loop_; + + DISALLOW_COPY_AND_ASSIGN(HidServiceMac); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_MAC_H_ diff --git a/chromium/device/hid/hid_service_unittest.cc b/chromium/device/hid/hid_service_unittest.cc new file mode 100644 index 00000000000..0b5043bd864 --- /dev/null +++ b/chromium/device/hid/hid_service_unittest.cc @@ -0,0 +1,27 @@ +// 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 <vector> + +#include "base/message_loop/message_loop.h" +#include "device/hid/hid_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +TEST(HidServiceTest, Create) { + base::MessageLoopForIO message_loop; + HidService* service = HidService::GetInstance(); + ASSERT_TRUE(service); + + std::vector<HidDeviceInfo> devices; + service->GetDevices(&devices); + for (std::vector<HidDeviceInfo>::iterator it = devices.begin(); + it != devices.end(); + ++it) { + ASSERT_TRUE(it->device_id != kInvalidHidDeviceId); + } +} + +} // namespace device diff --git a/chromium/device/hid/hid_service_win.cc b/chromium/device/hid/hid_service_win.cc new file mode 100644 index 00000000000..82477a52845 --- /dev/null +++ b/chromium/device/hid/hid_service_win.cc @@ -0,0 +1,250 @@ +// 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 "device/hid/hid_service_win.h" + +#include <cstdlib> + +#include "base/files/file.h" +#include "base/stl_util.h" +#include "base/strings/sys_string_conversions.h" +#include "device/hid/hid_connection_win.h" +#include "device/hid/hid_device_info.h" +#include "net/base/io_buffer.h" + +#if defined(OS_WIN) + +#define INITGUID + +#include <windows.h> +#include <hidclass.h> + +extern "C" { + +#include <hidsdi.h> +#include <hidpi.h> + +} + +#include <setupapi.h> +#include <winioctl.h> +#include "base/win/scoped_handle.h" + +#endif // defined(OS_WIN) + +// Setup API is required to enumerate HID devices. +#pragma comment(lib, "setupapi.lib") +#pragma comment(lib, "hid.lib") + +namespace device { +namespace { + +const char kHIDClass[] = "HIDClass"; + +} // namespace + +HidServiceWin::HidServiceWin() { + Enumerate(); +} + +HidServiceWin::~HidServiceWin() {} + +void HidServiceWin::Enumerate() { + BOOL res; + HDEVINFO device_info_set; + SP_DEVINFO_DATA devinfo_data; + SP_DEVICE_INTERFACE_DATA device_interface_data; + + memset(&devinfo_data, 0, sizeof(SP_DEVINFO_DATA)); + devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); + device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + device_info_set = SetupDiGetClassDevs( + &GUID_DEVINTERFACE_HID, + NULL, + NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (device_info_set == INVALID_HANDLE_VALUE) + return; + + for (int device_index = 0; + SetupDiEnumDeviceInterfaces(device_info_set, + NULL, + &GUID_DEVINTERFACE_HID, + device_index, + &device_interface_data); + ++device_index) { + DWORD required_size = 0; + + // Determime the required size of detail struct. + SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + NULL, + 0, + &required_size, + NULL); + + scoped_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA_A, base::FreeDeleter> + device_interface_detail_data( + static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_A*>( + malloc(required_size))); + device_interface_detail_data->cbSize = + sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + + // Get the detailed data for this device. + res = SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + device_interface_detail_data.get(), + required_size, + NULL, + NULL); + if (!res) + continue; + + // Enumerate device info. Looking for Setup Class "HIDClass". + for (DWORD i = 0; + SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data); + i++) { + char class_name[256] = {0}; + res = SetupDiGetDeviceRegistryPropertyA(device_info_set, + &devinfo_data, + SPDRP_CLASS, + NULL, + (PBYTE) class_name, + sizeof(class_name) - 1, + NULL); + if (!res) + break; + if (memcmp(class_name, kHIDClass, sizeof(kHIDClass)) == 0) { + char driver_name[256] = {0}; + // Get bounded driver. + res = SetupDiGetDeviceRegistryPropertyA(device_info_set, + &devinfo_data, + SPDRP_DRIVER, + NULL, + (PBYTE) driver_name, + sizeof(driver_name) - 1, + NULL); + if (res) { + // Found the driver. + break; + } + } + } + + if (!res) + continue; + + PlatformAddDevice(device_interface_detail_data->DevicePath); + } +} + +void HidServiceWin::PlatformAddDevice(const std::string& device_path) { + HidDeviceInfo device_info; + device_info.device_id = device_path; + + // Try to open the device. + base::win::ScopedHandle device_handle( + CreateFileA(device_path.c_str(), + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + 0)); + + if (!device_handle.IsValid() && + GetLastError() == base::File::FILE_ERROR_ACCESS_DENIED) { + base::win::ScopedHandle device_handle( + CreateFileA(device_path.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + 0)); + + if (!device_handle.IsValid()) + return; + } + + // Get VID/PID pair. + HIDD_ATTRIBUTES attrib = {0}; + attrib.Size = sizeof(HIDD_ATTRIBUTES); + if (!HidD_GetAttributes(device_handle.Get(), &attrib)) + return; + + device_info.vendor_id = attrib.VendorID; + device_info.product_id = attrib.ProductID; + + for (ULONG i = 32; + HidD_SetNumInputBuffers(device_handle.Get(), i); + i <<= 1); + + // Get usage and usage page (optional). + PHIDP_PREPARSED_DATA preparsed_data; + if (HidD_GetPreparsedData(device_handle.Get(), &preparsed_data) && + preparsed_data) { + HIDP_CAPS capabilities; + if (HidP_GetCaps(preparsed_data, &capabilities) == HIDP_STATUS_SUCCESS) { + device_info.input_report_size = capabilities.InputReportByteLength; + device_info.output_report_size = capabilities.OutputReportByteLength; + device_info.feature_report_size = capabilities.FeatureReportByteLength; + device_info.usages.push_back(HidUsageAndPage( + capabilities.Usage, + static_cast<HidUsageAndPage::Page>(capabilities.UsagePage))); + } + // Detect if the device supports report ids. + if (capabilities.NumberInputValueCaps > 0) { + scoped_ptr<HIDP_VALUE_CAPS[]> value_caps( + new HIDP_VALUE_CAPS[capabilities.NumberInputValueCaps]); + USHORT value_caps_length = capabilities.NumberInputValueCaps; + if (HidP_GetValueCaps(HidP_Input, &value_caps[0], &value_caps_length, + preparsed_data) == HIDP_STATUS_SUCCESS) { + device_info.has_report_id = (value_caps[0].ReportID != 0); + } + } + if (!device_info.has_report_id && capabilities.NumberInputButtonCaps > 0) + { + scoped_ptr<HIDP_BUTTON_CAPS[]> button_caps( + new HIDP_BUTTON_CAPS[capabilities.NumberInputButtonCaps]); + USHORT button_caps_length = capabilities.NumberInputButtonCaps; + if (HidP_GetButtonCaps(HidP_Input, + &button_caps[0], + &button_caps_length, + preparsed_data) == HIDP_STATUS_SUCCESS) { + device_info.has_report_id = (button_caps[0].ReportID != 0); + } + } + + HidD_FreePreparsedData(preparsed_data); + } + + AddDevice(device_info); +} + +void HidServiceWin::PlatformRemoveDevice(const std::string& device_path) { + RemoveDevice(device_path); +} + +void HidServiceWin::GetDevices(std::vector<HidDeviceInfo>* devices) { + Enumerate(); + HidService::GetDevices(devices); +} + +scoped_refptr<HidConnection> HidServiceWin::Connect( + const HidDeviceId& device_id) { + HidDeviceInfo device_info; + if (!GetDeviceInfo(device_id, &device_info)) + return NULL; + scoped_refptr<HidConnectionWin> connection(new HidConnectionWin(device_info)); + if (!connection->available()) { + PLOG(ERROR) << "Failed to open device."; + return NULL; + } + return connection; +} + +} // namespace device diff --git a/chromium/device/hid/hid_service_win.h b/chromium/device/hid/hid_service_win.h new file mode 100644 index 00000000000..8f187650328 --- /dev/null +++ b/chromium/device/hid/hid_service_win.h @@ -0,0 +1,38 @@ +// 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 DEVICE_HID_HID_SERVICE_WIN_H_ +#define DEVICE_HID_HID_SERVICE_WIN_H_ + +#include <map> + +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service.h" + +namespace device { + +class HidConnection; + +class HidServiceWin : public HidService { + public: + HidServiceWin(); + + virtual void GetDevices(std::vector<HidDeviceInfo>* devices) OVERRIDE; + + virtual scoped_refptr<HidConnection> Connect(const HidDeviceId& device_id) + OVERRIDE; + + private: + virtual ~HidServiceWin(); + + void Enumerate(); + void PlatformAddDevice(const std::string& device_path); + void PlatformRemoveDevice(const std::string& device_path); + + DISALLOW_COPY_AND_ASSIGN(HidServiceWin); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_WIN_H_ diff --git a/chromium/device/hid/hid_usage_and_page.cc b/chromium/device/hid/hid_usage_and_page.cc new file mode 100644 index 00000000000..773346b7a5b --- /dev/null +++ b/chromium/device/hid/hid_usage_and_page.cc @@ -0,0 +1,13 @@ +// 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 "device/hid/hid_usage_and_page.h" + +namespace device { + +bool HidUsageAndPage::operator==(const HidUsageAndPage& other) const { + return usage == other.usage && usage_page == other.usage_page; +} + +} // namespace device diff --git a/chromium/device/hid/hid_usage_and_page.h b/chromium/device/hid/hid_usage_and_page.h new file mode 100644 index 00000000000..98ac80d6b54 --- /dev/null +++ b/chromium/device/hid/hid_usage_and_page.h @@ -0,0 +1,134 @@ +// 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 DEVICE_HID_HID_USAGE_AND_PAGE_H_ +#define DEVICE_HID_HID_USAGE_AND_PAGE_H_ + +#include "base/basictypes.h" + +namespace device { + +struct HidUsageAndPage { + enum Page { + kPageUndefined = 0x00, + kPageGenericDesktop = 0x01, + kPageSimulation = 0x02, + kPageVirtualReality = 0x03, + kPageSport = 0x04, + kPageGame = 0x05, + kPageKeyboard = 0x07, + kPageLed = 0x08, + kPageButton = 0x09, + kPageOrdinal = 0x0A, + kPageTelephony = 0x0B, + kPageConsumer = 0x0C, + kPageDigitizer = 0x0D, + kPagePidPage = 0x0F, + kPageUnicode = 0x10, + kPageAlphanumericDisplay = 0x14, + kPageMedicalInstruments = 0x40, + kPageMonitor0 = 0x80, + kPageMonitor1 = 0x81, + kPageMonitor2 = 0x82, + kPageMonitor3 = 0x83, + kPagePower0 = 0x84, + kPagePower1 = 0x85, + kPagePower2 = 0x86, + kPagePower3 = 0x87, + kPageBarCodeScanner = 0x8C, + kPageScale = 0x8D, + kPageMagneticStripeReader = 0x8E, + kPageReservedPointOfSale = 0x8F, + kPageCameraControl = 0x90, + kPageArcade = 0x91, + kPageVendor = 0xFF00, + kPageMediaCenter = 0xFFBC + }; + + // These usage enumerations are derived from the HID Usage Tables v1.11 spec. + enum GenericDesktopUsage { + kGenericDesktopUndefined = 0, + kGenericDesktopPointer = 1, + kGenericDesktopMouse = 2, + kGenericDesktopJoystick = 4, + kGenericDesktopGamePad = 5, + kGenericDesktopKeyboard = 6, + kGenericDesktopKeypad = 7, + kGenericDesktopMultiAxisController = 8, + kGenericDesktopX = 0x30, + kGenericDesktopY = 0x31, + kGenericDesktopZ = 0x32, + kGenericDesktopRx = 0x33, + kGenericDesktopRy = 0x34, + kGenericDesktopRz = 0x35, + kGenericDesktopSlider = 0x36, + kGenericDesktopDial = 0x37, + kGenericDesktopWheel = 0x38, + kGenericDesktopHatSwitch = 0x39, + kGenericDesktopCountedBuffer = 0x3a, + kGenericDesktopByteCount = 0x3b, + kGenericDesktopMotionWakeup = 0x3c, + kGenericDesktopStart = 0x3d, + kGenericDesktopSelect = 0x3e, + kGenericDesktopVx = 0x40, + kGenericDesktopVy = 0x41, + kGenericDesktopVz = 0x42, + kGenericDesktopVbrx = 0x43, + kGenericDesktopVbry = 0x44, + kGenericDesktopVbrz = 0x45, + kGenericDesktopVno = 0x46, + + kGenericDesktopSystemControl = 0x80, + kGenericDesktopSystemPowerDown = 0x81, + kGenericDesktopSystemSleep = 0x82, + kGenericDesktopSystemWakeUp = 0x83, + kGenericDesktopSystemContextMenu = 0x84, + kGenericDesktopSystemMainMenu = 0x85, + kGenericDesktopSystemAppMenu = 0x86, + kGenericDesktopSystemMenuHelp = 0x87, + kGenericDesktopSystemMenuExit = 0x88, + kGenericDesktopSystemMenuSelect = 0x89, + kGenericDesktopSystemMenuRight = 0x8a, + kGenericDesktopSystemMenuLeft = 0x8b, + kGenericDesktopSystemMenuUp = 0x8c, + kGenericDesktopSystemMenuDown = 0x8d, + kGenericDesktopSystemColdRestart = 0x8e, + kGenericDesktopSystemWarmRestart = 0x8f, + + kGenericDesktopDPadUp = 0x90, + kGenericDesktopDPadDown = 0x91, + kGenericDesktopDPadLeft = 0x92, + kGenericDesktopDPadRight = 0x93, + + kGenericDesktopSystemDock = 0xa0, + kGenericDesktopSystemUndock = 0xa1, + kGenericDesktopSystemSetup = 0xa2, + kGenericDesktopSystemBreak = 0xa3, + kGenericDesktopSystemDebuggerBreak = 0xa4, + kGenericDesktopApplicationBreak = 0xa5, + kGenericDesktopApplicationDebuggerBreak = 0xa6, + kGenericDesktopSystemSpeakerMute = 0xa7, + kGenericDesktopSystemHibernate = 0xa8, + kGenericDesktopSystemDisplayInvert = 0xb0, + kGenericDesktopSystemDisplayInternal = 0xb1, + kGenericDesktopSystemDisplayExternal = 0xb2, + kGenericDesktopSystemDisplayBoth = 0xb3, + kGenericDesktopSystemDisplayDual = 0xb4, + kGenericDesktopSystemDisplayToggle = 0xb5, + kGenericDesktopSystemDisplaySwap = 0xb6, + }; + + HidUsageAndPage(uint16_t usage, Page usage_page) + : usage(usage), usage_page(usage_page) {} + ~HidUsageAndPage() {} + + uint16_t usage; + Page usage_page; + + bool operator==(const HidUsageAndPage& other) const; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_USAGE_AND_PAGE_H_ diff --git a/chromium/device/hid/hid_utils_mac.cc b/chromium/device/hid/hid_utils_mac.cc new file mode 100644 index 00000000000..46605d87bdb --- /dev/null +++ b/chromium/device/hid/hid_utils_mac.cc @@ -0,0 +1,45 @@ +// 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 "device/hid/hid_utils_mac.h" + +#include "base/mac/foundation_util.h" +#include "base/strings/sys_string_conversions.h" + +namespace device { + +int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) { + int32_t value; + if (TryGetHidIntProperty(device, key, &value)) + return value; + return 0; +} + +std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) { + std::string value; + TryGetHidStringProperty(device, key, &value); + return value; +} + +bool TryGetHidIntProperty(IOHIDDeviceRef device, + CFStringRef key, + int32_t* result) { + CFNumberRef ref = base::mac::CFCast<CFNumberRef>( + IOHIDDeviceGetProperty(device, key)); + return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result); +} + +bool TryGetHidStringProperty(IOHIDDeviceRef device, + CFStringRef key, + std::string* result) { + CFStringRef ref = base::mac::CFCast<CFStringRef>( + IOHIDDeviceGetProperty(device, key)); + if (!ref) { + return false; + } + *result = base::SysCFStringRefToUTF8(ref); + return true; +} + +} // namespace device diff --git a/chromium/device/hid/hid_utils_mac.h b/chromium/device/hid/hid_utils_mac.h new file mode 100644 index 00000000000..e9b252462a5 --- /dev/null +++ b/chromium/device/hid/hid_utils_mac.h @@ -0,0 +1,30 @@ +// 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 DEVICE_HID_HID_UTILS_MAC_H_ +#define DEVICE_HID_HID_UTILS_MAC_H_ + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> +#include <stdint.h> + +#include <string> + +namespace device { + +int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key); + +std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key); + +bool TryGetHidIntProperty(IOHIDDeviceRef device, + CFStringRef key, + int32_t* result); + +bool TryGetHidStringProperty(IOHIDDeviceRef device, + CFStringRef key, + std::string* result); + +} // namespace device + +#endif // DEVICE_HID_HID_UTILS_MAC_H_ diff --git a/chromium/device/hid/input_service_linux.cc b/chromium/device/hid/input_service_linux.cc new file mode 100644 index 00000000000..90132e91af2 --- /dev/null +++ b/chromium/device/hid/input_service_linux.cc @@ -0,0 +1,240 @@ +// 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 <libudev.h> + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "device/hid/input_service_linux.h" + +namespace device { + +namespace { + +const char kSubsystemHid[] = "hid"; +const char kSubsystemInput[] = "input"; +const char kTypeBluetooth[] = "bluetooth"; +const char kTypeUsb[] = "usb"; +const char kTypeSerio[] = "serio"; +const char kIdInputAccelerometer[] = "ID_INPUT_ACCELEROMETER"; +const char kIdInputJoystick[] = "ID_INPUT_JOYSTICK"; +const char kIdInputKey[] = "ID_INPUT_KEY"; +const char kIdInputKeyboard[] = "ID_INPUT_KEYBOARD"; +const char kIdInputMouse[] = "ID_INPUT_MOUSE"; +const char kIdInputTablet[] = "ID_INPUT_TABLET"; +const char kIdInputTouchpad[] = "ID_INPUT_TOUCHPAD"; +const char kIdInputTouchscreen[] = "ID_INPUT_TOUCHSCREEN"; + +// The instance will be reset when message loop destroys. +base::LazyInstance<scoped_ptr<InputServiceLinux> >::Leaky + g_input_service_linux_ptr = LAZY_INSTANCE_INITIALIZER; + +bool GetBoolProperty(udev_device* device, const char* key) { + CHECK(device); + CHECK(key); + const char* property = udev_device_get_property_value(device, key); + if (!property) + return false; + int value; + if (!base::StringToInt(property, &value)) { + LOG(ERROR) << "Not an integer value for " << key << " property"; + return false; + } + return (value != 0); +} + +InputServiceLinux::InputDeviceInfo::Type GetDeviceType(udev_device* device) { + if (udev_device_get_parent_with_subsystem_devtype( + device, kTypeBluetooth, NULL)) { + return InputServiceLinux::InputDeviceInfo::TYPE_BLUETOOTH; + } + if (udev_device_get_parent_with_subsystem_devtype(device, kTypeUsb, NULL)) + return InputServiceLinux::InputDeviceInfo::TYPE_USB; + if (udev_device_get_parent_with_subsystem_devtype(device, kTypeSerio, NULL)) + return InputServiceLinux::InputDeviceInfo::TYPE_SERIO; + return InputServiceLinux::InputDeviceInfo::TYPE_UNKNOWN; +} + +std::string GetParentDeviceName(udev_device* device, const char* subsystem) { + udev_device* parent = + udev_device_get_parent_with_subsystem_devtype(device, subsystem, NULL); + if (!parent) + return std::string(); + const char* name = udev_device_get_property_value(parent, "NAME"); + if (!name) + return std::string(); + std::string result; + base::TrimString(name, "\"", &result); + return result; +} + +class InputServiceLinuxImpl : public InputServiceLinux, + public DeviceMonitorLinux::Observer { + public: + // Implements DeviceMonitorLinux::Observer: + virtual void OnDeviceAdded(udev_device* device) OVERRIDE; + virtual void OnDeviceRemoved(udev_device* device) OVERRIDE; + + private: + friend class InputServiceLinux; + + InputServiceLinuxImpl(); + virtual ~InputServiceLinuxImpl(); + + DISALLOW_COPY_AND_ASSIGN(InputServiceLinuxImpl); +}; + +InputServiceLinuxImpl::InputServiceLinuxImpl() { + DeviceMonitorLinux::GetInstance()->AddObserver(this); + DeviceMonitorLinux::GetInstance()->Enumerate(base::Bind( + &InputServiceLinuxImpl::OnDeviceAdded, base::Unretained(this))); +} + +InputServiceLinuxImpl::~InputServiceLinuxImpl() { + if (DeviceMonitorLinux::HasInstance()) + DeviceMonitorLinux::GetInstance()->RemoveObserver(this); +} + +void InputServiceLinuxImpl::OnDeviceAdded(udev_device* device) { + DCHECK(CalledOnValidThread()); + if (!device) + return; + const char* devnode = udev_device_get_devnode(device); + if (!devnode) + return; + + InputDeviceInfo info; + info.id = devnode; + + const char* subsystem = udev_device_get_subsystem(device); + if (!subsystem) + return; + if (strcmp(subsystem, kSubsystemHid) == 0) { + info.subsystem = InputServiceLinux::InputDeviceInfo::SUBSYSTEM_HID; + info.name = GetParentDeviceName(device, kSubsystemHid); + } else if (strcmp(subsystem, kSubsystemInput) == 0) { + info.subsystem = InputServiceLinux::InputDeviceInfo::SUBSYSTEM_INPUT; + info.name = GetParentDeviceName(device, kSubsystemInput); + } else { + return; + } + + info.type = GetDeviceType(device); + + info.is_accelerometer = GetBoolProperty(device, kIdInputAccelerometer); + info.is_joystick = GetBoolProperty(device, kIdInputJoystick); + info.is_key = GetBoolProperty(device, kIdInputKey); + info.is_keyboard = GetBoolProperty(device, kIdInputKeyboard); + info.is_mouse = GetBoolProperty(device, kIdInputMouse); + info.is_tablet = GetBoolProperty(device, kIdInputTablet); + info.is_touchpad = GetBoolProperty(device, kIdInputTouchpad); + info.is_touchscreen = GetBoolProperty(device, kIdInputTouchscreen); + + AddDevice(info); +} + +void InputServiceLinuxImpl::OnDeviceRemoved(udev_device* device) { + DCHECK(CalledOnValidThread()); + if (!device) + return; + const char* devnode = udev_device_get_devnode(device); + if (devnode) + RemoveDevice(devnode); +} + +} // namespace + +InputServiceLinux::InputDeviceInfo::InputDeviceInfo() + : subsystem(SUBSYSTEM_UNKNOWN), + type(TYPE_UNKNOWN), + is_accelerometer(false), + is_joystick(false), + is_key(false), + is_keyboard(false), + is_mouse(false), + is_tablet(false), + is_touchpad(false), + is_touchscreen(false) {} + +InputServiceLinux::InputServiceLinux() { + base::ThreadRestrictions::AssertIOAllowed(); + base::MessageLoop::current()->AddDestructionObserver(this); +} + +InputServiceLinux::~InputServiceLinux() { + DCHECK(CalledOnValidThread()); + base::MessageLoop::current()->RemoveDestructionObserver(this); +} + +// static +InputServiceLinux* InputServiceLinux::GetInstance() { + if (!HasInstance()) + g_input_service_linux_ptr.Get().reset(new InputServiceLinuxImpl()); + return g_input_service_linux_ptr.Get().get(); +} + +// static +bool InputServiceLinux::HasInstance() { + return g_input_service_linux_ptr.Get().get(); +} + +// static +void InputServiceLinux::SetForTesting(InputServiceLinux* service) { + g_input_service_linux_ptr.Get().reset(service); +} + +void InputServiceLinux::AddObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + if (observer) + observers_.AddObserver(observer); +} + +void InputServiceLinux::RemoveObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + if (observer) + observers_.RemoveObserver(observer); +} + +void InputServiceLinux::GetDevices(std::vector<InputDeviceInfo>* devices) { + DCHECK(CalledOnValidThread()); + for (DeviceMap::iterator it = devices_.begin(), ie = devices_.end(); it != ie; + ++it) { + devices->push_back(it->second); + } +} + +bool InputServiceLinux::GetDeviceInfo(const std::string& id, + InputDeviceInfo* info) const { + DCHECK(CalledOnValidThread()); + DeviceMap::const_iterator it = devices_.find(id); + if (it == devices_.end()) + return false; + *info = it->second; + return true; +} + +void InputServiceLinux::WillDestroyCurrentMessageLoop() { + DCHECK(CalledOnValidThread()); + g_input_service_linux_ptr.Get().reset(NULL); +} + +void InputServiceLinux::AddDevice(const InputDeviceInfo& info) { + devices_[info.id] = info; + FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceAdded(info)); +} + +void InputServiceLinux::RemoveDevice(const std::string& id) { + devices_.erase(id); + FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceRemoved(id)); +} + +bool InputServiceLinux::CalledOnValidThread() const { + return thread_checker_.CalledOnValidThread(); +} + +} // namespace device diff --git a/chromium/device/hid/input_service_linux.h b/chromium/device/hid/input_service_linux.h new file mode 100644 index 00000000000..dc43dba9bc2 --- /dev/null +++ b/chromium/device/hid/input_service_linux.h @@ -0,0 +1,98 @@ +// 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 DEVICE_HID_INPUT_SERVICE_LINUX_H_ +#define DEVICE_HID_INPUT_SERVICE_LINUX_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "device/hid/device_monitor_linux.h" + +namespace device { + +// This class provides information and notifications about +// connected/disconnected input/HID devices. This class is *NOT* +// thread-safe and all methods must be called from the FILE thread. +class InputServiceLinux : public base::MessageLoop::DestructionObserver { + public: + struct InputDeviceInfo { + enum Subsystem { SUBSYSTEM_HID, SUBSYSTEM_INPUT, SUBSYSTEM_UNKNOWN }; + enum Type { TYPE_BLUETOOTH, TYPE_USB, TYPE_SERIO, TYPE_UNKNOWN }; + + InputDeviceInfo(); + + std::string id; + std::string name; + Subsystem subsystem; + Type type; + + bool is_accelerometer : 1; + bool is_joystick : 1; + bool is_key : 1; + bool is_keyboard : 1; + bool is_mouse : 1; + bool is_tablet : 1; + bool is_touchpad : 1; + bool is_touchscreen : 1; + }; + + class Observer { + public: + virtual ~Observer() {} + virtual void OnInputDeviceAdded(const InputDeviceInfo& info) = 0; + virtual void OnInputDeviceRemoved(const std::string& id) = 0; + }; + + InputServiceLinux(); + + static InputServiceLinux* GetInstance(); + static bool HasInstance(); + static void SetForTesting(InputServiceLinux* service); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Returns list of all currently connected input/hid devices. + void GetDevices(std::vector<InputDeviceInfo>* devices); + + // Returns an info about input device identified by |id|. When there're + // no input or hid device with such id, returns false and doesn't + // modify |info|. + bool GetDeviceInfo(const std::string& id, InputDeviceInfo* info) const; + + // Implements base::MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + protected: + virtual ~InputServiceLinux(); + + void AddDevice(const InputDeviceInfo& info); + void RemoveDevice(const std::string& id); + + bool CalledOnValidThread() const; + + private: + friend struct base::DefaultDeleter<InputServiceLinux>; + + typedef base::hash_map<std::string, InputDeviceInfo> DeviceMap; + + DeviceMap devices_; + ObserverList<Observer> observers_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(InputServiceLinux); +}; + +} // namespace device + +#endif // DEVICE_HID_INPUT_SERVICE_LINUX_H_ diff --git a/chromium/device/hid/input_service_linux_unittest.cc b/chromium/device/hid/input_service_linux_unittest.cc new file mode 100644 index 00000000000..9825c06f611 --- /dev/null +++ b/chromium/device/hid/input_service_linux_unittest.cc @@ -0,0 +1,24 @@ +// 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 <vector> + +#include "base/message_loop/message_loop.h" +#include "device/hid/input_service_linux.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +TEST(InputServiceLinux, Simple) { + base::MessageLoopForIO message_loop; + InputServiceLinux* service = InputServiceLinux::GetInstance(); + + ASSERT_TRUE(service); + std::vector<InputServiceLinux::InputDeviceInfo> devices; + service->GetDevices(&devices); + for (size_t i = 0; i < devices.size(); ++i) + ASSERT_TRUE(!devices[i].id.empty()); +} + +} // namespace device diff --git a/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc b/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc index 41ac37e0c8b..4edfc60f1e5 100644 --- a/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc +++ b/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc @@ -19,6 +19,7 @@ namespace device { namespace { const char kInvalidResponseMsg[] = "Invalid Response: "; +uint32 kMaxChunkSize = 1024*1024; // D-Bus has message size limits. // The MediaTransferProtocolDaemonClient implementation. class MediaTransferProtocolDaemonClientImpl @@ -140,6 +141,7 @@ class MediaTransferProtocolDaemonClientImpl uint32 bytes_to_read, const ReadFileCallback& callback, const ErrorCallback& error_callback) OVERRIDE { + DCHECK_LE(bytes_to_read, kMaxChunkSize); dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kReadFileChunkByPath); dbus::MessageWriter writer(&method_call); @@ -162,6 +164,7 @@ class MediaTransferProtocolDaemonClientImpl uint32 bytes_to_read, const ReadFileCallback& callback, const ErrorCallback& error_callback) OVERRIDE { + DCHECK_LE(bytes_to_read, kMaxChunkSize); dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kReadFileChunkById); dbus::MessageWriter writer(&method_call); @@ -349,7 +352,7 @@ class MediaTransferProtocolDaemonClientImpl return; } - uint8* data_bytes = NULL; + const uint8* data_bytes = NULL; size_t data_length = 0; dbus::MessageReader reader(response); if (!reader.PopArrayOfBytes(&data_bytes, &data_length)) { diff --git a/chromium/device/nfc/OWNERS b/chromium/device/nfc/OWNERS new file mode 100644 index 00000000000..7bf8537917f --- /dev/null +++ b/chromium/device/nfc/OWNERS @@ -0,0 +1,2 @@ +keybuk@chromium.org +armansito@chromium.org diff --git a/chromium/device/nfc/nfc.gyp b/chromium/device/nfc/nfc.gyp index d5e519bb95f..11e430852e6 100644 --- a/chromium/device/nfc/nfc.gyp +++ b/chromium/device/nfc/nfc.gyp @@ -12,6 +12,7 @@ 'type': 'static_library', 'dependencies': [ '../../base/base.gyp:base', + '../../url/url.gyp:url_lib', ], 'sources': [ 'nfc_adapter.cc', @@ -22,12 +23,20 @@ 'nfc_adapter_factory.h', 'nfc_ndef_record.cc', 'nfc_ndef_record.h', + 'nfc_ndef_record_utils_chromeos.cc', + 'nfc_ndef_record_utils_chromeos.h', 'nfc_peer.cc', 'nfc_peer.h', + 'nfc_peer_chromeos.cc', + 'nfc_peer_chromeos.h', 'nfc_tag.cc', 'nfc_tag.h', + 'nfc_tag_chromeos.cc', + 'nfc_tag_chromeos.h', 'nfc_tag_technology.cc', - 'nfc_tag_technology.h' + 'nfc_tag_technology.h', + 'nfc_tag_technology_chromeos.cc', + 'nfc_tag_technology_chromeos.h' ], 'conditions': [ ['chromeos==1', { diff --git a/chromium/device/nfc/nfc_adapter.cc b/chromium/device/nfc/nfc_adapter.cc index 1bef83d3ec2..520d4f31092 100644 --- a/chromium/device/nfc/nfc_adapter.cc +++ b/chromium/device/nfc/nfc_adapter.cc @@ -48,4 +48,50 @@ NfcTag* NfcAdapter::GetTag(const std::string& identifier) const { return NULL; } +void NfcAdapter::SetTag(const std::string& identifier, NfcTag* tag) { + if (GetTag(identifier)) { + VLOG(1) << "Tag object for tag \"" << identifier << "\" already exists."; + return; + } + tags_[identifier] = tag; +} + +void NfcAdapter::SetPeer(const std::string& identifier, NfcPeer* peer) { + if (GetPeer(identifier)) { + VLOG(1) << "Peer object for peer \"" << identifier << "\" already exists."; + return; + } + peers_[identifier] = peer; +} + +NfcTag* NfcAdapter::RemoveTag(const std::string& identifier) { + TagsMap::iterator iter = tags_.find(identifier); + if (iter == tags_.end()) { + VLOG(1) << "Tag with identifier \"" << identifier << "\" not found."; + return NULL; + } + NfcTag* tag = iter->second; + tags_.erase(iter); + return tag; +} + +NfcPeer* NfcAdapter::RemovePeer(const std::string& identifier) { + PeersMap::iterator iter = peers_.find(identifier); + if (iter == peers_.end()) { + VLOG(1) << "Peer object for peer \"" << identifier << "\" not found."; + return NULL; + } + NfcPeer* peer = iter->second; + peers_.erase(iter); + return peer; +} + +void NfcAdapter::ClearTags() { + tags_.clear(); +} + +void NfcAdapter::ClearPeers() { + peers_.clear(); +} + } // namespace device diff --git a/chromium/device/nfc/nfc_adapter.h b/chromium/device/nfc/nfc_adapter.h index 373f4cc3683..455b14289b8 100644 --- a/chromium/device/nfc/nfc_adapter.h +++ b/chromium/device/nfc/nfc_adapter.h @@ -181,10 +181,27 @@ class NfcAdapter : public base::RefCounted<NfcAdapter> { typedef std::map<const std::string, NfcPeer*> PeersMap; typedef std::map<const std::string, NfcTag*> TagsMap; + // Set the given tag or peer for |identifier|. If a tag or peer for + // |identifier| already exists, these methods won't do anything. + void SetTag(const std::string& identifier, NfcTag* tag); + void SetPeer(const std::string& identifier, NfcPeer* peer); + + // Removes the tag or peer for |identifier| and returns the removed object. + // Returns NULL, if no tag or peer for |identifier| was found. + NfcTag* RemoveTag(const std::string& identifier); + NfcPeer* RemovePeer(const std::string& identifier); + + // Clear the peer and tag maps. These methods won't delete the tag and peer + // objects, however after the call to these methods, the peers and tags won't + // be returned via calls to GetPeers and GetTags. + void ClearTags(); + void ClearPeers(); + + private: + // Peers and tags that are managed by this adapter. PeersMap peers_; TagsMap tags_; - private: DISALLOW_COPY_AND_ASSIGN(NfcAdapter); }; diff --git a/chromium/device/nfc/nfc_adapter_chromeos.cc b/chromium/device/nfc/nfc_adapter_chromeos.cc index 071b0808ae9..374e8ac83e9 100644 --- a/chromium/device/nfc/nfc_adapter_chromeos.cc +++ b/chromium/device/nfc/nfc_adapter_chromeos.cc @@ -9,19 +9,25 @@ #include "base/callback.h" #include "base/logging.h" #include "chromeos/dbus/dbus_thread_manager.h" -#include "device/nfc/nfc_peer.h" -#include "device/nfc/nfc_tag.h" +#include "device/nfc/nfc_peer_chromeos.h" +#include "device/nfc/nfc_tag_chromeos.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { +namespace { + +typedef std::vector<dbus::ObjectPath> ObjectPathVector; + +} // namespace + NfcAdapterChromeOS::NfcAdapterChromeOS() : weak_ptr_factory_(this) { DBusThreadManager::Get()->GetNfcAdapterClient()->AddObserver(this); DBusThreadManager::Get()->GetNfcDeviceClient()->AddObserver(this); DBusThreadManager::Get()->GetNfcTagClient()->AddObserver(this); - const std::vector<dbus::ObjectPath>& object_paths = + const ObjectPathVector& object_paths = DBusThreadManager::Get()->GetNfcAdapterClient()->GetAdapters(); if (!object_paths.empty()) { VLOG(1) << object_paths.size() << " NFC adapter(s) available."; @@ -126,9 +132,9 @@ void NfcAdapterChromeOS::AdapterRemoved(const dbus::ObjectPath& object_path) { // There may still be other adapters present on the system. Set the next // available adapter as the current one. - const std::vector<dbus::ObjectPath>& object_paths = + const ObjectPathVector& object_paths = DBusThreadManager::Get()->GetNfcAdapterClient()->GetAdapters(); - for (std::vector<dbus::ObjectPath>::const_iterator iter = + for (ObjectPathVector::const_iterator iter = object_paths.begin(); iter != object_paths.end(); ++iter) { // The removed object will still be available until the call to @@ -155,35 +161,91 @@ void NfcAdapterChromeOS::AdapterPropertyChanged( } void NfcAdapterChromeOS::DeviceAdded(const dbus::ObjectPath& object_path) { + if (!IsPresent()) + return; + + if (GetPeer(object_path.value())) + return; + VLOG(1) << "NFC device found: " << object_path.value(); - // TODO(armansito): Implement device logic. + + // Check to see if the device belongs to this adapter. + const ObjectPathVector& devices = + DBusThreadManager::Get()->GetNfcDeviceClient()-> + GetDevicesForAdapter(object_path_); + bool device_found = false; + for (ObjectPathVector::const_iterator iter = devices.begin(); + iter != devices.end(); ++iter) { + if (*iter == object_path) { + device_found = true; + break; + } + } + if (!device_found) { + VLOG(1) << "Found peer device does not belong to the current adapter."; + return; + } + + // Create the peer object. + NfcPeerChromeOS* peer_chromeos = new NfcPeerChromeOS(object_path); + SetPeer(object_path.value(), peer_chromeos); + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, + PeerFound(this, peer_chromeos)); } void NfcAdapterChromeOS::DeviceRemoved(const dbus::ObjectPath& object_path) { VLOG(1) << "NFC device lost: " << object_path.value(); - // TODO(armansito): Implement device logic. -} - -void NfcAdapterChromeOS::DevicePropertyChanged( - const dbus::ObjectPath& object_path, - const std::string& property_name) { - // TODO(armansito): Implement device logic. + device::NfcPeer* peer = RemovePeer(object_path.value()); + if (!peer) { + VLOG(1) << "Removed peer device does not belong to the current adapter."; + return; + } + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, PeerLost(this, peer)); + delete peer; } void NfcAdapterChromeOS::TagAdded(const dbus::ObjectPath& object_path) { - VLOG(1) << "NFC tag found: " << object_path.value(); - // TODO(armansito): Implement tag logic. -} + if (!IsPresent()) + return; + + if (GetTag(object_path.value())) + return; -void NfcAdapterChromeOS::TagRemoved(const dbus::ObjectPath& object_path) { VLOG(1) << "NFC tag found: " << object_path.value(); - // TODO(armansito): Implement tag logic. + + // Check to see if the tag belongs to this adapter. + const std::vector<dbus::ObjectPath>& tags = + DBusThreadManager::Get()->GetNfcTagClient()-> + GetTagsForAdapter(object_path_); + bool tag_found = false; + for (std::vector<dbus::ObjectPath>::const_iterator iter = tags.begin(); + iter != tags.end(); ++iter) { + if (*iter == object_path) { + tag_found = true; + break; + } + } + if (!tag_found) { + VLOG(1) << "Found tag does not belong to the current adapter."; + return; + } + + // Create the tag object. + NfcTagChromeOS* tag_chromeos = new NfcTagChromeOS(object_path); + SetTag(object_path.value(), tag_chromeos); + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, + TagFound(this, tag_chromeos)); } -void NfcAdapterChromeOS::TagPropertyChanged( - const dbus::ObjectPath& object_path, - const std::string& property_name) { - // TODO(armansito): Implement tag logic. +void NfcAdapterChromeOS::TagRemoved(const dbus::ObjectPath& object_path) { + VLOG(1) << "NFC tag lost : " << object_path.value(); + device::NfcTag* tag = RemoveTag(object_path.value()); + if (!tag) { + VLOG(1) << "Removed tag does not belong to the current adapter."; + return; + } + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, TagLost(this, tag)); + delete tag; } void NfcAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { @@ -200,9 +262,35 @@ void NfcAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { if (properties->polling.value()) PollingChanged(true); - // TODO(armansito): Create device::NfcPeer and device::NfcTag instances for - // all peers and tags that exist, once they have been implemented for - // ChromeOS. + // Create peer objects for peers that were added before the adapter was set. + const ObjectPathVector& devices = + DBusThreadManager::Get()->GetNfcDeviceClient()-> + GetDevicesForAdapter(object_path_); + for (ObjectPathVector::const_iterator iter = devices.begin(); + iter != devices.end(); ++iter) { + const dbus::ObjectPath& object_path = *iter; + if (GetPeer(object_path.value())) + continue; + NfcPeerChromeOS* peer_chromeos = new NfcPeerChromeOS(object_path); + SetPeer(object_path.value(), peer_chromeos); + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, + PeerFound(this, peer_chromeos)); + } + + // Create tag objects for tags that were added before the adapter was set. + const std::vector<dbus::ObjectPath>& tags = + DBusThreadManager::Get()->GetNfcTagClient()-> + GetTagsForAdapter(object_path_); + for (std::vector<dbus::ObjectPath>::const_iterator iter = tags.begin(); + iter != tags.end(); ++iter) { + const dbus::ObjectPath& object_path = *iter; + if (GetTag(object_path.value())) + continue; + NfcTagChromeOS* tag_chromeos = new NfcTagChromeOS(object_path); + SetTag(object_path.value(), tag_chromeos); + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, + TagFound(this, tag_chromeos)); + } } void NfcAdapterChromeOS::RemoveAdapter() { @@ -219,22 +307,26 @@ void NfcAdapterChromeOS::RemoveAdapter() { // Copy the tags and peers here and clear the original containers so that // GetPeers and GetTags return no values during the *Removed observer calls. - PeersMap peers = peers_; - TagsMap tags = tags_; - peers_.clear(); - tags_.clear(); - - for (PeersMap::iterator iter = peers_.begin(); - iter != peers_.end(); ++iter) { + PeerList peers; + TagList tags; + GetPeers(&peers); + GetTags(&tags); + ClearPeers(); + ClearTags(); + + for (PeerList::iterator iter = peers.begin(); + iter != peers.end(); ++iter) { + device::NfcPeer* peer = *iter; FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, - PeerLost(this, iter->second)); - delete iter->second; + PeerLost(this, peer)); + delete peer; } - for (TagsMap::iterator iter = tags_.begin(); - iter != tags_.end(); ++iter) { + for (TagList::iterator iter = tags.begin(); + iter != tags.end(); ++iter) { + device::NfcTag* tag = *iter; FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, - TagLost(this, iter->second)); - delete iter->second; + TagLost(this, tag)); + delete tag; } object_path_ = dbus::ObjectPath(""); @@ -270,7 +362,7 @@ void NfcAdapterChromeOS::OnSetPowered(const base::Closure& callback, } callback.Run(); } else { - LOG(WARNING) << "Failed to power up the NFC antenna radio."; + LOG(ERROR) << "Failed to power up the NFC antenna radio."; error_callback.Run(); } } @@ -283,8 +375,8 @@ void NfcAdapterChromeOS::OnStartPollingError( const ErrorCallback& error_callback, const std::string& error_name, const std::string& error_message) { - LOG(WARNING) << object_path_.value() << ": Failed to start polling: " - << error_name << ": " << error_message; + LOG(ERROR) << object_path_.value() << ": Failed to start polling: " + << error_name << ": " << error_message; error_callback.Run(); } @@ -296,8 +388,8 @@ void NfcAdapterChromeOS::OnStopPollingError( const ErrorCallback& error_callback, const std::string& error_name, const std::string& error_message) { - LOG(WARNING) << object_path_.value() << ": Failed to stop polling: " - << error_name << ": " << error_message; + LOG(ERROR) << object_path_.value() << ": Failed to stop polling: " + << error_name << ": " << error_message; error_callback.Run(); } diff --git a/chromium/device/nfc/nfc_adapter_chromeos.h b/chromium/device/nfc/nfc_adapter_chromeos.h index d70a98d2f79..3511e5dbc6e 100644 --- a/chromium/device/nfc/nfc_adapter_chromeos.h +++ b/chromium/device/nfc/nfc_adapter_chromeos.h @@ -60,14 +60,10 @@ class NfcAdapterChromeOS : public device::NfcAdapter, // NfcDeviceClient::Observer overrides. virtual void DeviceAdded(const dbus::ObjectPath& object_path) OVERRIDE; virtual void DeviceRemoved(const dbus::ObjectPath& object_path) OVERRIDE; - virtual void DevicePropertyChanged(const dbus::ObjectPath& object_path, - const std::string& property_name) OVERRIDE; // NfcTagClient::Observer overrides. virtual void TagAdded(const dbus::ObjectPath& object_path) OVERRIDE; virtual void TagRemoved(const dbus::ObjectPath& object_path) OVERRIDE; - virtual void TagPropertyChanged(const dbus::ObjectPath& object_path, - const std::string& property_name) OVERRIDE; // Set the tracked adapter to the one in |object_path|, this object will // subsequently operate on that adapter until it is removed. diff --git a/chromium/device/nfc/nfc_chromeos_unittest.cc b/chromium/device/nfc/nfc_chromeos_unittest.cc index dd94bebe4a5..672780f81bd 100644 --- a/chromium/device/nfc/nfc_chromeos_unittest.cc +++ b/chromium/device/nfc/nfc_chromeos_unittest.cc @@ -2,23 +2,57 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/values.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/fake_nfc_adapter_client.h" +#include "chromeos/dbus/fake_nfc_device_client.h" +#include "chromeos/dbus/fake_nfc_record_client.h" +#include "chromeos/dbus/fake_nfc_tag_client.h" #include "device/nfc/nfc_adapter_chromeos.h" +#include "device/nfc/nfc_ndef_record.h" +#include "device/nfc/nfc_ndef_record_utils_chromeos.h" +#include "device/nfc/nfc_peer.h" +#include "device/nfc/nfc_tag.h" +#include "device/nfc/nfc_tag_technology.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/cros_system_api/dbus/service_constants.h" using device::NfcAdapter; +using device::NfcNdefMessage; +using device::NfcNdefRecord; +using device::NfcNdefTagTechnology; +using device::NfcPeer; +using device::NfcTag; namespace chromeos { namespace { -class TestObserver : public NfcAdapter::Observer { +// Callback passed to property structures. +void OnPropertyChangedCallback(const std::string& property_name) { +} + +// Callback passed to dbus::PropertyBase::Set. +void OnSet(bool success) { +} + +class TestObserver : public NfcAdapter::Observer, + public NfcPeer::Observer, + public NfcTag::Observer, + public NfcNdefTagTechnology::Observer { public: TestObserver(scoped_refptr<NfcAdapter> adapter) : present_changed_count_(0), powered_changed_count_(0), + polling_changed_count_(0), + peer_records_received_count_(0), + tag_records_received_count_(0), + peer_count_(0), + tag_count_(0), adapter_(adapter) { } @@ -38,8 +72,68 @@ class TestObserver : public NfcAdapter::Observer { powered_changed_count_++; } + // NfcAdapter::Observer override. + virtual void AdapterPollingChanged(NfcAdapter* adapter, + bool powered) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + polling_changed_count_++; + } + + // NfcAdapter::Observer override. + virtual void PeerFound(NfcAdapter* adapter, NfcPeer* peer) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + peer_count_++; + peer_identifier_ = peer->GetIdentifier(); + } + + // NfcAdapter::Observer override. + virtual void PeerLost(NfcAdapter* adapter, NfcPeer* peer) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + EXPECT_EQ(peer_identifier_, peer->GetIdentifier()); + peer_count_--; + peer_identifier_.clear(); + } + + // NfcAdapter::Observer override. + virtual void TagFound(NfcAdapter* adapter, NfcTag* tag) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + tag_count_++; + tag_identifier_ = tag->GetIdentifier(); + } + + // NfcAdapter::Observer override. + virtual void TagLost(NfcAdapter* adapter, NfcTag* tag) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + EXPECT_EQ(tag_identifier_, tag->GetIdentifier()); + tag_count_--; + tag_identifier_.clear(); + } + + // NfcPeer::Observer override. + virtual void RecordReceived( + NfcPeer* peer, const NfcNdefRecord* record) OVERRIDE { + EXPECT_EQ(peer, adapter_->GetPeer(peer_identifier_)); + EXPECT_EQ(peer_identifier_, peer->GetIdentifier()); + peer_records_received_count_++; + } + + // NfcNdefTagTechnology::Observer override. + virtual void RecordReceived( + NfcTag* tag, const NfcNdefRecord* record) OVERRIDE { + EXPECT_EQ(tag, adapter_->GetTag(tag_identifier_)); + EXPECT_EQ(tag_identifier_, tag->GetIdentifier()); + tag_records_received_count_++; + } + int present_changed_count_; int powered_changed_count_; + int polling_changed_count_; + int peer_records_received_count_; + int tag_records_received_count_; + int peer_count_; + int tag_count_; + std::string peer_identifier_; + std::string tag_identifier_; scoped_refptr<NfcAdapter> adapter_; }; @@ -51,9 +145,16 @@ class NfcChromeOSTest : public testing::Test { DBusThreadManager::InitializeWithStub(); fake_nfc_adapter_client_ = static_cast<FakeNfcAdapterClient*>( DBusThreadManager::Get()->GetNfcAdapterClient()); - SetAdapter(); - message_loop_.RunUntilIdle(); - + fake_nfc_device_client_ = static_cast<FakeNfcDeviceClient*>( + DBusThreadManager::Get()->GetNfcDeviceClient()); + fake_nfc_record_client_ = static_cast<FakeNfcRecordClient*>( + DBusThreadManager::Get()->GetNfcRecordClient()); + fake_nfc_tag_client_ = static_cast<FakeNfcTagClient*>( + DBusThreadManager::Get()->GetNfcTagClient()); + + fake_nfc_adapter_client_->EnablePairingOnPoll(false); + fake_nfc_device_client_->DisableSimulationTimeout(); + fake_nfc_tag_client_->DisableSimulationTimeout(); success_callback_count_ = 0; error_callback_count_ = 0; } @@ -68,6 +169,7 @@ class NfcChromeOSTest : public testing::Test { adapter_ = new NfcAdapterChromeOS(); ASSERT_TRUE(adapter_.get() != NULL); ASSERT_TRUE(adapter_->IsInitialized()); + base::RunLoop().RunUntilIdle(); } // Generic callbacks for success and error. @@ -79,23 +181,36 @@ class NfcChromeOSTest : public testing::Test { error_callback_count_++; } + void ErrorCallbackWithParameters(const std::string& error_name, + const std::string& error_message) { + LOG(INFO) << "Error callback called: " << error_name << ", " + << error_message; + error_callback_count_++; + } + protected: + // MessageLoop instance, used to simulate asynchronous behavior. + base::MessageLoop message_loop_; + // Fields for storing the number of times SuccessCallback and ErrorCallback // have been called. int success_callback_count_; int error_callback_count_; - // A message loop to emulate asynchronous behavior. - base::MessageLoop message_loop_; - // The NfcAdapter instance under test. scoped_refptr<NfcAdapter> adapter_; // The fake D-Bus client instances used for testing. FakeNfcAdapterClient* fake_nfc_adapter_client_; + FakeNfcDeviceClient* fake_nfc_device_client_; + FakeNfcRecordClient* fake_nfc_record_client_; + FakeNfcTagClient* fake_nfc_tag_client_; }; +// Tests that the adapter updates correctly to reflect the current "default" +// adapter, when multiple adapters appear and disappear. TEST_F(NfcChromeOSTest, PresentChanged) { + SetAdapter(); EXPECT_TRUE(adapter_->IsPresent()); TestObserver observer(adapter_); @@ -122,7 +237,9 @@ TEST_F(NfcChromeOSTest, PresentChanged) { EXPECT_FALSE(adapter_->IsPresent()); } +// Tests that the adapter correctly reflects the power state. TEST_F(NfcChromeOSTest, SetPowered) { + SetAdapter(); TestObserver observer(adapter_); adapter_->AddObserver(&observer); @@ -177,7 +294,9 @@ TEST_F(NfcChromeOSTest, SetPowered) { EXPECT_EQ(2, error_callback_count_); } +// Tests that the power state updates correctly when the adapter disappears. TEST_F(NfcChromeOSTest, PresentChangedWhilePowered) { + SetAdapter(); TestObserver observer(adapter_); adapter_->AddObserver(&observer); @@ -199,4 +318,546 @@ TEST_F(NfcChromeOSTest, PresentChangedWhilePowered) { EXPECT_FALSE(adapter_->IsPresent()); } +// Tests that peer and record objects are created for all peers and records +// that already exist when the adapter is created. +TEST_F(NfcChromeOSTest, PeersInitializedWhenAdapterCreated) { + // Set up the adapter client. + NfcAdapterClient::Properties* properties = + fake_nfc_adapter_client_->GetProperties( + dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0)); + properties->powered.Set(true, base::Bind(&OnSet)); + + fake_nfc_adapter_client_->StartPollLoop( + dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0), + nfc_adapter::kModeInitiator, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_TRUE(properties->powered.value()); + EXPECT_TRUE(properties->polling.value()); + + // Start pairing simulation, which will add a fake device and fake records. + fake_nfc_device_client_->BeginPairingSimulation(0, 0); + base::RunLoop().RunUntilIdle(); + + // Create the adapter. + SetAdapter(); + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Observer shouldn't have received any calls, as it got created AFTER the + // notifications were sent. + EXPECT_EQ(0, observer.present_changed_count_); + EXPECT_EQ(0, observer.powered_changed_count_); + EXPECT_EQ(0, observer.polling_changed_count_); + EXPECT_EQ(0, observer.peer_count_); + + EXPECT_TRUE(adapter_->IsPresent()); + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsPolling()); + + NfcAdapter::PeerList peers; + adapter_->GetPeers(&peers); + EXPECT_EQ(static_cast<size_t>(1), peers.size()); + + NfcPeer* peer = peers[0]; + const NfcNdefMessage& message = peer->GetNdefMessage(); + EXPECT_EQ(static_cast<size_t>(3), message.records().size()); +} + +// Tests that tag and record objects are created for all tags and records that +// already exist when the adapter is created. +TEST_F(NfcChromeOSTest, TagsInitializedWhenAdapterCreated) { + const char kTestURI[] = "fake://path/for/testing"; + + // Set up the adapter client. + NfcAdapterClient::Properties* properties = + fake_nfc_adapter_client_->GetProperties( + dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0)); + properties->powered.Set(true, base::Bind(&OnSet)); + + fake_nfc_adapter_client_->StartPollLoop( + dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0), + nfc_adapter::kModeInitiator, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_TRUE(properties->powered.value()); + EXPECT_TRUE(properties->polling.value()); + + // Add the fake tag. + fake_nfc_tag_client_->BeginPairingSimulation(0); + base::RunLoop().RunUntilIdle(); + + // Create a fake record. + base::DictionaryValue test_record_data; + test_record_data.SetString(nfc_record::kTypeProperty, nfc_record::kTypeUri); + test_record_data.SetString(nfc_record::kUriProperty, kTestURI); + fake_nfc_tag_client_->Write( + dbus::ObjectPath(FakeNfcTagClient::kTagPath), + test_record_data, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + + // Create the adapter. + SetAdapter(); + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Observer shouldn't have received any calls, as it got created AFTER the + // notifications were sent. + EXPECT_EQ(0, observer.present_changed_count_); + EXPECT_EQ(0, observer.powered_changed_count_); + EXPECT_EQ(0, observer.polling_changed_count_); + EXPECT_EQ(0, observer.peer_count_); + + EXPECT_TRUE(adapter_->IsPresent()); + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsPolling()); + + NfcAdapter::TagList tags; + adapter_->GetTags(&tags); + EXPECT_EQ(static_cast<size_t>(1), tags.size()); + + NfcTag* tag = tags[0]; + const NfcNdefMessage& message = tag->GetNdefTagTechnology()->GetNdefMessage(); + EXPECT_EQ(static_cast<size_t>(1), message.records().size()); + + const NfcNdefRecord* record = message.records()[0]; + std::string uri; + EXPECT_TRUE(record->data().GetString(NfcNdefRecord::kFieldURI, &uri)); + EXPECT_EQ(kTestURI, uri); +} + +// Tests that the adapter correctly updates its state when polling is started +// and stopped. +TEST_F(NfcChromeOSTest, StartAndStopPolling) { + SetAdapter(); + EXPECT_TRUE(adapter_->IsPresent()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Start polling while not powered. Should fail. + EXPECT_FALSE(adapter_->IsPowered()); + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_FALSE(adapter_->IsPolling()); + + // Start polling while powered. Should succeed. + adapter_->SetPowered( + true, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(adapter_->IsPolling()); + + // Start polling while already polling. Should fail. + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_TRUE(adapter_->IsPolling()); + + // Stop polling. Should succeed. + adapter_->StopPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_FALSE(adapter_->IsPolling()); + + // Stop polling while not polling. Should fail. + adapter_->StopPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(3, error_callback_count_); + EXPECT_FALSE(adapter_->IsPolling()); +} + +// Tests a simple peer pairing simulation. +TEST_F(NfcChromeOSTest, PeerTest) { + SetAdapter(); + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + true, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_TRUE(adapter_->IsPolling()); + EXPECT_EQ(0, observer.peer_count_); + + // Add the fake device. + fake_nfc_device_client_->BeginPairingSimulation(0, -1); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, observer.peer_count_); + EXPECT_EQ(FakeNfcDeviceClient::kDevicePath, observer.peer_identifier_); + + NfcPeer* peer = adapter_->GetPeer(observer.peer_identifier_); + CHECK(peer); + peer->AddObserver(&observer); + + // Peer should have no records on it. + EXPECT_TRUE(peer->GetNdefMessage().records().empty()); + EXPECT_EQ(0, observer.peer_records_received_count_); + + // Make records visible. + fake_nfc_record_client_->SetDeviceRecordsVisible(true); + EXPECT_EQ(3, observer.peer_records_received_count_); + EXPECT_EQ(static_cast<size_t>(3), peer->GetNdefMessage().records().size()); + + // End the simulation. Peer should get removed. + fake_nfc_device_client_->EndPairingSimulation(); + EXPECT_EQ(0, observer.peer_count_); + EXPECT_TRUE(observer.peer_identifier_.empty()); + + peer = adapter_->GetPeer(observer.peer_identifier_); + EXPECT_FALSE(peer); + + // No record related notifications will be sent when a peer gets removed. + EXPECT_EQ(3, observer.peer_records_received_count_); +} + +// Tests a simple tag pairing simulation. +TEST_F(NfcChromeOSTest, TagTest) { + const char kTestURI[] = "fake://path/for/testing"; + + SetAdapter(); + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + true, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_TRUE(adapter_->IsPolling()); + EXPECT_EQ(0, observer.tag_count_); + + // Add the fake tag. + fake_nfc_tag_client_->BeginPairingSimulation(0); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, observer.tag_count_); + EXPECT_EQ(FakeNfcTagClient::kTagPath, observer.tag_identifier_); + + NfcTag* tag = adapter_->GetTag(observer.tag_identifier_); + CHECK(tag); + tag->AddObserver(&observer); + EXPECT_TRUE(tag->IsReady()); + CHECK(tag->GetNdefTagTechnology()); + tag->GetNdefTagTechnology()->AddObserver(&observer); + + NfcNdefTagTechnology* tag_technology = tag->GetNdefTagTechnology(); + EXPECT_TRUE(tag_technology->IsSupportedByTag()); + + // Tag should have no records on it. + EXPECT_TRUE(tag_technology->GetNdefMessage().records().empty()); + EXPECT_EQ(0, observer.tag_records_received_count_); + + // Set the tag record visible. By default the record has no content, so no + // NfcNdefMessage should be received. + fake_nfc_record_client_->SetTagRecordsVisible(true); + EXPECT_TRUE(tag_technology->GetNdefMessage().records().empty()); + EXPECT_EQ(0, observer.tag_records_received_count_); + fake_nfc_record_client_->SetTagRecordsVisible(false); + + // Write an NDEF record to the tag. + EXPECT_EQ(2, success_callback_count_); // 2 for SetPowered and StartPolling. + EXPECT_EQ(0, error_callback_count_); + + base::DictionaryValue record_data; + record_data.SetString(NfcNdefRecord::kFieldURI, kTestURI); + NfcNdefRecord written_record; + written_record.Populate(NfcNdefRecord::kTypeURI, &record_data); + NfcNdefMessage written_message; + written_message.AddRecord(&written_record); + + tag_technology->WriteNdef( + written_message, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(static_cast<size_t>(1), + tag_technology->GetNdefMessage().records().size()); + EXPECT_EQ(1, observer.tag_records_received_count_); + + NfcNdefRecord* received_record = + tag_technology->GetNdefMessage().records()[0]; + EXPECT_EQ(NfcNdefRecord::kTypeURI, received_record->type()); + std::string uri; + EXPECT_TRUE(received_record->data().GetString( + NfcNdefRecord::kFieldURI, &uri)); + EXPECT_EQ(kTestURI, uri); + + // End the simulation. Tag should get removed. + fake_nfc_tag_client_->EndPairingSimulation(); + EXPECT_EQ(0, observer.tag_count_); + EXPECT_TRUE(observer.tag_identifier_.empty()); + + tag = adapter_->GetTag(observer.tag_identifier_); + EXPECT_FALSE(tag); + + // No record related notifications will be sent when a tag gets removed. + EXPECT_EQ(1, observer.tag_records_received_count_); +} + +// Unit tests for nfc_ndef_record_utils methods. +TEST_F(NfcChromeOSTest, NfcNdefRecordToDBusAttributes) { + const char kText[] = "text"; + const char kURI[] = "test://uri"; + const char kEncoding[] = "encoding"; + const char kLanguageCode[] = "en"; + const char kMimeType[] = "mime-type"; + const double kSize = 5; + + // Text record. + base::DictionaryValue data; + data.SetString(NfcNdefRecord::kFieldText, kText); + data.SetString(NfcNdefRecord::kFieldLanguageCode, kLanguageCode); + data.SetString(NfcNdefRecord::kFieldEncoding, kEncoding); + + scoped_ptr<NfcNdefRecord> record(new NfcNdefRecord()); + ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data)); + + base::DictionaryValue result; + EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record.get(), &result)); + + std::string string_value; + EXPECT_TRUE(result.GetString( + nfc_record::kTypeProperty, &string_value)); + EXPECT_EQ(nfc_record::kTypeText, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kRepresentationProperty, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kLanguageProperty, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kEncodingProperty, &string_value)); + EXPECT_EQ(kEncoding, string_value); + + // URI record. + data.Clear(); + data.SetString(NfcNdefRecord::kFieldURI, kURI); + data.SetString(NfcNdefRecord::kFieldMimeType, kMimeType); + data.SetDouble(NfcNdefRecord::kFieldTargetSize, kSize); + + record.reset(new NfcNdefRecord()); + ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeURI, &data)); + + result.Clear(); + EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record.get(), &result)); + + EXPECT_TRUE(result.GetString(nfc_record::kTypeProperty, &string_value)); + EXPECT_EQ(nfc_record::kTypeUri, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kUriProperty, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kMimeTypeProperty, &string_value)); + EXPECT_EQ(kMimeType, string_value); + double double_value; + EXPECT_TRUE(result.GetDouble(nfc_record::kSizeProperty, &double_value)); + EXPECT_EQ(kSize, double_value); + + // SmartPoster record. + base::DictionaryValue* title = new base::DictionaryValue(); + title->SetString(NfcNdefRecord::kFieldText, kText); + title->SetString(NfcNdefRecord::kFieldLanguageCode, kLanguageCode); + title->SetString(NfcNdefRecord::kFieldEncoding, kEncoding); + + base::ListValue* titles = new base::ListValue(); + titles->Append(title); + data.Set(NfcNdefRecord::kFieldTitles, titles); + + record.reset(new NfcNdefRecord()); + ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data)); + + result.Clear(); + EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record.get(), &result)); + + EXPECT_TRUE(result.GetString( + nfc_record::kTypeProperty, &string_value)); + EXPECT_EQ(nfc_record::kTypeSmartPoster, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kRepresentationProperty, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kLanguageProperty, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kEncodingProperty, &string_value)); + EXPECT_EQ(kEncoding, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kUriProperty, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kMimeTypeProperty, &string_value)); + EXPECT_EQ(kMimeType, string_value); + EXPECT_TRUE(result.GetDouble(nfc_record::kSizeProperty, &double_value)); + EXPECT_EQ(kSize, double_value); +} + +TEST_F(NfcChromeOSTest, RecordPropertiesToNfcNdefRecord) { + const char kText[] = "text"; + const char kURI[] = "test://uri"; + const char kEncoding[] = "encoding"; + const char kLanguageCode[] = "en"; + const char kMimeType[] = "mime-type"; + const uint32 kSize = 5; + + FakeNfcRecordClient::Properties record_properties( + base::Bind(&OnPropertyChangedCallback)); + + // Text record. + record_properties.type.ReplaceValue(nfc_record::kTypeText); + record_properties.representation.ReplaceValue(kText); + record_properties.language.ReplaceValue(kLanguageCode); + record_properties.encoding.ReplaceValue(kEncoding); + + scoped_ptr<NfcNdefRecord> record(new NfcNdefRecord()); + EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_TRUE(record->IsPopulated()); + + std::string string_value; + EXPECT_EQ(NfcNdefRecord::kTypeText, record->type()); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldText, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldLanguageCode, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldEncoding, &string_value)); + EXPECT_EQ(kEncoding, string_value); + + // URI record. + record_properties.representation.ReplaceValue(""); + record_properties.language.ReplaceValue(""); + record_properties.encoding.ReplaceValue(""); + + record_properties.type.ReplaceValue(nfc_record::kTypeUri); + record_properties.uri.ReplaceValue(kURI); + record_properties.mime_type.ReplaceValue(kMimeType); + record_properties.size.ReplaceValue(kSize); + + record.reset(new NfcNdefRecord()); + EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_TRUE(record->IsPopulated()); + + EXPECT_EQ(NfcNdefRecord::kTypeURI, record->type()); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldURI, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldMimeType, &string_value)); + EXPECT_EQ(kMimeType, string_value); + double double_value; + EXPECT_TRUE(record->data().GetDouble( + NfcNdefRecord::kFieldTargetSize, &double_value)); + EXPECT_EQ(kSize, double_value); + + // Contents not matching type. + record_properties.representation.ReplaceValue(kText); + record_properties.language.ReplaceValue(kLanguageCode); + record_properties.encoding.ReplaceValue(kEncoding); + + record.reset(new NfcNdefRecord()); + EXPECT_FALSE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_FALSE(record->IsPopulated()); + + // SmartPoster record. + record_properties.type.ReplaceValue(nfc_record::kTypeSmartPoster); + EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_TRUE(record->IsPopulated()); + + EXPECT_EQ(NfcNdefRecord::kTypeSmartPoster, record->type()); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldURI, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldMimeType, &string_value)); + EXPECT_EQ(kMimeType, string_value); + EXPECT_TRUE(record->data().GetDouble( + NfcNdefRecord::kFieldTargetSize, &double_value)); + EXPECT_EQ(kSize, double_value); + + const base::ListValue* titles = NULL; + EXPECT_TRUE(record->data().GetList(NfcNdefRecord::kFieldTitles, &titles)); + EXPECT_EQ(static_cast<size_t>(1), titles->GetSize()); + ASSERT_TRUE(titles); + const base::DictionaryValue* title = NULL; + EXPECT_TRUE(titles->GetDictionary(0, &title)); + CHECK(title); + + EXPECT_TRUE(title->GetString(NfcNdefRecord::kFieldText, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(title->GetString( + NfcNdefRecord::kFieldLanguageCode, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(title->GetString(NfcNdefRecord::kFieldEncoding, &string_value)); + EXPECT_EQ(kEncoding, string_value); +} + } // namespace chromeos diff --git a/chromium/device/nfc/nfc_ndef_record.cc b/chromium/device/nfc/nfc_ndef_record.cc index ec13c2f3587..5b8bb994a81 100644 --- a/chromium/device/nfc/nfc_ndef_record.cc +++ b/chromium/device/nfc/nfc_ndef_record.cc @@ -7,6 +7,7 @@ #include <map> #include "base/logging.h" +#include "url/gurl.h" using base::DictionaryValue; using base::ListValue; @@ -17,6 +18,23 @@ namespace { typedef std::map<std::string, base::Value::Type> FieldValueMap; +bool ValidateURI(const DictionaryValue* data) { + std::string uri; + if (!data->GetString(NfcNdefRecord::kFieldURI, &uri)) { + VLOG(1) << "No URI entry in data."; + return false; + } + DCHECK(!uri.empty()); + + // Use GURL to check validity. + GURL url(uri); + if (!url.is_valid()) { + LOG(ERROR) << "Invalid URI given: " << uri; + return false; + } + return true; +} + bool CheckFieldsAreValid( const FieldValueMap& required_fields, const FieldValueMap& optional_fields, @@ -47,6 +65,12 @@ bool CheckFieldsAreValid( << field_iter->second; return false; } + // Make sure that the value is non-empty, if the value is a string. + std::string string_value; + if (iter.value().GetAsString(&string_value) && string_value.empty()) { + VLOG(1) << "Empty value given for field of type string: " << iter.key(); + return false; + } } // Check for required fields. if (required_count != required_fields.size()) { @@ -63,13 +87,23 @@ bool HandleTypeText(const DictionaryValue* data) { VLOG(1) << "Populating record with type \"Text\"."; FieldValueMap required_fields; required_fields[NfcNdefRecord::kFieldText] = base::Value::TYPE_STRING; + required_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING; + required_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING; FieldValueMap optional_fields; - optional_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING; - optional_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING; if (!CheckFieldsAreValid(required_fields, optional_fields, data)) { VLOG(1) << "Failed to populate record."; return false; } + + // Verify that the "Encoding" property has valid values. + std::string encoding; + if (!data->GetString(NfcNdefRecord::kFieldEncoding, &encoding)) { + if (encoding != NfcNdefRecord::kEncodingUtf8 || + encoding != NfcNdefRecord::kEncodingUtf16) { + VLOG(1) << "Invalid \"Encoding\" value:" << encoding; + return false; + } + } return true; } @@ -113,7 +147,7 @@ bool HandleTypeSmartPoster(const DictionaryValue* data) { } } } - return true; + return ValidateURI(data); } // Verifies that the contents of |data| conform to the fields of NDEF type @@ -125,11 +159,13 @@ bool HandleTypeUri(const DictionaryValue* data) { FieldValueMap optional_fields; optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING; optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE; + + // Allow passing TargetSize as an integer, but convert it to a double. if (!CheckFieldsAreValid(required_fields, optional_fields, data)) { VLOG(1) << "Failed to populate record."; return false; } - return true; + return ValidateURI(data); } } // namespace @@ -210,4 +246,15 @@ void NfcNdefMessage::AddRecord(NfcNdefRecord* record) { records_.push_back(record); } +bool NfcNdefMessage::RemoveRecord(NfcNdefRecord* record) { + for (RecordList::iterator iter = records_.begin(); + iter != records_.end(); ++iter) { + if (*iter == record) { + records_.erase(iter); + return true; + } + } + return false; +} + } // namespace device diff --git a/chromium/device/nfc/nfc_ndef_record.h b/chromium/device/nfc/nfc_ndef_record.h index 2be66b175a3..24cbc12090c 100644 --- a/chromium/device/nfc/nfc_ndef_record.h +++ b/chromium/device/nfc/nfc_ndef_record.h @@ -146,9 +146,15 @@ class NfcNdefMessage { const RecordList& records() const { return records_; } // Adds the NDEF record |record| to the sequence of records that this - // NdefMessage contains. + // NfcNdefMessage contains. This method simply adds the record to this message + // and does NOT take ownership of it. void AddRecord(NfcNdefRecord* record); + // Removes the NDEF record |record| from this message. Returns true, if the + // record was removed, otherwise returns false if |record| was not contained + // in this message. + bool RemoveRecord(NfcNdefRecord* record); + private: // The NDEF records that are contained by this message. RecordList records_; diff --git a/chromium/device/nfc/nfc_ndef_record_unittest.cc b/chromium/device/nfc/nfc_ndef_record_unittest.cc index 00c2d2f5133..2b6ef48b2bb 100644 --- a/chromium/device/nfc/nfc_ndef_record_unittest.cc +++ b/chromium/device/nfc/nfc_ndef_record_unittest.cc @@ -21,7 +21,7 @@ const char kTestLanguageCode[] = "test-language-code"; const char kTestMimeType[] = "test-mime-type"; const uint32 kTestTargetSize = 0; const char kTestText[] = "test-text"; -const char kTestURI[] = "test-uri"; +const char kTestURI[] = "test://uri"; } // namespace @@ -33,35 +33,32 @@ TEST(NfcNdefRecordTest, PopulateTextRecord) { EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); - // Text field with incorrect entry. Should fail. + // Text field with incorrect type. Should fail. data.SetInteger(NfcNdefRecord::kFieldText, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); - // Text field with correct entry. Should succeed. + // Text field with correct type but missing encoding and language. + // Should fail. data.SetString(NfcNdefRecord::kFieldText, kTestText); - EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data)); - EXPECT_TRUE(record->IsPopulated()); - EXPECT_EQ(NfcNdefRecord::kTypeText, record->type()); + EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); + EXPECT_FALSE(record->IsPopulated()); // Populating a successfully populated record should fail. EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); - // Recycle the record. - record.reset(new NfcNdefRecord()); - EXPECT_FALSE(record->IsPopulated()); - - // Incorrect optional fields. Should fail. + // Incorrect type for language code. data.SetInteger(NfcNdefRecord::kFieldLanguageCode, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); + // Correct type for language code, invalid encoding. data.SetString(NfcNdefRecord::kFieldLanguageCode, kTestLanguageCode); data.SetInteger(NfcNdefRecord::kFieldEncoding, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); - // Optional fields are correct. Should succeed. + // All entries valid. Should succeed. data.SetString(NfcNdefRecord::kFieldEncoding, kTestEncoding); EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_TRUE(record->IsPopulated()); @@ -88,12 +85,16 @@ TEST(NfcNdefRecordTest, PopulateUriRecord) { EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data)); EXPECT_FALSE(record->IsPopulated()); - // URI field with incorrect entry. Should fail. + // URI field with incorrect type. Should fail. data.SetInteger(NfcNdefRecord::kFieldURI, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data)); EXPECT_FALSE(record->IsPopulated()); - // URI field with correct entry. Should succeed. + // URI field with correct type but invalid format. + data.SetString(NfcNdefRecord::kFieldURI, "test.uri"); + EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data)); + EXPECT_FALSE(record->IsPopulated()); + data.SetString(NfcNdefRecord::kFieldURI, kTestURI); EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeURI, &data)); EXPECT_TRUE(record->IsPopulated()); @@ -203,8 +204,10 @@ TEST(NfcNdefRecordTest, PopulateSmartPoster) { EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data)); EXPECT_FALSE(record->IsPopulated()); - // Title value with valid "text" field. + // Title value with valid entries. title_value->SetString(NfcNdefRecord::kFieldText, kTestText); + title_value->SetString(NfcNdefRecord::kFieldLanguageCode, kTestLanguageCode); + title_value->SetString(NfcNdefRecord::kFieldEncoding, kTestEncoding); EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data)); EXPECT_TRUE(record->IsPopulated()); @@ -233,6 +236,12 @@ TEST(NfcNdefRecordTest, PopulateSmartPoster) { EXPECT_TRUE(const_dictionary_value->GetString( NfcNdefRecord::kFieldText, &string_value)); EXPECT_EQ(kTestText, string_value); + EXPECT_TRUE(const_dictionary_value->GetString( + NfcNdefRecord::kFieldLanguageCode, &string_value)); + EXPECT_EQ(kTestLanguageCode, string_value); + EXPECT_TRUE(const_dictionary_value->GetString( + NfcNdefRecord::kFieldEncoding, &string_value)); + EXPECT_EQ(kTestEncoding, string_value); } } // namespace device diff --git a/chromium/device/nfc/nfc_ndef_record_utils_chromeos.cc b/chromium/device/nfc/nfc_ndef_record_utils_chromeos.cc new file mode 100644 index 00000000000..466cf38b971 --- /dev/null +++ b/chromium/device/nfc/nfc_ndef_record_utils_chromeos.cc @@ -0,0 +1,238 @@ +// Copyright 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/nfc/nfc_ndef_record_utils_chromeos.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "device/nfc/nfc_ndef_record.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::NfcNdefRecord; + +namespace chromeos { +namespace nfc_ndef_record_utils { + +namespace { + +// Maps the NDEF type |type| as returned by neard to the corresponding +// device::NfcNdefRecord::Type value. +NfcNdefRecord::Type DBusRecordTypeValueToNfcNdefRecordType( + const std::string& type) { + if (type == nfc_record::kTypeSmartPoster) + return NfcNdefRecord::kTypeSmartPoster; + if (type == nfc_record::kTypeText) + return NfcNdefRecord::kTypeText; + if (type == nfc_record::kTypeUri) + return NfcNdefRecord::kTypeURI; + if (type == nfc_record::kTypeHandoverRequest) + return NfcNdefRecord::kTypeHandoverRequest; + if (type == nfc_record::kTypeHandoverSelect) + return NfcNdefRecord::kTypeHandoverSelect; + if (type == nfc_record::kTypeHandoverCarrier) + return NfcNdefRecord::kTypeHandoverCarrier; + return NfcNdefRecord::kTypeUnknown; +} + +// Maps the NDEF type |type| given as a NFC C++ API enumeration to the +// corresponding string value defined by neard. +std::string NfcRecordTypeEnumToPropertyValue(NfcNdefRecord::Type type) { + switch (type) { + case NfcNdefRecord::kTypeSmartPoster: + return nfc_record::kTypeSmartPoster; + case NfcNdefRecord::kTypeText: + return nfc_record::kTypeText; + case NfcNdefRecord::kTypeURI: + return nfc_record::kTypeUri; + case NfcNdefRecord::kTypeHandoverRequest: + return nfc_record::kTypeHandoverRequest; + case NfcNdefRecord::kTypeHandoverSelect: + return nfc_record::kTypeHandoverSelect; + case NfcNdefRecord::kTypeHandoverCarrier: + return nfc_record::kTypeHandoverCarrier; + default: + return ""; + } +} + +// Maps the field name |field_name| given as defined in NfcNdefRecord to the +// Neard Record D-Bus property name. This handles all fields except for +// NfcNdefRecord::kFieldTitles and NfcNdefRecord::kFieldAction, which need +// special handling. +std::string NdefRecordFieldToDBusProperty(const std::string& field_name) { + if (field_name == NfcNdefRecord::kFieldEncoding) + return nfc_record::kEncodingProperty; + if (field_name == NfcNdefRecord::kFieldLanguageCode) + return nfc_record::kLanguageProperty; + if (field_name == NfcNdefRecord::kFieldText) + return nfc_record::kRepresentationProperty; + if (field_name == NfcNdefRecord::kFieldURI) + return nfc_record::kUriProperty; + if (field_name == NfcNdefRecord::kFieldMimeType) + return nfc_record::kMimeTypeProperty; + if (field_name == NfcNdefRecord::kFieldTargetSize) + return nfc_record::kSizeProperty; + return ""; +} + +std::string NfcNdefRecordActionValueToDBusActionValue( + const std::string& action) { + // TODO(armansito): Add bindings for values returned by neard to + // service_constants.h. + if (action == device::NfcNdefRecord::kSmartPosterActionDo) + return "Do"; + if (action == device::NfcNdefRecord::kSmartPosterActionSave) + return "Save"; + if (action == device::NfcNdefRecord::kSmartPosterActionOpen) + return "Edit"; + return ""; +} + +std::string DBusActionValueToNfcNdefRecordActionValue( + const std::string& action) { + // TODO(armansito): Add bindings for values returned by neard to + // service_constants.h. + if (action == "Do") + return device::NfcNdefRecord::kSmartPosterActionDo; + if (action == "Save") + return device::NfcNdefRecord::kSmartPosterActionSave; + if (action == "Edit") + return device::NfcNdefRecord::kSmartPosterActionOpen; + return ""; +} + + +// Translates the given dictionary of NDEF fields by recursively converting +// each key in |record_data| to its corresponding Record property name defined +// by the Neard D-Bus API. The output is stored in |out|. Returns false if an +// error occurs. +bool ConvertNdefFieldsToDBusAttributes( + const base::DictionaryValue& fields, + base::DictionaryValue* out) { + DCHECK(out); + for (base::DictionaryValue::Iterator iter(fields); + !iter.IsAtEnd(); iter.Advance()) { + // Special case the "titles" and "action" fields. + if (iter.key() == NfcNdefRecord::kFieldTitles) { + const base::ListValue* titles = NULL; + bool value_result = iter.value().GetAsList(&titles); + DCHECK(value_result); + DCHECK(titles->GetSize() != 0); + // TODO(armansito): For now, pick the first title in the list and write + // its contents directly to the top level of the field. This is due to an + // error in the Neard D-Bus API design. This code will need to be updated + // if the neard API changes to correct this. + const base::DictionaryValue* first_title = NULL; + value_result = titles->GetDictionary(0, &first_title); + DCHECK(value_result); + if (!ConvertNdefFieldsToDBusAttributes(*first_title, out)) { + LOG(ERROR) << "Invalid title field."; + return false; + } + } else if (iter.key() == NfcNdefRecord::kFieldAction) { + // The value of the action field needs to be translated. + std::string action_value; + bool value_result = iter.value().GetAsString(&action_value); + DCHECK(value_result); + std::string action = + NfcNdefRecordActionValueToDBusActionValue(action_value); + if (action.empty()) { + VLOG(1) << "Invalid action value: \"" << action_value << "\""; + return false; + } + out->SetString(nfc_record::kActionProperty, action); + } else { + std::string dbus_property = NdefRecordFieldToDBusProperty(iter.key()); + if (dbus_property.empty()) { + LOG(ERROR) << "Invalid field: " << iter.key(); + return false; + } + out->Set(dbus_property, iter.value().DeepCopy()); + } + } + return true; +} + +} // namespace + +bool NfcNdefRecordToDBusAttributes( + const NfcNdefRecord* record, + base::DictionaryValue* out) { + DCHECK(record); + DCHECK(out); + if (!record->IsPopulated()) { + LOG(ERROR) << "Record is not populated."; + return false; + } + out->SetString(nfc_record::kTypeProperty, + NfcRecordTypeEnumToPropertyValue(record->type())); + return ConvertNdefFieldsToDBusAttributes(record->data(), out); +} + +bool RecordPropertiesToNfcNdefRecord( + const NfcRecordClient::Properties* properties, + device::NfcNdefRecord* out) { + if (out->IsPopulated()) { + LOG(ERROR) << "Record is already populated!"; + return false; + } + NfcNdefRecord::Type type = + DBusRecordTypeValueToNfcNdefRecordType(properties->type.value()); + if (type == NfcNdefRecord::kTypeUnknown) { + LOG(ERROR) << "Record type is unknown."; + return false; + } + + // Extract each property. + base::DictionaryValue attributes; + if (!properties->uri.value().empty()) + attributes.SetString(NfcNdefRecord::kFieldURI, properties->uri.value()); + if (!properties->mime_type.value().empty()) { + attributes.SetString(NfcNdefRecord::kFieldMimeType, + properties->mime_type.value()); + } + if (properties->size.value() != 0) { + attributes.SetDouble(NfcNdefRecord::kFieldTargetSize, + static_cast<double>(properties->size.value())); + } + std::string action_value = + DBusActionValueToNfcNdefRecordActionValue(properties->action.value()); + if (!action_value.empty()) + attributes.SetString(NfcNdefRecord::kFieldAction, action_value); + + // The "representation", "encoding", and "language" properties will be stored + // differently, depending on whether the record type is "SmartPoster" or + // "Text". + { + scoped_ptr<base::DictionaryValue> text_attributes( + new base::DictionaryValue()); + if (!properties->representation.value().empty()) { + text_attributes->SetString(NfcNdefRecord::kFieldText, + properties->representation.value()); + } + if (!properties->encoding.value().empty()) { + text_attributes->SetString(NfcNdefRecord::kFieldEncoding, + properties->encoding.value()); + } + if (!properties->language.value().empty()) { + text_attributes->SetString(NfcNdefRecord::kFieldLanguageCode, + properties->language.value()); + } + if (!text_attributes->empty()) { + if (type == NfcNdefRecord::kTypeSmartPoster) { + base::ListValue* titles = new base::ListValue(); + titles->Append(text_attributes.release()); + attributes.Set(NfcNdefRecord::kFieldTitles, titles); + } else { + attributes.MergeDictionary(text_attributes.get()); + } + } + } + + // Populate the given record. + return out->Populate(type, &attributes); +} + +} // namespace nfc_ndef_record_utils +} // namespace chromeos diff --git a/chromium/device/nfc/nfc_ndef_record_utils_chromeos.h b/chromium/device/nfc/nfc_ndef_record_utils_chromeos.h new file mode 100644 index 00000000000..2d96b97a323 --- /dev/null +++ b/chromium/device/nfc/nfc_ndef_record_utils_chromeos.h @@ -0,0 +1,36 @@ +// Copyright 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/values.h" +#include "chromeos/dbus/nfc_record_client.h" + +#ifndef DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_ +#define DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_ + +namespace device { +class NfcNdefRecord; +} // namespace device; + +namespace chromeos { +namespace nfc_ndef_record_utils { + +// Converts the NfcNdefRecord |record| to a dictionary that can be passed to +// NfcDeviceClient::Push and NfcTagClient::Write and stores it in |out|. +// Returns false, if an error occurs during conversion. +bool NfcNdefRecordToDBusAttributes( + const device::NfcNdefRecord* record, + base::DictionaryValue* out); + +// Converts an NDEF record D-Bus properties structure to an NfcNdefRecord +// instance by populating the instance passed in |out|. |out| must not be NULL +// and must not be already populated. Returns false, if an error occurs during +// conversion. +bool RecordPropertiesToNfcNdefRecord( + const NfcRecordClient::Properties* properties, + device::NfcNdefRecord* out); + +} // namespace nfc_ndef_record_utils +} // namespace chromeos + +#endif // DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_ diff --git a/chromium/device/nfc/nfc_peer.h b/chromium/device/nfc/nfc_peer.h index 157873872ee..a38192f166a 100644 --- a/chromium/device/nfc/nfc_peer.h +++ b/chromium/device/nfc/nfc_peer.h @@ -37,12 +37,12 @@ class NfcPeer { public: virtual ~Observer() {} - // This method will be called when an NDEF message |message| from the peer + // This method will be called when an NDEF record |record| from the peer // device |peer| is received. Users can use this method to be notified of // new records on the device and when the initial set of records are - // received from it, if any. - virtual void RecordsReceived(NfcPeer* peer, - const NfcNdefMessage& message) {} + // received from it, if any. All records received from |peer| can be + // accessed by calling |peer->GetNdefMessage()|. + virtual void RecordReceived(NfcPeer* peer, const NfcNdefRecord* record) {} }; // The ErrorCallback is used by methods to asynchronously report errors. @@ -64,12 +64,12 @@ class NfcPeer { // this only means that no records have yet been received from the device. // Users should use this method in conjunction with the Observer methods // to be notified when the records are ready. - virtual NfcNdefMessage GetNdefMessage() const = 0; + virtual const NfcNdefMessage& GetNdefMessage() const = 0; // Sends the NDEF records contained in |message| to the peer device. On // success, |callback| will be invoked. On failure, |error_callback| will be // invoked. - virtual void PushNdef(NfcNdefMessage* message, + virtual void PushNdef(const NfcNdefMessage& message, const base::Closure& callback, const ErrorCallback& error_callback) = 0; diff --git a/chromium/device/nfc/nfc_peer_chromeos.cc b/chromium/device/nfc/nfc_peer_chromeos.cc new file mode 100644 index 00000000000..4ef1804b857 --- /dev/null +++ b/chromium/device/nfc/nfc_peer_chromeos.cc @@ -0,0 +1,194 @@ +// Copyright 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/nfc/nfc_peer_chromeos.h" + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/nfc_device_client.h" +#include "device/nfc/nfc_ndef_record_utils_chromeos.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::NfcNdefMessage; +using device::NfcNdefRecord; + +namespace chromeos { + +namespace { + +typedef std::vector<dbus::ObjectPath> ObjectPathVector; + +} // namespace + +NfcPeerChromeOS::NfcPeerChromeOS(const dbus::ObjectPath& object_path) + : object_path_(object_path), + weak_ptr_factory_(this) { + // Create record objects for all records that were received before. + const ObjectPathVector& records = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetRecordsForDevice(object_path_); + for (ObjectPathVector::const_iterator iter = records.begin(); + iter != records.end(); ++iter) { + AddRecord(*iter); + } + DBusThreadManager::Get()->GetNfcRecordClient()->AddObserver(this); +} + +NfcPeerChromeOS::~NfcPeerChromeOS() { + DBusThreadManager::Get()->GetNfcRecordClient()->RemoveObserver(this); + STLDeleteValues(&records_); +} + +void NfcPeerChromeOS::AddObserver(device::NfcPeer::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); +} + +void NfcPeerChromeOS::RemoveObserver(device::NfcPeer::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); +} + +std::string NfcPeerChromeOS::GetIdentifier() const { + return object_path_.value(); +} + +const NfcNdefMessage& NfcPeerChromeOS::GetNdefMessage() const { + return message_; +} + +void NfcPeerChromeOS::PushNdef(const NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (message.records().empty()) { + LOG(ERROR) << "Given NDEF message is empty. Cannot push it."; + error_callback.Run(); + return; + } + // TODO(armansito): neard currently supports pushing only one NDEF record + // to a remote device and won't support multiple records until 0.15. Until + // then, report failure if |message| contains more than one record. + if (message.records().size() > 1) { + LOG(ERROR) << "Currently, pushing only 1 NDEF record is supported."; + error_callback.Run(); + return; + } + const NfcNdefRecord* record = message.records()[0]; + base::DictionaryValue attributes; + if (!nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record, &attributes)) { + LOG(ERROR) << "Failed to extract NDEF record fields for NDEF push."; + error_callback.Run(); + return; + } + DBusThreadManager::Get()->GetNfcDeviceClient()->Push( + object_path_, + attributes, + base::Bind(&NfcPeerChromeOS::OnPushNdef, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&NfcPeerChromeOS::OnPushNdefError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void NfcPeerChromeOS::StartHandover(HandoverType handover_type, + const base::Closure& callback, + const ErrorCallback& error_callback) { + // TODO(armansito): Initiating handover with a peer is currently not + // supported. For now, return an error immediately. + LOG(ERROR) << "NFC Handover currently not supported."; + error_callback.Run(); +} + +void NfcPeerChromeOS::RecordAdded(const dbus::ObjectPath& object_path) { + // Don't create the record object yet. Instead, wait until all record + // properties have been received and contruct the object and notify observers + // then. + VLOG(1) << "Record added: " << object_path.value() << ". Waiting until " + << "all properties have been fetched to create record object."; +} + +void NfcPeerChromeOS::RecordRemoved(const dbus::ObjectPath& object_path) { + NdefRecordMap::iterator iter = records_.find(object_path); + if (iter == records_.end()) + return; + VLOG(1) << "Lost remote NDEF record object: " << object_path.value() + << ", removing record."; + NfcNdefRecord* record = iter->second; + message_.RemoveRecord(record); + delete record; + records_.erase(iter); +} + +void NfcPeerChromeOS::RecordPropertiesReceived( + const dbus::ObjectPath& object_path) { + VLOG(1) << "Record properties received for: " << object_path.value(); + + // Check if the found record belongs to this device. + bool record_found = false; + const ObjectPathVector& records = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetRecordsForDevice(object_path_); + for (ObjectPathVector::const_iterator iter = records.begin(); + iter != records.end(); ++iter) { + if (*iter == object_path) { + record_found = true; + break; + } + } + if (!record_found) { + VLOG(1) << "Record \"" << object_path.value() << "\" doesn't belong to this" + << " device. Ignoring."; + return; + } + + AddRecord(object_path); +} + +void NfcPeerChromeOS::OnPushNdef(const base::Closure& callback) { + callback.Run(); +} + +void NfcPeerChromeOS::OnPushNdefError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(ERROR) << object_path_.value() << ": Failed to Push NDEF message: " + << error_name << ": " << error_message; + error_callback.Run(); +} + +void NfcPeerChromeOS::AddRecord(const dbus::ObjectPath& object_path) { + // Ignore this call if an entry for this record already exists. + if (records_.find(object_path) != records_.end()) { + VLOG(1) << "Record object for remote \"" << object_path.value() + << "\" already exists."; + return; + } + + NfcRecordClient::Properties* record_properties = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetProperties(object_path); + DCHECK(record_properties); + + NfcNdefRecord* record = new NfcNdefRecord(); + if (!nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + record_properties, record)) { + LOG(ERROR) << "Failed to create record object for record with object " + << "path \"" << object_path.value() << "\""; + delete record; + return; + } + + message_.AddRecord(record); + records_[object_path] = record; + FOR_EACH_OBSERVER(NfcPeer::Observer, observers_, + RecordReceived(this, record)); +} + +} // namespace chromeos diff --git a/chromium/device/nfc/nfc_peer_chromeos.h b/chromium/device/nfc/nfc_peer_chromeos.h new file mode 100644 index 00000000000..eec8e4bce2a --- /dev/null +++ b/chromium/device/nfc/nfc_peer_chromeos.h @@ -0,0 +1,81 @@ +// Copyright 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_NFC_NFC_PEER_CHROMEOS_H_ +#define DEVICE_NFC_NFC_PEER_CHROMEOS_H_ + +#include <map> + +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "chromeos/dbus/nfc_record_client.h" +#include "dbus/object_path.h" +#include "device/nfc/nfc_ndef_record.h" +#include "device/nfc/nfc_peer.h" + +namespace chromeos { + +// The NfcPeerChromeOS class implements NfcPeer for the Chrome OS platform. +class NfcPeerChromeOS : public device::NfcPeer, + public NfcRecordClient::Observer { + public: + // NfcPeer overrides. + virtual void AddObserver(device::NfcPeer::Observer* observer) OVERRIDE; + virtual void RemoveObserver(device::NfcPeer::Observer* observer) OVERRIDE; + virtual std::string GetIdentifier() const OVERRIDE; + virtual const device::NfcNdefMessage& GetNdefMessage() const OVERRIDE; + virtual void PushNdef(const device::NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void StartHandover(HandoverType handover_type, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + + private: + friend class NfcAdapterChromeOS; + + // Mapping from D-Bus object paths to NfcNdefRecord objects. + typedef std::map<dbus::ObjectPath, device::NfcNdefRecord*> NdefRecordMap; + + explicit NfcPeerChromeOS(const dbus::ObjectPath& object_path); + virtual ~NfcPeerChromeOS(); + + // NfcRecordClient::Observer overrides. + virtual void RecordAdded(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void RecordRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void RecordPropertiesReceived( + const dbus::ObjectPath& object_path) OVERRIDE; + + // Called by dbus:: on completion of the D-Bus method call to push an NDEF. + void OnPushNdef(const base::Closure& callback); + void OnPushNdefError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Creates a record object for the record with object path |object_path| and + // notifies the observers, if a record object did not already exist for it. + void AddRecord(const dbus::ObjectPath& object_path); + + // Object path of the peer that we are currently tracking. + dbus::ObjectPath object_path_; + + // A map containing the NDEF records that were received from the peer. + NdefRecordMap records_; + + // Message instance that contains pointers to all created records. + device::NfcNdefMessage message_; + + // List of observers interested in event notifications from us. + ObserverList<device::NfcPeer::Observer> observers_; + + // 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<NfcPeerChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NfcPeerChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_NFC_NFC_PEER_CHROMEOS_H_ diff --git a/chromium/device/nfc/nfc_tag.h b/chromium/device/nfc/nfc_tag.h index 650f37b0599..039fc78133d 100644 --- a/chromium/device/nfc/nfc_tag.h +++ b/chromium/device/nfc/nfc_tag.h @@ -31,7 +31,8 @@ class NfcTag { kTagType1, kTagType2, kTagType3, - kTagType4 + kTagType4, + kTagTypeUnknown, }; // NFC protocols that a tag can support. A tag will usually support only one @@ -41,7 +42,8 @@ class NfcTag { kProtocolIsoDep, kProtocolJewel, kProtocolMifare, - kProtocolNfcDep + kProtocolNfcDep, + kProtocolUnknown }; // Interface for observing changes from NFC tags. @@ -49,12 +51,18 @@ class NfcTag { public: virtual ~Observer() {} - // This method will be called when an NDEF message |message|, stored on the - // NFC tag |tag| has been read. Although NDEF is the most common record - // storage format for NFC tags, not all tags support it. This method won't - // be called if there are no records on an NDEF compliant tag or if the tag - // doesn't support NDEF. - virtual void RecordsReceived(NfcTag* tag, const NfcNdefMessage& message) {} + // Called when the tag type has been determined. + virtual void TagTypeChanged(NfcTag* tag, TagType type) {} + + // Called when the write access to the tag has been determined or changed. + virtual void TagWritePermissionChanged(NfcTag* tag, bool read_only) {} + + // Called when the underlying NFC protocol has been determined. + virtual void TagSupportedProtocolChanged(NfcTag* tag, Protocol protocol) {} + + // Called when all initial values of the tag properties have been received + // from the remote tag and |tag| is ready to use. + virtual void TagReady(NfcTag* tag) {} }; virtual ~NfcTag(); @@ -81,6 +89,17 @@ class NfcTag { virtual NfcTagTechnology::TechnologyTypeMask GetSupportedTechnologies() const = 0; + // Returns true, if all tag properties have been received from the remote tag + // and this object is ready to use. + virtual bool IsReady() const = 0; + + // Returns a pointer to the NDEF technology object that allows I/O on NDEF + // records. If NDEF is not supported by this tag, operations that are + // performed on the returned instance may not succeed. Users can determine + // support by calling NfcTagTechnology::IsSupportedByTag. The returned + // instance is owned by this tag. + virtual NfcNdefTagTechnology* GetNdefTagTechnology() = 0; + protected: NfcTag(); diff --git a/chromium/device/nfc/nfc_tag_chromeos.cc b/chromium/device/nfc/nfc_tag_chromeos.cc new file mode 100644 index 00000000000..e2390fa04fb --- /dev/null +++ b/chromium/device/nfc/nfc_tag_chromeos.cc @@ -0,0 +1,164 @@ +// 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 "device/nfc/nfc_tag_chromeos.h" + +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/nfc/nfc_tag_technology_chromeos.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::NfcTag; +using device::NfcTagTechnology; +using device::NfcNdefTagTechnology; + +namespace chromeos { + +namespace { + +// Converts an NFC tag type value returned by neard to a NfcTag::TagType enum +// value. +NfcTag::TagType DBusTypePropertyToTagType(const std::string& type) { + if (type == nfc_tag::kTagType1) + return NfcTag::kTagType1; + if (type == nfc_tag::kTagType2) + return NfcTag::kTagType2; + if (type == nfc_tag::kTagType3) + return NfcTag::kTagType3; + if (type == nfc_tag::kTagType4) + return NfcTag::kTagType4; + return NfcTag::kTagTypeUnknown; +} + +// Converts an NFC tag protocol value returned by neard to a NfcTag::Protocol +// enum value. +NfcTag::Protocol DBusProtocolPropertyToTagProtocol( + const std::string& protocol) { + if (protocol == nfc_common::kProtocolFelica) + return NfcTag::kProtocolFelica; + if (protocol == nfc_common::kProtocolIsoDep) + return NfcTag::kProtocolIsoDep; + if (protocol == nfc_common::kProtocolJewel) + return NfcTag::kProtocolJewel; + if (protocol == nfc_common::kProtocolMifare) + return NfcTag::kProtocolMifare; + if (protocol == nfc_common::kProtocolNfcDep) + return NfcTag::kProtocolNfcDep; + return NfcTag::kProtocolUnknown; +} + +} // namespace + +NfcTagChromeOS::NfcTagChromeOS(const dbus::ObjectPath& object_path) + : object_path_(object_path), + is_ready_(false), + ndef_technology_(new NfcNdefTagTechnologyChromeOS(this)) { + DBusThreadManager::Get()->GetNfcTagClient()->AddObserver(this); +} + +NfcTagChromeOS::~NfcTagChromeOS() { + DBusThreadManager::Get()->GetNfcTagClient()->RemoveObserver(this); +} + +void NfcTagChromeOS::AddObserver(NfcTag::Observer* observer) { + observers_.AddObserver(observer); +} + +void NfcTagChromeOS::RemoveObserver(NfcTag::Observer* observer) { + observers_.RemoveObserver(observer); +} + +std::string NfcTagChromeOS::GetIdentifier() const { + return object_path_.value(); +} + +NfcTag::TagType NfcTagChromeOS::GetType() const { + DCHECK(object_path_.IsValid()); + return DBusTypePropertyToTagType( + DBusThreadManager::Get()->GetNfcTagClient()-> + GetProperties(object_path_)->type.value()); +} + +bool NfcTagChromeOS::IsReadOnly() const { + DCHECK(object_path_.IsValid()); + return DBusThreadManager::Get()->GetNfcTagClient()-> + GetProperties(object_path_)->read_only.value(); +} + +NfcTag::Protocol NfcTagChromeOS::GetSupportedProtocol() const { + DCHECK(object_path_.IsValid()); + return DBusProtocolPropertyToTagProtocol( + DBusThreadManager::Get()->GetNfcTagClient()-> + GetProperties(object_path_)->protocol.value()); +} + +NfcTagTechnology::TechnologyTypeMask +NfcTagChromeOS::GetSupportedTechnologies() const { + // Determine supported technologies based on the tag's protocol and + // type. + NfcTag::TagType type = GetType(); + NfcTag::Protocol protocol = GetSupportedProtocol(); + if (type == NfcTag::kTagTypeUnknown || protocol == kProtocolUnknown) { + VLOG(1) << "Tag type and protocol unknown."; + return 0; + } + + NfcTagTechnology::TechnologyTypeMask technologies = 0; + technologies |= NfcTagTechnology::kTechnologyTypeNdef; + if (type == NfcTag::kTagType3) { + DCHECK(protocol == NfcTag::kProtocolFelica); + return technologies | NfcTagTechnology::kTechnologyTypeNfcF; + } + + if (protocol == NfcTag::kProtocolIsoDep) { + DCHECK(type == NfcTag::kTagType4); + technologies |= NfcTagTechnology::kTechnologyTypeIsoDep; + // TODO(armansito): Neard doesn't provide enough information to determine + // if the underlying wave-form is type A or type B. For now, report + // neither. + return technologies; + } + + return technologies | NfcTagTechnology::kTechnologyTypeNfcA; +} + +bool NfcTagChromeOS::IsReady() const { + return is_ready_; +} + +NfcNdefTagTechnology* NfcTagChromeOS::GetNdefTagTechnology() { + return ndef_technology_.get(); +} + +void NfcTagChromeOS::TagPropertyChanged(const dbus::ObjectPath& object_path, + const std::string& property_name) { + if (object_path != object_path_) + return; + + NfcTagClient::Properties* properties = + DBusThreadManager::Get()->GetNfcTagClient()->GetProperties(object_path_); + DCHECK(properties); + + if (property_name == properties->type.name()) { + FOR_EACH_OBSERVER(NfcTag::Observer, observers_, + TagTypeChanged(this, GetType())); + } else if (property_name == properties->read_only.name()) { + FOR_EACH_OBSERVER(NfcTag::Observer, observers_, + TagWritePermissionChanged(this, IsReadOnly())); + } else if (property_name == properties->protocol.name()) { + FOR_EACH_OBSERVER( + NfcTag::Observer, observers_, + TagSupportedProtocolChanged(this, GetSupportedProtocol())); + } +} + +void NfcTagChromeOS::TagPropertiesReceived( + const dbus::ObjectPath& object_path) { + if (is_ready_ || object_path != object_path_) + return; + + is_ready_ = true; + FOR_EACH_OBSERVER(NfcTag::Observer, observers_, TagReady(this)); +} + +} // namespace chromeos diff --git a/chromium/device/nfc/nfc_tag_chromeos.h b/chromium/device/nfc/nfc_tag_chromeos.h new file mode 100644 index 00000000000..5e4a723eeda --- /dev/null +++ b/chromium/device/nfc/nfc_tag_chromeos.h @@ -0,0 +1,70 @@ +// 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 DEVICE_NFC_NFC_TAG_CHROMEOS_H_ +#define DEVICE_NFC_NFC_TAG_CHROMEOS_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "chromeos/dbus/nfc_tag_client.h" +#include "dbus/object_path.h" +#include "device/nfc/nfc_tag.h" + +namespace chromeos { + +class NfcNdefTagTechnologyChromeOS; + +// The NfcTagChromeOS class implements device::NfcTag for the Chrome OS +// platform. +class NfcTagChromeOS : public device::NfcTag, + public NfcTagClient::Observer { + public: + // device::NfcTag overrides. + virtual void AddObserver(device::NfcTag::Observer* observer) OVERRIDE; + virtual void RemoveObserver(device::NfcTag::Observer* observer) OVERRIDE; + virtual std::string GetIdentifier() const OVERRIDE; + virtual TagType GetType() const OVERRIDE; + virtual bool IsReadOnly() const OVERRIDE; + virtual device::NfcTag::Protocol GetSupportedProtocol() const OVERRIDE; + virtual device::NfcTagTechnology::TechnologyTypeMask + GetSupportedTechnologies() const OVERRIDE; + virtual bool IsReady() const OVERRIDE; + virtual device::NfcNdefTagTechnology* GetNdefTagTechnology() OVERRIDE; + + // NfcTagClient::Observer overrides. + virtual void TagPropertyChanged(const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + virtual void TagPropertiesReceived( + const dbus::ObjectPath& object_path) OVERRIDE; + + // Object path representing the remote tag object. + const dbus::ObjectPath& object_path() const { return object_path_; } + + private: + friend class NfcAdapterChromeOS; + + explicit NfcTagChromeOS(const dbus::ObjectPath& object_path); + virtual ~NfcTagChromeOS(); + + // Object path of the tag that we are currently tracking. + dbus::ObjectPath object_path_; + + // Stores whether or not the initial set of properties have been received. + bool is_ready_; + + // The NfcNdefTagTechnology instance that allows users to perform NDEF + // read and write on this tag. + scoped_ptr<NfcNdefTagTechnologyChromeOS> ndef_technology_; + + // List of observers interested in event notifications from us. + ObserverList<device::NfcTag::Observer> observers_; + + DISALLOW_COPY_AND_ASSIGN(NfcTagChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_NFC_NFC_TAG_CHROMEOS_H_ diff --git a/chromium/device/nfc/nfc_tag_technology.cc b/chromium/device/nfc/nfc_tag_technology.cc index fa2b66a2baf..b07fb5263f1 100644 --- a/chromium/device/nfc/nfc_tag_technology.cc +++ b/chromium/device/nfc/nfc_tag_technology.cc @@ -29,11 +29,4 @@ NfcNdefTagTechnology::NfcNdefTagTechnology(NfcTag* tag) NfcNdefTagTechnology::~NfcNdefTagTechnology() { } -// static -NfcNdefTagTechnology* NfcNdefTagTechnology::Create(NfcTag* tag) { - // TODO(armansito): Create and return platform-specific implementation - // instances here. - return NULL; -} - } // namespace device diff --git a/chromium/device/nfc/nfc_tag_technology.h b/chromium/device/nfc/nfc_tag_technology.h index c5d1c19af37..35def12d8ef 100644 --- a/chromium/device/nfc/nfc_tag_technology.h +++ b/chromium/device/nfc/nfc_tag_technology.h @@ -15,7 +15,8 @@ class NfcTag; // NfcTagTechnology represents an NFC technology that allows a certain type of // I/O operation on an NFC tag. NFC tags can support a wide array of protocols. // The NfcTagTechnology hierarchy allows both raw and high-level I/O operations -// on NFC tags. +// on NFC tags. Do not create instances of these objects directly. Instead, +// obtain a handle directly from an NfcTag object. class NfcTagTechnology { public: // The various I/O technologies that an NFC tag can support. @@ -56,7 +57,15 @@ class NfcTagTechnology { }; // NfcNdefTagTechnology allows reading and writing NDEF messages to a tag. This -// is the most commonly used data exchange format in NFC. +// is the most commonly used data exchange format in NFC. NDEF is a data +// exchange format and is the top most layer of the protocol stack. NDEF itself +// is not a protocol; data is typically formatted in a way that is defined by +// the NDEF format and then transmitted via one of the underlying protocols. +// Hence all tags are capable of NDEF data exchange, however, all tags don't +// necessarily use NDEF to operate (e.g. a tag may contain a smart chip that +// does data processing on ISO-DEP based APDUs and ignores NDEF). This is why, +// even if a tag inherently supports NDEF, operations done via this class may +// not necessarily succeed. class NfcNdefTagTechnology : public NfcTagTechnology { public: // The ErrorCallback is used by methods to asynchronously report errors. @@ -64,6 +73,25 @@ class NfcNdefTagTechnology : public NfcTagTechnology { virtual ~NfcNdefTagTechnology(); + // Interface for observing changes from NFC tags related to NDEF records. + class Observer { + public: + virtual ~Observer() {} + + // This method will be called when an NDEF record |record|, stored on the + // NFC tag |tag| has been read. This method will be called multiple times + // as records are read from the tag or when the tag's records change (e.g. + // when the tag has been rewritten). All received records can be accessed by + // calling GetNdefMessage(). + virtual void RecordReceived(NfcTag* tag, const NfcNdefRecord* record) {} + }; + + // Adds and removes observers for events on this NFC tag. If monitoring + // multiple tags, check the |tag| parameter of observer methods to determine + // which tag is issuing the event. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + // NfcTagTechnology override. virtual bool IsSupportedByTag() const OVERRIDE; @@ -72,22 +100,17 @@ class NfcNdefTagTechnology : public NfcTagTechnology { // means that no records have yet been received from the tag. Users should // use this method in conjunction with the NfcTag::Observer::RecordsReceived // method to be notified when the records are ready. - virtual NfcNdefMessage GetNdefMessage() const = 0; + virtual const NfcNdefMessage& GetNdefMessage() const = 0; // Writes the given NDEF message to the underlying tag, overwriting any // existing NDEF message on it. On success, |callback| will be invoked. On // failure, |error_callback| will be invoked. This method can fail, if the // underlying tag does not support NDEF as a technology. - virtual void WriteNdefMessage(const NfcNdefMessage& message, - const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + virtual void WriteNdef(const NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) = 0; - // Static factory method for constructing an instance. The ownership of the - // returned instance belongs to the caller. Returns NULL, if NFC is not - // supported on the current platform. - static NfcNdefTagTechnology* Create(NfcTag* tag); - - private: + protected: // Constructs a technology instance, where |tag| is the NFC tag that this // instance will operate on. explicit NfcNdefTagTechnology(NfcTag* tag); diff --git a/chromium/device/nfc/nfc_tag_technology_chromeos.cc b/chromium/device/nfc/nfc_tag_technology_chromeos.cc new file mode 100644 index 00000000000..25d1b240dae --- /dev/null +++ b/chromium/device/nfc/nfc_tag_technology_chromeos.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 "device/nfc/nfc_tag_technology_chromeos.h" + +#include "base/stl_util.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/nfc/nfc_ndef_record_utils_chromeos.h" +#include "device/nfc/nfc_tag_chromeos.h" + +using device::NfcNdefMessage; +using device::NfcNdefRecord; + +namespace chromeos { + +namespace { + +typedef std::vector<dbus::ObjectPath> ObjectPathVector; + +} // namespace + +NfcNdefTagTechnologyChromeOS::NfcNdefTagTechnologyChromeOS(NfcTagChromeOS* tag) + : NfcNdefTagTechnology(tag), + object_path_(tag->object_path()), + weak_ptr_factory_(this) { + DCHECK(tag); + // Create record objects for all records that were received before. + const ObjectPathVector& records = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetRecordsForTag(object_path_); + for (ObjectPathVector::const_iterator iter = records.begin(); + iter != records.end(); ++iter) { + AddRecord(*iter); + } + DBusThreadManager::Get()->GetNfcRecordClient()->AddObserver(this); +} + +NfcNdefTagTechnologyChromeOS::~NfcNdefTagTechnologyChromeOS() { + DBusThreadManager::Get()->GetNfcRecordClient()->RemoveObserver(this); + STLDeleteValues(&records_); +} + +void NfcNdefTagTechnologyChromeOS::AddObserver( + NfcNdefTagTechnology::Observer* observer) { + observers_.AddObserver(observer); +} + +void NfcNdefTagTechnologyChromeOS::RemoveObserver( + NfcNdefTagTechnology::Observer* observer) { + observers_.RemoveObserver(observer); +} + +const NfcNdefMessage& NfcNdefTagTechnologyChromeOS::GetNdefMessage() const { + return message_; +} + +void NfcNdefTagTechnologyChromeOS::WriteNdef( + const device::NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (message.records().empty()) { + LOG(ERROR) << "Given NDEF message is empty. Cannot write it."; + error_callback.Run(); + return; + } + if (!tag()->IsReady()) { + LOG(ERROR) << "The tag is not ready yet: " << tag()->GetIdentifier(); + error_callback.Run(); + return; + } + // TODO(armansito): neard currently supports writing only one NDEF record + // to a tag and won't support multiple records until 0.15. Until then, report + // failure if |message| contains more than one record. + if (message.records().size() > 1) { + LOG(ERROR) << "Currently, writing only 1 NDEF record is supported."; + error_callback.Run(); + return; + } + const NfcNdefRecord* record = message.records()[0]; + base::DictionaryValue attributes; + if (!nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record, &attributes)) { + LOG(ERROR) << "Failed to extract NDEF record fields for NDEF push."; + error_callback.Run(); + return; + } + DBusThreadManager::Get()->GetNfcTagClient()->Write( + object_path_, + attributes, + base::Bind(&NfcNdefTagTechnologyChromeOS::OnWriteNdefMessage, + weak_ptr_factory_.GetWeakPtr(), callback), + base::Bind(&NfcNdefTagTechnologyChromeOS::OnWriteNdefMessageError, + weak_ptr_factory_.GetWeakPtr(), error_callback)); +} + +void NfcNdefTagTechnologyChromeOS::RecordAdded( + const dbus::ObjectPath& object_path) { + // Don't create the record object yet. Instead, wait until all record + // properties have been received and construct the object and notify observers + // then. + VLOG(1) << "Record added: " << object_path.value() << ". Waiting until " + "all properties have been fetched to create record object."; +} + +void NfcNdefTagTechnologyChromeOS::RecordRemoved( + const dbus::ObjectPath& object_path) { + NdefRecordMap::iterator iter = records_.find(object_path); + if (iter == records_.end()) + return; + VLOG(1) << "Lost remote NDEF record object: " << object_path.value() + << ", removing record."; + NfcNdefRecord* record = iter->second; + message_.RemoveRecord(record); + delete record; + records_.erase(iter); +} + +void NfcNdefTagTechnologyChromeOS::RecordPropertiesReceived( + const dbus::ObjectPath& object_path) { + VLOG(1) << "Record properties received for: " << object_path.value(); + + // Check if the found record belongs to this tag. + bool record_found = false; + const ObjectPathVector& records = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetRecordsForTag(object_path_); + for (ObjectPathVector::const_iterator iter = records.begin(); + iter != records.end(); ++iter) { + if (*iter == object_path) { + record_found = true; + break; + } + } + if (!record_found) { + VLOG(1) << "Record \"" << object_path.value() << "\" doesn't belong to this" + << " tag. Ignoring."; + return; + } + AddRecord(object_path); +} + +void NfcNdefTagTechnologyChromeOS::OnWriteNdefMessage( + const base::Closure& callback) { + callback.Run(); +} + +void NfcNdefTagTechnologyChromeOS::OnWriteNdefMessageError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(ERROR) << object_path_.value() << ": Failed to Push NDEF message: " + << error_name << ": " << error_message; + error_callback.Run(); +} + +void NfcNdefTagTechnologyChromeOS::AddRecord( + const dbus::ObjectPath& object_path) { + // Ignore this call if an entry for this record already exists. + if (records_.find(object_path) != records_.end()) { + VLOG(1) << "Record object for remote \"" << object_path.value() + << "\" already exists."; + return; + } + + NfcRecordClient::Properties* record_properties = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetProperties(object_path); + DCHECK(record_properties); + + NfcNdefRecord* record = new NfcNdefRecord(); + if (!nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + record_properties, record)) { + LOG(ERROR) << "Failed to create record object for record with object " + << "path \"" << object_path.value() << "\""; + delete record; + return; + } + + message_.AddRecord(record); + records_[object_path] = record; + FOR_EACH_OBSERVER(NfcNdefTagTechnology::Observer, observers_, + RecordReceived(tag(), record)); +} + +} // namespace chromeos diff --git a/chromium/device/nfc/nfc_tag_technology_chromeos.h b/chromium/device/nfc/nfc_tag_technology_chromeos.h new file mode 100644 index 00000000000..71b070ef74f --- /dev/null +++ b/chromium/device/nfc/nfc_tag_technology_chromeos.h @@ -0,0 +1,87 @@ +// 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 DEVICE_NFC_NFC_TAG_TECHNOLOGY_CHROMEOS_H_ +#define DEVICE_NFC_NFC_TAG_TECHNOLOGY_CHROMEOS_H_ + +#include <map> + +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "chromeos/dbus/nfc_record_client.h" +#include "device/nfc/nfc_tag_technology.h" + +namespace chromeos { + +class NfcTagChromeOS; + +// The NfcNdefTagTechnologyChromeOS class implements +// device::NfcNdefTagTechnology for the Chrome OS platform. The lifetime of an +// instance of this class must be tied to an instance of NfcTagChromeOS. +// Instances of this class must never outlast the owning NfcTagChromeOS +// instance. +class NfcNdefTagTechnologyChromeOS : public device::NfcNdefTagTechnology, + public NfcRecordClient::Observer { + public: + virtual ~NfcNdefTagTechnologyChromeOS(); + + // device::NfcNdefTagTechnology overrides. + virtual void AddObserver(device::NfcNdefTagTechnology::Observer* observer) + OVERRIDE; + virtual void RemoveObserver(device::NfcNdefTagTechnology::Observer* observer) + OVERRIDE; + virtual const device::NfcNdefMessage& GetNdefMessage() const OVERRIDE; + virtual void WriteNdef(const device::NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + + // NfcRecordClient::Observer overrides. + virtual void RecordAdded(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void RecordRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void RecordPropertiesReceived( + const dbus::ObjectPath& object_path) OVERRIDE; + + private: + friend class NfcTagChromeOS; + + // Mapping from D-Bus object paths to NfcNdefRecord objects. + typedef std::map<dbus::ObjectPath, device::NfcNdefRecord*> NdefRecordMap; + + explicit NfcNdefTagTechnologyChromeOS(NfcTagChromeOS* tag); + + // Called by dbus:: on completion of the D-Bus method call to write an NDEF. + void OnWriteNdefMessage(const base::Closure& callback); + void OnWriteNdefMessageError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Creates a record object for the record with object path |object_path| and + // notifies the observers, if a record object did not already exist for it. + void AddRecord(const dbus::ObjectPath& object_path); + + // A map containing the NDEF records that were received from the tag. + NdefRecordMap records_; + + // Message instance that contains pointers to all created records that are + // in |records_|. This is mainly used as the cached return value for + // GetNdefMessage(). + device::NfcNdefMessage message_; + + // List of observers interested in event notifications from us. + ObserverList<device::NfcNdefTagTechnology::Observer> observers_; + + // D-Bus object path of the remote tag or device that this object operates + // on. + dbus::ObjectPath object_path_; + + // 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<NfcNdefTagTechnologyChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NfcNdefTagTechnologyChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_NFC_NFC_TAG_TECHNOLOGY_CHROMEOS_H_ diff --git a/chromium/device/serial/BUILD.gn b/chromium/device/serial/BUILD.gn new file mode 100644 index 00000000000..0b38e93cbae --- /dev/null +++ b/chromium/device/serial/BUILD.gn @@ -0,0 +1,37 @@ +# 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +# GYP version: device/serial/serial.gyp:device_serial +static_library("serial") { + output_name = "device_serial" + + sources = [ + "serial_device_enumerator.cc", + "serial_device_enumerator.h", + "serial_device_enumerator_linux.cc", + "serial_device_enumerator_linux.h", + "serial_device_enumerator_mac.cc", + "serial_device_enumerator_mac.h", + "serial_device_enumerator_win.cc", + "serial_device_enumerator_win.h", + ] + + if (is_linux) { + configs += [ "//build/config/linux:udev" ] + } + + deps = [ + ":serial_mojo", + ] +} + +mojom("serial_mojo") { + visibility = ":serial" + + sources = [ + "serial.mojom", + ] +} diff --git a/chromium/device/serial/serial.gyp b/chromium/device/serial/serial.gyp new file mode 100644 index 00000000000..0df7005af83 --- /dev/null +++ b/chromium/device/serial/serial.gyp @@ -0,0 +1,43 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + # GN version: //device/serial + 'target_name': 'device_serial', + 'type': 'static_library', + 'include_dirs': [ + '../..', + ], + 'conditions': [ + ['OS=="linux"', { + 'dependencies': [ + '../../build/linux/system.gyp:udev', + ], + }], + ], + 'variables': { + 'mojom_base_output_dir': 'device/serial', + }, + 'includes': [ + '../../mojo/public/tools/bindings/mojom_bindings_generator.gypi', + ], + 'sources': [ + 'serial.mojom', + 'serial_device_enumerator.cc', + 'serial_device_enumerator.h', + 'serial_device_enumerator_linux.cc', + 'serial_device_enumerator_linux.h', + 'serial_device_enumerator_mac.cc', + 'serial_device_enumerator_mac.h', + 'serial_device_enumerator_win.cc', + 'serial_device_enumerator_win.h', + ], + }, + ], +} diff --git a/chromium/device/serial/serial.mojom b/chromium/device/serial/serial.mojom new file mode 100644 index 00000000000..f925dff33cd --- /dev/null +++ b/chromium/device/serial/serial.mojom @@ -0,0 +1,16 @@ +// 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. + +module device { + +struct SerialDeviceInfo { + string path; + uint16 vendor_id; + bool has_vendor_id = false; + uint16 product_id; + bool has_product_id = false; + string display_name; +}; + +} diff --git a/chromium/device/serial/serial_device_enumerator.cc b/chromium/device/serial/serial_device_enumerator.cc new file mode 100644 index 00000000000..586d33d8446 --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator.cc @@ -0,0 +1,13 @@ +// 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 "device/serial/serial_device_enumerator.h" + +namespace device { + +SerialDeviceEnumerator::SerialDeviceEnumerator() {} + +SerialDeviceEnumerator::~SerialDeviceEnumerator() {} + +} // namespace device diff --git a/chromium/device/serial/serial_device_enumerator.h b/chromium/device/serial/serial_device_enumerator.h new file mode 100644 index 00000000000..c1245c73d6b --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator.h @@ -0,0 +1,27 @@ +// 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 DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_ +#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_ + +#include "base/memory/scoped_ptr.h" +#include "device/serial/serial.mojom.h" +#include "mojo/public/cpp/bindings/array.h" + +namespace device { + +// Discovers and enumerates serial devices available to the host. +class SerialDeviceEnumerator { + public: + static scoped_ptr<SerialDeviceEnumerator> Create(); + + SerialDeviceEnumerator(); + virtual ~SerialDeviceEnumerator(); + + virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() = 0; +}; + +} // namespace device + +#endif // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_ diff --git a/chromium/device/serial/serial_device_enumerator_linux.cc b/chromium/device/serial/serial_device_enumerator_linux.cc new file mode 100644 index 00000000000..269c7ef9b28 --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_linux.cc @@ -0,0 +1,107 @@ +// 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 "device/serial/serial_device_enumerator_linux.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" + +namespace device { + +namespace { + +const char kSerialSubsystem[] = "tty"; + +const char kHostPathKey[] = "DEVNAME"; +const char kHostBusKey[] = "ID_BUS"; +const char kVendorIDKey[] = "ID_VENDOR_ID"; +const char kProductIDKey[] = "ID_MODEL_ID"; +const char kProductNameKey[] = "ID_MODEL"; + +struct UdevEnumerateDeleter { + void operator()(udev_enumerate* enumerate) { + udev_enumerate_unref(enumerate); + } +}; + +struct UdevDeviceDeleter { + void operator()(udev_device* device) { udev_device_unref(device); } +}; + +typedef scoped_ptr<udev_enumerate, UdevEnumerateDeleter> ScopedUdevEnumeratePtr; +typedef scoped_ptr<udev_device, UdevDeviceDeleter> ScopedUdevDevicePtr; +} + +// static +scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() { + return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorLinux()); +} + +SerialDeviceEnumeratorLinux::SerialDeviceEnumeratorLinux() { + udev_.reset(udev_new()); +} + +SerialDeviceEnumeratorLinux::~SerialDeviceEnumeratorLinux() {} + +mojo::Array<SerialDeviceInfoPtr> SerialDeviceEnumeratorLinux::GetDevices() { + mojo::Array<SerialDeviceInfoPtr> devices; + ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get())); + if (!enumerate) { + LOG(ERROR) << "Serial device enumeration failed."; + return devices.Pass(); + } + if (udev_enumerate_add_match_subsystem(enumerate.get(), kSerialSubsystem)) { + LOG(ERROR) << "Serial device enumeration failed."; + return devices.Pass(); + } + if (udev_enumerate_scan_devices(enumerate.get())) { + LOG(ERROR) << "Serial device enumeration failed."; + return devices.Pass(); + } + + udev_list_entry* entry = udev_enumerate_get_list_entry(enumerate.get()); + for (; entry != NULL; entry = udev_list_entry_get_next(entry)) { + ScopedUdevDevicePtr device(udev_device_new_from_syspath( + udev_.get(), udev_list_entry_get_name(entry))); + // TODO(rockot): There may be a better way to filter serial devices here, + // but it's not clear what that would be. Udev will list lots of virtual + // devices with no real endpoint to back them anywhere. The presence of + // a bus identifier (e.g., "pci" or "usb") seems to be a good heuristic + // for detecting actual devices. + const char* path = + udev_device_get_property_value(device.get(), kHostPathKey); + const char* bus = udev_device_get_property_value(device.get(), kHostBusKey); + if (path != NULL && bus != NULL) { + SerialDeviceInfoPtr info(SerialDeviceInfo::New()); + info->path = path; + + const char* vendor_id = + udev_device_get_property_value(device.get(), kVendorIDKey); + const char* product_id = + udev_device_get_property_value(device.get(), kProductIDKey); + const char* product_name = + udev_device_get_property_value(device.get(), kProductNameKey); + + uint32 int_value; + if (vendor_id && base::HexStringToUInt(vendor_id, &int_value)) { + info->vendor_id = int_value; + info->has_vendor_id = true; + } + if (product_id && base::HexStringToUInt(product_id, &int_value)) { + info->product_id = int_value; + info->has_product_id = true; + } + if (product_name) + info->display_name = product_name; + devices.push_back(info.Pass()); + } + } + return devices.Pass(); +} + +void SerialDeviceEnumeratorLinux::UdevDeleter::operator()(udev* handle) { + udev_unref(handle); +} + +} // namespace device diff --git a/chromium/device/serial/serial_device_enumerator_linux.h b/chromium/device/serial/serial_device_enumerator_linux.h new file mode 100644 index 00000000000..0b34fe841de --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_linux.h @@ -0,0 +1,36 @@ +// 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 DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_LINUX_H_ +#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_LINUX_H_ + +#include <libudev.h> + +#include "base/memory/scoped_ptr.h" +#include "device/serial/serial_device_enumerator.h" + +namespace device { + +// Discovers and enumerates serial devices available to the host. +class SerialDeviceEnumeratorLinux : public SerialDeviceEnumerator { + public: + SerialDeviceEnumeratorLinux(); + virtual ~SerialDeviceEnumeratorLinux(); + + // Implementation for SerialDeviceEnumerator. + virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() OVERRIDE; + + private: + struct UdevDeleter { + void operator()(udev* handle); + }; + + scoped_ptr<udev, UdevDeleter> udev_; + + DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorLinux); +}; + +} // namespace device + +#endif // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_LINUX_H_ diff --git a/chromium/device/serial/serial_device_enumerator_mac.cc b/chromium/device/serial/serial_device_enumerator_mac.cc new file mode 100644 index 00000000000..b29352f425e --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_mac.cc @@ -0,0 +1,60 @@ +// 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 "device/serial/serial_device_enumerator_mac.h" + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" + +namespace device { + +// static +scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() { + return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorMac()); +} + +SerialDeviceEnumeratorMac::SerialDeviceEnumeratorMac() {} + +SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {} + +// TODO(rockot): Use IOKit to enumerate serial interfaces. +mojo::Array<SerialDeviceInfoPtr> SerialDeviceEnumeratorMac::GetDevices() { + const base::FilePath kDevRoot("/dev"); + const int kFilesAndSymLinks = + base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS; + + std::set<std::string> valid_patterns; + valid_patterns.insert("/dev/*Bluetooth*"); + valid_patterns.insert("/dev/*Modem*"); + valid_patterns.insert("/dev/*bluetooth*"); + valid_patterns.insert("/dev/*modem*"); + valid_patterns.insert("/dev/*serial*"); + valid_patterns.insert("/dev/tty.*"); + valid_patterns.insert("/dev/cu.*"); + + mojo::Array<SerialDeviceInfoPtr> devices; + base::FileEnumerator enumerator(kDevRoot, false, kFilesAndSymLinks); + do { + const base::FilePath next_device_path(enumerator.Next()); + const std::string next_device = next_device_path.value(); + if (next_device.empty()) + break; + + std::set<std::string>::const_iterator i = valid_patterns.begin(); + for (; i != valid_patterns.end(); ++i) { + if (MatchPattern(next_device, *i)) { + SerialDeviceInfoPtr info(SerialDeviceInfo::New()); + info->path = next_device; + devices.push_back(info.Pass()); + break; + } + } + } while (true); + return devices.Pass(); +} + +} // namespace device diff --git a/chromium/device/serial/serial_device_enumerator_mac.h b/chromium/device/serial/serial_device_enumerator_mac.h new file mode 100644 index 00000000000..d1c6db378df --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_mac.h @@ -0,0 +1,27 @@ +// 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 DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_MAC_H_ +#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_MAC_H_ + +#include "device/serial/serial_device_enumerator.h" + +namespace device { + +// Discovers and enumerates serial devices available to the host. +class SerialDeviceEnumeratorMac : public SerialDeviceEnumerator { + public: + SerialDeviceEnumeratorMac(); + virtual ~SerialDeviceEnumeratorMac(); + + // Implementation for SerialDeviceEnumerator. + virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorMac); +}; + +} // namespace device + +#endif // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_MAC_H_ diff --git a/chromium/device/serial/serial_device_enumerator_win.cc b/chromium/device/serial/serial_device_enumerator_win.cc new file mode 100644 index 00000000000..c00d4d4c20f --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_win.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 "device/serial/serial_device_enumerator_win.h" + +#include <windows.h> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/registry.h" + +namespace device { + +// static +scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() { + return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorWin()); +} + +SerialDeviceEnumeratorWin::SerialDeviceEnumeratorWin() {} + +SerialDeviceEnumeratorWin::~SerialDeviceEnumeratorWin() {} + +// TODO(rockot): Query the system for more information than just device paths. +// This may or may not require using a different strategy than scanning the +// registry location below. +mojo::Array<SerialDeviceInfoPtr> SerialDeviceEnumeratorWin::GetDevices() { + base::win::RegistryValueIterator iter_key( + HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM\\"); + mojo::Array<SerialDeviceInfoPtr> devices; + for (; iter_key.Valid(); ++iter_key) { + SerialDeviceInfoPtr info(SerialDeviceInfo::New()); + info->path = base::UTF16ToASCII(iter_key.Value()); + devices.push_back(info.Pass()); + } + return devices.Pass(); +} + +} // namespace device diff --git a/chromium/device/serial/serial_device_enumerator_win.h b/chromium/device/serial/serial_device_enumerator_win.h new file mode 100644 index 00000000000..082b8f92b73 --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_win.h @@ -0,0 +1,27 @@ +// 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 DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_ +#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_ + +#include "device/serial/serial_device_enumerator.h" + +namespace device { + +// Discovers and enumerates serial devices available to the host. +class SerialDeviceEnumeratorWin : public SerialDeviceEnumerator { + public: + SerialDeviceEnumeratorWin(); + virtual ~SerialDeviceEnumeratorWin(); + + // Implementation for SerialDeviceEnumerator. + virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorWin); +}; + +} // namespace device + +#endif // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_ diff --git a/chromium/device/udev_linux/BUILD.gn b/chromium/device/udev_linux/BUILD.gn new file mode 100644 index 00000000000..917a7e593d4 --- /dev/null +++ b/chromium/device/udev_linux/BUILD.gn @@ -0,0 +1,22 @@ +# 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. + +import("//build/config/features.gni") + +if (use_udev) { + source_set("udev_linux") { + sources = [ + "udev.cc", + "udev.h", + ] + + deps = [ + "//base", + ] + + configs += [ + "//build/config/linux:udev", + ] + } +} diff --git a/chromium/device/udev_linux/udev.cc b/chromium/device/udev_linux/udev.cc new file mode 100644 index 00000000000..e10074b5aa7 --- /dev/null +++ b/chromium/device/udev_linux/udev.cc @@ -0,0 +1,27 @@ +// 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 <libudev.h> + +#include "device/udev_linux/udev.h" + +namespace device { + +void UdevDeleter::operator()(udev* dev) const { + udev_unref(dev); +} + +void UdevEnumerateDeleter::operator()(udev_enumerate* enumerate) const { + udev_enumerate_unref(enumerate); +} + +void UdevDeviceDeleter::operator()(udev_device* device) const { + udev_device_unref(device); +} + +void UdevMonitorDeleter::operator()(udev_monitor* monitor) const { + udev_monitor_unref(monitor); +} + +} // namespace device diff --git a/chromium/device/udev_linux/udev.gyp b/chromium/device/udev_linux/udev.gyp new file mode 100644 index 00000000000..6d142ac8766 --- /dev/null +++ b/chromium/device/udev_linux/udev.gyp @@ -0,0 +1,30 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'conditions': [ + ['use_udev==1', { + 'targets': [ + { + 'target_name': 'udev_linux', + 'type': 'static_library', + 'dependencies': [ + '../../base/base.gyp:base', + '../../build/linux/system.gyp:udev', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'udev.cc', + 'udev.h', + ], + }, + ], + }], + ] +} diff --git a/chromium/device/udev_linux/udev.h b/chromium/device/udev_linux/udev.h new file mode 100644 index 00000000000..cffb6723cc4 --- /dev/null +++ b/chromium/device/udev_linux/udev.h @@ -0,0 +1,38 @@ +// 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 DEVICE_UDEV_LINUX_UDEV_H_ +#define DEVICE_UDEV_LINUX_UDEV_H_ + +#include <libudev.h> + +#include "base/memory/scoped_ptr.h" + +#if !defined(USE_UDEV) +#error "USE_UDEV not defined" +#endif + +namespace device { + +struct UdevDeleter { + void operator()(udev* dev) const; +}; +struct UdevEnumerateDeleter { + void operator()(udev_enumerate* enumerate) const; +}; +struct UdevDeviceDeleter { + void operator()(udev_device* device) const; +}; +struct UdevMonitorDeleter { + void operator()(udev_monitor* monitor) const; +}; + +typedef scoped_ptr<udev, UdevDeleter> ScopedUdevPtr; +typedef scoped_ptr<udev_enumerate, UdevEnumerateDeleter> ScopedUdevEnumeratePtr; +typedef scoped_ptr<udev_device, UdevDeviceDeleter> ScopedUdevDevicePtr; +typedef scoped_ptr<udev_monitor, UdevMonitorDeleter> ScopedUdevMonitorPtr; + +} // namespace device + +#endif // DEVICE_UDEV_LINUX_UDEV_H_ diff --git a/chromium/device/usb/BUILD.gn b/chromium/device/usb/BUILD.gn new file mode 100644 index 00000000000..f024965e72e --- /dev/null +++ b/chromium/device/usb/BUILD.gn @@ -0,0 +1,31 @@ +# 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. + +source_ids = "//third_party/usb_ids/usb.ids" +generated_ids = "$target_gen_dir/usb_ids_gen.cc" + +source_set("usb") { + sources = [ + "usb_ids.cc", + "usb_ids.h", + generated_ids, + ] + deps = [ + ":usb_device_ids", + "//base", + ] +} + +action("usb_device_ids") { + script = "//device/usb/tools/usb_ids.py" + source_prereqs = [ source_ids ] + outputs = [ generated_ids ] + args = [ + "-i", rebase_path(source_ids, root_build_dir), + "-o", rebase_path(generated_ids, root_build_dir), + ] + + # Only the device_usb target can depend on us. + visibility = [ ":usb" ] +} diff --git a/chromium/device/usb/tools/usb_ids.py b/chromium/device/usb/tools/usb_ids.py new file mode 100644 index 00000000000..e07bd4ca6f7 --- /dev/null +++ b/chromium/device/usb/tools/usb_ids.py @@ -0,0 +1,110 @@ +# 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. + +import itertools +import optparse +import re + +VENDOR_PATTERN = re.compile("^(?P<id>[0-9a-fA-F]{4})\s+(?P<name>.+)$") +PRODUCT_PATTERN = re.compile("^\t(?P<id>[0-9a-fA-F]{4})\s+(?P<name>.+)$") + +def EscapeName(name): + name = name.replace("\\", "\\\\") + name = name.replace("\"", "\\\"") + name = name.replace("?", "\?") + return name + +def ParseTable(input_path): + input_file = open(input_path, "r") + input = input_file.read().split("\n") + input_file.close() + + table = {} + vendor = None + + for line in input: + vendor_match = VENDOR_PATTERN.match(line) + if vendor_match: + if vendor: + table[vendor["id"]] = vendor + vendor = {} + vendor["id"] = int(vendor_match.group("id"), 16) + vendor["name"] = vendor_match.group("name") + vendor["products"] = [] + continue + + product_match = PRODUCT_PATTERN.match(line) + if product_match: + if not vendor: + raise Exception("Product seems to appear before vendor.") + product = {} + product["id"] = int(product_match.group("id"), 16) + product["name"] = product_match.group("name") + vendor["products"].append(product) + + return table + +def GenerateDeviceDefinitions(table): + output = "" + + for vendor_id in sorted(table.keys()): + vendor = table[vendor_id] + if len(vendor["products"]) == 0: + continue + + output += "static const UsbProduct vendor_%.4x_products[] = {\n" % \ + vendor["id"] + for product in vendor["products"]: + output += " {0x%.4x, \"%s\"},\n" % (product["id"], + EscapeName(product["name"])) + output += "};\n" + + return output + +def GenerateVendorDefinitions(table): + output = "const size_t UsbIds::vendor_size_ = %d;\n" % len(table.keys()) + output += "const UsbVendor UsbIds::vendors_[] = {\n" + + for vendor_id in sorted(table.keys()): + vendor = table[vendor_id] + + product_table = "NULL" + if len(vendor["products"]) != 0: + product_table = "vendor_%.4x_products" % (vendor["id"]) + output += " {0x%.4x, \"%s\", %d, %s},\n" % (vendor["id"], + EscapeName(vendor["name"]), len(vendor["products"]), product_table) + + output += "};\n" + return output + +if __name__ == "__main__": + parser = optparse.OptionParser( + description="Generates a C++ USB ID lookup table.") + parser.add_option("-i", "--input", help="Path to usb.ids") + parser.add_option("-o", "--output", help="Output file path") + + (opts, args) = parser.parse_args() + table = ParseTable(opts.input) + + output = """// Generated from %s +#ifndef GENERATED_USB_IDS_H_ +#define GENERATED_USB_IDS_H_ + +#include "device/usb/usb_ids.h" + +namespace device { + +""" % (opts.input) + output += GenerateDeviceDefinitions(table) + output += GenerateVendorDefinitions(table) + output += """ + +} // namespace device + +#endif // GENERATED_USB_IDS_H_ +""" + + output_file = open(opts.output, "w+") + output_file.write(output) + output_file.close() diff --git a/chromium/device/usb/usb.gyp b/chromium/device/usb/usb.gyp index b7cae895b0c..47cf824593f 100644 --- a/chromium/device/usb/usb.gyp +++ b/chromium/device/usb/usb.gyp @@ -21,8 +21,8 @@ { 'action_name': 'generate_usb_ids', 'variables': { - 'usb_ids_path%': '<(DEPTH)/third_party/usb_ids/usb.ids', - 'usb_ids_py_path': '<(DEPTH)/tools/usb_ids/usb_ids.py', + 'usb_ids_path%': '../../third_party/usb_ids/usb.ids', + 'usb_ids_py_path': 'tools/usb_ids.py', }, 'inputs': [ '<(usb_ids_path)', |