Merge from Chromium at DEPS revision 284076
This commit was generated by merge_to_master.py.
Change-Id: I9a279485b02fe7ceddcd32d992a714ff132e99ae
diff --git a/device/bluetooth/BUILD.gn b/device/bluetooth/BUILD.gn
new file mode 100644
index 0000000..f291ada
--- /dev/null
+++ b/device/bluetooth/BUILD.gn
@@ -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.
+
+import("//tools/grit/grit_rule.gni")
+
+config("bluetooth_config") {
+ if (is_win) {
+ ldflags = [
+ # Despite MSDN stating that Bthprops.dll contains the
+ # symbols declared by bthprops.lib, they actually reside here:
+ "/DELAYLOAD:Bthprops.cpl",
+ "/DELAYLOAD:setupapi.dll",
+ ]
+ }
+}
+
+static_library("bluetooth") {
+ sources = [
+ "bluetooth_adapter.cc",
+ "bluetooth_adapter.h",
+ "bluetooth_adapter_chromeos.cc",
+ "bluetooth_adapter_chromeos.h",
+ "bluetooth_adapter_factory.cc",
+ "bluetooth_adapter_factory.h",
+ "bluetooth_adapter_mac.h",
+ "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",
+ "bluetooth_device_chromeos.h",
+ "bluetooth_device_mac.h",
+ "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_l2cap_channel_mac.mm",
+ "bluetooth_l2cap_channel_mac.h",
+ "bluetooth_low_energy_win.cc",
+ "bluetooth_low_energy_win.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_uuid.cc",
+ "bluetooth_uuid.h",
+ ]
+
+ all_dependent_configs = [ ":bluetooth_config" ]
+
+ deps = [
+ ":strings",
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//net",
+ "//third_party/libxml",
+ "//ui/base",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+
+ if (is_chromeos) {
+ deps += [
+ "//dbus",
+ #'../../chromeos/chromeos.gyp:chromeos', TODO(GYP)
+ ]
+ }
+
+ if (is_mac) {
+ libs = [ "IOBluetooth.framework" ]
+ }
+}
+
+grit("strings") {
+ visibility = ":*"
+ source = "bluetooth_strings.grd"
+}
+
+static_library("mocks") {
+ sources = [
+ "test/mock_bluetooth_adapter.cc",
+ "test/mock_bluetooth_adapter.h",
+ "test/mock_bluetooth_device.cc",
+ "test/mock_bluetooth_device.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",
+ ]
+
+ deps = [
+ ":bluetooth",
+ "//testing/gmock",
+ ]
+}
diff --git a/device/bluetooth/bluetooth.gyp b/device/bluetooth/bluetooth.gyp
index e0f1134..96dc9f3 100644
--- a/device/bluetooth/bluetooth.gyp
+++ b/device/bluetooth/bluetooth.gyp
@@ -8,6 +8,7 @@
},
'targets': [
{
+ # GN version: //device/bluetooth
'target_name': 'device_bluetooth',
'type': 'static_library',
'dependencies': [
@@ -21,6 +22,7 @@
'bluetooth_strings.gyp:device_bluetooth_strings',
],
'sources': [
+ # Note: file list duplicated in GN build.
'bluetooth_adapter.cc',
'bluetooth_adapter.h',
'bluetooth_adapter_chromeos.cc',
@@ -53,12 +55,18 @@
'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_l2cap_channel_mac.mm',
'bluetooth_l2cap_channel_mac.h',
+ 'bluetooth_low_energy_win.cc',
+ 'bluetooth_low_energy_win.h',
'bluetooth_pairing_chromeos.cc',
'bluetooth_pairing_chromeos.h',
'bluetooth_remote_gatt_characteristic_chromeos.cc',
@@ -104,6 +112,7 @@
# Despite MSDN stating that Bthprops.dll contains the
# symbols declared by bthprops.lib, they actually reside here:
'Bthprops.cpl',
+ 'setupapi.dll',
],
},
},
@@ -119,6 +128,7 @@
],
},
{
+ # GN version: //device/bluetooth:mocks
'target_name': 'device_bluetooth_mocks',
'type': 'static_library',
'dependencies': [
@@ -129,6 +139,7 @@
'../../',
],
'sources': [
+ # Note: file list duplicated in GN build.
'test/mock_bluetooth_adapter.cc',
'test/mock_bluetooth_adapter.h',
'test/mock_bluetooth_device.cc',
@@ -141,6 +152,8 @@
'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',
diff --git a/device/bluetooth/bluetooth_adapter.cc b/device/bluetooth/bluetooth_adapter.cc
index 1f59222..3a2ab0e 100644
--- a/device/bluetooth/bluetooth_adapter.cc
+++ b/device/bluetooth/bluetooth_adapter.cc
@@ -11,6 +11,11 @@
namespace device {
+BluetoothAdapter::ServiceOptions::ServiceOptions() {
+}
+BluetoothAdapter::ServiceOptions::~ServiceOptions() {
+}
+
#if !defined(OS_CHROMEOS) && !defined(OS_WIN) && !defined(OS_MACOSX)
//static
base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
@@ -19,9 +24,6 @@
}
#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) {
}
diff --git a/device/bluetooth/bluetooth_adapter.h b/device/bluetooth/bluetooth_adapter.h
index cec051f..75d1bcc 100644
--- a/device/bluetooth/bluetooth_adapter.h
+++ b/device/bluetooth/bluetooth_adapter.h
@@ -79,6 +79,16 @@
BluetoothDevice* device) {}
};
+ // Used to configure a listening servie.
+ struct ServiceOptions {
+ ServiceOptions();
+ ~ServiceOptions();
+
+ scoped_ptr<int> channel;
+ scoped_ptr<int> psm;
+ scoped_ptr<std::string> name;
+ };
+
// 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;
@@ -214,32 +224,32 @@
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.
+ // listening on channel |options.channel|, which may be left null to
+ // automatically allocate one. The service will be advertised with
+ // |options.name| as the English name of the service. |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 ServiceOptions& options,
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
+ // listening on PSM |options.psm|, which may be left null to automatically
+ // allocate one. The service will be advertised with |options.name| as the
+ // English name of the service. |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 ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) = 0;
diff --git a/device/bluetooth/bluetooth_adapter_chromeos.cc b/device/bluetooth/bluetooth_adapter_chromeos.cc
index c392af9..3f4a00e 100644
--- a/device/bluetooth/bluetooth_adapter_chromeos.cc
+++ b/device/bluetooth/bluetooth_adapter_chromeos.cc
@@ -233,7 +233,7 @@
void BluetoothAdapterChromeOS::CreateRfcommService(
const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) {
VLOG(1) << object_path_.value() << ": Creating RFCOMM service: "
@@ -247,14 +247,14 @@
socket->Listen(this,
BluetoothSocketChromeOS::kRfcomm,
uuid,
- channel,
+ options,
base::Bind(callback, socket),
error_callback);
}
void BluetoothAdapterChromeOS::CreateL2capService(
const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) {
VLOG(1) << object_path_.value() << ": Creating L2CAP service: "
@@ -268,7 +268,7 @@
socket->Listen(this,
BluetoothSocketChromeOS::kL2cap,
uuid,
- psm,
+ options,
base::Bind(callback, socket),
error_callback);
}
diff --git a/device/bluetooth/bluetooth_adapter_chromeos.h b/device/bluetooth/bluetooth_adapter_chromeos.h
index 4225744..87518bd 100644
--- a/device/bluetooth/bluetooth_adapter_chromeos.h
+++ b/device/bluetooth/bluetooth_adapter_chromeos.h
@@ -64,12 +64,12 @@
virtual bool IsDiscovering() const OVERRIDE;
virtual void CreateRfcommService(
const device::BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE;
virtual void CreateL2capService(
const device::BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE;
diff --git a/device/bluetooth/bluetooth_adapter_mac.h b/device/bluetooth/bluetooth_adapter_mac.h
index 7040932..a460d1d 100644
--- a/device/bluetooth/bluetooth_adapter_mac.h
+++ b/device/bluetooth/bluetooth_adapter_mac.h
@@ -60,20 +60,18 @@
virtual bool IsDiscovering() const OVERRIDE;
virtual void CreateRfcommService(
const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE;
virtual void CreateL2capService(
const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
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;
+ virtual void DeviceFound(IOBluetoothDevice* device) OVERRIDE;
+ virtual void DiscoveryStopped(bool unexpected) OVERRIDE;
// Registers that a new |device| has connected to the local host.
void DeviceConnected(IOBluetoothDevice* device);
@@ -101,6 +99,10 @@
void InitForTest(scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
void PollAdapter();
+ // Registers that a new |device| has replied to an Inquiry, is paired, or has
+ // connected to the local host.
+ void DeviceAdded(IOBluetoothDevice* device);
+
// 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.
@@ -115,15 +117,6 @@
// 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
- // the discovery between consecutive inquiries.
- base::hash_set<std::string> discovered_devices_;
-
- // Timestamp for the recently accessed device.
- // Used to determine if |devices_| needs an update.
- base::scoped_nsobject<NSDate> recently_accessed_device_timestamp_;
-
scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
// List of observers interested in event notifications from us.
diff --git a/device/bluetooth/bluetooth_adapter_mac.mm b/device/bluetooth/bluetooth_adapter_mac.mm
index d44a0f3..2d0556d 100644
--- a/device/bluetooth/bluetooth_adapter_mac.mm
+++ b/device/bluetooth/bluetooth_adapter_mac.mm
@@ -26,8 +26,13 @@
namespace {
+// The frequency with which to poll the adapter for updates.
const int kPollIntervalMs = 500;
+// The length of time that must elapse since the last Inquiry response before a
+// discovered Classic device is considered to be no longer available.
+const NSTimeInterval kDiscoveryTimeoutSec = 3 * 60; // 3 minutes
+
} // namespace
namespace device {
@@ -118,43 +123,29 @@
void BluetoothAdapterMac::CreateRfcommService(
const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) {
scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
socket->ListenUsingRfcomm(
- this, uuid, channel, base::Bind(callback, socket), error_callback);
+ this, uuid, options, base::Bind(callback, socket), error_callback);
}
void BluetoothAdapterMac::CreateL2capService(
const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) {
scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
socket->ListenUsingL2cap(
- this, uuid, psm, base::Bind(callback, socket), error_callback);
+ this, uuid, options, 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::DeviceFound(IOBluetoothDevice* device) {
+ DeviceAdded(device);
}
-void BluetoothAdapterMac::DiscoveryStopped(
- BluetoothDiscoveryManagerMac* manager,
- bool unexpected) {
- DCHECK_EQ(manager, classic_discovery_manager_.get());
+void BluetoothAdapterMac::DiscoveryStopped(bool unexpected) {
if (unexpected) {
DVLOG(1) << "Discovery stopped unexpectedly";
num_discovery_sessions_ = 0;
@@ -166,22 +157,11 @@
}
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);
+ // TODO(isherman): Investigate whether this method can be replaced with a call
+ // to +registerForConnectNotifications:selector:.
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();
+ << BluetoothDeviceMac::GetDeviceAddress(device);
+ DeviceAdded(device);
}
void BluetoothAdapterMac::AddDiscoverySession(
@@ -285,17 +265,7 @@
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];
- if (recently_accessed_device_timestamp_ == nil ||
- access_timestamp == nil ||
- [recently_accessed_device_timestamp_ compare:access_timestamp] ==
- NSOrderedAscending) {
- UpdateDevices();
- recently_accessed_device_timestamp_.reset([access_timestamp copy]);
- }
+ UpdateDevices();
ui_task_runner_->PostDelayedTask(
FROM_HERE,
@@ -304,39 +274,50 @@
base::TimeDelta::FromMilliseconds(kPollIntervalMs));
}
+void BluetoothAdapterMac::DeviceAdded(IOBluetoothDevice* device) {
+ std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device);
+
+ // Only notify observers once per device.
+ if (devices_.count(device_address))
+ return;
+
+ devices_[device_address] = new BluetoothDeviceMac(device);
+ FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
+ observers_,
+ DeviceAdded(this, devices_[device_address]));
+}
+
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);
- }
-
- // Add any unpaired connected devices.
- for (const auto& old_device : old_devices) {
- if (!old_device.second->IsConnected())
+ // Notify observers if any previously seen devices are no longer available,
+ // i.e. if they are no longer paired, connected, nor recently discovered via
+ // an inquiry.
+ std::set<std::string> removed_devices;
+ for (DevicesMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
+ BluetoothDevice* device = it->second;
+ if (device->IsPaired() || device->IsConnected())
continue;
- const std::string& device_address = old_device.first;
- DCHECK(!devices_.count(device_address));
- devices_[device_address] = old_device.second;
- old_devices.erase(device_address);
+ NSDate* last_inquiry_update =
+ static_cast<BluetoothDeviceMac*>(device)->GetLastInquiryUpdate();
+ if (last_inquiry_update &&
+ -[last_inquiry_update timeIntervalSinceNow] < kDiscoveryTimeoutSec)
+ continue;
+
+ FOR_EACH_OBSERVER(
+ BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
+ delete device;
+ removed_devices.insert(it->first);
+ // The device will be erased from the map in the loop immediately below.
+ }
+ for (const std::string& device_address : removed_devices) {
+ size_t num_removed = devices_.erase(device_address);
+ DCHECK_EQ(num_removed, 1U);
}
- // 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);
+ // Add any new paired devices.
+ for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
+ DeviceAdded(device);
+ }
}
} // namespace device
diff --git a/device/bluetooth/bluetooth_adapter_unittest.cc b/device/bluetooth/bluetooth_adapter_unittest.cc
index 19ff603..06fd5fe 100644
--- a/device/bluetooth/bluetooth_adapter_unittest.cc
+++ b/device/bluetooth/bluetooth_adapter_unittest.cc
@@ -76,14 +76,14 @@
virtual void CreateRfcommService(
const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE {
}
virtual void CreateL2capService(
const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE {
}
diff --git a/device/bluetooth/bluetooth_adapter_win.cc b/device/bluetooth/bluetooth_adapter_win.cc
index 8ef5470..467aa4f 100644
--- a/device/bluetooth/bluetooth_adapter_win.cc
+++ b/device/bluetooth/bluetooth_adapter_win.cc
@@ -163,7 +163,7 @@
void BluetoothAdapterWin::CreateRfcommService(
const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) {
scoped_refptr<BluetoothSocketWin> socket =
@@ -172,14 +172,14 @@
socket_thread_,
NULL,
net::NetLog::Source());
- socket->Listen(this, uuid, channel,
+ socket->Listen(this, uuid, options,
base::Bind(callback, socket),
error_callback);
}
void BluetoothAdapterWin::CreateL2capService(
const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) {
// TODO(keybuk): implement.
diff --git a/device/bluetooth/bluetooth_adapter_win.h b/device/bluetooth/bluetooth_adapter_win.h
index 80e1859..eb69003 100644
--- a/device/bluetooth/bluetooth_adapter_win.h
+++ b/device/bluetooth/bluetooth_adapter_win.h
@@ -57,12 +57,12 @@
virtual bool IsDiscovering() const OVERRIDE;
virtual void CreateRfcommService(
const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE;
virtual void CreateL2capService(
const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback) OVERRIDE;
diff --git a/device/bluetooth/bluetooth_device_chromeos.cc b/device/bluetooth/bluetooth_device_chromeos.cc
index c808569..2045a1e 100644
--- a/device/bluetooth/bluetooth_device_chromeos.cc
+++ b/device/bluetooth/bluetooth_device_chromeos.cc
@@ -509,7 +509,8 @@
VLOG(1) << "Adding new remote GATT service for device: " << GetAddress();
BluetoothRemoteGattServiceChromeOS* service =
- new BluetoothRemoteGattServiceChromeOS(this, object_path);
+ new BluetoothRemoteGattServiceChromeOS(adapter_, this, object_path);
+
gatt_services_[service->GetIdentifier()] = service;
DCHECK(service->object_path() == object_path);
DCHECK(service->GetUUID().IsValid());
diff --git a/device/bluetooth/bluetooth_device_mac.h b/device/bluetooth/bluetooth_device_mac.h
index 462cd35..68380c2 100644
--- a/device/bluetooth/bluetooth_device_mac.h
+++ b/device/bluetooth/bluetooth_device_mac.h
@@ -69,6 +69,10 @@
const base::Closure& callback,
const ErrorCallback& error_callback) OVERRIDE;
+ // Returns the timestamp when the device was last seen during an inquiry.
+ // Returns nil if the device has never been seen during an inquiry.
+ NSDate* GetLastInquiryUpdate();
+
// Returns the Bluetooth address for the |device|. The returned address has a
// normalized format (see below).
static std::string GetDeviceAddress(IOBluetoothDevice* device);
diff --git a/device/bluetooth/bluetooth_device_mac.mm b/device/bluetooth/bluetooth_device_mac.mm
index 43d9fb6..a913300 100644
--- a/device/bluetooth/bluetooth_device_mac.mm
+++ b/device/bluetooth/bluetooth_device_mac.mm
@@ -232,6 +232,10 @@
NOTIMPLEMENTED();
}
+NSDate* BluetoothDeviceMac::GetLastInquiryUpdate() {
+ return [device_ getLastInquiryUpdate];
+}
+
int BluetoothDeviceMac::GetHostTransmitPower(
BluetoothHCITransmitPowerLevelType power_level_type) const {
IOBluetoothHostController* controller =
diff --git a/device/bluetooth/bluetooth_discovery_manager_mac.h b/device/bluetooth/bluetooth_discovery_manager_mac.h
index 575a033..534646a 100644
--- a/device/bluetooth/bluetooth_discovery_manager_mac.h
+++ b/device/bluetooth/bluetooth_discovery_manager_mac.h
@@ -19,17 +19,15 @@
// 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 |this| manager has found a device through classic device
+ // inquiry in the form of an IOBluetoothDevice.
+ virtual void DeviceFound(IOBluetoothDevice* device) = 0;
// 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 void DiscoveryStopped(bool unexpected) = 0;
};
virtual ~BluetoothDiscoveryManagerMac();
diff --git a/device/bluetooth/bluetooth_discovery_manager_mac.mm b/device/bluetooth/bluetooth_discovery_manager_mac.mm
index 528e22d..2262891 100644
--- a/device/bluetooth/bluetooth_discovery_manager_mac.mm
+++ b/device/bluetooth/bluetooth_discovery_manager_mac.mm
@@ -124,7 +124,7 @@
void DeviceFound(IOBluetoothDeviceInquiry* inquiry,
IOBluetoothDevice* device) {
DCHECK(observer_);
- observer_->DeviceFound(this, device);
+ observer_->DeviceFound(device);
}
void DeviceInquiryComplete(IOBluetoothDeviceInquiry* inquiry,
@@ -138,7 +138,7 @@
// If discovery is no longer desired, notify observers that discovery
// has stopped and return.
if (!should_do_discovery_) {
- observer_->DiscoveryStopped(this, false /* unexpected */);
+ observer_->DiscoveryStopped(false /* unexpected */);
return;
}
@@ -147,7 +147,7 @@
if (error != kIOReturnSuccess) {
DVLOG(1) << "Inquiry has stopped with an error: " << error;
should_do_discovery_ = false;
- observer_->DiscoveryStopped(this, true /* unexpected */);
+ observer_->DiscoveryStopped(true /* unexpected */);
return;
}
@@ -161,7 +161,7 @@
DVLOG(1) << "Failed to restart discovery";
should_do_discovery_ = false;
DCHECK(observer_);
- observer_->DiscoveryStopped(this, true /* unexpected */);
+ observer_->DiscoveryStopped(true /* unexpected */);
}
private:
diff --git a/device/bluetooth/bluetooth_discovery_session.cc b/device/bluetooth/bluetooth_discovery_session.cc
index ba5f5fd..bfcc59f 100644
--- a/device/bluetooth/bluetooth_discovery_session.cc
+++ b/device/bluetooth/bluetooth_discovery_session.cc
@@ -15,17 +15,11 @@
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;
+ if (active_) {
+ Stop(base::Bind(&base::DoNothing), base::Bind(&base::DoNothing));
+ MarkAsInactive();
}
- Stop(base::Bind(&base::DoNothing), base::Bind(&base::DoNothing));
- MarkAsInactive();
}
bool BluetoothDiscoverySession::IsActive() const {
@@ -41,7 +35,6 @@
return;
}
VLOG(1) << "Stopping device discovery session.";
- DCHECK(adapter_.get());
adapter_->RemoveDiscoverySession(
base::Bind(&BluetoothDiscoverySession::OnStop,
weak_ptr_factory_.GetWeakPtr(),
@@ -58,7 +51,6 @@
if (!active_)
return;
active_ = false;
- DCHECK(adapter_.get());
adapter_->DiscoverySessionBecameInactive(this);
}
diff --git a/device/bluetooth/bluetooth_discovery_session.h b/device/bluetooth/bluetooth_discovery_session.h
index 666538b..08a1de8 100644
--- a/device/bluetooth/bluetooth_discovery_session.h
+++ b/device/bluetooth/bluetooth_discovery_session.h
@@ -58,11 +58,10 @@
const ErrorCallback& error_callback);
protected:
- BluetoothDiscoverySession(); // Called by mock.
+ explicit BluetoothDiscoverySession(scoped_refptr<BluetoothAdapter> adapter);
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);
diff --git a/device/bluetooth/bluetooth_gatt_characteristic.h b/device/bluetooth/bluetooth_gatt_characteristic.h
index e71d2fd..7b952e0 100644
--- a/device/bluetooth/bluetooth_gatt_characteristic.h
+++ b/device/bluetooth/bluetooth_gatt_characteristic.h
@@ -10,12 +10,14 @@
#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
@@ -82,6 +84,11 @@
// 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
@@ -133,6 +140,10 @@
// 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*>
@@ -162,6 +173,13 @@
// 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.
diff --git a/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc b/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc
index 0454399..673ed5f 100644
--- a/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc
+++ b/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc
@@ -2,6 +2,7 @@
// 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"
@@ -19,6 +20,7 @@
#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"
@@ -29,6 +31,7 @@
using device::BluetoothGattConnection;
using device::BluetoothGattDescriptor;
using device::BluetoothGattService;
+using device::BluetoothGattNotifySession;
using device::BluetoothUUID;
namespace chromeos {
@@ -343,6 +346,7 @@
virtual void TearDown() {
adapter_ = NULL;
+ update_sessions_.clear();
gatt_conn_.reset();
DBusThreadManager::Shutdown();
}
@@ -374,15 +378,32 @@
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_;
@@ -391,6 +412,7 @@
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_;
@@ -600,7 +622,7 @@
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(1, service_observer.gatt_characteristic_value_changed_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.
@@ -608,7 +630,7 @@
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(1, service_observer.gatt_characteristic_value_changed_count_);
+ EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_);
EXPECT_TRUE(service->GetCharacteristics().empty());
// Re-expose the heart rate characteristics.
@@ -617,7 +639,7 @@
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(2, service_observer.gatt_characteristic_value_changed_count_);
+ EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_);
EXPECT_EQ(3U, service->GetCharacteristics().size());
// Hide the service. All characteristics should disappear.
@@ -625,7 +647,7 @@
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(2, service_observer.gatt_characteristic_value_changed_count_);
+ EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_);
}
TEST_F(BluetoothGattChromeOSTest, GattDescriptorAddedAndRemoved) {
@@ -726,7 +748,7 @@
// This unit test tests that all remote GATT objects are created for D-Bus
// objects that were already exposed.
adapter_ = NULL;
- EXPECT_FALSE(device::BluetoothAdapterFactory::HasSharedInstanceForTesting());
+ ASSERT_FALSE(device::BluetoothAdapterFactory::HasSharedInstanceForTesting());
// Create the fake D-Bus objects.
fake_bluetooth_device_client_->CreateDevice(
@@ -823,31 +845,6 @@
// Run the message loop so that the characteristics appear.
base::MessageLoop::current()->Run();
- // We should get an initial value changed signal from the Heart Rate
- // Measurement characteristic when it getsadded.
- EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_);
-
- // The Heart Rate Measurement characteristic should send regular
- // notifications.
- base::MessageLoop::current()->Run();
- EXPECT_EQ(2, service_observer.gatt_characteristic_value_changed_count_);
- EXPECT_EQ(kHeartRateMeasurementUUID,
- service_observer.last_gatt_characteristic_uuid_);
- EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_->
- GetHeartRateMeasurementPath().value(),
- service_observer.last_gatt_characteristic_id_);
-
- // Receive another notification.
- service_observer.last_gatt_characteristic_id_.clear();
- service_observer.last_gatt_characteristic_uuid_ = BluetoothUUID();
- base::MessageLoop::current()->Run();
- EXPECT_EQ(3, service_observer.gatt_characteristic_value_changed_count_);
- EXPECT_EQ(kHeartRateMeasurementUUID,
- service_observer.last_gatt_characteristic_uuid_);
- EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_->
- GetHeartRateMeasurementPath().value(),
- service_observer.last_gatt_characteristic_id_);
-
// Issue write request to non-writeable characteristics.
service_observer.last_gatt_characteristic_id_.clear();
service_observer.last_gatt_characteristic_uuid_ = BluetoothUUID();
@@ -858,6 +855,7 @@
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());
@@ -872,7 +870,7 @@
EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid());
EXPECT_EQ(0, success_callback_count_);
EXPECT_EQ(1, error_callback_count_);
- EXPECT_EQ(3, service_observer.gatt_characteristic_value_changed_count_);
+ EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_);
characteristic = service->GetCharacteristic(
fake_bluetooth_gatt_characteristic_client_->
@@ -892,7 +890,7 @@
EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid());
EXPECT_EQ(0, success_callback_count_);
EXPECT_EQ(2, error_callback_count_);
- EXPECT_EQ(3, service_observer.gatt_characteristic_value_changed_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
@@ -916,7 +914,7 @@
EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid());
EXPECT_EQ(1, success_callback_count_);
EXPECT_EQ(2, error_callback_count_);
- EXPECT_EQ(3, service_observer.gatt_characteristic_value_changed_count_);
+ EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_);
// Issue a read request. A successful read results in a
// CharacteristicValueChanged notification.
@@ -935,17 +933,8 @@
base::Unretained(this)));
EXPECT_EQ(2, success_callback_count_);
EXPECT_EQ(2, error_callback_count_);
- EXPECT_EQ(4, service_observer.gatt_characteristic_value_changed_count_);
+ EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_);
EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_));
-
- // One last value changed notification.
- base::MessageLoop::current()->Run();
- EXPECT_EQ(5, service_observer.gatt_characteristic_value_changed_count_);
- EXPECT_EQ(kHeartRateMeasurementUUID,
- service_observer.last_gatt_characteristic_uuid_);
- EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_->
- GetHeartRateMeasurementPath().value(),
- service_observer.last_gatt_characteristic_id_);
}
TEST_F(BluetoothGattChromeOSTest, GattCharacteristicProperties) {
@@ -1086,4 +1075,272 @@
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/device/bluetooth/bluetooth_gatt_notify_session.cc b/device/bluetooth/bluetooth_gatt_notify_session.cc
new file mode 100644
index 0000000..3c9a242
--- /dev/null
+++ b/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/device/bluetooth/bluetooth_gatt_notify_session.h b/device/bluetooth/bluetooth_gatt_notify_session.h
new file mode 100644
index 0000000..9837e3a
--- /dev/null
+++ b/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/device/bluetooth/bluetooth_gatt_notify_session_chromeos.cc b/device/bluetooth/bluetooth_gatt_notify_session_chromeos.cc
new file mode 100644
index 0000000..ba7b843
--- /dev/null
+++ b/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/device/bluetooth/bluetooth_gatt_notify_session_chromeos.h b/device/bluetooth/bluetooth_gatt_notify_session_chromeos.h
new file mode 100644
index 0000000..1202fd8
--- /dev/null
+++ b/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/device/bluetooth/bluetooth_low_energy_win.cc b/device/bluetooth/bluetooth_low_energy_win.cc
new file mode 100644
index 0000000..21f4ecf
--- /dev/null
+++ b/device/bluetooth/bluetooth_low_energy_win.cc
@@ -0,0 +1,576 @@
+// 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_low_energy_win.h"
+
+#include <cfg.h>
+#define INITGUID // For DEVPKEY_Xxxx guid/pid pairs
+#include <devpkey.h>
+
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/win/windows_version.h"
+
+namespace {
+
+using device::win::DeviceRegistryPropertyValue;
+using device::win::DevicePropertyValue;
+
+const char kPlatformNotSupported[] =
+ "Bluetooth Low energy is only supported on Windows 8 and later.";
+const char kDeviceEnumError[] = "Error enumerating Bluetooth LE devices.";
+const char kDeviceInfoError[] =
+ "Error retrieving Bluetooth LE device information.";
+const char kDeviceAddressError[] =
+ "Device instance ID value does not seem to contain a Bluetooth Adapter "
+ "address.";
+const char kDeviceFriendlyNameError[] = "Device name is not valid.";
+const char kInvalidBluetoothAddress[] = "Bluetooth address format is invalid.";
+
+// Like ScopedHandle but for HDEVINFO. Only use this on HDEVINFO returned from
+// SetupDiGetClassDevs.
+class DeviceInfoSetTraits {
+ public:
+ typedef HDEVINFO Handle;
+
+ static bool CloseHandle(HDEVINFO handle) {
+ return ::SetupDiDestroyDeviceInfoList(handle) != FALSE;
+ }
+
+ static bool IsHandleValid(HDEVINFO handle) {
+ return handle != INVALID_HANDLE_VALUE;
+ }
+
+ static HDEVINFO NullHandle() { return INVALID_HANDLE_VALUE; }
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DeviceInfoSetTraits);
+};
+
+typedef base::win::GenericScopedHandle<DeviceInfoSetTraits,
+ base::win::VerifierTraits>
+ ScopedDeviceInfoSetHandle;
+
+bool StringToBluetoothAddress(const std::string& value,
+ BLUETOOTH_ADDRESS* btha,
+ std::string* error) {
+ if (value.length() != 6 * 2) {
+ *error = kInvalidBluetoothAddress;
+ return false;
+ }
+
+ int buffer[6];
+ int result = sscanf_s(value.c_str(),
+ "%02X%02X%02X%02X%02X%02X",
+ &buffer[5],
+ &buffer[4],
+ &buffer[3],
+ &buffer[2],
+ &buffer[1],
+ &buffer[0]);
+ if (result != 6) {
+ *error = kInvalidBluetoothAddress;
+ return false;
+ }
+
+ ZeroMemory(btha, sizeof(*btha));
+ btha->rgBytes[0] = buffer[0];
+ btha->rgBytes[1] = buffer[1];
+ btha->rgBytes[2] = buffer[2];
+ btha->rgBytes[3] = buffer[3];
+ btha->rgBytes[4] = buffer[4];
+ btha->rgBytes[5] = buffer[5];
+ return true;
+}
+
+std::string FormatBluetoothError(const char* message, HRESULT hr) {
+ std::ostringstream string_stream;
+ string_stream << message;
+ if (FAILED(hr))
+ string_stream << logging::SystemErrorCodeToString(hr);
+ return string_stream.str();
+}
+
+bool CheckInsufficientBuffer(bool success,
+ const char* message,
+ std::string* error) {
+ if (success) {
+ *error = FormatBluetoothError(message, S_OK);
+ return false;
+ }
+
+ HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
+ if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
+ *error = FormatBluetoothError(message, hr);
+ return false;
+ }
+
+ return true;
+}
+
+bool CheckSuccess(HRESULT hr, const char* message, std::string* error) {
+ if (FAILED(hr)) {
+ *error = FormatBluetoothError(message, hr);
+ return false;
+ }
+
+ return true;
+}
+
+bool CheckExpectedLength(size_t actual_length,
+ size_t expected_length,
+ const char* message,
+ std::string* error) {
+ if (actual_length != expected_length) {
+ *error = FormatBluetoothError(message, E_FAIL);
+ return false;
+ }
+
+ return true;
+}
+
+bool CollectBluetoothLowEnergyDeviceProperty(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVINFO_DATA device_info_data,
+ const DEVPROPKEY& key,
+ scoped_ptr<DevicePropertyValue>* value,
+ std::string* error) {
+ DWORD required_length;
+ DEVPROPTYPE prop_type;
+ BOOL success = SetupDiGetDeviceProperty(device_info_handle,
+ device_info_data,
+ &key,
+ &prop_type,
+ NULL,
+ 0,
+ &required_length,
+ 0);
+ if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
+ return false;
+
+ scoped_ptr<uint8_t[]> prop_value(new uint8_t[required_length]);
+ DWORD actual_length = required_length;
+ success = SetupDiGetDeviceProperty(device_info_handle,
+ device_info_data,
+ &key,
+ &prop_type,
+ prop_value.get(),
+ actual_length,
+ &required_length,
+ 0);
+ if (!CheckSuccess(!!success, kDeviceInfoError, error))
+ return false;
+ if (!CheckExpectedLength(
+ actual_length, required_length, kDeviceInfoError, error)) {
+ return false;
+ }
+
+ (*value) = scoped_ptr<DevicePropertyValue>(
+ new DevicePropertyValue(prop_type, prop_value.Pass(), actual_length));
+ return true;
+}
+
+bool CollectBluetoothLowEnergyDeviceRegistryProperty(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVINFO_DATA device_info_data,
+ DWORD property_id,
+ scoped_ptr<DeviceRegistryPropertyValue>* value,
+ std::string* error) {
+ ULONG required_length = 0;
+ BOOL success = SetupDiGetDeviceRegistryProperty(device_info_handle,
+ device_info_data,
+ property_id,
+ NULL,
+ NULL,
+ 0,
+ &required_length);
+ if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
+ return false;
+
+ scoped_ptr<uint8_t[]> property_value(new uint8_t[required_length]);
+ ULONG actual_length = required_length;
+ DWORD property_type;
+ success = SetupDiGetDeviceRegistryProperty(device_info_handle,
+ device_info_data,
+ property_id,
+ &property_type,
+ property_value.get(),
+ actual_length,
+ &required_length);
+ if (!CheckSuccess(!!success, kDeviceInfoError, error))
+ return false;
+ if (!CheckExpectedLength(
+ actual_length, required_length, kDeviceInfoError, error)) {
+ return false;
+ }
+
+ (*value) = DeviceRegistryPropertyValue::Create(
+ property_type, property_value.Pass(), actual_length).Pass();
+ return true;
+}
+
+bool CollectBluetoothLowEnergyDeviceInstanceId(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVINFO_DATA device_info_data,
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
+ std::string* error) {
+ ULONG required_length = 0;
+ BOOL success = SetupDiGetDeviceInstanceId(
+ device_info_handle, device_info_data, NULL, 0, &required_length);
+ if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
+ return false;
+
+ scoped_ptr<WCHAR[]> instance_id(new WCHAR[required_length]);
+ ULONG actual_length = required_length;
+ success = SetupDiGetDeviceInstanceId(device_info_handle,
+ device_info_data,
+ instance_id.get(),
+ actual_length,
+ &required_length);
+ if (!CheckSuccess(!!success, kDeviceInfoError, error))
+ return false;
+ if (!CheckExpectedLength(
+ actual_length, required_length, kDeviceInfoError, error)) {
+ return false;
+ }
+
+ if (actual_length >= 1) {
+ // Ensure string is zero terminated.
+ instance_id.get()[actual_length - 1] = 0;
+ device_info->id = base::SysWideToUTF8(instance_id.get());
+ }
+ return true;
+}
+
+bool CollectDeviceFriendlyName(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVINFO_DATA device_info_data,
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
+ std::string* error) {
+ scoped_ptr<DeviceRegistryPropertyValue> property_value;
+ if (!CollectBluetoothLowEnergyDeviceRegistryProperty(device_info_handle,
+ device_info_data,
+ SPDRP_FRIENDLYNAME,
+ &property_value,
+ error)) {
+ return false;
+ }
+
+ if (property_value->property_type() != REG_SZ) {
+ *error = kDeviceFriendlyNameError;
+ return false;
+ }
+
+ device_info->friendly_name = property_value->AsString();
+ return true;
+}
+
+bool ExtractBluetoothAddressFromDeviceInstanceId(const std::string& instance_id,
+ BLUETOOTH_ADDRESS* btha,
+ std::string* error) {
+ size_t start = instance_id.find("_");
+ if (start == std::string::npos) {
+ *error = kDeviceAddressError;
+ return false;
+ }
+ size_t end = instance_id.find("\\", start);
+ if (end == std::string::npos) {
+ *error = kDeviceAddressError;
+ return false;
+ }
+
+ start++;
+ std::string address = instance_id.substr(start, end - start);
+ if (!StringToBluetoothAddress(address, btha, error))
+ return false;
+
+ return true;
+}
+
+bool CollectBluetoothLowEnergyDeviceAddress(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVINFO_DATA device_info_data,
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
+ std::string* error) {
+ // TODO(rpaquay): We exctract the bluetooth device address from the device
+ // instance ID string, as we did not find a more formal API for retrieving the
+ // bluetooth address of a Bluetooth Low Energy device.
+ // An Bluetooth device instance ID has the following format (under Win8+):
+ // BTHLE\DEV_BC6A29AB5FB0\8&31038925&0&BC6A29AB5FB0
+ return ExtractBluetoothAddressFromDeviceInstanceId(
+ device_info->id, &device_info->address, error);
+}
+
+bool CollectBluetoothLowEnergyDeviceStatus(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVINFO_DATA device_info_data,
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
+ std::string* error) {
+ scoped_ptr<DevicePropertyValue> value;
+ if (!CollectBluetoothLowEnergyDeviceProperty(device_info_handle,
+ device_info_data,
+ DEVPKEY_Device_DevNodeStatus,
+ &value,
+ error)) {
+ return false;
+ }
+
+ if (value->property_type() != DEVPROP_TYPE_UINT32) {
+ *error = kDeviceInfoError;
+ return false;
+ }
+
+ device_info->connected = !(value->AsUint32() & DN_DEVICE_DISCONNECTED);
+ // Windows 8 exposes BLE devices only if they are visible and paired. This
+ // might change in the future if Windows offers a public API for discovering
+ // and pairing BLE devices.
+ device_info->visible = true;
+ device_info->authenticated = true;
+ return true;
+}
+
+bool CollectBluetoothLowEnergyDeviceInfo(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ PSP_DEVICE_INTERFACE_DATA device_interface_data,
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>* device_info,
+ std::string* error) {
+ // Retrieve required # of bytes for interface details
+ ULONG required_length = 0;
+ BOOL success = SetupDiGetDeviceInterfaceDetail(device_info_handle,
+ device_interface_data,
+ NULL,
+ 0,
+ &required_length,
+ NULL);
+ if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
+ return false;
+
+ scoped_ptr<uint8_t[]> interface_data(new uint8_t[required_length]);
+ ZeroMemory(interface_data.get(), required_length);
+
+ PSP_DEVICE_INTERFACE_DETAIL_DATA device_interface_detail_data =
+ reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(interface_data.get());
+ device_interface_detail_data->cbSize =
+ sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
+
+ SP_DEVINFO_DATA device_info_data = {0};
+ device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+
+ ULONG actual_length = required_length;
+ success = SetupDiGetDeviceInterfaceDetail(device_info_handle,
+ device_interface_data,
+ device_interface_detail_data,
+ actual_length,
+ &required_length,
+ &device_info_data);
+ if (!CheckSuccess(!!success, kDeviceInfoError, error))
+ return false;
+ if (!CheckExpectedLength(
+ actual_length, required_length, kDeviceInfoError, error)) {
+ return false;
+ }
+
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo> result(
+ new device::win::BluetoothLowEnergyDeviceInfo());
+ result->path =
+ base::FilePath(std::wstring(device_interface_detail_data->DevicePath));
+ if (!CollectBluetoothLowEnergyDeviceInstanceId(
+ device_info_handle, &device_info_data, result, error)) {
+ return false;
+ }
+ if (!CollectDeviceFriendlyName(
+ device_info_handle, &device_info_data, result, error)) {
+ return false;
+ }
+ if (!CollectBluetoothLowEnergyDeviceAddress(
+ device_info_handle, &device_info_data, result, error)) {
+ return false;
+ }
+ if (!CollectBluetoothLowEnergyDeviceStatus(
+ device_info_handle, &device_info_data, result, error)) {
+ return false;
+ }
+ (*device_info) = result.Pass();
+ return true;
+}
+
+enum DeviceInfoResult { kOk, kError, kNoMoreDevices };
+
+DeviceInfoResult EnumerateSingleBluetoothLowEnergyDevice(
+ const ScopedDeviceInfoSetHandle& device_info_handle,
+ DWORD device_index,
+ scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>* device_info,
+ std::string* error) {
+ // Enumerate device of BLUETOOTHLE_DEVICE interface class
+ GUID BluetoothInterfaceGUID = GUID_BLUETOOTHLE_DEVICE_INTERFACE;
+ SP_DEVICE_INTERFACE_DATA device_interface_data = {0};
+ device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+ BOOL success = ::SetupDiEnumDeviceInterfaces(device_info_handle,
+ NULL,
+ &BluetoothInterfaceGUID,
+ device_index,
+ &device_interface_data);
+ if (!success) {
+ HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
+ if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) {
+ return kNoMoreDevices;
+ }
+ *error = FormatBluetoothError(kDeviceInfoError, hr);
+ return kError;
+ }
+
+ if (!CollectBluetoothLowEnergyDeviceInfo(
+ device_info_handle, &device_interface_data, device_info, error)) {
+ return kError;
+ }
+
+ return kOk;
+}
+
+// Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
+// present on the machine.
+HRESULT OpenBluetoothLowEnergyDevices(ScopedDeviceInfoSetHandle* handle) {
+ GUID BluetoothClassGUID = GUID_BLUETOOTHLE_DEVICE_INTERFACE;
+ ScopedDeviceInfoSetHandle result(SetupDiGetClassDevs(
+ &BluetoothClassGUID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
+ if (!result.IsValid()) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ (*handle) = result.Pass();
+ return S_OK;
+}
+
+// Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
+// exposing a service GUID.
+HRESULT OpenBluetoothLowEnergyService(const GUID& service_guid,
+ ScopedDeviceInfoSetHandle* handle) {
+ ScopedDeviceInfoSetHandle result(SetupDiGetClassDevs(
+ &service_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
+ if (!result.IsValid()) {
+ return HRESULT_FROM_WIN32(::GetLastError());
+ }
+
+ (*handle) = result.Pass();
+ return S_OK;
+}
+
+} // namespace
+
+namespace device {
+namespace win {
+
+// static
+scoped_ptr<DeviceRegistryPropertyValue> DeviceRegistryPropertyValue::Create(
+ DWORD property_type,
+ scoped_ptr<uint8_t[]> value,
+ size_t value_size) {
+ switch (property_type) {
+ case REG_SZ: {
+ // Ensure string is zero terminated.
+ size_t character_size = value_size / sizeof(WCHAR);
+ CHECK_EQ(character_size * sizeof(WCHAR), value_size);
+ CHECK_GE(character_size, 1u);
+ WCHAR* value_string = reinterpret_cast<WCHAR*>(value.get());
+ value_string[character_size - 1] = 0;
+ break;
+ }
+ case REG_DWORD: {
+ CHECK_EQ(value_size, sizeof(DWORD));
+ break;
+ }
+ }
+ return scoped_ptr<DeviceRegistryPropertyValue>(
+ new DeviceRegistryPropertyValue(property_type, value.Pass(), value_size));
+}
+
+DeviceRegistryPropertyValue::DeviceRegistryPropertyValue(
+ DWORD property_type,
+ scoped_ptr<uint8_t[]> value,
+ size_t value_size)
+ : property_type_(property_type),
+ value_(value.Pass()),
+ value_size_(value_size) {
+}
+
+DeviceRegistryPropertyValue::~DeviceRegistryPropertyValue() {
+}
+
+std::string DeviceRegistryPropertyValue::AsString() const {
+ CHECK_EQ(property_type_, static_cast<DWORD>(REG_SZ));
+ WCHAR* value_string = reinterpret_cast<WCHAR*>(value_.get());
+ return base::SysWideToUTF8(value_string);
+}
+
+DWORD DeviceRegistryPropertyValue::AsDWORD() const {
+ CHECK_EQ(property_type_, static_cast<DWORD>(REG_DWORD));
+ DWORD* value = reinterpret_cast<DWORD*>(value_.get());
+ return *value;
+}
+
+DevicePropertyValue::DevicePropertyValue(DEVPROPTYPE property_type,
+ scoped_ptr<uint8_t[]> value,
+ size_t value_size)
+ : property_type_(property_type),
+ value_(value.Pass()),
+ value_size_(value_size) {
+}
+
+uint32_t DevicePropertyValue::AsUint32() const {
+ CHECK_EQ(property_type_, static_cast<DEVPROPTYPE>(DEVPROP_TYPE_UINT32));
+ CHECK_EQ(value_size_, sizeof(uint32_t));
+ return *reinterpret_cast<uint32_t*>(value_.get());
+}
+
+BluetoothLowEnergyDeviceInfo::BluetoothLowEnergyDeviceInfo()
+ : visible(false), authenticated(false), connected(false) {
+ address.ullLong = BLUETOOTH_NULL_ADDRESS;
+}
+
+BluetoothLowEnergyDeviceInfo::~BluetoothLowEnergyDeviceInfo() {
+}
+
+bool IsBluetoothLowEnergySupported() {
+ return base::win::GetVersion() >= base::win::VERSION_WIN8;
+}
+
+bool EnumerateKnownBluetoothLowEnergyDevices(
+ ScopedVector<BluetoothLowEnergyDeviceInfo>* devices,
+ std::string* error) {
+ if (!IsBluetoothLowEnergySupported()) {
+ *error = kPlatformNotSupported;
+ return false;
+ }
+
+ ScopedDeviceInfoSetHandle info_set_handle;
+ HRESULT hr = OpenBluetoothLowEnergyDevices(&info_set_handle);
+ if (FAILED(hr)) {
+ *error = FormatBluetoothError(kDeviceEnumError, hr);
+ return false;
+ }
+
+ for (DWORD i = 0;; ++i) {
+ scoped_ptr<BluetoothLowEnergyDeviceInfo> device_info;
+ DeviceInfoResult result = EnumerateSingleBluetoothLowEnergyDevice(
+ info_set_handle, i, &device_info, error);
+ switch (result) {
+ case kNoMoreDevices:
+ return true;
+ case kError:
+ return false;
+ case kOk:
+ devices->push_back(device_info.release());
+ }
+ }
+}
+
+bool ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
+ const std::string& instance_id,
+ BLUETOOTH_ADDRESS* btha,
+ std::string* error) {
+ return ExtractBluetoothAddressFromDeviceInstanceId(instance_id, btha, error);
+}
+
+} // namespace win
+} // namespace device
diff --git a/device/bluetooth/bluetooth_low_energy_win.h b/device/bluetooth/bluetooth_low_energy_win.h
new file mode 100644
index 0000000..6fb1107
--- /dev/null
+++ b/device/bluetooth/bluetooth_low_energy_win.h
@@ -0,0 +1,138 @@
+// 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_LOW_ENERGY_WIN_H_
+#define DEVICE_BLUETOOTH_BLUETOOTH_LOW_ENERGY_WIN_H_
+
+#include <windows.h>
+#include <setupapi.h>
+// #include <bthledef.h>
+// TODO(rpaquay):
+// bthledef.h from Win8 SDK has a couple of issues when used in a Win32 app:
+// * line 420: usage of "pragma pop" instead of "pragma warning(pop)"
+// * line 349: no CALLBACK modifier in the definition of
+// PFNBLUETOOTH_GATT_EVENT_CALLBACK.
+//
+// So, we duplicate the definitions we need and prevent the build from including
+// the content of bthledef.h.
+#ifndef __BTHLEDEF_H__
+#define __BTHLEDEF_H__
+
+//
+// Bluetooth LE device interface GUID
+//
+// {781aee18-7733-4ce4-adb0-91f41c67b592}
+DEFINE_GUID(GUID_BLUETOOTHLE_DEVICE_INTERFACE,
+ 0x781aee18,
+ 0x7733,
+ 0x4ce4,
+ 0xad,
+ 0xd0,
+ 0x91,
+ 0xf4,
+ 0x1c,
+ 0x67,
+ 0xb5,
+ 0x92);
+
+#endif // __BTHLEDEF_H__
+#include <bluetoothapis.h>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/win/scoped_handle.h"
+
+namespace device {
+namespace win {
+
+// Represents a device registry property value
+class DeviceRegistryPropertyValue {
+ public:
+ // Creates a property value instance, where |property_type| is one of REG_xxx
+ // registry value type (e.g. REG_SZ, REG_DWORD), |value| is a byte array
+ // containing the propery value and |value_size| is the number of bytes in
+ // |value|. Note the returned instance takes ownership of the bytes in
+ // |value|.
+ static scoped_ptr<DeviceRegistryPropertyValue> Create(
+ DWORD property_type,
+ scoped_ptr<uint8_t[]> value,
+ size_t value_size);
+ ~DeviceRegistryPropertyValue();
+
+ // Returns the vaue type a REG_xxx value (e.g. REG_SZ, REG_DWORD, ...)
+ DWORD property_type() const { return property_type_; }
+
+ std::string AsString() const;
+ DWORD AsDWORD() const;
+
+ private:
+ DeviceRegistryPropertyValue(DWORD property_type,
+ scoped_ptr<uint8_t[]> value,
+ size_t value_size);
+
+ DWORD property_type_;
+ scoped_ptr<uint8_t[]> value_;
+ size_t value_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceRegistryPropertyValue);
+};
+
+// Represents the value associated to a DEVPROPKEY.
+class DevicePropertyValue {
+ public:
+ // Creates a property value instance, where |property_type| is one of
+ // DEVPROP_TYPE_xxx value type , |value| is a byte array containing the
+ // propery value and |value_size| is the number of bytes in |value|. Note the
+ // returned instance takes ownership of the bytes in |value|.
+ DevicePropertyValue(DEVPROPTYPE property_type,
+ scoped_ptr<uint8_t[]> value,
+ size_t value_size);
+
+ DEVPROPTYPE property_type() const { return property_type_; }
+
+ uint32_t AsUint32() const;
+
+ private:
+ DEVPROPTYPE property_type_;
+ scoped_ptr<uint8_t[]> value_;
+ size_t value_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePropertyValue);
+};
+
+// Returns true only on Windows platforms supporting Bluetooth Low Energy.
+bool IsBluetoothLowEnergySupported();
+
+struct BluetoothLowEnergyDeviceInfo {
+ BluetoothLowEnergyDeviceInfo();
+ ~BluetoothLowEnergyDeviceInfo();
+
+ base::FilePath path;
+ std::string id;
+ std::string friendly_name;
+ BLUETOOTH_ADDRESS address;
+ bool visible;
+ bool authenticated;
+ bool connected;
+};
+
+// Enumerates the list of known (i.e. already paired) Bluetooth LE devices on
+// this machine. In case of error, returns false and sets |error| with an error
+// message describing the problem.
+// Note: This function returns an error if Bluetooth Low Energy is not supported
+// on this Windows platform.
+bool EnumerateKnownBluetoothLowEnergyDevices(
+ ScopedVector<BluetoothLowEnergyDeviceInfo>* devices,
+ std::string* error);
+
+bool ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
+ const std::string& instance_id,
+ BLUETOOTH_ADDRESS* btha,
+ std::string* error);
+
+} // namespace win
+} // namespace device
+
+#endif // DEVICE_BLUETOOTH_BLUETOOTH_LOW_ENERGY_WIN_H_
diff --git a/device/bluetooth/bluetooth_low_energy_win_unittest.cc b/device/bluetooth/bluetooth_low_energy_win_unittest.cc
new file mode 100644
index 0000000..3e9116e
--- /dev/null
+++ b/device/bluetooth/bluetooth_low_energy_win_unittest.cc
@@ -0,0 +1,85 @@
+// 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/strings/sys_string_conversions.h"
+#include "device/bluetooth/bluetooth_low_energy_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kValidDeviceInstanceId[] =
+ "BTHLE\\DEV_BC6A29AB5FB0\\8&31038925&0&BC6A29AB5FB0";
+
+const char kInvalidDeviceInstanceId[] =
+ "BTHLE\\BC6A29AB5FB0_DEV_\\8&31038925&0&BC6A29AB5FB0";
+
+} // namespace
+
+namespace device {
+
+class BluetoothLowEnergyWinTest : public testing::Test {};
+
+TEST_F(BluetoothLowEnergyWinTest, ExtractValidBluetoothAddress) {
+ BLUETOOTH_ADDRESS btha;
+ std::string error;
+ bool success =
+ device::win::ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
+ kValidDeviceInstanceId, &btha, &error);
+
+ EXPECT_TRUE(success);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(0xbc, btha.rgBytes[5]);
+ EXPECT_EQ(0x6a, btha.rgBytes[4]);
+ EXPECT_EQ(0x29, btha.rgBytes[3]);
+ EXPECT_EQ(0xab, btha.rgBytes[2]);
+ EXPECT_EQ(0x5f, btha.rgBytes[1]);
+ EXPECT_EQ(0xb0, btha.rgBytes[0]);
+}
+
+TEST_F(BluetoothLowEnergyWinTest, ExtractInvalidBluetoothAddress) {
+ BLUETOOTH_ADDRESS btha;
+ std::string error;
+ bool success =
+ device::win::ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
+ kInvalidDeviceInstanceId, &btha, &error);
+
+ EXPECT_FALSE(success);
+ EXPECT_FALSE(error.empty());
+}
+
+TEST_F(BluetoothLowEnergyWinTest, DeviceRegistryPropertyValueAsString) {
+ std::string test_value = "String used for round trip test.";
+ std::wstring wide_value = base::SysUTF8ToWide(test_value);
+ size_t buffer_size = (wide_value.size() + 1) * sizeof(wchar_t);
+ scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
+ memcpy(buffer.get(), wide_value.c_str(), buffer_size);
+ scoped_ptr<device::win::DeviceRegistryPropertyValue> value =
+ device::win::DeviceRegistryPropertyValue::Create(
+ REG_SZ, buffer.Pass(), buffer_size).Pass();
+ EXPECT_EQ(test_value, value->AsString());
+}
+
+TEST_F(BluetoothLowEnergyWinTest, DeviceRegistryPropertyValueAsDWORD) {
+ DWORD test_value = 5u;
+ size_t buffer_size = sizeof(DWORD);
+ scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
+ memcpy(buffer.get(), &test_value, buffer_size);
+ scoped_ptr<device::win::DeviceRegistryPropertyValue> value =
+ device::win::DeviceRegistryPropertyValue::Create(
+ REG_DWORD, buffer.Pass(), buffer_size).Pass();
+ EXPECT_EQ(test_value, value->AsDWORD());
+}
+
+TEST_F(BluetoothLowEnergyWinTest, DevicePropertyValueAsUint32) {
+ uint32_t test_value = 5u;
+ size_t buffer_size = sizeof(uint32_t);
+ scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
+ memcpy(buffer.get(), &test_value, buffer_size);
+ scoped_ptr<device::win::DevicePropertyValue> value(
+ new device::win::DevicePropertyValue(
+ DEVPROP_TYPE_UINT32, buffer.Pass(), buffer_size));
+ EXPECT_EQ(test_value, value->AsUint32());
+}
+
+} // namespace device
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc
index e3de615..83231b9 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.cc
@@ -4,9 +4,14 @@
#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"
@@ -31,9 +36,11 @@
BluetoothRemoteGattCharacteristicChromeOS(
BluetoothRemoteGattServiceChromeOS* service,
const dbus::ObjectPath& object_path)
- : object_path_(object_path),
- service_(service),
- weak_ptr_factory_(this) {
+ : 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()->
@@ -62,6 +69,13 @@
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 {
@@ -135,6 +149,16 @@
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;
@@ -200,6 +224,96 @@
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) {
@@ -289,4 +403,77 @@
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/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h b/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h
index 35a0884..26fda0b 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_chromeos.h
@@ -6,7 +6,9 @@
#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"
@@ -44,6 +46,7 @@
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(
@@ -58,6 +61,14 @@
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_; }
@@ -92,6 +103,29 @@
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_;
@@ -102,6 +136,18 @@
// 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
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc b/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc
index 4f88edf..7038270 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_chromeos.cc
@@ -8,6 +8,7 @@
#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"
@@ -15,13 +16,17 @@
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);
@@ -146,6 +151,11 @@
error_callback.Run();
}
+scoped_refptr<device::BluetoothAdapter>
+BluetoothRemoteGattServiceChromeOS::GetAdapter() const {
+ return adapter_;
+}
+
void BluetoothRemoteGattServiceChromeOS::NotifyServiceChanged() {
FOR_EACH_OBSERVER(device::BluetoothGattService::Observer, observers_,
GattServiceChanged(this));
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h b/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h
index c9de5ce..ce481ec 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h
+++ b/device/bluetooth/bluetooth_remote_gatt_service_chromeos.h
@@ -9,6 +9,7 @@
#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"
@@ -19,12 +20,14 @@
namespace device {
+class BluetoothAdapter;
class BluetoothGattCharacteristic;
} // namespace device
namespace chromeos {
+class BluetoothAdapterChromeOS;
class BluetoothDeviceChromeOS;
class BluetoothRemoteGattCharacteristicChromeOS;
class BluetoothRemoteGattDescriptorChromeOS;
@@ -64,6 +67,9 @@
// 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.
@@ -99,7 +105,8 @@
private:
friend class BluetoothDeviceChromeOS;
- BluetoothRemoteGattServiceChromeOS(BluetoothDeviceChromeOS* device,
+ BluetoothRemoteGattServiceChromeOS(BluetoothAdapterChromeOS* adapter,
+ BluetoothDeviceChromeOS* device,
const dbus::ObjectPath& object_path);
virtual ~BluetoothRemoteGattServiceChromeOS();
@@ -123,7 +130,12 @@
// List of observers interested in event notifications from us.
ObserverList<device::BluetoothGattService::Observer> observers_;
- // The device this GATT service belongs to.
+ // 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.
diff --git a/device/bluetooth/bluetooth_socket_chromeos.cc b/device/bluetooth/bluetooth_socket_chromeos.cc
index 9e6ba35..e073ad8 100644
--- a/device/bluetooth/bluetooth_socket_chromeos.cc
+++ b/device/bluetooth/bluetooth_socket_chromeos.cc
@@ -120,7 +120,7 @@
scoped_refptr<BluetoothAdapter> adapter,
SocketType socket_type,
const BluetoothUUID& uuid,
- int psm_or_channel,
+ const BluetoothAdapter::ServiceOptions& service_options,
const base::Closure& success_callback,
const ErrorCompletionCallback& error_callback) {
DCHECK(ui_task_runner()->RunsTasksOnCurrentThread());
@@ -137,17 +137,17 @@
uuid_ = uuid;
options_.reset(new BluetoothProfileManagerClient::Options());
+ if (service_options.name)
+ options_->name.reset(new std::string(*service_options.name));
switch (socket_type) {
case kRfcomm:
- options_->channel.reset(new uint16(
- psm_or_channel == BluetoothAdapter::kChannelAuto
- ? 0 : psm_or_channel));
+ options_->channel.reset(
+ new uint16(service_options.channel ? *service_options.channel : 0));
break;
case kL2cap:
- options_->psm.reset(new uint16(
- psm_or_channel == BluetoothAdapter::kPsmAuto
- ? 0 : psm_or_channel));
+ options_->psm.reset(
+ new uint16(service_options.psm ? *service_options.psm : 0));
break;
default:
NOTREACHED();
diff --git a/device/bluetooth/bluetooth_socket_chromeos.h b/device/bluetooth/bluetooth_socket_chromeos.h
index 43f5126..77d5eb1 100644
--- a/device/bluetooth/bluetooth_socket_chromeos.h
+++ b/device/bluetooth/bluetooth_socket_chromeos.h
@@ -52,17 +52,18 @@
// 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
+ // as UUID |uuid|. The |service_options| 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);
+ virtual void Listen(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ SocketType socket_type,
+ const device::BluetoothUUID& uuid,
+ const device::BluetoothAdapter::ServiceOptions& service_options,
+ const base::Closure& success_callback,
+ const ErrorCompletionCallback& error_callback);
// BluetoothSocket:
virtual void Close() OVERRIDE;
diff --git a/device/bluetooth/bluetooth_socket_chromeos_unittest.cc b/device/bluetooth/bluetooth_socket_chromeos_unittest.cc
index 7c86837..e4079f0 100644
--- a/device/bluetooth/bluetooth_socket_chromeos_unittest.cc
+++ b/device/bluetooth/bluetooth_socket_chromeos_unittest.cc
@@ -292,7 +292,7 @@
TEST_F(BluetoothSocketChromeOSTest, Listen) {
adapter_->CreateRfcommService(
BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid),
- BluetoothAdapter::kChannelAuto,
+ BluetoothAdapter::ServiceOptions(),
base::Bind(&BluetoothSocketChromeOSTest::CreateServiceSuccessCallback,
base::Unretained(this)),
base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback,
@@ -412,7 +412,7 @@
adapter_->CreateRfcommService(
BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid),
- BluetoothAdapter::kChannelAuto,
+ BluetoothAdapter::ServiceOptions(),
base::Bind(&BluetoothSocketChromeOSTest::CreateServiceSuccessCallback,
base::Unretained(this)),
base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback,
@@ -461,7 +461,7 @@
adapter_->CreateRfcommService(
BluetoothUUID(FakeBluetoothProfileManagerClient::kRfcommUuid),
- BluetoothAdapter::kChannelAuto,
+ BluetoothAdapter::ServiceOptions(),
base::Bind(&BluetoothSocketChromeOSTest::CreateServiceSuccessCallback,
base::Unretained(this)),
base::Bind(&BluetoothSocketChromeOSTest::ErrorCallback,
diff --git a/device/bluetooth/bluetooth_socket_mac.h b/device/bluetooth/bluetooth_socket_mac.h
index bbb107d..34f8136 100644
--- a/device/bluetooth/bluetooth_socket_mac.h
+++ b/device/bluetooth/bluetooth_socket_mac.h
@@ -16,6 +16,7 @@
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/thread_checker.h"
+#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/bluetooth_uuid.h"
@@ -48,24 +49,28 @@
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.
+ // RFCOMM service on the |adapter| as UUID |uuid| with Channel
+ // |options.channel|, or an automatically allocated Channel if
+ // |options.channel| is left null. The service is published with English name
+ // |options.name| if that is non-null. |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 BluetoothAdapter::ServiceOptions& options,
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|.
+ // L2CAP service on the |adapter| as UUID |uuid| with PSM |options.psm|, or an
+ // automatically allocated PSM if |options.psm| is left null. The service is
+ // published with English name |options.name| if that is non-null.
// |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 BluetoothAdapter::ServiceOptions& options,
const base::Closure& success_callback,
const ErrorCompletionCallback& error_callback);
diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm
index e428ba5..02bd3ea 100644
--- a/device/bluetooth/bluetooth_socket_mac.mm
+++ b/device/bluetooth/bluetooth_socket_mac.mm
@@ -21,7 +21,6 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.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"
@@ -221,6 +220,11 @@
// documentation at [ http://goo.gl/YRtCkF ].
const BluetoothSDPServiceRecordHandle kInvalidServiceRecordHandle = 0;
+// Likewise, it's safe to use 0 to represent invalid channel or PSM port
+// numbers, as both are required to be non-zero for valid services.
+const BluetoothRFCOMMChannelID kInvalidRfcommChannelId = 0;
+const BluetoothL2CAPPSM kInvalidL2capPsm = 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";
@@ -264,21 +268,24 @@
}
// Returns a dictionary containing the Bluetooth service definition
-// corresponding to the provided |uuid| and |protocol_definition|.
+// corresponding to the provided |uuid|, |name|, and |protocol_definition|. Does
+// not include a service name in the definition if |name| is null.
NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid,
+ const std::string* name,
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)];
+ if (name) {
+ // TODO(isherman): The service's language is currently hardcoded to English.
+ // The language should ideally be specified in the chrome.bluetooth API
+ // instead.
+ const int kEnglishLanguageBase = 100;
+ const int kServiceNameKey =
+ kEnglishLanguageBase + kBluetoothSDPAttributeIdentifierServiceName;
+ NSString* service_name = base::SysUTF8ToNSString(*name);
+ [service_definition setObject:service_name
+ forKey:IntToNSString(kServiceNameKey)];
+ }
const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList;
NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid)];
@@ -293,9 +300,11 @@
}
// 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) {
+// corresponding to the provided |uuid| and |options|.
+NSDictionary* BuildRfcommServiceDefinition(
+ const BluetoothUUID& uuid,
+ const BluetoothAdapter::ServiceOptions& options) {
+ int channel_id = options.channel ? *options.channel : kInvalidRfcommChannelId;
NSArray* rfcomm_protocol_definition =
@[
@[
@@ -310,13 +319,16 @@
}
]
];
- return BuildServiceDefinition(uuid, rfcomm_protocol_definition);
+ return BuildServiceDefinition(
+ uuid, options.name.get(), 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) {
+// corresponding to the provided |uuid| and |options|.
+NSDictionary* BuildL2capServiceDefinition(
+ const BluetoothUUID& uuid,
+ const BluetoothAdapter::ServiceOptions& options) {
+ int psm = options.psm ? *options.psm : kInvalidL2capPsm;
NSArray* l2cap_protocol_definition =
@[
@[
@@ -328,7 +340,8 @@
}
]
];
- return BuildServiceDefinition(uuid, l2cap_protocol_definition);
+ return BuildServiceDefinition(
+ uuid, options.name.get(), l2cap_protocol_definition);
}
// Registers a Bluetooth service with the specified |service_definition| in the
@@ -372,17 +385,16 @@
// 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,
+bool VerifyRfcommService(const 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|.
+ // pass in |kInvalidRfcommChannelId|.
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)) {
+ (requested_channel_id && rfcomm_channel_id != *requested_channel_id)) {
return false;
}
@@ -390,33 +402,35 @@
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
+// Registers an RFCOMM service with the specified |uuid|, |options.channel_id|,
+// and |options.name| in the system SDP server. Automatically allocates a
+// channel if |options.channel_id| is null. Does not specify a name if
+// |options.name| is null. 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,
+ const BluetoothAdapter::ServiceOptions& options,
BluetoothRFCOMMChannelID* registered_channel_id) {
return RegisterService(
- BuildRfcommServiceDefinition(uuid, channel_id),
- base::Bind(&VerifyRfcommService, channel_id, registered_channel_id));
+ BuildRfcommServiceDefinition(uuid, options),
+ base::Bind(
+ &VerifyRfcommService, options.channel.get(), 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,
+bool VerifyL2capService(const 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|.
+ // pass in |kInvalidL2capPsm|.
BluetoothL2CAPPSM l2cap_psm;
IOReturn result = [service_record getL2CAPPSM:&l2cap_psm];
if (result != kIOReturnSuccess ||
- (requested_psm != BluetoothAdapter::kPsmAuto &&
- l2cap_psm != requested_psm)) {
+ (requested_psm && l2cap_psm != *requested_psm)) {
return false;
}
@@ -424,16 +438,19 @@
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.
+// Registers an L2CAP service with the specified |uuid|, |options.psm|, and
+// |options.name| in the system SDP server. Automatically allocates a PSM if
+// |options.psm| is null. Does not register a name if |options.name| is null.
+// 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,
+ const BluetoothAdapter::ServiceOptions& options,
BluetoothL2CAPPSM* registered_psm) {
- return RegisterService(BuildL2capServiceDefinition(uuid, psm),
- base::Bind(&VerifyL2capService, psm, registered_psm));
+ return RegisterService(
+ BuildL2capServiceDefinition(uuid, options),
+ base::Bind(&VerifyL2capService, options.psm.get(), registered_psm));
}
} // namespace
@@ -469,7 +486,7 @@
void BluetoothSocketMac::ListenUsingRfcomm(
scoped_refptr<BluetoothAdapterMac> adapter,
const BluetoothUUID& uuid,
- int channel_id,
+ const BluetoothAdapter::ServiceOptions& options,
const base::Closure& success_callback,
const ErrorCompletionCallback& error_callback) {
DCHECK(thread_checker_.CalledOnValidThread());
@@ -480,7 +497,7 @@
DVLOG(1) << uuid_.canonical_value() << ": Registering RFCOMM service.";
BluetoothRFCOMMChannelID registered_channel_id;
service_record_handle_ =
- RegisterRfcommService(uuid, channel_id, ®istered_channel_id);
+ RegisterRfcommService(uuid, options, ®istered_channel_id);
if (service_record_handle_ == kInvalidServiceRecordHandle) {
error_callback.Run(kInvalidOrUsedChannel);
return;
@@ -497,7 +514,7 @@
void BluetoothSocketMac::ListenUsingL2cap(
scoped_refptr<BluetoothAdapterMac> adapter,
const BluetoothUUID& uuid,
- int psm,
+ const BluetoothAdapter::ServiceOptions& options,
const base::Closure& success_callback,
const ErrorCompletionCallback& error_callback) {
DCHECK(thread_checker_.CalledOnValidThread());
@@ -507,7 +524,7 @@
DVLOG(1) << uuid_.canonical_value() << ": Registering L2CAP service.";
BluetoothL2CAPPSM registered_psm;
- service_record_handle_ = RegisterL2capService(uuid, psm, ®istered_psm);
+ service_record_handle_ = RegisterL2capService(uuid, options, ®istered_psm);
if (service_record_handle_ == kInvalidServiceRecordHandle) {
error_callback.Run(kInvalidOrUsedPsm);
return;
@@ -553,8 +570,8 @@
// 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;
+ BluetoothRFCOMMChannelID rfcomm_channel_id = kInvalidRfcommChannelId;
+ BluetoothL2CAPPSM l2cap_psm = kInvalidL2capPsm;
status = [record getRFCOMMChannelID:&rfcomm_channel_id];
if (status != kIOReturnSuccess) {
status = [record getL2CAPPSM:&l2cap_psm];
@@ -564,12 +581,12 @@
}
}
- if (rfcomm_channel_id != BluetoothAdapter::kChannelAuto) {
+ if (rfcomm_channel_id != kInvalidRfcommChannelId) {
DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
<< uuid_.canonical_value() << ": Opening RFCOMM channel: "
<< rfcomm_channel_id;
} else {
- DCHECK_NE(l2cap_psm, BluetoothAdapter::kPsmAuto);
+ DCHECK_NE(l2cap_psm, kInvalidL2capPsm);
DVLOG(1) << BluetoothDeviceMac::GetDeviceAddress(device) << " "
<< uuid_.canonical_value() << ": Opening L2CAP channel: "
<< l2cap_psm;
@@ -582,11 +599,11 @@
connect_callbacks_->success_callback = success_callback;
connect_callbacks_->error_callback = error_callback;
- if (rfcomm_channel_id != BluetoothAdapter::kChannelAuto) {
+ if (rfcomm_channel_id != kInvalidRfcommChannelId) {
channel_ = BluetoothRfcommChannelMac::OpenAsync(
this, device, rfcomm_channel_id, &status);
} else {
- DCHECK_NE(l2cap_psm, BluetoothAdapter::kPsmAuto);
+ DCHECK_NE(l2cap_psm, kInvalidL2capPsm);
channel_ =
BluetoothL2capChannelMac::OpenAsync(this, device, l2cap_psm, &status);
}
@@ -614,17 +631,14 @@
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): Currently, the socket remains alive even after the app that
+ // requested it 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
+ // underlying 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(
diff --git a/device/bluetooth/bluetooth_socket_net.cc b/device/bluetooth/bluetooth_socket_net.cc
index 8007d77..c42d46e 100644
--- a/device/bluetooth/bluetooth_socket_net.cc
+++ b/device/bluetooth/bluetooth_socket_net.cc
@@ -7,6 +7,7 @@
#include <queue>
#include <string>
+#include "base/location.h"
#include "base/logging.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
diff --git a/device/bluetooth/bluetooth_socket_win.cc b/device/bluetooth/bluetooth_socket_win.cc
index a46f4b5..e08bba0 100644
--- a/device/bluetooth/bluetooth_socket_win.cc
+++ b/device/bluetooth/bluetooth_socket_win.cc
@@ -91,7 +91,7 @@
const net::NetLog::Source& source)
: BluetoothSocketNet(ui_task_runner, socket_thread, net_log, source),
supports_rfcomm_(false),
- rfcomm_channel_(-1),
+ rfcomm_channel_(0xFF),
bth_addr_(BTH_ADDR_NULL) {
}
@@ -137,13 +137,15 @@
void BluetoothSocketWin::Listen(scoped_refptr<BluetoothAdapter> adapter,
const BluetoothUUID& uuid,
- int rfcomm_channel,
+ const BluetoothAdapter::ServiceOptions& options,
const base::Closure& success_callback,
const ErrorCompletionCallback& error_callback) {
DCHECK(ui_task_runner()->RunsTasksOnCurrentThread());
adapter_ = adapter;
+ int rfcomm_channel = options.channel ? *options.channel : 0;
+ // TODO(xiyuan): Use |options.name|.
socket_thread()->task_runner()->PostTask(
FROM_HERE,
base::Bind(&BluetoothSocketWin::DoListen,
diff --git a/device/bluetooth/bluetooth_socket_win.h b/device/bluetooth/bluetooth_socket_win.h
index 22ec412..59fec08 100644
--- a/device/bluetooth/bluetooth_socket_win.h
+++ b/device/bluetooth/bluetooth_socket_win.h
@@ -10,6 +10,7 @@
#include <string>
#include "base/memory/ref_counted.h"
+#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_service_record_win.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/bluetooth_socket_net.h"
@@ -41,12 +42,13 @@
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
+ // with Channel |options.channel|, or an automatically allocated Channel if
+ // |options.channel| is null. |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 BluetoothAdapter::ServiceOptions& options,
const base::Closure& success_callback,
const ErrorCompletionCallback& error_callback);
diff --git a/device/bluetooth/bluetooth_strings.gyp b/device/bluetooth/bluetooth_strings.gyp
index 4bf3a30..8b9dca9 100644
--- a/device/bluetooth/bluetooth_strings.gyp
+++ b/device/bluetooth/bluetooth_strings.gyp
@@ -8,23 +8,18 @@
'target_name': 'device_bluetooth_strings',
'type': 'none',
'variables': {
- 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/device/bluetooth',
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/device/bluetooth/strings',
},
'actions': [
{
- 'action_name': 'device_bluetooth_strings',
+ 'action_name': 'generate_device_bluetooth_strings',
'variables': {
'grit_grd_file': 'bluetooth_strings.grd',
- 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/device/bluetooth/strings',
},
'includes': [ '../../build/grit_action.gypi' ],
},
],
- 'direct_dependent_settings': {
- 'include_dirs': [
- '<(SHARED_INTERMEDIATE_DIR)/device/bluetooth/strings',
- ],
- },
+ 'includes': [ '../../build/grit_target.gypi' ],
},
],
}
diff --git a/device/bluetooth/bluetooth_task_manager_win.cc b/device/bluetooth/bluetooth_task_manager_win.cc
index 7d8da08..fc803d5 100644
--- a/device/bluetooth/bluetooth_task_manager_win.cc
+++ b/device/bluetooth/bluetooth_task_manager_win.cc
@@ -11,7 +11,6 @@
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/stringprintf.h"
@@ -19,6 +18,7 @@
#include "base/threading/sequenced_worker_pool.h"
#include "base/win/scoped_handle.h"
#include "device/bluetooth/bluetooth_init_win.h"
+#include "device/bluetooth/bluetooth_low_energy_win.h"
#include "device/bluetooth/bluetooth_service_record_win.h"
#include "net/base/winsock_init.h"
@@ -32,6 +32,16 @@
typedef device::BluetoothTaskManagerWin::ServiceRecordState ServiceRecordState;
+std::string BluetoothAddressToString(const BLUETOOTH_ADDRESS& btha) {
+ return base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X",
+ btha.rgBytes[5],
+ btha.rgBytes[4],
+ btha.rgBytes[3],
+ btha.rgBytes[2],
+ btha.rgBytes[1],
+ btha.rgBytes[0]);
+}
+
// Populates bluetooth adapter state using adapter_handle.
void GetAdapterState(HANDLE adapter_handle,
device::BluetoothTaskManagerWin::AdapterState* state) {
@@ -43,13 +53,7 @@
ERROR_SUCCESS == BluetoothGetRadioInfo(adapter_handle,
&adapter_info)) {
name = base::SysWideToUTF8(adapter_info.szName);
- address = base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X",
- adapter_info.address.rgBytes[5],
- adapter_info.address.rgBytes[4],
- adapter_info.address.rgBytes[3],
- adapter_info.address.rgBytes[2],
- adapter_info.address.rgBytes[1],
- adapter_info.address.rgBytes[0]);
+ address = BluetoothAddressToString(adapter_info.address);
powered = !!BluetoothIsConnectable(adapter_handle);
}
state->name = name;
@@ -60,13 +64,7 @@
void GetDeviceState(const BLUETOOTH_DEVICE_INFO& device_info,
device::BluetoothTaskManagerWin::DeviceState* state) {
state->name = base::SysWideToUTF8(device_info.szName);
- state->address = base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X",
- device_info.Address.rgBytes[5],
- device_info.Address.rgBytes[4],
- device_info.Address.rgBytes[3],
- device_info.Address.rgBytes[2],
- device_info.Address.rgBytes[1],
- device_info.Address.rgBytes[0]);
+ state->address = BluetoothAddressToString(device_info.Address);
state->bluetooth_class = device_info.ulClassofDevice;
state->visible = true;
state->connected = !!device_info.fConnected;
@@ -287,6 +285,7 @@
GetKnownDevices();
BluetoothFindRadioClose(handle);
}
+
PostAdapterStateToUi();
}
@@ -390,6 +389,32 @@
void BluetoothTaskManagerWin::GetKnownDevices() {
ScopedVector<DeviceState>* device_list = new ScopedVector<DeviceState>();
SearchDevices(1, true, device_list);
+
+ // Search for Bluetooth Low Energy devices
+ if (win::IsBluetoothLowEnergySupported()) {
+ ScopedVector<win::BluetoothLowEnergyDeviceInfo> btle_devices;
+ std::string error;
+ bool success =
+ win::EnumerateKnownBluetoothLowEnergyDevices(&btle_devices, &error);
+ if (success) {
+ for (ScopedVector<win::BluetoothLowEnergyDeviceInfo>::iterator iter =
+ btle_devices.begin();
+ iter != btle_devices.end();
+ ++iter) {
+ win::BluetoothLowEnergyDeviceInfo* device_info = (*iter);
+
+ DeviceState* device_state = new DeviceState();
+ device_state->name = device_info->friendly_name;
+ device_state->address = BluetoothAddressToString(device_info->address);
+ device_state->visible = device_info->visible;
+ device_state->authenticated = device_info->authenticated;
+ device_state->connected = device_info->connected;
+ device_state->path = device_info->path;
+ device_list->push_back(device_state);
+ }
+ }
+ }
+
if (device_list->empty()) {
delete device_list;
return;
diff --git a/device/bluetooth/bluetooth_task_manager_win.h b/device/bluetooth/bluetooth_task_manager_win.h
index 991e95c..f2d389b 100644
--- a/device/bluetooth/bluetooth_task_manager_win.h
+++ b/device/bluetooth/bluetooth_task_manager_win.h
@@ -8,6 +8,7 @@
#include <string>
#include <vector>
+#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_vector.h"
#include "base/observer_list.h"
@@ -53,13 +54,17 @@
struct DeviceState {
DeviceState();
~DeviceState();
+ // Properties common to Bluetooth Radio and LE devices.
std::string name;
std::string address;
- uint32 bluetooth_class;
bool visible;
bool connected;
bool authenticated;
ScopedVector<ServiceRecordState> service_record_states;
+ // Properties specific to Bluetooth Radio devices.
+ uint32 bluetooth_class;
+ // Properties specific to Bluetooth LE devices.
+ base::FilePath path;
};
class Observer {
diff --git a/device/bluetooth/test/mock_bluetooth_adapter.h b/device/bluetooth/test/mock_bluetooth_adapter.h
index ddbb819..2208bfd 100644
--- a/device/bluetooth/test/mock_bluetooth_adapter.h
+++ b/device/bluetooth/test/mock_bluetooth_adapter.h
@@ -68,12 +68,12 @@
MOCK_METHOD0(DefaultPairingDelegate, BluetoothDevice::PairingDelegate*());
MOCK_METHOD4(CreateRfcommService,
void(const BluetoothUUID& uuid,
- int channel,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback));
MOCK_METHOD4(CreateL2capService,
void(const BluetoothUUID& uuid,
- int psm,
+ const ServiceOptions& options,
const CreateServiceCallback& callback,
const CreateServiceErrorCallback& error_callback));
diff --git a/device/bluetooth/test/mock_bluetooth_discovery_session.cc b/device/bluetooth/test/mock_bluetooth_discovery_session.cc
index 72449ee..295f954 100644
--- a/device/bluetooth/test/mock_bluetooth_discovery_session.cc
+++ b/device/bluetooth/test/mock_bluetooth_discovery_session.cc
@@ -4,11 +4,19 @@
#include "device/bluetooth/test/mock_bluetooth_discovery_session.h"
-#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
namespace device {
-MockBluetoothDiscoverySession::MockBluetoothDiscoverySession() {}
+// Note: Because |this| class mocks out all the interesting method calls, the
+// mock BluetoothAdapter will not be used, except for a trivial call from the
+// destructor. It's passed in simply because the base class expects one, and
+// it's nice not to need to complicate production code for the sake of simpler
+// test code.
+MockBluetoothDiscoverySession::MockBluetoothDiscoverySession()
+ : BluetoothDiscoverySession(
+ scoped_refptr<BluetoothAdapter>(
+ new testing::NiceMock<MockBluetoothAdapter>())) {}
MockBluetoothDiscoverySession::~MockBluetoothDiscoverySession() {}
} // namespace device
diff --git a/device/bluetooth/test/mock_bluetooth_discovery_session.h b/device/bluetooth/test/mock_bluetooth_discovery_session.h
index 98f2125..bd724a7 100644
--- a/device/bluetooth/test/mock_bluetooth_discovery_session.h
+++ b/device/bluetooth/test/mock_bluetooth_discovery_session.h
@@ -11,8 +11,6 @@
namespace device {
-class BluetoothAdapter;
-
class MockBluetoothDiscoverySession : public BluetoothDiscoverySession {
public:
MockBluetoothDiscoverySession();
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc
index 5cc4da3..9cacfbe 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc
+++ b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc
@@ -27,6 +27,7 @@
ON_CALL(*this, GetService()).WillByDefault(Return(service));
ON_CALL(*this, GetProperties()).WillByDefault(Return(properties));
ON_CALL(*this, GetPermissions()).WillByDefault(Return(permissions));
+ ON_CALL(*this, IsNotifying()).WillByDefault(Return(false));
ON_CALL(*this, GetDescriptors())
.WillByDefault(Return(std::vector<BluetoothGattDescriptor*>()));
}
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h
index 85323f4..8789b82 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h
+++ b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h
@@ -37,11 +37,14 @@
MOCK_CONST_METHOD0(GetService, BluetoothGattService*());
MOCK_CONST_METHOD0(GetProperties, Properties());
MOCK_CONST_METHOD0(GetPermissions, Permissions());
+ MOCK_CONST_METHOD0(IsNotifying, bool());
MOCK_CONST_METHOD0(GetDescriptors, std::vector<BluetoothGattDescriptor*>());
MOCK_CONST_METHOD1(GetDescriptor,
BluetoothGattDescriptor*(const std::string&));
MOCK_METHOD1(AddDescriptor, bool(BluetoothGattDescriptor*));
MOCK_METHOD1(UpdateValue, bool(const std::vector<uint8>&));
+ MOCK_METHOD2(StartNotifySession,
+ void(const NotifySessionCallback&, const ErrorCallback&));
MOCK_METHOD2(ReadRemoteCharacteristic,
void(const ValueCallback&, const ErrorCallback&));
MOCK_METHOD3(WriteRemoteCharacteristic,
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc b/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc
new file mode 100644
index 0000000..a4e9843
--- /dev/null
+++ b/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc
@@ -0,0 +1,21 @@
+// 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/test/mock_bluetooth_gatt_notify_session.h"
+
+using testing::Return;
+
+namespace device {
+
+MockBluetoothGattNotifySession::MockBluetoothGattNotifySession(
+ const std::string& characteristic_identifier) {
+ ON_CALL(*this, GetCharacteristicIdentifier())
+ .WillByDefault(Return(characteristic_identifier));
+ ON_CALL(*this, IsActive()).WillByDefault(Return(true));
+}
+
+MockBluetoothGattNotifySession::~MockBluetoothGattNotifySession() {
+}
+
+} // namespace device
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_notify_session.h b/device/bluetooth/test/mock_bluetooth_gatt_notify_session.h
new file mode 100644
index 0000000..97c28e7
--- /dev/null
+++ b/device/bluetooth/test/mock_bluetooth_gatt_notify_session.h
@@ -0,0 +1,32 @@
+// 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_TEST_MOCK_BLUETOOTH_GATT_NOTIFY_SESSION_H_
+#define DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_GATT_NOTIFY_SESSION_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "device/bluetooth/bluetooth_gatt_notify_session.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace device {
+
+class MockBluetoothGattNotifySession : public BluetoothGattNotifySession {
+ public:
+ explicit MockBluetoothGattNotifySession(
+ const std::string& characteristic_identifier);
+ virtual ~MockBluetoothGattNotifySession();
+
+ MOCK_CONST_METHOD0(GetCharacteristicIdentifier, std::string());
+ MOCK_METHOD0(IsActive, bool());
+ MOCK_METHOD1(Stop, void(const base::Closure&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBluetoothGattNotifySession);
+};
+
+} // namespace device
+
+#endif // DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_GATT_NOTIFY_SESSION_H_
diff --git a/device/device_tests.gyp b/device/device_tests.gyp
index 577b28d..29667d4 100644
--- a/device/device_tests.gyp
+++ b/device/device_tests.gyp
@@ -11,8 +11,9 @@
'target_name': 'device_unittests',
'type': '<(gtest_target_type)',
'dependencies': [
- '../base/base.gyp:run_all_unittests',
'../base/base.gyp:test_support_base',
+ '../mojo/mojo.gyp:mojo_environment_chromium',
+ '../mojo/mojo.gyp:mojo_system_impl',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
'bluetooth/bluetooth.gyp:device_bluetooth',
@@ -20,6 +21,7 @@
'nfc/nfc.gyp:device_nfc',
'usb/usb.gyp:device_usb',
'hid/hid.gyp:device_hid',
+ 'serial/serial.gyp:device_serial',
],
'sources': [
'bluetooth/bluetooth_adapter_mac_unittest.mm',
@@ -29,6 +31,7 @@
'bluetooth/bluetooth_device_win_unittest.cc',
'bluetooth/bluetooth_chromeos_unittest.cc',
'bluetooth/bluetooth_gatt_chromeos_unittest.cc',
+ 'bluetooth/bluetooth_low_energy_win_unittest.cc',
'bluetooth/bluetooth_service_record_win_unittest.cc',
'bluetooth/bluetooth_socket_chromeos_unittest.cc',
'bluetooth/bluetooth_task_manager_win_unittest.cc',
@@ -40,6 +43,8 @@
'hid/hid_report_descriptor_unittest.cc',
'hid/hid_service_unittest.cc',
'hid/input_service_linux_unittest.cc',
+ 'serial/serial_service_unittest.cc',
+ 'test/run_all_unittests.cc',
],
'conditions': [
['chromeos==1', {
diff --git a/device/hid/hid.gyp b/device/hid/hid.gyp
index c12845a..2db80fd 100644
--- a/device/hid/hid.gyp
+++ b/device/hid/hid.gyp
@@ -16,6 +16,8 @@
'sources': [
'device_monitor_linux.cc',
'device_monitor_linux.h',
+ 'hid_collection_info.cc',
+ 'hid_collection_info.h',
'hid_connection.cc',
'hid_connection.h',
'hid_connection_linux.cc',
@@ -40,8 +42,6 @@
'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',
],
diff --git a/device/hid/hid_collection_info.cc b/device/hid/hid_collection_info.cc
new file mode 100644
index 0000000..4c55d08
--- /dev/null
+++ b/device/hid/hid_collection_info.cc
@@ -0,0 +1,17 @@
+// 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_collection_info.h"
+
+namespace device {
+
+HidCollectionInfo::HidCollectionInfo()
+ : usage(HidUsageAndPage::kGenericDesktopUndefined,
+ HidUsageAndPage::kPageUndefined) {
+}
+
+HidCollectionInfo::~HidCollectionInfo() {
+}
+
+} // namespace device
diff --git a/device/hid/hid_collection_info.h b/device/hid/hid_collection_info.h
new file mode 100644
index 0000000..3b35e5e
--- /dev/null
+++ b/device/hid/hid_collection_info.h
@@ -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.
+
+#ifndef DEVICE_HID_HID_COLLECTION_INFO_H_
+#define DEVICE_HID_HID_COLLECTION_INFO_H_
+
+#include <set>
+
+#include "device/hid/hid_usage_and_page.h"
+
+namespace device {
+
+struct HidCollectionInfo {
+ HidCollectionInfo();
+ ~HidCollectionInfo();
+
+ // Collection's usage ID.
+ HidUsageAndPage usage;
+
+ // HID report IDs which belong
+ // to this collection or to its
+ // embedded collections.
+ std::set<int> report_ids;
+};
+
+} // namespace device"
+
+#endif // DEVICE_HID_HID_COLLECTION_INFO_H_
diff --git a/device/hid/hid_connection.cc b/device/hid/hid_connection.cc
index c134bf2..76809d3 100644
--- a/device/hid/hid_connection.cc
+++ b/device/hid/hid_connection.cc
@@ -4,8 +4,183 @@
#include "device/hid/hid_connection.h"
+#include <algorithm>
+
namespace device {
+namespace {
+
+// Functor used to filter collections by report ID.
+struct CollectionHasReportId {
+ explicit CollectionHasReportId(const uint8_t report_id)
+ : report_id_(report_id) {}
+
+ bool operator()(const HidCollectionInfo& info) const {
+ if (info.report_ids.size() == 0 ||
+ report_id_ == HidConnection::kNullReportId)
+ return false;
+
+ if (report_id_ == HidConnection::kAnyReportId)
+ return true;
+
+ return std::find(info.report_ids.begin(),
+ info.report_ids.end(),
+ report_id_) != info.report_ids.end();
+ }
+
+ private:
+ const uint8_t report_id_;
+};
+
+// Functor returning true if collection has a protected usage.
+struct CollectionIsProtected {
+ bool operator()(const HidCollectionInfo& info) const {
+ return info.usage.IsProtected();
+ }
+};
+
+bool FindCollectionByReportId(const HidDeviceInfo& device_info,
+ const uint8_t report_id,
+ HidCollectionInfo* collection_info) {
+ std::vector<HidCollectionInfo>::const_iterator collection_iter =
+ std::find_if(device_info.collections.begin(),
+ device_info.collections.end(),
+ CollectionHasReportId(report_id));
+ if (collection_iter != device_info.collections.end()) {
+ if (collection_info) {
+ *collection_info = *collection_iter;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool HasReportId(const HidDeviceInfo& device_info) {
+ return FindCollectionByReportId(
+ device_info, HidConnection::kAnyReportId, NULL);
+}
+
+bool HasProtectedCollection(const HidDeviceInfo& device_info) {
+ return std::find_if(device_info.collections.begin(),
+ device_info.collections.end(),
+ CollectionIsProtected()) != device_info.collections.end();
+}
+
+} // namespace
+
+HidConnection::HidConnection(const HidDeviceInfo& device_info)
+ : device_info_(device_info) {
+ has_protected_collection_ = HasProtectedCollection(device_info);
+ has_report_id_ = HasReportId(device_info);
+}
+
+HidConnection::~HidConnection() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void HidConnection::Read(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (device_info_.max_input_report_size == 0) {
+ // The device does not support input reports.
+ callback.Run(false, 0);
+ return;
+ }
+ int expected_buffer_size = device_info_.max_input_report_size;
+ if (!has_report_id()) {
+ expected_buffer_size--;
+ }
+ if (buffer->size() < expected_buffer_size) {
+ // Receive buffer is too small.
+ callback.Run(false, 0);
+ return;
+ }
+
+ PlatformRead(buffer, callback);
+}
+
+void HidConnection::Write(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (device_info_.max_output_report_size == 0) {
+ // The device does not support output reports.
+ callback.Run(false, 0);
+ return;
+ }
+ if (IsReportIdProtected(report_id)) {
+ callback.Run(false, 0);
+ return;
+ }
+
+ PlatformWrite(report_id, buffer, callback);
+}
+
+void HidConnection::GetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (device_info_.max_feature_report_size == 0) {
+ // The device does not support feature reports.
+ callback.Run(false, 0);
+ return;
+ }
+ if (IsReportIdProtected(report_id)) {
+ callback.Run(false, 0);
+ return;
+ }
+ int expected_buffer_size = device_info_.max_feature_report_size;
+ if (!has_report_id()) {
+ expected_buffer_size--;
+ }
+ if (buffer->size() < expected_buffer_size) {
+ // Receive buffer is too small.
+ callback.Run(false, 0);
+ return;
+ }
+
+ PlatformGetFeatureReport(report_id, buffer, callback);
+}
+
+void HidConnection::SendFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (device_info_.max_feature_report_size == 0) {
+ // The device does not support feature reports.
+ callback.Run(false, 0);
+ return;
+ }
+ if (IsReportIdProtected(report_id)) {
+ callback.Run(false, 0);
+ return;
+ }
+
+ PlatformSendFeatureReport(report_id, buffer, callback);
+}
+
+bool HidConnection::CompleteRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
+ if (buffer->size() == 0 || IsReportIdProtected(buffer->data()[0])) {
+ return false;
+ }
+
+ callback.Run(true, buffer->size());
+ return true;
+}
+
+bool HidConnection::IsReportIdProtected(const uint8_t report_id) {
+ HidCollectionInfo collection_info;
+ if (FindCollectionByReportId(device_info_, report_id, &collection_info)) {
+ return collection_info.usage.IsProtected();
+ }
+
+ return has_protected_collection();
+}
+
PendingHidReport::PendingHidReport() {}
PendingHidReport::~PendingHidReport() {}
@@ -14,9 +189,4 @@
PendingHidRead::~PendingHidRead() {}
-HidConnection::HidConnection(const HidDeviceInfo& device_info)
- : device_info_(device_info) {}
-
-HidConnection::~HidConnection() {}
-
} // namespace device
diff --git a/device/hid/hid_connection.h b/device/hid/hid_connection.h
index 5c08fbc..963b89f 100644
--- a/device/hid/hid_connection.h
+++ b/device/hid/hid_connection.h
@@ -9,6 +9,7 @@
#include "base/callback.h"
#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
#include "device/hid/hid_device_info.h"
#include "net/base/io_buffer.h"
@@ -16,31 +17,65 @@
class HidConnection : public base::RefCountedThreadSafe<HidConnection> {
public:
+ enum SpecialReportIds {
+ kNullReportId = 0x00,
+ kAnyReportId = 0xFF,
+ };
+
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_; }
+ bool has_protected_collection() const { return has_protected_collection_; }
+ bool has_report_id() const { return has_report_id_; }
+ const base::ThreadChecker& thread_checker() const { return thread_checker_; }
+
+ void Read(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback);
+ void Write(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback);
+ void GetFeatureReport(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback);
+ void SendFeatureReport(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback);
protected:
friend class base::RefCountedThreadSafe<HidConnection>;
- friend struct HidDeviceInfo;
explicit HidConnection(const HidDeviceInfo& device_info);
virtual ~HidConnection();
+ virtual void PlatformRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) = 0;
+ virtual void PlatformWrite(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) = 0;
+ virtual void PlatformGetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) = 0;
+ virtual void PlatformSendFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) = 0;
+
+ // PlatformRead implementation must call this method on read
+ // success, rather than directly running the callback.
+ // In case incoming buffer is empty or protected, it is filtered
+ // and this method returns false. Otherwise it runs the callback
+ // and returns true.
+ bool CompleteRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback);
+
private:
+ bool IsReportIdProtected(const uint8_t report_id);
+
const HidDeviceInfo device_info_;
+ bool has_report_id_;
+ bool has_protected_collection_;
+ base::ThreadChecker thread_checker_;
DISALLOW_COPY_AND_ASSIGN(HidConnection);
};
diff --git a/device/hid/hid_connection_linux.cc b/device/hid/hid_connection_linux.cc
index 425d88f..0220c2e 100644
--- a/device/hid/hid_connection_linux.cc
+++ b/device/hid/hid_connection_linux.cc
@@ -46,8 +46,6 @@
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;
@@ -72,7 +70,7 @@
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.";
+ PLOG(ERROR) << "Failed to set non-blocking flag to device file";
return;
}
device_file_ = device_file.Pass();
@@ -88,48 +86,13 @@
}
HidConnectionLinux::~HidConnectionLinux() {
- DCHECK(thread_checker_.CalledOnValidThread());
Disconnect();
+ Flush();
}
-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());
+void HidConnectionLinux::PlatformRead(
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
PendingHidRead pending_read;
pending_read.buffer = buffer;
pending_read.callback = callback;
@@ -137,16 +100,17 @@
ProcessReadQueue();
}
-void HidConnectionLinux::Write(uint8_t report_id,
- scoped_refptr<net::IOBufferWithSize> buffer,
- const IOCallback& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
+void HidConnectionLinux::PlatformWrite(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
// 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) {
+ VPLOG(1) << "Write failed";
Disconnect();
callback.Run(false, 0);
} else {
@@ -154,12 +118,10 @@
}
}
-void HidConnectionLinux::GetFeatureReport(
+void HidConnectionLinux::PlatformGetFeatureReport(
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;
@@ -170,39 +132,97 @@
int result = ioctl(device_file_.GetPlatformFile(),
HIDIOCGFEATURE(buffer->size()),
buffer->data());
- if (result < 0)
+ if (result < 0) {
+ VPLOG(1) << "Failed to get feature report";
callback.Run(false, 0);
- else
+ } else {
callback.Run(true, result);
+ }
}
-void HidConnectionLinux::SendFeatureReport(
+void HidConnectionLinux::PlatformSendFeatureReport(
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)
+ if (result < 0) {
+ VPLOG(1) << "Failed to send feature report";
callback.Run(false, 0);
- else
+ } else {
callback.Run(true, result);
+ }
+}
+
+void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK(thread_checker().CalledOnValidThread());
+ DCHECK_EQ(fd, device_file_.GetPlatformFile());
+
+ uint8 raw_buffer[1024] = {0};
+ int bytes_read =
+ HANDLE_EINTR(read(device_file_.GetPlatformFile(), raw_buffer, 1024));
+ if (bytes_read < 0) {
+ if (errno == EAGAIN) {
+ return;
+ }
+ VPLOG(1) << "Read failed";
+ Disconnect();
+ return;
+ }
+
+ scoped_refptr<net::IOBufferWithSize> buffer =
+ new net::IOBufferWithSize(bytes_read);
+ memcpy(buffer->data(), raw_buffer, bytes_read);
+
+ ProcessInputReport(buffer);
+}
+
+void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {
+}
+
+void HidConnectionLinux::Disconnect() {
+ DCHECK(thread_checker().CalledOnValidThread());
+ device_file_watcher_.StopWatchingFileDescriptor();
+ device_file_.Close();
+
+ Flush();
+}
+
+void HidConnectionLinux::Flush() {
+ while (!pending_reads_.empty()) {
+ pending_reads_.front().callback.Run(false, 0);
+ pending_reads_.pop();
+ }
+}
+
+void HidConnectionLinux::ProcessInputReport(
+ scoped_refptr<net::IOBufferWithSize> buffer) {
+ DCHECK(thread_checker().CalledOnValidThread());
+ PendingHidReport report;
+ report.buffer = buffer;
+ pending_reports_.push(report);
+ ProcessReadQueue();
}
void HidConnectionLinux::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 (report.buffer->size() > read.buffer->size()) {
- read.callback.Run(false, report.buffer->size());
+
+ if (read.buffer->size() < report.buffer->size()) {
+ read.callback.Run(false, 0);
+ pending_reads_.pop();
} else {
memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size());
pending_reports_.pop();
- read.callback.Run(true, report.buffer->size());
+
+ if (CompleteRead(report.buffer, read.callback)) {
+ pending_reads_.pop();
+ }
}
}
}
diff --git a/device/hid/hid_connection_linux.h b/device/hid/hid_connection_linux.h
index 108ad88..1f5a7a8 100644
--- a/device/hid/hid_connection_linux.h
+++ b/device/hid/hid_connection_linux.h
@@ -8,10 +8,8 @@
#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 {
@@ -20,19 +18,22 @@
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;
+ // HidConnection implementation.
+ virtual void PlatformRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformWrite(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformGetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformSendFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
- // Implements base::MessagePumpLibevent::Watcher
+ // base::MessagePumpLibevent::Watcher implementation.
virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
@@ -40,17 +41,18 @@
friend class base::RefCountedThreadSafe<HidConnectionLinux>;
virtual ~HidConnectionLinux();
- void ProcessReadQueue();
void Disconnect();
+ void Flush();
+ void ProcessInputReport(scoped_refptr<net::IOBufferWithSize> buffer);
+ void ProcessReadQueue();
+
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);
};
diff --git a/device/hid/hid_connection_mac.cc b/device/hid/hid_connection_mac.cc
index ce17df4..521f287 100644
--- a/device/hid/hid_connection_mac.cc
+++ b/device/hid/hid_connection_mac.cc
@@ -7,7 +7,6 @@
#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 {
@@ -15,85 +14,70 @@
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));
+ inbound_buffer_.reset((uint8_t*)malloc(device_info.max_input_report_size));
IOHIDDeviceRegisterInputReportCallback(device_.get(),
inbound_buffer_.get(),
- device_info.input_report_size,
+ device_info.max_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);
+ Flush();
}
-void HidConnectionMac::Read(scoped_refptr<net::IOBufferWithSize> buffer,
- const IOCallback& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
+void HidConnectionMac::PlatformRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
if (!device_) {
callback.Run(false, 0);
return;
}
- PendingHidRead read;
- read.buffer = buffer;
- read.callback = callback;
- pending_reads_.push(read);
+
+ PendingHidRead pending_read;
+ pending_read.buffer = buffer;
+ pending_read.callback = callback;
+ pending_reads_.push(pending_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(
+void HidConnectionMac::PlatformWrite(
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;
- }
+ WriteReport(kIOHIDReportTypeOutput, report_id, buffer, callback);
+}
- if (buffer->size() < device_info().feature_report_size) {
+void HidConnectionMac::PlatformGetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) {
+ if (!device_) {
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;
+ CFIndex max_feature_report_size = device_info().max_feature_report_size;
IOReturn result = IOHIDDeviceGetReport(device_,
kIOHIDReportTypeFeature,
report_id,
feature_report_buffer,
- &feature_report_size);
+ &max_feature_report_size);
if (result == kIOReturnSuccess)
- callback.Run(true, feature_report_size);
+ callback.Run(true, max_feature_report_size);
else
callback.Run(false, 0);
}
-void HidConnectionMac::SendFeatureReport(
+void HidConnectionMac::PlatformSendFeatureReport(
uint8_t report_id,
scoped_refptr<net::IOBufferWithSize> buffer,
const IOCallback& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
WriteReport(kIOHIDReportTypeFeature, report_id, buffer, callback);
}
@@ -112,45 +96,18 @@
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();
+ base::Bind(&HidConnectionMac::ProcessInputReport, connection, buffer));
}
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);
@@ -173,4 +130,40 @@
}
}
+void HidConnectionMac::Flush() {
+ while (!pending_reads_.empty()) {
+ pending_reads_.front().callback.Run(false, 0);
+ pending_reads_.pop();
+ }
+}
+
+void HidConnectionMac::ProcessInputReport(
+ scoped_refptr<net::IOBufferWithSize> buffer) {
+ DCHECK(thread_checker().CalledOnValidThread());
+ PendingHidReport report;
+ report.buffer = buffer;
+ pending_reports_.push(report);
+ ProcessReadQueue();
+}
+
+void HidConnectionMac::ProcessReadQueue() {
+ DCHECK(thread_checker().CalledOnValidThread());
+ while (pending_reads_.size() && pending_reports_.size()) {
+ PendingHidRead read = pending_reads_.front();
+ PendingHidReport report = pending_reports_.front();
+
+ if (read.buffer->size() < report.buffer->size()) {
+ read.callback.Run(false, 0);
+ pending_reads_.pop();
+ } else {
+ memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size());
+ pending_reports_.pop();
+
+ if (CompleteRead(report.buffer, read.callback)) {
+ pending_reads_.pop();
+ }
+ }
+ }
+}
+
} // namespace device
diff --git a/device/hid/hid_connection_mac.h b/device/hid/hid_connection_mac.h
index c307fb6..02dde04 100644
--- a/device/hid/hid_connection_mac.h
+++ b/device/hid/hid_connection_mac.h
@@ -11,11 +11,8 @@
#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;
@@ -31,17 +28,20 @@
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;
+ // HidConnection implementation.
+ virtual void PlatformRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformWrite(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformGetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformSendFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
private:
virtual ~HidConnectionMac();
@@ -53,29 +53,26 @@
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_;
+ void Flush();
+ void ProcessInputReport(scoped_refptr<net::IOBufferWithSize> buffer);
+ void ProcessReadQueue();
base::ScopedCFTypeRef<IOHIDDeviceRef> device_;
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
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/device/hid/hid_connection_win.cc b/device/hid/hid_connection_win.cc
index 17448f0..767feac 100644
--- a/device/hid/hid_connection_win.cc
+++ b/device/hid/hid_connection_win.cc
@@ -8,12 +8,7 @@
#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
@@ -103,7 +98,6 @@
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,
@@ -124,40 +118,15 @@
}
}
-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;
- }
+void HidConnectionWin::PlatformRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const HidConnection::IOCallback& callback) {
+ scoped_refptr<net::IOBufferWithSize> receive_buffer =
+ new net::IOBufferWithSize(device_info().max_input_report_size);
- // 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);
@@ -169,16 +138,10 @@
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;
- }
-
+void HidConnectionWin::PlatformWrite(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const HidConnection::IOCallback& callback) {
// 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);
@@ -197,32 +160,15 @@
transfer->GetOverlapped()));
}
-void HidConnectionWin::GetFeatureReport(
+void HidConnectionWin::PlatformGetFeatureReport(
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);
-
+ scoped_refptr<net::IOBufferWithSize> receive_buffer =
+ new net::IOBufferWithSize(device_info().max_feature_report_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);
@@ -237,17 +183,10 @@
transfer->GetOverlapped()));
}
-void HidConnectionWin::SendFeatureReport(
+void HidConnectionWin::PlatformSendFeatureReport(
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);
@@ -270,25 +209,42 @@
void HidConnectionWin::OnTransferFinished(
scoped_refptr<PendingHidTransfer> transfer) {
- DWORD bytes_transferred;
transfers_.erase(transfer);
+
+ DWORD bytes_transferred;
if (GetOverlappedResult(
file_, transfer->GetOverlapped(), &bytes_transferred, FALSE)) {
- if (bytes_transferred == 0)
+ 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);
+ return;
}
- transfer->callback_.Run(true, bytes_transferred);
+
+ if (transfer->receive_buffer_) {
+ // If owner HID top-level collection does not have report ID, 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 (!has_report_id()) {
+ uint8_t report_id = transfer->receive_buffer_->data()[0];
+ // Assert first byte is 0x00
+ if (report_id != HidConnection::kNullReportId) {
+ VLOG(1) << "Unexpected report ID in HID report:" << report_id;
+ transfer->callback_.Run(false, 0);
+ } else {
+ // Move one byte forward.
+ --bytes_transferred;
+ memcpy(transfer->target_buffer_->data(),
+ transfer->receive_buffer_->data() + 1,
+ bytes_transferred);
+ }
+ } else {
+ memcpy(transfer->target_buffer_->data(),
+ transfer->receive_buffer_->data(),
+ bytes_transferred);
+ }
+ }
+
+ CompleteRead(transfer->target_buffer_, transfer->callback_);
} else {
transfer->callback_.Run(false, 0);
}
diff --git a/device/hid/hid_connection_win.h b/device/hid/hid_connection_win.h
index 263897a..6706044 100644
--- a/device/hid/hid_connection_win.h
+++ b/device/hid/hid_connection_win.h
@@ -9,12 +9,8 @@
#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 "base/win/scoped_handle.h"
#include "device/hid/hid_connection.h"
-#include "device/hid/hid_device_info.h"
namespace device {
@@ -24,30 +20,35 @@
public:
explicit HidConnectionWin(const HidDeviceInfo& device_info);
- bool available() const;
+ // HidConnection implementation.
+ virtual void PlatformRead(scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformWrite(uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformGetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
+ virtual void PlatformSendFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBufferWithSize> buffer,
+ const IOCallback& callback) OVERRIDE;
- 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:
+ friend class HidServiceWin;
+ friend struct PendingHidTransfer;
+
+ ~HidConnectionWin();
+
+ bool available() const { return file_.IsValid(); }
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_;
+ std::set<scoped_refptr<PendingHidTransfer> > transfers_;
DISALLOW_COPY_AND_ASSIGN(HidConnectionWin);
};
diff --git a/device/hid/hid_device_info.cc b/device/hid/hid_device_info.cc
index 89be442..26891f8 100644
--- a/device/hid/hid_device_info.cc
+++ b/device/hid/hid_device_info.cc
@@ -12,13 +12,13 @@
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) {}
+ bus_type(kHIDBusTypeUSB),
+ max_input_report_size(0),
+ max_output_report_size(0),
+ max_feature_report_size(0) {
+}
HidDeviceInfo::~HidDeviceInfo() {}
diff --git a/device/hid/hid_device_info.h b/device/hid/hid_device_info.h
index 1b143c2..2ed51ac 100644
--- a/device/hid/hid_device_info.h
+++ b/device/hid/hid_device_info.h
@@ -9,7 +9,7 @@
#include <vector>
#include "build/build_config.h"
-#include "device/hid/hid_usage_and_page.h"
+#include "device/hid/hid_collection_info.h"
#if defined(OS_MACOSX)
#include <IOKit/hid/IOHIDDevice.h>
@@ -34,20 +34,19 @@
HidDeviceInfo();
~HidDeviceInfo();
+ // Device identification.
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;
+ HidBusType bus_type;
+
+ // Top-Level Collections information.
+ std::vector<HidCollectionInfo> collections;
+ int max_input_report_size;
+ int max_output_report_size;
+ int max_feature_report_size;
};
} // namespace device
diff --git a/device/hid/hid_report_descriptor.cc b/device/hid/hid_report_descriptor.cc
index f2cb0f4..a06d284 100644
--- a/device/hid/hid_report_descriptor.cc
+++ b/device/hid/hid_report_descriptor.cc
@@ -8,6 +8,12 @@
namespace device {
+namespace {
+
+const int kBitsPerByte = 8;
+
+} // namespace
+
HidReportDescriptor::HidReportDescriptor(const uint8_t* bytes, size_t size) {
size_t header_index = 0;
HidReportDescriptorItem* item = NULL;
@@ -20,42 +26,148 @@
HidReportDescriptor::~HidReportDescriptor() {}
-void HidReportDescriptor::GetTopLevelCollections(
- std::vector<HidUsageAndPage>* topLevelCollections) {
- DCHECK(topLevelCollections);
- STLClearObject(topLevelCollections);
+void HidReportDescriptor::GetDetails(
+ std::vector<HidCollectionInfo>* top_level_collections,
+ int* max_input_report_size,
+ int* max_output_report_size,
+ int* max_feature_report_size) {
+ DCHECK(top_level_collections);
+ DCHECK(max_input_report_size);
+ DCHECK(max_output_report_size);
+ DCHECK(max_feature_report_size);
+ STLClearObject(top_level_collections);
+
+ *max_input_report_size = 0;
+ *max_output_report_size = 0;
+ *max_feature_report_size = 0;
+
+ // Global tags data:
+ HidUsageAndPage::Page current_usage_page = HidUsageAndPage::kPageUndefined;
+ int current_report_count = 0;
+ int cached_report_count = 0;
+ int current_report_size = 0;
+ int cached_report_size = 0;
+ int current_input_report_size = 0;
+ int current_output_report_size = 0;
+ int current_feature_report_size = 0;
+
+ // Local tags data:
+ uint16_t current_usage = 0;
for (std::vector<linked_ptr<HidReportDescriptorItem> >::const_iterator
items_iter = items().begin();
items_iter != items().end();
++items_iter) {
- linked_ptr<HidReportDescriptorItem> item = *items_iter;
+ linked_ptr<HidReportDescriptorItem> current_item = *items_iter;
- bool isTopLevelCollection =
- item->tag() == HidReportDescriptorItem::kTagCollection &&
- item->parent() == NULL;
+ switch (current_item->tag()) {
+ // Main tags:
+ case HidReportDescriptorItem::kTagCollection:
+ if (!current_item->parent()) {
+ // This is a top-level collection.
+ HidCollectionInfo collection;
+ collection.usage = HidUsageAndPage(current_usage, current_usage_page);
+ top_level_collections->push_back(collection);
+ }
+ break;
+ case HidReportDescriptorItem::kTagInput:
+ current_input_report_size += current_report_count * current_report_size;
+ break;
+ case HidReportDescriptorItem::kTagOutput:
+ current_output_report_size +=
+ current_report_count * current_report_size;
+ break;
+ case HidReportDescriptorItem::kTagFeature:
+ current_feature_report_size +=
+ current_report_count * current_report_size;
+ break;
- if (isTopLevelCollection) {
- uint16_t collection_usage = 0;
- HidUsageAndPage::Page collection_usage_page =
- HidUsageAndPage::kPageUndefined;
+ // Global tags:
+ case HidReportDescriptorItem::kTagUsagePage:
+ current_usage_page =
+ (HidUsageAndPage::Page)current_item->GetShortData();
+ break;
+ case HidReportDescriptorItem::kTagReportId:
+ if (top_level_collections->size() > 0) {
+ // Store report ID.
+ top_level_collections->back().report_ids.insert(
+ current_item->GetShortData());
- HidReportDescriptorItem* usage = item->previous();
- if (usage && usage->tag() == HidReportDescriptorItem::kTagUsage) {
- collection_usage = usage->GetShortData();
- }
+ // We need to increase report sizes by report ID field length.
+ if (current_input_report_size > 0)
+ current_input_report_size += kBitsPerByte;
+ if (current_output_report_size > 0)
+ current_output_report_size += kBitsPerByte;
+ if (current_feature_report_size > 0)
+ current_feature_report_size += kBitsPerByte;
- HidReportDescriptorItem* usage_page = usage->previous();
- if (usage_page &&
- usage_page->tag() == HidReportDescriptorItem::kTagUsagePage) {
- collection_usage_page =
- (HidUsageAndPage::Page)usage_page->GetShortData();
- }
+ // Update max report sizes.
+ *max_input_report_size =
+ std::max(*max_input_report_size, current_input_report_size);
+ *max_output_report_size =
+ std::max(*max_output_report_size, current_output_report_size);
+ *max_feature_report_size =
+ std::max(*max_feature_report_size, current_feature_report_size);
- topLevelCollections->push_back(
- HidUsageAndPage(collection_usage, collection_usage_page));
+ // Set report sizes to be 1-byte long (report ID field).
+ current_input_report_size = 0;
+ current_output_report_size = 0;
+ current_feature_report_size = 0;
+ }
+ break;
+ case HidReportDescriptorItem::kTagReportCount:
+ current_report_count = current_item->GetShortData();
+ break;
+ case HidReportDescriptorItem::kTagReportSize:
+ current_report_size = current_item->GetShortData();
+ break;
+ case HidReportDescriptorItem::kTagPush:
+ // Cache report count and size.
+ cached_report_count = current_report_count;
+ cached_report_size = current_report_size;
+ break;
+ case HidReportDescriptorItem::kTagPop:
+ // Restore cache.
+ current_report_count = cached_report_count;
+ current_report_size = cached_report_size;
+ // Reset cache.
+ cached_report_count = 0;
+ cached_report_size = 0;
+ break;
+
+ // Local tags:
+ case HidReportDescriptorItem::kTagUsage:
+ current_usage = current_item->GetShortData();
+ break;
+
+ default:
+ break;
}
}
+
+ if (top_level_collections->size() > 0 &&
+ top_level_collections->back().report_ids.size() > 0) {
+ // We need to increase report sizes by report ID field length.
+ if (current_input_report_size > 0)
+ current_input_report_size += kBitsPerByte;
+ if (current_output_report_size > 0)
+ current_output_report_size += kBitsPerByte;
+ if (current_feature_report_size > 0)
+ current_feature_report_size += kBitsPerByte;
+ }
+
+ // Update max report sizes
+ *max_input_report_size =
+ std::max(*max_input_report_size, current_input_report_size);
+ *max_output_report_size =
+ std::max(*max_output_report_size, current_output_report_size);
+ *max_feature_report_size =
+ std::max(*max_feature_report_size, current_feature_report_size);
+
+ // Convert bits into bytes
+ *max_input_report_size /= kBitsPerByte;
+ *max_output_report_size /= kBitsPerByte;
+ *max_feature_report_size /= kBitsPerByte;
}
} // namespace device
diff --git a/device/hid/hid_report_descriptor.h b/device/hid/hid_report_descriptor.h
index fa67fa4..94d90ad 100644
--- a/device/hid/hid_report_descriptor.h
+++ b/device/hid/hid_report_descriptor.h
@@ -8,8 +8,8 @@
#include <vector>
#include "base/memory/linked_ptr.h"
+#include "device/hid/hid_collection_info.h"
#include "device/hid/hid_report_descriptor_item.h"
-#include "device/hid/hid_usage_and_page.h"
namespace device {
@@ -25,9 +25,12 @@
return items_;
}
- // Returns HID usages of top-level collections present in the descriptor.
- void GetTopLevelCollections(
- std::vector<HidUsageAndPage>* topLevelCollections);
+ // Returns top-level collections present in the descriptor,
+ // together with max report sizes
+ void GetDetails(std::vector<HidCollectionInfo>* top_level_collections,
+ int* max_input_report_size,
+ int* max_output_report_size,
+ int* max_feature_report_size);
private:
std::vector<linked_ptr<HidReportDescriptorItem> > items_;
diff --git a/device/hid/hid_report_descriptor_item.cc b/device/hid/hid_report_descriptor_item.cc
index bdd03ce..60ba764 100644
--- a/device/hid/hid_report_descriptor_item.cc
+++ b/device/hid/hid_report_descriptor_item.cc
@@ -90,17 +90,17 @@
case 0x00:
return kCollectionTypePhysical;
case 0x01:
- return kCollectionTypePhysical;
+ return kCollectionTypeApplication;
case 0x02:
- return kCollectionTypePhysical;
+ return kCollectionTypeLogical;
case 0x03:
- return kCollectionTypePhysical;
+ return kCollectionTypeReport;
case 0x04:
- return kCollectionTypePhysical;
+ return kCollectionTypeNamedArray;
case 0x05:
- return kCollectionTypePhysical;
+ return kCollectionTypeUsageSwitch;
case 0x06:
- return kCollectionTypePhysical;
+ return kCollectionTypeUsageModifier;
default:
break;
}
diff --git a/device/hid/hid_report_descriptor_unittest.cc b/device/hid/hid_report_descriptor_unittest.cc
index 0d25889..619e682 100644
--- a/device/hid/hid_report_descriptor_unittest.cc
+++ b/device/hid/hid_report_descriptor_unittest.cc
@@ -14,392 +14,272 @@
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;
-}
+// Digitizer descriptor from HID descriptor tool
+// http://www.usb.org/developers/hidpage/dt2_4.zip
+const uint8_t kDigitizer[] = {
+ 0x05, 0x0d, // Usage Page (Digitizer)
+ 0x09, 0x01, // Usage (0x1)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x01, // Report ID (0x1)
+ 0x09, 0x21, // Usage (0x21)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x30, // Usage (0x30)
+ 0x09, 0x31, // Usage (0x31)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x02, // Report Count (2)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xe0, 0x2e, // Logical Maximum (12000)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x0c, // Physical Maximum (12)
+ 0x65, 0x13, // Unit (19)
+ 0x55, 0x00, // Unit Exponent (0)
+ 0xa4, // Push
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x05, 0x0d, // Usage Page (Digitizer)
+ 0x09, 0x32, // Usage (0x32)
+ 0x09, 0x44, // Usage (0x44)
+ 0x09, 0x42, // Usage (0x42)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x01, // Physical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x65, 0x00, // Unit (0)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x05, // Report Size (5)
+ 0x81, 0x03, // Input (Con|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xc0, // End Collection
+ 0x85, 0x02, // Report ID (0x2)
+ 0x09, 0x20, // Usage (0x20)
+ 0xa1, 0x00, // Collection (Physical)
+ 0xb4, // Pop
+ 0xa4, // Push
+ 0x09, 0x30, // Usage (0x30)
+ 0x09, 0x31, // Usage (0x31)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x05, 0x0d, // Usage Page (Digitizer)
+ 0x09, 0x32, // Usage (0x32)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x01, // Physical Maximum (1)
+ 0x65, 0x00, // Unit (0)
+ 0x75, 0x01, // Report Size (1)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x19, 0x00, // Usage Minimum (0)
+ 0x29, 0x10, // Usage Maximum (16)
+ 0x25, 0x10, // Logical Maximum (16)
+ 0x75, 0x05, // Report Size (5)
+ 0x81, 0x40, // Input (Dat|Var|Rel|NoWrp|Lin|Prf|Null|BitF)
+ 0x75, 0x02, // Report Size (2)
+ 0x81, 0x01, // Input (Con|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xc0, // End Collection
+ 0x85, 0x03, // Report ID (0x3)
+ 0x05, 0x0d, // Usage Page (Digitizer)
+ 0x09, 0x20, // Usage (0x20)
+ 0xa1, 0x00, // Collection (Physical)
+ 0xb4, // Pop
+ 0x09, 0x30, // Usage (0x30)
+ 0x09, 0x31, // Usage (0x31)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x05, 0x0d, // Usage Page (Digitizer)
+ 0x09, 0x32, // Usage (0x32)
+ 0x09, 0x44, // Usage (0x44)
+ 0x75, 0x01, // Report Size (1)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x01, // Physical Maximum (1)
+ 0x65, 0x00, // Unit (0)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x06, // Report Count (6)
+ 0x81, 0x03, // Input (Con|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x09, 0x30, // Usage (0x30)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x2d, // Physical Maximum (45)
+ 0x67, 0x11, 0xe1, // Unit (57617)
+ 0x00, 0x00, // Default
+ 0x55, 0x04, // Unit Exponent (4)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x12, // Input (Dat|Arr|Rel|NoWrp|NoLin|Prf|NoNull|BitF)
+ 0xc0, // End Collection
+ 0xc0 // End Collection
+};
-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)
+// Keyboard descriptor from HID descriptor tool
+// http://www.usb.org/developers/hidpage/dt2_4.zip
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};
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x06, // Usage (0x6)
+ 0xa1, 0x01, // Collection (Application)
+ 0x05, 0x07, // Usage Page (Keyboard)
+ 0x19, 0xe0, // Usage Minimum (224)
+ 0x29, 0xe7, // Usage Maximum (231)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x08, // Report Size (8)
+ 0x81, 0x03, // Input (Con|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x05, // Report Count (5)
+ 0x75, 0x01, // Report Size (1)
+ 0x05, 0x08, // Usage Page (Led)
+ 0x19, 0x01, // Usage Minimum (1)
+ 0x29, 0x05, // Usage Maximum (5)
+ 0x91, 0x02, // Output (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x03, // Report Size (3)
+ 0x91, 0x03, // Output (Con|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x06, // Report Count (6)
+ 0x75, 0x08, // Report Size (8)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x65, // Logical Maximum (101)
+ 0x05, 0x07, // Usage Page (Keyboard)
+ 0x19, 0x00, // Usage Minimum (0)
+ 0x29, 0x65, // Usage Maximum (101)
+ 0x81, 0x00, // Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xc0 // End Collection
+};
-// 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};
+// Monitor descriptor from HID descriptor tool
+// http://www.usb.org/developers/hidpage/dt2_4.zip
+const uint8_t kMonitor[] = {
+ 0x05, 0x80, // Usage Page (Monitor 0)
+ 0x09, 0x01, // Usage (0x1)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x01, // Report ID (0x1)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x80, // Report Count (128)
+ 0x09, 0x02, // Usage (0x2)
+ 0xb2, 0x02, 0x01, // Feature (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|Buff)
+ 0x85, 0x02, // Report ID (0x2)
+ 0x95, 0xf3, // Report Count (243)
+ 0x09, 0x03, // Usage (0x3)
+ 0xb2, 0x02, 0x01, // Feature (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|Buff)
+ 0x85, 0x03, // Report ID (0x3)
+ 0x05, 0x82, // Usage Page (Monitor 2)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x10, // Report Size (16)
+ 0x26, 0xc8, 0x00, // Logical Maximum (200)
+ 0x09, 0x10, // Usage (0x10)
+ 0xb1, 0x02, // Feature (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x85, 0x04, // Report ID (0x4)
+ 0x25, 0x64, // Logical Maximum (100)
+ 0x09, 0x12, // Usage (0x12)
+ 0xb1, 0x02, // Feature (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x06, // Report Count (6)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x09, 0x16, // Usage (0x16)
+ 0x09, 0x18, // Usage (0x18)
+ 0x09, 0x1a, // Usage (0x1A)
+ 0x09, 0x6c, // Usage (0x6C)
+ 0x09, 0x6e, // Usage (0x6E)
+ 0x09, 0x70, // Usage (0x70)
+ 0xb1, 0x02, // Feature (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x85, 0x05, // Report ID (0x5)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x09, 0x20, // Usage (0x20)
+ 0x09, 0x22, // Usage (0x22)
+ 0x09, 0x30, // Usage (0x30)
+ 0x09, 0x32, // Usage (0x32)
+ 0x09, 0x42, // Usage (0x42)
+ 0x09, 0x44, // Usage (0x44)
+ 0xb1, 0x02, // Feature (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xc0 // End Collection
+};
+// Mouse descriptor from HID descriptor tool
+// http://www.usb.org/developers/hidpage/dt2_4.zip
+const uint8_t kMouse[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x02, // Usage (0x2)
+ 0xa1, 0x01, // Collection (Application)
+ 0x09, 0x01, // Usage (0x1)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x19, 0x01, // Usage Minimum (1)
+ 0x29, 0x03, // Usage Maximum (3)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x75, 0x01, // Report Size (1)
+ 0x81, 0x02, // Input (Dat|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x05, // Report Size (5)
+ 0x81, 0x03, // Input (Con|Arr|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x30, // Usage (0x30)
+ 0x09, 0x31, // Usage (0x31)
+ 0x15, 0x81, // Logical Minimum (129)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x06, // Input (Dat|Arr|Abs|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xc0, // End Collection
+ 0xc0 // End Collection
+};
+
+// Logitech Unifying receiver descriptor
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};
+ 0x06, 0x00, 0xFF, // Usage Page (Vendor)
+ 0x09, 0x01, // Usage (0x1)
+ 0xA1, 0x01, // Collection (Application)
+ 0x85, 0x10, // Report ID (0x10)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x06, // Report Count (6)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x09, 0x01, // Usage (0x1)
+ 0x81, 0x00, // Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x09, 0x01, // Usage (0x1)
+ 0x91, 0x00, // Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xC0, // End Collection
+ 0x06, 0x00, 0xFF, // Usage Page (Vendor)
+ 0x09, 0x02, // Usage (0x2)
+ 0xA1, 0x01, // Collection (Application)
+ 0x85, 0x11, // Report ID (0x11)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x13, // Report Count (19)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x09, 0x02, // Usage (0x2)
+ 0x81, 0x00, // Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x09, 0x02, // Usage (0x2)
+ 0x91, 0x00, // Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xC0, // End Collection
+ 0x06, 0x00, 0xFF, // Usage Page (Vendor)
+ 0x09, 0x04, // Usage (0x4)
+ 0xA1, 0x01, // Collection (Application)
+ 0x85, 0x20, // Report ID (0x20)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x0E, // Report Count (14)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x09, 0x41, // Usage (0x41)
+ 0x81, 0x00, // Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x09, 0x41, // Usage (0x41)
+ 0x91, 0x00, // Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x85, 0x21, // Report ID (0x21)
+ 0x95, 0x1F, // Report Count (31)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x09, 0x42, // Usage (0x42)
+ 0x81, 0x00, // Input (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0x09, 0x42, // Usage (0x42)
+ 0x91, 0x00, // Output (Dat|Var|Rel|NoWrp|Lin|Prf|NoNull|BitF)
+ 0xC0 // End Collection
+};
} // namespace
@@ -415,199 +295,135 @@
}
public:
- void ParseDescriptor(const std::string& expected,
- const uint8_t* bytes,
- size_t size) {
+ void ValidateDetails(
+ const std::vector<HidCollectionInfo>& expected_collections,
+ const int expected_max_input_report_size,
+ const int expected_max_output_report_size,
+ const int expected_max_feature_report_size,
+ const uint8_t* bytes,
+ size_t size) {
descriptor_ = new HidReportDescriptor(bytes, size);
- std::stringstream actual;
- actual << *descriptor_;
+ std::vector<HidCollectionInfo> actual_collections;
+ int actual_max_input_report_size;
+ int actual_max_output_report_size;
+ int actual_max_feature_report_size;
+ descriptor_->GetDetails(&actual_collections,
+ &actual_max_input_report_size,
+ &actual_max_output_report_size,
+ &actual_max_feature_report_size);
- std::cout << "HID report descriptor:" << std::endl;
- std::cout << actual.str();
+ ASSERT_EQ(expected_collections.size(), actual_collections.size());
- // TODO(jracle@logitech.com): refactor string comparison in favor of
- // testing individual fields.
- ASSERT_EQ(expected, actual.str());
- }
+ std::vector<HidCollectionInfo>::const_iterator actual_collections_iter =
+ actual_collections.begin();
+ std::vector<HidCollectionInfo>::const_iterator expected_collections_iter =
+ expected_collections.begin();
- void GetTopLevelCollections(const std::vector<HidUsageAndPage>& expected,
- const uint8_t* bytes,
- size_t size) {
- descriptor_ = new HidReportDescriptor(bytes, size);
+ while (expected_collections_iter != expected_collections.end() &&
+ actual_collections_iter != actual_collections.end()) {
+ HidCollectionInfo expected_collection = *expected_collections_iter;
+ HidCollectionInfo actual_collection = *actual_collections_iter;
- std::vector<HidUsageAndPage> actual;
- descriptor_->GetTopLevelCollections(&actual);
+ ASSERT_EQ(expected_collection.usage.usage_page,
+ actual_collection.usage.usage_page);
+ ASSERT_EQ(expected_collection.usage.usage, actual_collection.usage.usage);
+ ASSERT_THAT(actual_collection.report_ids,
+ ContainerEq(expected_collection.report_ids));
- 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;
+ expected_collections_iter++;
+ actual_collections_iter++;
}
- ASSERT_THAT(actual, ContainerEq(expected));
+ ASSERT_EQ(expected_max_input_report_size, actual_max_input_report_size);
+ ASSERT_EQ(expected_max_output_report_size, actual_max_output_report_size);
+ ASSERT_EQ(expected_max_feature_report_size, actual_max_feature_report_size);
}
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, ValidateDetails_Digitizer) {
+ HidCollectionInfo digitizer;
+ digitizer.usage = HidUsageAndPage(0x01, HidUsageAndPage::kPageDigitizer);
+ digitizer.report_ids.insert(1);
+ digitizer.report_ids.insert(2);
+ digitizer.report_ids.insert(3);
+ HidCollectionInfo expected[] = {digitizer};
+ ValidateDetails(std::vector<HidCollectionInfo>(
+ expected, expected + ARRAYSIZE_UNSAFE(expected)),
+ 7,
+ 0,
+ 0,
+ kDigitizer,
+ sizeof(kDigitizer));
}
-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, ValidateDetails_Keyboard) {
+ HidCollectionInfo keyboard;
+ keyboard.usage = HidUsageAndPage(0x06, HidUsageAndPage::kPageGenericDesktop);
+ HidCollectionInfo expected[] = {keyboard};
+ ValidateDetails(std::vector<HidCollectionInfo>(
+ expected, expected + ARRAYSIZE_UNSAFE(expected)),
+ 8,
+ 1,
+ 0,
+ 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, ValidateDetails_Monitor) {
+ HidCollectionInfo monitor;
+ monitor.usage = HidUsageAndPage(0x01, HidUsageAndPage::kPageMonitor0);
+ monitor.report_ids.insert(1);
+ monitor.report_ids.insert(2);
+ monitor.report_ids.insert(3);
+ monitor.report_ids.insert(4);
+ monitor.report_ids.insert(5);
+ HidCollectionInfo expected[] = {monitor};
+ ValidateDetails(std::vector<HidCollectionInfo>(
+ expected, expected + ARRAYSIZE_UNSAFE(expected)),
+ 0,
+ 0,
+ 244,
+ kMonitor,
+ sizeof(kMonitor));
}
-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, ValidateDetails_Mouse) {
+ HidCollectionInfo mouse;
+ mouse.usage = HidUsageAndPage(0x02, HidUsageAndPage::kPageGenericDesktop);
+ HidCollectionInfo expected[] = {mouse};
+ ValidateDetails(std::vector<HidCollectionInfo>(
+ expected, expected + ARRAYSIZE_UNSAFE(expected)),
+ 3,
+ 0,
+ 0,
+ 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"};
+TEST_F(HidReportDescriptorTest, ValidateDetails_LogitechUnifyingReceiver) {
+ HidCollectionInfo hidpp_short;
+ hidpp_short.usage = HidUsageAndPage(0x01, HidUsageAndPage::kPageVendor);
+ hidpp_short.report_ids.insert(0x10);
+ HidCollectionInfo hidpp_long;
+ hidpp_long.usage = HidUsageAndPage(0x02, HidUsageAndPage::kPageVendor);
+ hidpp_long.report_ids.insert(0x11);
+ HidCollectionInfo hidpp_dj;
+ hidpp_dj.usage = HidUsageAndPage(0x04, HidUsageAndPage::kPageVendor);
+ hidpp_dj.report_ids.insert(0x20);
+ hidpp_dj.report_ids.insert(0x21);
- ParseDescriptor(std::string(expected),
+ HidCollectionInfo expected[] = {hidpp_short, hidpp_long, hidpp_dj};
+ ValidateDetails(std::vector<HidCollectionInfo>(
+ expected, expected + ARRAYSIZE_UNSAFE(expected)),
+ 32,
+ 32,
+ 0,
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/device/hid/hid_service_linux.cc b/device/hid/hid_service_linux.cc
index 5257dcd..4f1d8c7 100644
--- a/device/hid/hid_service_linux.cc
+++ b/device/hid/hid_service_linux.cc
@@ -2,17 +2,18 @@
// 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_linux.h"
+
#include <linux/hidraw.h>
#include <sys/ioctl.h>
-
#include <stdint.h>
#include <string>
#include "base/bind.h"
+#include "base/files/file.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"
@@ -20,14 +21,12 @@
#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";
@@ -53,11 +52,7 @@
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;
- }
+ std::string dev_node = udev_device_get_devnode(device.get());
return new HidConnectionLinux(device_info, dev_node);
}
@@ -77,7 +72,7 @@
if (!device_path)
return;
const char* subsystem = udev_device_get_subsystem(device);
- if (!subsystem || strcmp(subsystem, kHIDSubSystem) != 0)
+ if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0)
return;
HidDeviceInfo device_info;
@@ -86,7 +81,12 @@
uint32_t int_property = 0;
const char* str_property = NULL;
- const char* hid_id = udev_device_get_property_value(device, kHIDID);
+ udev_device *parent = udev_device_get_parent(device);
+ if (!parent) {
+ return;
+ }
+
+ const char* hid_id = udev_device_get_property_value(parent, kHIDID);
if (!hid_id)
return;
@@ -104,21 +104,16 @@
device_info.product_id = int_property;
}
- str_property = udev_device_get_property_value(device, kHIDUnique);
+ str_property = udev_device_get_property_value(parent, kHIDUnique);
if (str_property != NULL)
device_info.serial_number = str_property;
- str_property = udev_device_get_property_value(device, kHIDName);
+ str_property = udev_device_get_property_value(parent, 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;
+ const std::string dev_node = udev_device_get_devnode(device);
+ const int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
base::File device_file(base::FilePath(dev_node), flags);
if (!device_file.IsValid()) {
@@ -130,7 +125,7 @@
int desc_size = 0;
int res = ioctl(device_file.GetPlatformFile(), HIDIOCGRDESCSIZE, &desc_size);
if (res < 0) {
- LOG(ERROR) << "HIDIOCGRDESCSIZE failed.";
+ PLOG(ERROR) << "Failed to get report descriptor size";
device_file.Close();
return;
}
@@ -140,7 +135,7 @@
res = ioctl(device_file.GetPlatformFile(), HIDIOCGRDESC, &rpt_desc);
if (res < 0) {
- LOG(ERROR) << "HIDIOCGRDESC failed.";
+ PLOG(ERROR) << "Failed to get report descriptor";
device_file.Close();
return;
}
@@ -148,7 +143,10 @@
device_file.Close();
HidReportDescriptor report_descriptor(rpt_desc.value, rpt_desc.size);
- report_descriptor.GetTopLevelCollections(&device_info.usages);
+ report_descriptor.GetDetails(&device_info.collections,
+ &device_info.max_input_report_size,
+ &device_info.max_output_report_size,
+ &device_info.max_feature_report_size);
AddDevice(device_info);
}
@@ -159,44 +157,4 @@
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/device/hid/hid_service_linux.h b/device/hid/hid_service_linux.h
index 8d5b115..c69096d 100644
--- a/device/hid/hid_service_linux.h
+++ b/device/hid/hid_service_linux.h
@@ -32,8 +32,6 @@
private:
virtual ~HidServiceLinux();
- static bool FindHidrawDevNode(udev_device* parent, std::string* result);
-
DISALLOW_COPY_AND_ASSIGN(HidServiceLinux);
};
diff --git a/device/hid/hid_service_mac.cc b/device/hid/hid_service_mac.cc
index ed85ec2..98bfd92 100644
--- a/device/hid/hid_service_mac.cc
+++ b/device/hid/hid_service_mac.cc
@@ -7,17 +7,19 @@
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDManager.h>
+#include <set>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
+#include "base/mac/foundation_util.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/sys_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "device/hid/hid_connection_mac.h"
-#include "device/hid/hid_utils_mac.h"
namespace device {
@@ -48,6 +50,89 @@
CFSetApplyFunction(devices, HidEnumerationBackInserter, device_list);
}
+bool TryGetHidIntProperty(IOHIDDeviceRef device,
+ CFStringRef key,
+ int32_t* result) {
+ CFNumberRef ref =
+ base::mac::CFCast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
+ return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result);
+}
+
+int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) {
+ int32_t value;
+ if (TryGetHidIntProperty(device, key, &value))
+ return value;
+ return 0;
+}
+
+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;
+}
+
+std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) {
+ std::string value;
+ TryGetHidStringProperty(device, key, &value);
+ return value;
+}
+
+void GetReportIds(IOHIDElementRef element, std::set<int>& reportIDs) {
+ CFArrayRef children = IOHIDElementGetChildren(element);
+ if (!children)
+ return;
+ CFIndex childrenCount = CFArrayGetCount(children);
+ for (CFIndex j = 0; j < childrenCount; ++j) {
+ const IOHIDElementRef child = static_cast<IOHIDElementRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(children, j)));
+ uint32_t reportID = IOHIDElementGetReportID(child);
+ if (reportID) {
+ reportIDs.insert(reportID);
+ }
+ GetReportIds(child, reportIDs);
+ }
+}
+
+void GetCollectionInfos(IOHIDDeviceRef device,
+ std::vector<HidCollectionInfo>* top_level_collections) {
+ STLClearObject(top_level_collections);
+ CFMutableDictionaryRef collections_filter =
+ CFDictionaryCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ const int kCollectionTypeValue = kIOHIDElementTypeCollection;
+ CFNumberRef collection_type_id = CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberIntType, &kCollectionTypeValue);
+ CFDictionarySetValue(
+ collections_filter, CFSTR(kIOHIDElementTypeKey), collection_type_id);
+ CFRelease(collection_type_id);
+ CFArrayRef collections = IOHIDDeviceCopyMatchingElements(
+ device, collections_filter, kIOHIDOptionsTypeNone);
+ CFIndex collectionsCount = CFArrayGetCount(collections);
+ for (CFIndex i = 0; i < collectionsCount; i++) {
+ const IOHIDElementRef collection = static_cast<IOHIDElementRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(collections, i)));
+ // Top-Level Collection has no parent
+ if (IOHIDElementGetParent(collection) == 0) {
+ HidCollectionInfo collection_info;
+ HidUsageAndPage::Page page = static_cast<HidUsageAndPage::Page>(
+ IOHIDElementGetUsagePage(collection));
+ uint16_t usage = IOHIDElementGetUsage(collection);
+ collection_info.usage = HidUsageAndPage(usage, page);
+ // Explore children recursively and retrieve their report IDs
+ GetReportIds(collection, collection_info.report_ids);
+ top_level_collections->push_back(collection_info);
+ }
+ }
+}
+
} // namespace
HidServiceMac::HidServiceMac() {
@@ -136,41 +221,23 @@
// 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));
+ GetCollectionInfos(hid_device, &device_info.collections);
+ device_info.max_input_report_size =
+ GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey));
+ device_info.max_output_report_size =
+ GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey));
+ device_info.max_feature_report_size =
+ GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey));
AddDevice(device_info);
}
diff --git a/device/hid/hid_service_win.cc b/device/hid/hid_service_win.cc
index 82477a5..9f27cff 100644
--- a/device/hid/hid_service_win.cc
+++ b/device/hid/hid_service_win.cc
@@ -187,38 +187,50 @@
PHIDP_PREPARSED_DATA preparsed_data;
if (HidD_GetPreparsedData(device_handle.Get(), &preparsed_data) &&
preparsed_data) {
- HIDP_CAPS capabilities;
+ HIDP_CAPS capabilities = {0};
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]);
+ device_info.max_input_report_size = capabilities.InputReportByteLength;
+ device_info.max_output_report_size = capabilities.OutputReportByteLength;
+ device_info.max_feature_report_size =
+ capabilities.FeatureReportByteLength;
+ HidCollectionInfo collection_info;
+ collection_info.usage = HidUsageAndPage(
+ capabilities.Usage,
+ static_cast<HidUsageAndPage::Page>(capabilities.UsagePage));
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);
+ if (button_caps_length > 0) {
+ scoped_ptr<HIDP_BUTTON_CAPS[]> button_caps(
+ new HIDP_BUTTON_CAPS[button_caps_length]);
+ if (HidP_GetButtonCaps(HidP_Input,
+ &button_caps[0],
+ &button_caps_length,
+ preparsed_data) == HIDP_STATUS_SUCCESS) {
+ for (int i = 0; i < button_caps_length; i++) {
+ int report_id = button_caps[i].ReportID;
+ if (report_id != 0) {
+ collection_info.report_ids.insert(report_id);
+ }
+ }
+ }
}
+ USHORT value_caps_length = capabilities.NumberInputValueCaps;
+ if (value_caps_length > 0) {
+ scoped_ptr<HIDP_VALUE_CAPS[]> value_caps(
+ new HIDP_VALUE_CAPS[value_caps_length]);
+ if (HidP_GetValueCaps(HidP_Input,
+ &value_caps[0],
+ &value_caps_length,
+ preparsed_data) == HIDP_STATUS_SUCCESS) {
+ for (int i = 0; i < value_caps_length; i++) {
+ int report_id = value_caps[i].ReportID;
+ if (report_id != 0) {
+ collection_info.report_ids.insert(report_id);
+ }
+ }
+ }
+ }
+ device_info.collections.push_back(collection_info);
}
-
HidD_FreePreparsedData(preparsed_data);
}
diff --git a/device/hid/hid_usage_and_page.cc b/device/hid/hid_usage_and_page.cc
index 773346b..079ec66 100644
--- a/device/hid/hid_usage_and_page.cc
+++ b/device/hid/hid_usage_and_page.cc
@@ -6,8 +6,31 @@
namespace device {
-bool HidUsageAndPage::operator==(const HidUsageAndPage& other) const {
- return usage == other.usage && usage_page == other.usage_page;
+bool HidUsageAndPage::IsProtected() const {
+ if (usage_page == HidUsageAndPage::kPageKeyboard)
+ return true;
+
+ if (usage_page != HidUsageAndPage::kPageGenericDesktop)
+ return false;
+
+ if (usage == HidUsageAndPage::kGenericDesktopPointer ||
+ usage == HidUsageAndPage::kGenericDesktopMouse ||
+ usage == HidUsageAndPage::kGenericDesktopKeyboard ||
+ usage == HidUsageAndPage::kGenericDesktopKeypad) {
+ return true;
+ }
+
+ if (usage >= HidUsageAndPage::kGenericDesktopSystemControl &&
+ usage <= HidUsageAndPage::kGenericDesktopSystemWarmRestart) {
+ return true;
+ }
+
+ if (usage >= HidUsageAndPage::kGenericDesktopSystemDock &&
+ usage <= HidUsageAndPage::kGenericDesktopSystemDisplaySwap) {
+ return true;
+ }
+
+ return false;
}
} // namespace device
diff --git a/device/hid/hid_usage_and_page.h b/device/hid/hid_usage_and_page.h
index 98ac80d..635e9b3 100644
--- a/device/hid/hid_usage_and_page.h
+++ b/device/hid/hid_usage_and_page.h
@@ -126,7 +126,8 @@
uint16_t usage;
Page usage_page;
- bool operator==(const HidUsageAndPage& other) const;
+ // Indicates whether this usage is protected by Chrome.
+ bool IsProtected() const;
};
} // namespace device
diff --git a/device/hid/hid_utils_mac.cc b/device/hid/hid_utils_mac.cc
deleted file mode 100644
index 46605d8..0000000
--- a/device/hid/hid_utils_mac.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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/device/hid/hid_utils_mac.h b/device/hid/hid_utils_mac.h
deleted file mode 100644
index e9b2524..0000000
--- a/device/hid/hid_utils_mac.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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/device/media_transfer_protocol/BUILD.gn b/device/media_transfer_protocol/BUILD.gn
new file mode 100644
index 0000000..aea3c6b
--- /dev/null
+++ b/device/media_transfer_protocol/BUILD.gn
@@ -0,0 +1,35 @@
+# 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("//third_party/protobuf/proto_library.gni")
+
+# GYP version: device/media_transfer_protocol/media_transfer_protocol.gyp:mtp_file_entry_proto
+proto_library("mtp_file_entry_proto") {
+ sources = [ "//third_party/cros_system_api/dbus/mtp_file_entry.proto" ]
+ proto_out_dir = "device/media_transfer_protocol"
+}
+
+# GYP version: device/media_transfer_protocol/media_transfer_protocol.gyp:mtp_storage_info_proto
+proto_library("mtp_storage_info_proto") {
+ sources = [ "//third_party/cros_system_api/dbus/mtp_storage_info.proto" ]
+ proto_out_dir = "device/media_transfer_protocol"
+}
+
+# GYP version: device/media_transfer_protocol:media_transfer_protocol.gyp:media_transfer_protocol
+static_library("media_transfer_protocol") {
+ sources = [
+ "media_transfer_protocol_daemon_client.cc",
+ "media_transfer_protocol_daemon_client.h",
+ "media_transfer_protocol_manager.cc",
+ "media_transfer_protocol_manager.h",
+ ]
+
+ configs += [ "//build/config/linux:dbus" ]
+
+ deps = [
+ ":mtp_file_entry_proto",
+ ":mtp_storage_info_proto",
+ ]
+ forward_dependent_configs_from = deps
+}
diff --git a/device/media_transfer_protocol/media_transfer_protocol.gyp b/device/media_transfer_protocol/media_transfer_protocol.gyp
index 469a9e5..05c55b9 100644
--- a/device/media_transfer_protocol/media_transfer_protocol.gyp
+++ b/device/media_transfer_protocol/media_transfer_protocol.gyp
@@ -10,6 +10,7 @@
{
# Protobuf compiler / generator for the MtpFileEntry and
# MtpFileEntries protocol buffers.
+ # GN version: //device/media_transfer_protocol:mtp_file_entry_proto
'target_name': 'mtp_file_entry_proto',
'type': 'static_library',
'sources': [
@@ -24,6 +25,7 @@
{
# Protobuf compiler / generator for the MtpStorageInfo protocol
# buffer.
+ # GN version: //device/media_transfer_protocol:mtp_storage_info_proto
'target_name': 'mtp_storage_info_proto',
'type': 'static_library',
'sources': [
@@ -36,6 +38,7 @@
'includes': ['../../build/protoc.gypi'],
},
{
+ # GN version: //device/media_transfer_protocol
'target_name': 'device_media_transfer_protocol',
'type': 'static_library',
'dependencies': [
diff --git a/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc b/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc
index 4edfc60..062a37c 100644
--- a/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc
+++ b/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc
@@ -96,25 +96,6 @@
}
// MediaTransferProtocolDaemonClient override.
- virtual void ReadDirectoryByPath(
- const std::string& handle,
- const std::string& path,
- const ReadDirectoryCallback& callback,
- const ErrorCallback& error_callback) OVERRIDE {
- dbus::MethodCall method_call(mtpd::kMtpdInterface,
- mtpd::kReadDirectoryByPath);
- dbus::MessageWriter writer(&method_call);
- writer.AppendString(handle);
- writer.AppendString(path);
- proxy_->CallMethod(
- &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
- base::Bind(&MediaTransferProtocolDaemonClientImpl::OnReadDirectory,
- weak_ptr_factory_.GetWeakPtr(),
- callback,
- error_callback));
- }
-
- // MediaTransferProtocolDaemonClient override.
virtual void ReadDirectoryById(
const std::string& handle,
uint32 file_id,
@@ -134,30 +115,6 @@
}
// MediaTransferProtocolDaemonClient override.
- virtual void ReadFileChunkByPath(
- const std::string& handle,
- const std::string& path,
- uint32 offset,
- 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);
- writer.AppendString(handle);
- writer.AppendString(path);
- writer.AppendUint32(offset);
- writer.AppendUint32(bytes_to_read);
- proxy_->CallMethod(
- &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
- base::Bind(&MediaTransferProtocolDaemonClientImpl::OnReadFile,
- weak_ptr_factory_.GetWeakPtr(),
- callback,
- error_callback));
- }
-
- // MediaTransferProtocolDaemonClient override.
virtual void ReadFileChunkById(const std::string& handle,
uint32 file_id,
uint32 offset,
@@ -181,24 +138,6 @@
}
// MediaTransferProtocolDaemonClient override.
- virtual void GetFileInfoByPath(const std::string& handle,
- const std::string& path,
- const GetFileInfoCallback& callback,
- const ErrorCallback& error_callback) OVERRIDE {
- dbus::MethodCall method_call(mtpd::kMtpdInterface,
- mtpd::kGetFileInfoByPath);
- dbus::MessageWriter writer(&method_call);
- writer.AppendString(handle);
- writer.AppendString(path);
- proxy_->CallMethod(
- &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
- base::Bind(&MediaTransferProtocolDaemonClientImpl::OnGetFileInfo,
- weak_ptr_factory_.GetWeakPtr(),
- callback,
- error_callback));
- }
-
- // MediaTransferProtocolDaemonClient override.
virtual void GetFileInfoById(const std::string& handle,
uint32 file_id,
const GetFileInfoCallback& callback,
@@ -273,6 +212,7 @@
const GetStorageInfoCallback& callback,
const ErrorCallback& error_callback,
dbus::Response* response) {
+ LOG(ERROR) << "Client OnGetStorageInfo " << storage_name;
if (!response) {
error_callback.Run();
return;
@@ -318,7 +258,7 @@
callback.Run();
}
- // Handles the result of ReadDirectoryByPath/Id and calls |callback| or
+ // Handles the result of ReadDirectoryById and calls |callback| or
// |error_callback|.
void OnReadDirectory(const ReadDirectoryCallback& callback,
const ErrorCallback& error_callback,
@@ -342,7 +282,7 @@
callback.Run(file_entries);
}
- // Handles the result of ReadFileChunkByPath/Id and calls |callback| or
+ // Handles the result of ReadFileChunkById and calls |callback| or
// |error_callback|.
void OnReadFile(const ReadFileCallback& callback,
const ErrorCallback& error_callback,
@@ -363,7 +303,7 @@
callback.Run(data);
}
- // Handles the result of GetFileInfoByPath/Id and calls |callback| or
+ // Handles the result of GetFileInfoById and calls |callback| or
// |error_callback|.
void OnGetFileInfo(const GetFileInfoCallback& callback,
const ErrorCallback& error_callback,
diff --git a/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h b/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h
index 43a9dd1..90a1d29 100644
--- a/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h
+++ b/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h
@@ -54,16 +54,16 @@
// A callback to handle the result of CloseStorage.
typedef base::Closure CloseStorageCallback;
- // A callback to handle the result of ReadDirectoryByPath/Id.
+ // A callback to handle the result of ReadDirectoryById.
// The argument is a vector of file entries.
typedef base::Callback<void(const std::vector<MtpFileEntry>& file_entries)
> ReadDirectoryCallback;
- // A callback to handle the result of ReadFileChunkByPath/Id.
+ // A callback to handle the result of ReadFileChunkById.
// The argument is a string containing the file data.
typedef base::Callback<void(const std::string& data)> ReadFileCallback;
- // A callback to handle the result of GetFileInfoByPath/Id.
+ // A callback to handle the result of GetFileInfoById.
// The argument is a file entry.
typedef base::Callback<void(const MtpFileEntry& file_entry)
> GetFileInfoCallback;
@@ -104,13 +104,6 @@
const CloseStorageCallback& callback,
const ErrorCallback& error_callback) = 0;
- // Calls ReadDirectoryByPath method. |callback| is called after the method
- // call succeeds, otherwise, |error_callback| is called.
- virtual void ReadDirectoryByPath(const std::string& handle,
- const std::string& path,
- const ReadDirectoryCallback& callback,
- const ErrorCallback& error_callback) = 0;
-
// Calls ReadDirectoryById method. |callback| is called after the method
// call succeeds, otherwise, |error_callback| is called.
// |file_id| is a MTP-device specific id for a file.
@@ -119,18 +112,6 @@
const ReadDirectoryCallback& callback,
const ErrorCallback& error_callback) = 0;
- // Calls ReadFileChunkByPath method. |callback| is called after the method
- // call succeeds, otherwise, |error_callback| is called.
- // |bytes_to_read| cannot exceed 1 MiB.
- virtual void ReadFileChunkByPath(const std::string& handle,
- const std::string& path,
- uint32 offset,
- uint32 bytes_to_read,
- const ReadFileCallback& callback,
- const ErrorCallback& error_callback) = 0;
-
- // TODO(thestig) Remove this in the near future if we don't see anyone using
- // it.
// Calls ReadFilePathById method. |callback| is called after the method call
// succeeds, otherwise, |error_callback| is called.
// |file_id| is a MTP-device specific id for a file.
@@ -142,13 +123,6 @@
const ReadFileCallback& callback,
const ErrorCallback& error_callback) = 0;
- // Calls GetFileInfoByPath method. |callback| is called after the method
- // call succeeds, otherwise, |error_callback| is called.
- virtual void GetFileInfoByPath(const std::string& handle,
- const std::string& path,
- const GetFileInfoCallback& callback,
- const ErrorCallback& error_callback) = 0;
-
// Calls GetFileInfoById method. |callback| is called after the method
// call succeeds, otherwise, |error_callback| is called.
// |file_id| is a MTP-device specific id for a file.
diff --git a/device/media_transfer_protocol/media_transfer_protocol_manager.cc b/device/media_transfer_protocol/media_transfer_protocol_manager.cc
index 3ba7934..968d936 100644
--- a/device/media_transfer_protocol/media_transfer_protocol_manager.cc
+++ b/device/media_transfer_protocol/media_transfer_protocol_manager.cc
@@ -149,26 +149,6 @@
}
// MediaTransferProtocolManager override.
- virtual void ReadDirectoryByPath(
- const std::string& storage_handle,
- const std::string& path,
- const ReadDirectoryCallback& callback) OVERRIDE {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
- callback.Run(std::vector<MtpFileEntry>(), true);
- return;
- }
- read_directory_callbacks_.push(callback);
- mtp_client_->ReadDirectoryByPath(
- storage_handle,
- path,
- base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectory,
- weak_ptr_factory_.GetWeakPtr()),
- base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectoryError,
- weak_ptr_factory_.GetWeakPtr()));
- }
-
- // MediaTransferProtocolManager override.
virtual void ReadDirectoryById(
const std::string& storage_handle,
uint32 file_id,
@@ -189,26 +169,6 @@
}
// MediaTransferProtocolManager override.
- virtual void ReadFileChunkByPath(const std::string& storage_handle,
- const std::string& path,
- uint32 offset,
- uint32 count,
- const ReadFileCallback& callback) OVERRIDE {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
- callback.Run(std::string(), true);
- return;
- }
- read_file_callbacks_.push(callback);
- mtp_client_->ReadFileChunkByPath(
- storage_handle, path, offset, count,
- base::Bind(&MediaTransferProtocolManagerImpl::OnReadFile,
- weak_ptr_factory_.GetWeakPtr()),
- base::Bind(&MediaTransferProtocolManagerImpl::OnReadFileError,
- weak_ptr_factory_.GetWeakPtr()));
- }
-
- // MediaTransferProtocolManager override.
virtual void ReadFileChunkById(const std::string& storage_handle,
uint32 file_id,
uint32 offset,
@@ -228,24 +188,6 @@
weak_ptr_factory_.GetWeakPtr()));
}
- virtual void GetFileInfoByPath(const std::string& storage_handle,
- const std::string& path,
- const GetFileInfoCallback& callback) OVERRIDE {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (!ContainsKey(handles_, storage_handle) || !mtp_client_) {
- callback.Run(MtpFileEntry(), true);
- return;
- }
- get_file_info_callbacks_.push(callback);
- mtp_client_->GetFileInfoByPath(
- storage_handle,
- path,
- base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfo,
- weak_ptr_factory_.GetWeakPtr()),
- base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfoError,
- weak_ptr_factory_.GetWeakPtr()));
- }
-
virtual void GetFileInfoById(const std::string& storage_handle,
uint32 file_id,
const GetFileInfoCallback& callback) OVERRIDE {
diff --git a/device/media_transfer_protocol/media_transfer_protocol_manager.h b/device/media_transfer_protocol/media_transfer_protocol_manager.h
index 29d5fd5..b3a80c8 100644
--- a/device/media_transfer_protocol/media_transfer_protocol_manager.h
+++ b/device/media_transfer_protocol/media_transfer_protocol_manager.h
@@ -39,19 +39,19 @@
// The argument is true if there was an error.
typedef base::Callback<void(bool error)> CloseStorageCallback;
- // A callback to handle the result of ReadDirectoryByPath/Id.
+ // A callback to handle the result of ReadDirectoryById.
// The first argument is a vector of file entries.
// The second argument is true if there was an error.
typedef base::Callback<void(const std::vector<MtpFileEntry>& file_entries,
bool error)> ReadDirectoryCallback;
- // A callback to handle the result of ReadFileChunkByPath/Id.
+ // A callback to handle the result of ReadFileChunkById.
// The first argument is a string containing the file data.
// The second argument is true if there was an error.
typedef base::Callback<void(const std::string& data,
bool error)> ReadFileCallback;
- // A callback to handle the result of GetFileInfoByPath/Id.
+ // A callback to handle the result of GetFileInfoById.
// The first argument is a file entry.
// The second argument is true if there was an error.
typedef base::Callback<void(const MtpFileEntry& file_entry,
@@ -93,26 +93,12 @@
virtual void CloseStorage(const std::string& storage_handle,
const CloseStorageCallback& callback) = 0;
- // Reads directory entries from |path| on |storage_handle| and runs
- // |callback|.
- virtual void ReadDirectoryByPath(const std::string& storage_handle,
- const std::string& path,
- const ReadDirectoryCallback& callback) = 0;
-
// Reads directory entries from |file_id| on |storage_handle| and runs
// |callback|.
virtual void ReadDirectoryById(const std::string& storage_handle,
uint32 file_id,
const ReadDirectoryCallback& callback) = 0;
- // Reads file data from |path| on |storage_handle| and runs |callback|.
- // Reads |count| bytes of data starting at |offset|.
- virtual void ReadFileChunkByPath(const std::string& storage_handle,
- const std::string& path,
- uint32 offset,
- uint32 count,
- const ReadFileCallback& callback) = 0;
-
// Reads file data from |file_id| on |storage_handle| and runs |callback|.
// Reads |count| bytes of data starting at |offset|.
virtual void ReadFileChunkById(const std::string& storage_handle,
@@ -121,11 +107,6 @@
uint32 count,
const ReadFileCallback& callback) = 0;
- // Gets the file metadata for |path| on |storage_handle| and runs |callback|.
- virtual void GetFileInfoByPath(const std::string& storage_handle,
- const std::string& path,
- const GetFileInfoCallback& callback) = 0;
-
// Gets the file metadata for |file_id| on |storage_handle| and runs
// |callback|.
virtual void GetFileInfoById(const std::string& storage_handle,
diff --git a/device/nfc/BUILD.gn b/device/nfc/BUILD.gn
new file mode 100644
index 0000000..999853d
--- /dev/null
+++ b/device/nfc/BUILD.gn
@@ -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.
+
+static_library("nfc") {
+ sources = [
+ "nfc_adapter.cc",
+ "nfc_adapter.h",
+ "nfc_adapter_chromeos.cc",
+ "nfc_adapter_chromeos.h",
+ "nfc_adapter_factory.cc",
+ "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_chromeos.cc",
+ "nfc_tag_technology_chromeos.h"
+ ]
+
+ deps = [
+ "//base",
+ "//url",
+ ]
+
+ if (is_chromeos) {
+ deps += [
+ "//dbus",
+ #'../../chromeos/chromeos.gyp:chromeos', TODO(GYP)
+ ]
+ }
+}
diff --git a/device/nfc/nfc.gyp b/device/nfc/nfc.gyp
index 11e4308..ccd62b5 100644
--- a/device/nfc/nfc.gyp
+++ b/device/nfc/nfc.gyp
@@ -8,6 +8,7 @@
},
'targets': [
{
+ # GN version: //device/nfc
'target_name': 'device_nfc',
'type': 'static_library',
'dependencies': [
@@ -15,6 +16,7 @@
'../../url/url.gyp:url_lib',
],
'sources': [
+ # Note: file list duplicated in GN build.
'nfc_adapter.cc',
'nfc_adapter.h',
'nfc_adapter_chromeos.cc',
diff --git a/device/serial/DEPS b/device/serial/DEPS
new file mode 100644
index 0000000..6a2f02e
--- /dev/null
+++ b/device/serial/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+net/base",
+]
diff --git a/device/serial/serial.gyp b/device/serial/serial.gyp
index 0df7005..5c0e386 100644
--- a/device/serial/serial.gyp
+++ b/device/serial/serial.gyp
@@ -27,6 +27,13 @@
'includes': [
'../../mojo/public/tools/bindings/mojom_bindings_generator.gypi',
],
+ 'dependencies': [
+ '../../mojo/mojo.gyp:mojo_cpp_bindings',
+ '../../net/net.gyp:net',
+ ],
+ 'export_dependent_settings': [
+ '../../mojo/mojo.gyp:mojo_cpp_bindings',
+ ],
'sources': [
'serial.mojom',
'serial_device_enumerator.cc',
@@ -37,6 +44,14 @@
'serial_device_enumerator_mac.h',
'serial_device_enumerator_win.cc',
'serial_device_enumerator_win.h',
+ 'serial_io_handler.cc',
+ 'serial_io_handler.h',
+ 'serial_io_handler_posix.cc',
+ 'serial_io_handler_posix.h',
+ 'serial_io_handler_win.cc',
+ 'serial_io_handler_win.h',
+ 'serial_service_impl.cc',
+ 'serial_service_impl.h',
],
},
],
diff --git a/device/serial/serial.mojom b/device/serial/serial.mojom
index f925dff..0c4f100 100644
--- a/device/serial/serial.mojom
+++ b/device/serial/serial.mojom
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-module device {
+module device.serial {
-struct SerialDeviceInfo {
+struct DeviceInfo {
string path;
uint16 vendor_id;
bool has_vendor_id = false;
@@ -13,4 +13,74 @@
string display_name;
};
+enum SendError {
+ NONE,
+ DISCONNECTED,
+ PENDING,
+ TIMEOUT,
+ SYSTEM_ERROR,
+};
+
+enum ReceiveError {
+ NONE,
+ DISCONNECTED,
+ TIMEOUT,
+ DEVICE_LOST,
+ SYSTEM_ERROR,
+};
+
+enum DataBits {
+ NONE,
+ SEVEN,
+ EIGHT,
+};
+
+enum ParityBit {
+ NONE,
+ NO,
+ ODD,
+ EVEN,
+};
+
+enum StopBits {
+ NONE,
+ ONE,
+ TWO,
+};
+
+struct ConnectionOptions {
+ uint32 bitrate = 0;
+ DataBits data_bits = NONE;
+ ParityBit parity_bit = NONE;
+ StopBits stop_bits = NONE;
+ bool cts_flow_control;
+ bool has_cts_flow_control = false;
+};
+
+struct ConnectionInfo {
+ uint32 bitrate = 0;
+ DataBits data_bits = NONE;
+ ParityBit parity_bit = NONE;
+ StopBits stop_bits = NONE;
+ bool cts_flow_control;
+};
+
+struct HostControlSignals {
+ bool dtr;
+ bool has_dtr = false;
+ bool rts;
+ bool has_rts = false;
+};
+
+struct DeviceControlSignals {
+ bool dcd;
+ bool cts;
+ bool ri;
+ bool dsr;
+};
+
+interface SerialService {
+ GetDevices() => (DeviceInfo[] devices);
+};
+
}
diff --git a/device/serial/serial_device_enumerator.h b/device/serial/serial_device_enumerator.h
index c1245c7..77e2ae5 100644
--- a/device/serial/serial_device_enumerator.h
+++ b/device/serial/serial_device_enumerator.h
@@ -19,7 +19,7 @@
SerialDeviceEnumerator();
virtual ~SerialDeviceEnumerator();
- virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() = 0;
+ virtual mojo::Array<serial::DeviceInfoPtr> GetDevices() = 0;
};
} // namespace device
diff --git a/device/serial/serial_device_enumerator_linux.cc b/device/serial/serial_device_enumerator_linux.cc
index 269c7ef..f2fb9fa 100644
--- a/device/serial/serial_device_enumerator_linux.cc
+++ b/device/serial/serial_device_enumerator_linux.cc
@@ -44,8 +44,8 @@
SerialDeviceEnumeratorLinux::~SerialDeviceEnumeratorLinux() {}
-mojo::Array<SerialDeviceInfoPtr> SerialDeviceEnumeratorLinux::GetDevices() {
- mojo::Array<SerialDeviceInfoPtr> devices;
+mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorLinux::GetDevices() {
+ mojo::Array<serial::DeviceInfoPtr> devices(0);
ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get()));
if (!enumerate) {
LOG(ERROR) << "Serial device enumeration failed.";
@@ -73,7 +73,7 @@
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());
+ serial::DeviceInfoPtr info(serial::DeviceInfo::New());
info->path = path;
const char* vendor_id =
diff --git a/device/serial/serial_device_enumerator_linux.h b/device/serial/serial_device_enumerator_linux.h
index 0b34fe8..193475d 100644
--- a/device/serial/serial_device_enumerator_linux.h
+++ b/device/serial/serial_device_enumerator_linux.h
@@ -19,7 +19,7 @@
virtual ~SerialDeviceEnumeratorLinux();
// Implementation for SerialDeviceEnumerator.
- virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() OVERRIDE;
+ virtual mojo::Array<serial::DeviceInfoPtr> GetDevices() OVERRIDE;
private:
struct UdevDeleter {
diff --git a/device/serial/serial_device_enumerator_mac.cc b/device/serial/serial_device_enumerator_mac.cc
index b29352f..3222daa 100644
--- a/device/serial/serial_device_enumerator_mac.cc
+++ b/device/serial/serial_device_enumerator_mac.cc
@@ -22,7 +22,7 @@
SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {}
// TODO(rockot): Use IOKit to enumerate serial interfaces.
-mojo::Array<SerialDeviceInfoPtr> SerialDeviceEnumeratorMac::GetDevices() {
+mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorMac::GetDevices() {
const base::FilePath kDevRoot("/dev");
const int kFilesAndSymLinks =
base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS;
@@ -36,7 +36,7 @@
valid_patterns.insert("/dev/tty.*");
valid_patterns.insert("/dev/cu.*");
- mojo::Array<SerialDeviceInfoPtr> devices;
+ mojo::Array<serial::DeviceInfoPtr> devices(0);
base::FileEnumerator enumerator(kDevRoot, false, kFilesAndSymLinks);
do {
const base::FilePath next_device_path(enumerator.Next());
@@ -47,7 +47,7 @@
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());
+ serial::DeviceInfoPtr info(serial::DeviceInfo::New());
info->path = next_device;
devices.push_back(info.Pass());
break;
diff --git a/device/serial/serial_device_enumerator_mac.h b/device/serial/serial_device_enumerator_mac.h
index d1c6db3..cbbdb5f 100644
--- a/device/serial/serial_device_enumerator_mac.h
+++ b/device/serial/serial_device_enumerator_mac.h
@@ -16,7 +16,7 @@
virtual ~SerialDeviceEnumeratorMac();
// Implementation for SerialDeviceEnumerator.
- virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() OVERRIDE;
+ virtual mojo::Array<serial::DeviceInfoPtr> GetDevices() OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorMac);
diff --git a/device/serial/serial_device_enumerator_win.cc b/device/serial/serial_device_enumerator_win.cc
index c00d4d4..124c6b7 100644
--- a/device/serial/serial_device_enumerator_win.cc
+++ b/device/serial/serial_device_enumerator_win.cc
@@ -26,12 +26,12 @@
// 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() {
+mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorWin::GetDevices() {
base::win::RegistryValueIterator iter_key(
HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM\\");
- mojo::Array<SerialDeviceInfoPtr> devices;
+ mojo::Array<serial::DeviceInfoPtr> devices(0);
for (; iter_key.Valid(); ++iter_key) {
- SerialDeviceInfoPtr info(SerialDeviceInfo::New());
+ serial::DeviceInfoPtr info(serial::DeviceInfo::New());
info->path = base::UTF16ToASCII(iter_key.Value());
devices.push_back(info.Pass());
}
diff --git a/device/serial/serial_device_enumerator_win.h b/device/serial/serial_device_enumerator_win.h
index 082b8f9..4a5ac4d 100644
--- a/device/serial/serial_device_enumerator_win.h
+++ b/device/serial/serial_device_enumerator_win.h
@@ -16,7 +16,7 @@
virtual ~SerialDeviceEnumeratorWin();
// Implementation for SerialDeviceEnumerator.
- virtual mojo::Array<SerialDeviceInfoPtr> GetDevices() OVERRIDE;
+ virtual mojo::Array<serial::DeviceInfoPtr> GetDevices() OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorWin);
diff --git a/device/serial/serial_io_handler.cc b/device/serial/serial_io_handler.cc
new file mode 100644
index 0000000..df2fee4
--- /dev/null
+++ b/device/serial/serial_io_handler.cc
@@ -0,0 +1,189 @@
+// 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_io_handler.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+
+namespace device {
+
+SerialIoHandler::SerialIoHandler()
+ : pending_read_buffer_len_(0), pending_write_buffer_len_(0) {
+}
+
+SerialIoHandler::~SerialIoHandler() {
+ DCHECK(CalledOnValidThread());
+ Close();
+}
+
+void SerialIoHandler::Initialize(
+ const ReadCompleteCallback& read_callback,
+ const WriteCompleteCallback& write_callback,
+ scoped_refptr<base::MessageLoopProxy> file_thread_message_loop) {
+ DCHECK(CalledOnValidThread());
+ read_complete_ = read_callback;
+ write_complete_ = write_callback;
+ file_thread_message_loop_ = file_thread_message_loop;
+}
+
+void SerialIoHandler::Open(const std::string& port,
+ const OpenCompleteCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(open_complete_.is_null());
+ open_complete_ = callback;
+ DCHECK(file_thread_message_loop_);
+ file_thread_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&SerialIoHandler::StartOpen,
+ this,
+ port,
+ base::MessageLoopProxy::current()));
+}
+
+void SerialIoHandler::StartOpen(
+ const std::string& port,
+ scoped_refptr<base::MessageLoopProxy> io_message_loop) {
+ DCHECK(!open_complete_.is_null());
+ DCHECK(file_thread_message_loop_->RunsTasksOnCurrentThread());
+ DCHECK(!file_.IsValid());
+ // It's the responsibility of the API wrapper around SerialIoHandler to
+ // validate the supplied path against the set of valid port names, and
+ // it is a reasonable assumption that serial port names are ASCII.
+ DCHECK(base::IsStringASCII(port));
+ base::FilePath path(base::FilePath::FromUTF8Unsafe(MaybeFixUpPortName(port)));
+ int flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
+ base::File::FLAG_EXCLUSIVE_READ | base::File::FLAG_WRITE |
+ base::File::FLAG_EXCLUSIVE_WRITE | base::File::FLAG_ASYNC |
+ base::File::FLAG_TERMINAL_DEVICE;
+ base::File file(path, flags);
+ io_message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&SerialIoHandler::FinishOpen, this, Passed(file.Pass())));
+}
+
+void SerialIoHandler::FinishOpen(base::File file) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!open_complete_.is_null());
+ OpenCompleteCallback callback = open_complete_;
+ open_complete_.Reset();
+
+ if (!file.IsValid()) {
+ callback.Run(false);
+ return;
+ }
+
+ file_ = file.Pass();
+
+ bool success = PostOpen();
+ if (!success)
+ Close();
+ callback.Run(success);
+}
+
+bool SerialIoHandler::PostOpen() {
+ return true;
+}
+
+void SerialIoHandler::Close() {
+ if (file_.IsValid()) {
+ DCHECK(file_thread_message_loop_);
+ file_thread_message_loop_->PostTask(
+ FROM_HERE, base::Bind(&SerialIoHandler::DoClose, Passed(file_.Pass())));
+ }
+}
+
+// static
+void SerialIoHandler::DoClose(base::File port) {
+ // port closed by destructor.
+}
+
+void SerialIoHandler::Read(int max_bytes) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!IsReadPending());
+ pending_read_buffer_ = new net::IOBuffer(max_bytes);
+ pending_read_buffer_len_ = max_bytes;
+ read_canceled_ = false;
+ AddRef();
+ ReadImpl();
+}
+
+void SerialIoHandler::Write(const std::string& data) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!IsWritePending());
+ int length = static_cast<int>(data.length());
+ pending_write_buffer_ = new net::IOBuffer(length);
+ pending_write_buffer_len_ = length;
+ memcpy(pending_write_buffer_->data(), data.data(), pending_write_buffer_len_);
+ write_canceled_ = false;
+ AddRef();
+ WriteImpl();
+}
+
+void SerialIoHandler::ReadCompleted(int bytes_read,
+ serial::ReceiveError error) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(IsReadPending());
+ read_complete_.Run(std::string(pending_read_buffer_->data(), bytes_read),
+ error);
+ pending_read_buffer_ = NULL;
+ pending_read_buffer_len_ = 0;
+ Release();
+}
+
+void SerialIoHandler::WriteCompleted(int bytes_written,
+ serial::SendError error) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(IsWritePending());
+ write_complete_.Run(bytes_written, error);
+ pending_write_buffer_ = NULL;
+ pending_write_buffer_len_ = 0;
+ Release();
+}
+
+bool SerialIoHandler::IsReadPending() const {
+ DCHECK(CalledOnValidThread());
+ return pending_read_buffer_ != NULL;
+}
+
+bool SerialIoHandler::IsWritePending() const {
+ DCHECK(CalledOnValidThread());
+ return pending_write_buffer_ != NULL;
+}
+
+void SerialIoHandler::CancelRead(serial::ReceiveError reason) {
+ DCHECK(CalledOnValidThread());
+ if (IsReadPending()) {
+ read_canceled_ = true;
+ read_cancel_reason_ = reason;
+ CancelReadImpl();
+ }
+}
+
+void SerialIoHandler::CancelWrite(serial::SendError reason) {
+ DCHECK(CalledOnValidThread());
+ if (IsWritePending()) {
+ write_canceled_ = true;
+ write_cancel_reason_ = reason;
+ CancelWriteImpl();
+ }
+}
+
+void SerialIoHandler::QueueReadCompleted(int bytes_read,
+ serial::ReceiveError error) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SerialIoHandler::ReadCompleted, this, bytes_read, error));
+}
+
+void SerialIoHandler::QueueWriteCompleted(int bytes_written,
+ serial::SendError error) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SerialIoHandler::WriteCompleted, this, bytes_written, error));
+}
+
+} // namespace device
diff --git a/device/serial/serial_io_handler.h b/device/serial/serial_io_handler.h
new file mode 100644
index 0000000..a7cad74
--- /dev/null
+++ b/device/serial/serial_io_handler.h
@@ -0,0 +1,212 @@
+// 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_IO_HANDLER_H_
+#define DEVICE_SERIAL_SERIAL_IO_HANDLER_H_
+
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/non_thread_safe.h"
+#include "device/serial/serial.mojom.h"
+#include "net/base/io_buffer.h"
+
+namespace device {
+
+// Provides a simplified interface for performing asynchronous I/O on serial
+// devices by hiding platform-specific MessageLoop interfaces. Pending I/O
+// operations hold a reference to this object until completion so that memory
+// doesn't disappear out from under the OS.
+class SerialIoHandler : public base::NonThreadSafe,
+ public base::RefCounted<SerialIoHandler> {
+ public:
+ // Constructs an instance of some platform-specific subclass.
+ static scoped_refptr<SerialIoHandler> Create();
+
+ typedef base::Callback<void(bool success)> OpenCompleteCallback;
+
+ // Called with a string of bytes read, and a result code. Note that an error
+ // result does not necessarily imply 0 bytes read.
+ typedef base::Callback<void(const std::string& data,
+ serial::ReceiveError error)> ReadCompleteCallback;
+
+ // Called with the number of bytes written and a result code. Note that an
+ // error result does not necessarily imply 0 bytes written.
+ typedef base::Callback<void(int bytes_written, serial::SendError error)>
+ WriteCompleteCallback;
+
+ // Initializes the handler on the current message loop. Must be called exactly
+ // once before performing any I/O through the handler.
+ virtual void Initialize(
+ const ReadCompleteCallback& read_callback,
+ const WriteCompleteCallback& write_callback,
+ scoped_refptr<base::MessageLoopProxy> file_thread_message_loop);
+
+ // Initiates an asynchronous Open of the device.
+ virtual void Open(const std::string& port,
+ const OpenCompleteCallback& callback);
+
+ // Performs an async Read operation. Behavior is undefined if this is called
+ // while a Read is already pending. Otherwise, the ReadCompleteCallback
+ // (see above) will eventually be called with a result.
+ void Read(int max_bytes);
+
+ // Performs an async Write operation. Behavior is undefined if this is called
+ // while a Write is already pending. Otherwise, the WriteCompleteCallback
+ // (see above) will eventually be called with a result.
+ void Write(const std::string& data);
+
+ // Indicates whether or not a read is currently pending.
+ bool IsReadPending() const;
+
+ // Indicates whether or not a write is currently pending.
+ bool IsWritePending() const;
+
+ // Attempts to cancel a pending read operation.
+ void CancelRead(serial::ReceiveError reason);
+
+ // Attempts to cancel a pending write operation.
+ void CancelWrite(serial::SendError reason);
+
+ // Flushes input and output buffers.
+ virtual bool Flush() const = 0;
+
+ // Reads current control signals (DCD, CTS, etc.) into an existing
+ // DeviceControlSignals structure. Returns |true| iff the signals were
+ // successfully read.
+ virtual serial::DeviceControlSignalsPtr GetControlSignals() const = 0;
+
+ // Sets one or more control signals (DTR and/or RTS). Returns |true| iff
+ // the signals were successfully set. Unininitialized flags in the
+ // HostControlSignals structure are left unchanged.
+ virtual bool SetControlSignals(
+ const serial::HostControlSignals& control_signals) = 0;
+
+ // Performs platform-specific port configuration. Returns |true| iff
+ // configuration was successful.
+ virtual bool ConfigurePort(const serial::ConnectionOptions& options) = 0;
+
+ // Performs a platform-specific port configuration query. Fills values in an
+ // existing ConnectionInfo. Returns |true| iff port configuration was
+ // successfully retrieved.
+ virtual serial::ConnectionInfoPtr GetPortInfo() const = 0;
+
+ protected:
+ SerialIoHandler();
+ virtual ~SerialIoHandler();
+
+ // Performs a platform-specific read operation. This must guarantee that
+ // ReadCompleted is called when the underlying async operation is completed
+ // or the SerialIoHandler instance will leak.
+ // NOTE: Implementations of ReadImpl should never call ReadCompleted directly.
+ // Use QueueReadCompleted instead to avoid reentrancy.
+ virtual void ReadImpl() = 0;
+
+ // Performs a platform-specific write operation. This must guarantee that
+ // WriteCompleted is called when the underlying async operation is completed
+ // or the SerialIoHandler instance will leak.
+ // NOTE: Implementations of Writempl should never call WriteCompleted
+ // directly. Use QueueWriteCompleted instead to avoid reentrancy.
+ virtual void WriteImpl() = 0;
+
+ // Platform-specific read cancelation.
+ virtual void CancelReadImpl() = 0;
+
+ // Platform-specific write cancelation.
+ virtual void CancelWriteImpl() = 0;
+
+ // Performs platform-specific, one-time port configuration on open.
+ virtual bool PostOpen();
+
+ // Called by the implementation to signal that the active read has completed.
+ // WARNING: Calling this method can destroy the SerialIoHandler instance
+ // if the associated I/O operation was the only thing keeping it alive.
+ void ReadCompleted(int bytes_read, serial::ReceiveError error);
+
+ // Called by the implementation to signal that the active write has completed.
+ // WARNING: Calling this method may destroy the SerialIoHandler instance
+ // if the associated I/O operation was the only thing keeping it alive.
+ void WriteCompleted(int bytes_written, serial::SendError error);
+
+ // Queues a ReadCompleted call on the current thread. This is used to allow
+ // ReadImpl to immediately signal completion with 0 bytes and an error,
+ // without being reentrant.
+ void QueueReadCompleted(int bytes_read, serial::ReceiveError error);
+
+ // Queues a WriteCompleted call on the current thread. This is used to allow
+ // WriteImpl to immediately signal completion with 0 bytes and an error,
+ // without being reentrant.
+ void QueueWriteCompleted(int bytes_written, serial::SendError error);
+
+ const base::File& file() const { return file_; }
+
+ net::IOBuffer* pending_read_buffer() const {
+ return pending_read_buffer_.get();
+ }
+
+ int pending_read_buffer_len() const { return pending_read_buffer_len_; }
+
+ serial::ReceiveError read_cancel_reason() const {
+ return read_cancel_reason_;
+ }
+
+ bool read_canceled() const { return read_canceled_; }
+
+ net::IOBuffer* pending_write_buffer() const {
+ return pending_write_buffer_.get();
+ }
+
+ int pending_write_buffer_len() const { return pending_write_buffer_len_; }
+
+ serial::SendError write_cancel_reason() const { return write_cancel_reason_; }
+
+ bool write_canceled() const { return write_canceled_; }
+
+ // Possibly fixes up a serial port path name in a platform-specific manner.
+ static std::string MaybeFixUpPortName(const std::string& port_name);
+
+ private:
+ friend class base::RefCounted<SerialIoHandler>;
+
+ // Continues an Open operation on the FILE thread.
+ void StartOpen(const std::string& port,
+ scoped_refptr<base::MessageLoopProxy> io_message_loop);
+
+ // Finalizes an Open operation (continued from StartOpen) on the IO thread.
+ void FinishOpen(base::File file);
+
+ void Close();
+
+ // Continues a Close operation on the FILE thread.
+ static void DoClose(base::File port);
+
+ // File for the opened serial device. This value is only modified from the IO
+ // thread.
+ base::File file_;
+
+ scoped_refptr<net::IOBuffer> pending_read_buffer_;
+ int pending_read_buffer_len_;
+ serial::ReceiveError read_cancel_reason_;
+ bool read_canceled_;
+
+ scoped_refptr<net::IOBuffer> pending_write_buffer_;
+ int pending_write_buffer_len_;
+ serial::SendError write_cancel_reason_;
+ bool write_canceled_;
+
+ ReadCompleteCallback read_complete_;
+ WriteCompleteCallback write_complete_;
+
+ // Callback to handle the completion of a pending Open() request.
+ OpenCompleteCallback open_complete_;
+
+ scoped_refptr<base::MessageLoopProxy> file_thread_message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerialIoHandler);
+};
+
+} // namespace device
+
+#endif // DEVICE_SERIAL_SERIAL_IO_HANDLER_H_
diff --git a/device/serial/serial_io_handler_posix.cc b/device/serial/serial_io_handler_posix.cc
new file mode 100644
index 0000000..806e49f
--- /dev/null
+++ b/device/serial/serial_io_handler_posix.cc
@@ -0,0 +1,408 @@
+// 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_io_handler_posix.h"
+
+#include <sys/ioctl.h>
+#include <termios.h>
+
+#include "base/posix/eintr_wrapper.h"
+
+#if defined(OS_LINUX)
+#include <linux/serial.h>
+#endif
+
+namespace {
+
+// Convert an integral bit rate to a nominal one. Returns |true|
+// if the conversion was successful and |false| otherwise.
+bool BitrateToSpeedConstant(int bitrate, speed_t* speed) {
+#define BITRATE_TO_SPEED_CASE(x) \
+ case x: \
+ *speed = B##x; \
+ return true;
+ switch (bitrate) {
+ BITRATE_TO_SPEED_CASE(0)
+ BITRATE_TO_SPEED_CASE(50)
+ BITRATE_TO_SPEED_CASE(75)
+ BITRATE_TO_SPEED_CASE(110)
+ BITRATE_TO_SPEED_CASE(134)
+ BITRATE_TO_SPEED_CASE(150)
+ BITRATE_TO_SPEED_CASE(200)
+ BITRATE_TO_SPEED_CASE(300)
+ BITRATE_TO_SPEED_CASE(600)
+ BITRATE_TO_SPEED_CASE(1200)
+ BITRATE_TO_SPEED_CASE(1800)
+ BITRATE_TO_SPEED_CASE(2400)
+ BITRATE_TO_SPEED_CASE(4800)
+ BITRATE_TO_SPEED_CASE(9600)
+ BITRATE_TO_SPEED_CASE(19200)
+ BITRATE_TO_SPEED_CASE(38400)
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+ BITRATE_TO_SPEED_CASE(57600)
+ BITRATE_TO_SPEED_CASE(115200)
+ BITRATE_TO_SPEED_CASE(230400)
+ BITRATE_TO_SPEED_CASE(460800)
+ BITRATE_TO_SPEED_CASE(576000)
+ BITRATE_TO_SPEED_CASE(921600)
+#endif
+ default:
+ return false;
+ }
+#undef BITRATE_TO_SPEED_CASE
+}
+
+// Convert a known nominal speed into an integral bitrate. Returns |true|
+// if the conversion was successful and |false| otherwise.
+bool SpeedConstantToBitrate(speed_t speed, int* bitrate) {
+#define SPEED_TO_BITRATE_CASE(x) \
+ case B##x: \
+ *bitrate = x; \
+ return true;
+ switch (speed) {
+ SPEED_TO_BITRATE_CASE(0)
+ SPEED_TO_BITRATE_CASE(50)
+ SPEED_TO_BITRATE_CASE(75)
+ SPEED_TO_BITRATE_CASE(110)
+ SPEED_TO_BITRATE_CASE(134)
+ SPEED_TO_BITRATE_CASE(150)
+ SPEED_TO_BITRATE_CASE(200)
+ SPEED_TO_BITRATE_CASE(300)
+ SPEED_TO_BITRATE_CASE(600)
+ SPEED_TO_BITRATE_CASE(1200)
+ SPEED_TO_BITRATE_CASE(1800)
+ SPEED_TO_BITRATE_CASE(2400)
+ SPEED_TO_BITRATE_CASE(4800)
+ SPEED_TO_BITRATE_CASE(9600)
+ SPEED_TO_BITRATE_CASE(19200)
+ SPEED_TO_BITRATE_CASE(38400)
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+ SPEED_TO_BITRATE_CASE(57600)
+ SPEED_TO_BITRATE_CASE(115200)
+ SPEED_TO_BITRATE_CASE(230400)
+ SPEED_TO_BITRATE_CASE(460800)
+ SPEED_TO_BITRATE_CASE(576000)
+ SPEED_TO_BITRATE_CASE(921600)
+#endif
+ default:
+ return false;
+ }
+#undef SPEED_TO_BITRATE_CASE
+}
+
+bool SetCustomBitrate(base::PlatformFile file,
+ struct termios* config,
+ int bitrate) {
+#if defined(OS_LINUX)
+ struct serial_struct serial;
+ if (ioctl(file, TIOCGSERIAL, &serial) < 0) {
+ return false;
+ }
+ serial.flags = (serial.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;
+ serial.custom_divisor = serial.baud_base / bitrate;
+ if (serial.custom_divisor < 1) {
+ serial.custom_divisor = 1;
+ }
+ cfsetispeed(config, B38400);
+ cfsetospeed(config, B38400);
+ return ioctl(file, TIOCSSERIAL, &serial) >= 0;
+#elif defined(OS_MACOSX)
+ speed_t speed = static_cast<speed_t>(bitrate);
+ cfsetispeed(config, speed);
+ cfsetospeed(config, speed);
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // namespace
+
+namespace device {
+
+// static
+scoped_refptr<SerialIoHandler> SerialIoHandler::Create() {
+ return new SerialIoHandlerPosix();
+}
+
+void SerialIoHandlerPosix::ReadImpl() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(pending_read_buffer());
+ DCHECK(file().IsValid());
+
+ EnsureWatchingReads();
+}
+
+void SerialIoHandlerPosix::WriteImpl() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(pending_write_buffer());
+ DCHECK(file().IsValid());
+
+ EnsureWatchingWrites();
+}
+
+void SerialIoHandlerPosix::CancelReadImpl() {
+ DCHECK(CalledOnValidThread());
+ is_watching_reads_ = false;
+ file_read_watcher_.StopWatchingFileDescriptor();
+ QueueReadCompleted(0, read_cancel_reason());
+}
+
+void SerialIoHandlerPosix::CancelWriteImpl() {
+ DCHECK(CalledOnValidThread());
+ is_watching_writes_ = false;
+ file_write_watcher_.StopWatchingFileDescriptor();
+ QueueWriteCompleted(0, write_cancel_reason());
+}
+
+SerialIoHandlerPosix::SerialIoHandlerPosix()
+ : is_watching_reads_(false), is_watching_writes_(false) {
+}
+
+SerialIoHandlerPosix::~SerialIoHandlerPosix() {
+}
+
+void SerialIoHandlerPosix::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(fd, file().GetPlatformFile());
+
+ if (pending_read_buffer()) {
+ int bytes_read = HANDLE_EINTR(read(file().GetPlatformFile(),
+ pending_read_buffer()->data(),
+ pending_read_buffer_len()));
+ if (bytes_read < 0) {
+ if (errno == ENXIO) {
+ ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST);
+ } else {
+ ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ }
+ } else if (bytes_read == 0) {
+ ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST);
+ } else {
+ ReadCompleted(bytes_read, serial::RECEIVE_ERROR_NONE);
+ }
+ } else {
+ // Stop watching the fd if we get notifications with no pending
+ // reads or writes to avoid starving the message loop.
+ is_watching_reads_ = false;
+ file_read_watcher_.StopWatchingFileDescriptor();
+ }
+}
+
+void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking(int fd) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(fd, file().GetPlatformFile());
+
+ if (pending_write_buffer()) {
+ int bytes_written = HANDLE_EINTR(write(file().GetPlatformFile(),
+ pending_write_buffer()->data(),
+ pending_write_buffer_len()));
+ if (bytes_written < 0) {
+ WriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
+ } else {
+ WriteCompleted(bytes_written, serial::SEND_ERROR_NONE);
+ }
+ } else {
+ // Stop watching the fd if we get notifications with no pending
+ // writes to avoid starving the message loop.
+ is_watching_writes_ = false;
+ file_write_watcher_.StopWatchingFileDescriptor();
+ }
+}
+
+void SerialIoHandlerPosix::EnsureWatchingReads() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(file().IsValid());
+ if (!is_watching_reads_) {
+ is_watching_reads_ = base::MessageLoopForIO::current()->WatchFileDescriptor(
+ file().GetPlatformFile(),
+ true,
+ base::MessageLoopForIO::WATCH_READ,
+ &file_read_watcher_,
+ this);
+ }
+}
+
+void SerialIoHandlerPosix::EnsureWatchingWrites() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(file().IsValid());
+ if (!is_watching_writes_) {
+ is_watching_writes_ =
+ base::MessageLoopForIO::current()->WatchFileDescriptor(
+ file().GetPlatformFile(),
+ true,
+ base::MessageLoopForIO::WATCH_WRITE,
+ &file_write_watcher_,
+ this);
+ }
+}
+
+bool SerialIoHandlerPosix::ConfigurePort(
+ const serial::ConnectionOptions& options) {
+ struct termios config;
+ tcgetattr(file().GetPlatformFile(), &config);
+ if (options.bitrate) {
+ speed_t bitrate_opt = B0;
+ if (BitrateToSpeedConstant(options.bitrate, &bitrate_opt)) {
+ cfsetispeed(&config, bitrate_opt);
+ cfsetospeed(&config, bitrate_opt);
+ } else {
+ // Attempt to set a custom speed.
+ if (!SetCustomBitrate(
+ file().GetPlatformFile(), &config, options.bitrate)) {
+ return false;
+ }
+ }
+ }
+ if (options.data_bits != serial::DATA_BITS_NONE) {
+ config.c_cflag &= ~CSIZE;
+ switch (options.data_bits) {
+ case serial::DATA_BITS_SEVEN:
+ config.c_cflag |= CS7;
+ break;
+ case serial::DATA_BITS_EIGHT:
+ default:
+ config.c_cflag |= CS8;
+ break;
+ }
+ }
+ if (options.parity_bit != serial::PARITY_BIT_NONE) {
+ switch (options.parity_bit) {
+ case serial::PARITY_BIT_EVEN:
+ config.c_cflag |= PARENB;
+ config.c_cflag &= ~PARODD;
+ break;
+ case serial::PARITY_BIT_ODD:
+ config.c_cflag |= (PARODD | PARENB);
+ break;
+ case serial::PARITY_BIT_NO:
+ default:
+ config.c_cflag &= ~(PARODD | PARENB);
+ break;
+ }
+ }
+ if (options.stop_bits != serial::STOP_BITS_NONE) {
+ switch (options.stop_bits) {
+ case serial::STOP_BITS_TWO:
+ config.c_cflag |= CSTOPB;
+ break;
+ case serial::STOP_BITS_ONE:
+ default:
+ config.c_cflag &= ~CSTOPB;
+ break;
+ }
+ }
+ if (options.has_cts_flow_control) {
+ if (options.cts_flow_control) {
+ config.c_cflag |= CRTSCTS;
+ } else {
+ config.c_cflag &= ~CRTSCTS;
+ }
+ }
+ return tcsetattr(file().GetPlatformFile(), TCSANOW, &config) == 0;
+}
+
+bool SerialIoHandlerPosix::PostOpen() {
+ struct termios config;
+ tcgetattr(file().GetPlatformFile(), &config);
+
+ // Set flags for 'raw' operation
+ config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
+ config.c_iflag &=
+ ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+ config.c_oflag &= ~OPOST;
+
+ // CLOCAL causes the system to disregard the DCD signal state.
+ // CREAD enables reading from the port.
+ config.c_cflag |= (CLOCAL | CREAD);
+
+ return tcsetattr(file().GetPlatformFile(), TCSANOW, &config) == 0;
+}
+
+bool SerialIoHandlerPosix::Flush() const {
+ return tcflush(file().GetPlatformFile(), TCIOFLUSH) == 0;
+}
+
+serial::DeviceControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
+ const {
+ int status;
+ if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
+ return serial::DeviceControlSignalsPtr();
+ }
+
+ serial::DeviceControlSignalsPtr signals(serial::DeviceControlSignals::New());
+ signals->dcd = (status & TIOCM_CAR) != 0;
+ signals->cts = (status & TIOCM_CTS) != 0;
+ signals->dsr = (status & TIOCM_DSR) != 0;
+ signals->ri = (status & TIOCM_RI) != 0;
+ return signals.Pass();
+}
+
+bool SerialIoHandlerPosix::SetControlSignals(
+ const serial::HostControlSignals& signals) {
+ int status;
+
+ if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
+ return false;
+ }
+
+ if (signals.has_dtr) {
+ if (signals.dtr) {
+ status |= TIOCM_DTR;
+ } else {
+ status &= ~TIOCM_DTR;
+ }
+ }
+
+ if (signals.has_rts) {
+ if (signals.rts) {
+ status |= TIOCM_RTS;
+ } else {
+ status &= ~TIOCM_RTS;
+ }
+ }
+
+ return ioctl(file().GetPlatformFile(), TIOCMSET, &status) == 0;
+}
+
+serial::ConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
+ struct termios config;
+ if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
+ return serial::ConnectionInfoPtr();
+ }
+ serial::ConnectionInfoPtr info(serial::ConnectionInfo::New());
+ speed_t ispeed = cfgetispeed(&config);
+ speed_t ospeed = cfgetospeed(&config);
+ if (ispeed == ospeed) {
+ int bitrate = 0;
+ if (SpeedConstantToBitrate(ispeed, &bitrate)) {
+ info->bitrate = bitrate;
+ } else if (ispeed > 0) {
+ info->bitrate = static_cast<int>(ispeed);
+ }
+ }
+ if ((config.c_cflag & CSIZE) == CS7) {
+ info->data_bits = serial::DATA_BITS_SEVEN;
+ } else if ((config.c_cflag & CSIZE) == CS8) {
+ info->data_bits = serial::DATA_BITS_EIGHT;
+ } else {
+ info->data_bits = serial::DATA_BITS_NONE;
+ }
+ if (config.c_cflag & PARENB) {
+ info->parity_bit = (config.c_cflag & PARODD) ? serial::PARITY_BIT_ODD
+ : serial::PARITY_BIT_EVEN;
+ } else {
+ info->parity_bit = serial::PARITY_BIT_NO;
+ }
+ info->stop_bits =
+ (config.c_cflag & CSTOPB) ? serial::STOP_BITS_TWO : serial::STOP_BITS_ONE;
+ info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0;
+ return info.Pass();
+}
+
+std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
+ return port_name;
+}
+
+} // namespace device
diff --git a/device/serial/serial_io_handler_posix.h b/device/serial/serial_io_handler_posix.h
new file mode 100644
index 0000000..2cd00c6
--- /dev/null
+++ b/device/serial/serial_io_handler_posix.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_SERIAL_SERIAL_IO_HANDLER_POSIX_H_
+#define DEVICE_SERIAL_SERIAL_IO_HANDLER_POSIX_H_
+
+#include "base/message_loop/message_loop.h"
+#include "device/serial/serial_io_handler.h"
+
+namespace device {
+
+class SerialIoHandlerPosix : public SerialIoHandler,
+ public base::MessageLoopForIO::Watcher {
+ protected:
+ // SerialIoHandler impl.
+ virtual void ReadImpl() OVERRIDE;
+ virtual void WriteImpl() OVERRIDE;
+ virtual void CancelReadImpl() OVERRIDE;
+ virtual void CancelWriteImpl() OVERRIDE;
+ virtual bool Flush() const OVERRIDE;
+ virtual serial::DeviceControlSignalsPtr GetControlSignals() const OVERRIDE;
+ virtual bool SetControlSignals(
+ const serial::HostControlSignals& control_signals) OVERRIDE;
+ virtual bool ConfigurePort(const serial::ConnectionOptions& options) OVERRIDE;
+ virtual serial::ConnectionInfoPtr GetPortInfo() const OVERRIDE;
+ virtual bool PostOpen() OVERRIDE;
+
+ private:
+ friend class SerialIoHandler;
+
+ SerialIoHandlerPosix();
+ virtual ~SerialIoHandlerPosix();
+
+ // base::MessageLoopForIO::Watcher implementation.
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+
+ void EnsureWatchingReads();
+ void EnsureWatchingWrites();
+
+ base::MessageLoopForIO::FileDescriptorWatcher file_read_watcher_;
+ base::MessageLoopForIO::FileDescriptorWatcher file_write_watcher_;
+
+ // Flags indicating if the message loop is watching the device for IO events.
+ bool is_watching_reads_;
+ bool is_watching_writes_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerialIoHandlerPosix);
+};
+
+} // namespace device
+
+#endif // DEVICE_SERIAL_SERIAL_IO_HANDLER_POSIX_H_
diff --git a/device/serial/serial_io_handler_win.cc b/device/serial/serial_io_handler_win.cc
new file mode 100644
index 0000000..275a189
--- /dev/null
+++ b/device/serial/serial_io_handler_win.cc
@@ -0,0 +1,381 @@
+// 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 <windows.h>
+
+#include "device/serial/serial_io_handler_win.h"
+
+namespace device {
+
+namespace {
+
+int BitrateToSpeedConstant(int bitrate) {
+#define BITRATE_TO_SPEED_CASE(x) \
+ case x: \
+ return CBR_##x;
+ switch (bitrate) {
+ BITRATE_TO_SPEED_CASE(110);
+ BITRATE_TO_SPEED_CASE(300);
+ BITRATE_TO_SPEED_CASE(600);
+ BITRATE_TO_SPEED_CASE(1200);
+ BITRATE_TO_SPEED_CASE(2400);
+ BITRATE_TO_SPEED_CASE(4800);
+ BITRATE_TO_SPEED_CASE(9600);
+ BITRATE_TO_SPEED_CASE(14400);
+ BITRATE_TO_SPEED_CASE(19200);
+ BITRATE_TO_SPEED_CASE(38400);
+ BITRATE_TO_SPEED_CASE(57600);
+ BITRATE_TO_SPEED_CASE(115200);
+ BITRATE_TO_SPEED_CASE(128000);
+ BITRATE_TO_SPEED_CASE(256000);
+ default:
+ // If the bitrate doesn't match that of one of the standard
+ // index constants, it may be provided as-is to the DCB
+ // structure, according to MSDN.
+ return bitrate;
+ }
+#undef BITRATE_TO_SPEED_CASE
+}
+
+int DataBitsEnumToConstant(serial::DataBits data_bits) {
+ switch (data_bits) {
+ case serial::DATA_BITS_SEVEN:
+ return 7;
+ case serial::DATA_BITS_EIGHT:
+ default:
+ return 8;
+ }
+}
+
+int ParityBitEnumToConstant(serial::ParityBit parity_bit) {
+ switch (parity_bit) {
+ case serial::PARITY_BIT_EVEN:
+ return EVENPARITY;
+ case serial::PARITY_BIT_ODD:
+ return ODDPARITY;
+ case serial::PARITY_BIT_NO:
+ default:
+ return NOPARITY;
+ }
+}
+
+int StopBitsEnumToConstant(serial::StopBits stop_bits) {
+ switch (stop_bits) {
+ case serial::STOP_BITS_TWO:
+ return TWOSTOPBITS;
+ case serial::STOP_BITS_ONE:
+ default:
+ return ONESTOPBIT;
+ }
+}
+
+int SpeedConstantToBitrate(int speed) {
+#define SPEED_TO_BITRATE_CASE(x) \
+ case CBR_##x: \
+ return x;
+ switch (speed) {
+ SPEED_TO_BITRATE_CASE(110);
+ SPEED_TO_BITRATE_CASE(300);
+ SPEED_TO_BITRATE_CASE(600);
+ SPEED_TO_BITRATE_CASE(1200);
+ SPEED_TO_BITRATE_CASE(2400);
+ SPEED_TO_BITRATE_CASE(4800);
+ SPEED_TO_BITRATE_CASE(9600);
+ SPEED_TO_BITRATE_CASE(14400);
+ SPEED_TO_BITRATE_CASE(19200);
+ SPEED_TO_BITRATE_CASE(38400);
+ SPEED_TO_BITRATE_CASE(57600);
+ SPEED_TO_BITRATE_CASE(115200);
+ SPEED_TO_BITRATE_CASE(128000);
+ SPEED_TO_BITRATE_CASE(256000);
+ default:
+ // If it's not one of the standard index constants,
+ // it should be an integral baud rate, according to
+ // MSDN.
+ return speed;
+ }
+#undef SPEED_TO_BITRATE_CASE
+}
+
+serial::DataBits DataBitsConstantToEnum(int data_bits) {
+ switch (data_bits) {
+ case 7:
+ return serial::DATA_BITS_SEVEN;
+ case 8:
+ default:
+ return serial::DATA_BITS_EIGHT;
+ }
+}
+
+serial::ParityBit ParityBitConstantToEnum(int parity_bit) {
+ switch (parity_bit) {
+ case EVENPARITY:
+ return serial::PARITY_BIT_EVEN;
+ case ODDPARITY:
+ return serial::PARITY_BIT_ODD;
+ case NOPARITY:
+ default:
+ return serial::PARITY_BIT_NO;
+ }
+}
+
+serial::StopBits StopBitsConstantToEnum(int stop_bits) {
+ switch (stop_bits) {
+ case TWOSTOPBITS:
+ return serial::STOP_BITS_TWO;
+ case ONESTOPBIT:
+ default:
+ return serial::STOP_BITS_ONE;
+ }
+}
+
+} // namespace
+
+// static
+scoped_refptr<SerialIoHandler> SerialIoHandler::Create() {
+ return new SerialIoHandlerWin();
+}
+
+bool SerialIoHandlerWin::PostOpen() {
+ DCHECK(!comm_context_);
+ DCHECK(!read_context_);
+ DCHECK(!write_context_);
+
+ base::MessageLoopForIO::current()->RegisterIOHandler(file().GetPlatformFile(),
+ this);
+
+ comm_context_.reset(new base::MessageLoopForIO::IOContext());
+ comm_context_->handler = this;
+ memset(&comm_context_->overlapped, 0, sizeof(comm_context_->overlapped));
+
+ read_context_.reset(new base::MessageLoopForIO::IOContext());
+ read_context_->handler = this;
+ memset(&read_context_->overlapped, 0, sizeof(read_context_->overlapped));
+
+ write_context_.reset(new base::MessageLoopForIO::IOContext());
+ write_context_->handler = this;
+ memset(&write_context_->overlapped, 0, sizeof(write_context_->overlapped));
+
+ // A ReadIntervalTimeout of MAXDWORD will cause async reads to complete
+ // immediately with any data that's available, even if there is none.
+ // This is OK because we never issue a read request until WaitCommEvent
+ // signals that data is available.
+ COMMTIMEOUTS timeouts = {0};
+ timeouts.ReadIntervalTimeout = MAXDWORD;
+ if (!::SetCommTimeouts(file().GetPlatformFile(), &timeouts)) {
+ return false;
+ }
+
+ DCB config = {0};
+ config.DCBlength = sizeof(config);
+ if (!GetCommState(file().GetPlatformFile(), &config)) {
+ return false;
+ }
+ // Setup some sane default state.
+ config.fBinary = TRUE;
+ config.fParity = FALSE;
+ config.fAbortOnError = TRUE;
+ config.fOutxCtsFlow = FALSE;
+ config.fOutxDsrFlow = FALSE;
+ config.fRtsControl = RTS_CONTROL_ENABLE;
+ config.fDtrControl = DTR_CONTROL_ENABLE;
+ config.fDsrSensitivity = FALSE;
+ config.fOutX = FALSE;
+ config.fInX = FALSE;
+ return SetCommState(file().GetPlatformFile(), &config) != 0;
+}
+
+void SerialIoHandlerWin::ReadImpl() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(pending_read_buffer());
+ DCHECK(file().IsValid());
+
+ DWORD errors;
+ COMSTAT status;
+ if (!ClearCommError(file().GetPlatformFile(), &errors, &status) ||
+ errors != 0) {
+ QueueReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ return;
+ }
+
+ SetCommMask(file().GetPlatformFile(), EV_RXCHAR);
+
+ event_mask_ = 0;
+ BOOL ok = ::WaitCommEvent(
+ file().GetPlatformFile(), &event_mask_, &comm_context_->overlapped);
+ if (!ok && GetLastError() != ERROR_IO_PENDING) {
+ QueueReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ }
+ is_comm_pending_ = true;
+}
+
+void SerialIoHandlerWin::WriteImpl() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(pending_write_buffer());
+ DCHECK(file().IsValid());
+
+ BOOL ok = ::WriteFile(file().GetPlatformFile(),
+ pending_write_buffer()->data(),
+ pending_write_buffer_len(),
+ NULL,
+ &write_context_->overlapped);
+ if (!ok && GetLastError() != ERROR_IO_PENDING) {
+ QueueWriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
+ }
+}
+
+void SerialIoHandlerWin::CancelReadImpl() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(file().IsValid());
+ ::CancelIo(file().GetPlatformFile());
+}
+
+void SerialIoHandlerWin::CancelWriteImpl() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(file().IsValid());
+ ::CancelIo(file().GetPlatformFile());
+}
+
+SerialIoHandlerWin::SerialIoHandlerWin()
+ : event_mask_(0), is_comm_pending_(false) {
+}
+
+SerialIoHandlerWin::~SerialIoHandlerWin() {
+}
+
+void SerialIoHandlerWin::OnIOCompleted(
+ base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transferred,
+ DWORD error) {
+ DCHECK(CalledOnValidThread());
+ if (context == comm_context_) {
+ if (read_canceled()) {
+ ReadCompleted(bytes_transferred, read_cancel_reason());
+ } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
+ ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ } else if (pending_read_buffer()) {
+ BOOL ok = ::ReadFile(file().GetPlatformFile(),
+ pending_read_buffer()->data(),
+ pending_read_buffer_len(),
+ NULL,
+ &read_context_->overlapped);
+ if (!ok && GetLastError() != ERROR_IO_PENDING) {
+ ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ }
+ }
+ } else if (context == read_context_) {
+ if (read_canceled()) {
+ ReadCompleted(bytes_transferred, read_cancel_reason());
+ } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
+ ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ } else {
+ ReadCompleted(bytes_transferred,
+ error == ERROR_SUCCESS
+ ? serial::RECEIVE_ERROR_NONE
+ : serial::RECEIVE_ERROR_SYSTEM_ERROR);
+ }
+ } else if (context == write_context_) {
+ DCHECK(pending_write_buffer());
+ if (write_canceled()) {
+ WriteCompleted(0, write_cancel_reason());
+ } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
+ WriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
+ } else {
+ WriteCompleted(bytes_transferred,
+ error == ERROR_SUCCESS ? serial::SEND_ERROR_NONE
+ : serial::SEND_ERROR_SYSTEM_ERROR);
+ }
+ } else {
+ NOTREACHED() << "Invalid IOContext";
+ }
+}
+
+bool SerialIoHandlerWin::ConfigurePort(
+ const serial::ConnectionOptions& options) {
+ DCB config = {0};
+ config.DCBlength = sizeof(config);
+ if (!GetCommState(file().GetPlatformFile(), &config)) {
+ return false;
+ }
+ if (options.bitrate)
+ config.BaudRate = BitrateToSpeedConstant(options.bitrate);
+ if (options.data_bits != serial::DATA_BITS_NONE)
+ config.ByteSize = DataBitsEnumToConstant(options.data_bits);
+ if (options.parity_bit != serial::PARITY_BIT_NONE)
+ config.Parity = ParityBitEnumToConstant(options.parity_bit);
+ if (options.stop_bits != serial::STOP_BITS_NONE)
+ config.StopBits = StopBitsEnumToConstant(options.stop_bits);
+ if (options.has_cts_flow_control) {
+ if (options.cts_flow_control) {
+ config.fOutxCtsFlow = TRUE;
+ config.fRtsControl = RTS_CONTROL_HANDSHAKE;
+ } else {
+ config.fOutxCtsFlow = FALSE;
+ config.fRtsControl = RTS_CONTROL_ENABLE;
+ }
+ }
+ return SetCommState(file().GetPlatformFile(), &config) != 0;
+}
+
+bool SerialIoHandlerWin::Flush() const {
+ return PurgeComm(file().GetPlatformFile(), PURGE_RXCLEAR | PURGE_TXCLEAR) !=
+ 0;
+}
+
+serial::DeviceControlSignalsPtr SerialIoHandlerWin::GetControlSignals() const {
+ DWORD status;
+ if (!GetCommModemStatus(file().GetPlatformFile(), &status)) {
+ return serial::DeviceControlSignalsPtr();
+ }
+
+ serial::DeviceControlSignalsPtr signals(serial::DeviceControlSignals::New());
+ signals->dcd = (status & MS_RLSD_ON) != 0;
+ signals->cts = (status & MS_CTS_ON) != 0;
+ signals->dsr = (status & MS_DSR_ON) != 0;
+ signals->ri = (status & MS_RING_ON) != 0;
+ return signals.Pass();
+}
+
+bool SerialIoHandlerWin::SetControlSignals(
+ const serial::HostControlSignals& signals) {
+ if (signals.has_dtr) {
+ if (!EscapeCommFunction(file().GetPlatformFile(),
+ signals.dtr ? SETDTR : CLRDTR)) {
+ return false;
+ }
+ }
+ if (signals.has_rts) {
+ if (!EscapeCommFunction(file().GetPlatformFile(),
+ signals.rts ? SETRTS : CLRRTS)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+serial::ConnectionInfoPtr SerialIoHandlerWin::GetPortInfo() const {
+ DCB config = {0};
+ config.DCBlength = sizeof(config);
+ if (!GetCommState(file().GetPlatformFile(), &config)) {
+ return serial::ConnectionInfoPtr();
+ }
+ serial::ConnectionInfoPtr info(serial::ConnectionInfo::New());
+ info->bitrate = SpeedConstantToBitrate(config.BaudRate);
+ info->data_bits = DataBitsConstantToEnum(config.ByteSize);
+ info->parity_bit = ParityBitConstantToEnum(config.Parity);
+ info->stop_bits = StopBitsConstantToEnum(config.StopBits);
+ info->cts_flow_control = config.fOutxCtsFlow != 0;
+ return info.Pass();
+}
+
+std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
+ // For COM numbers less than 9, CreateFile is called with a string such as
+ // "COM1". For numbers greater than 9, a prefix of "\\\\.\\" must be added.
+ if (port_name.length() > std::string("COM9").length())
+ return std::string("\\\\.\\").append(port_name);
+
+ return port_name;
+}
+
+} // namespace device
diff --git a/device/serial/serial_io_handler_win.h b/device/serial/serial_io_handler_win.h
new file mode 100644
index 0000000..c75ec60
--- /dev/null
+++ b/device/serial/serial_io_handler_win.h
@@ -0,0 +1,63 @@
+// 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_IO_HANDLER_WIN_H_
+#define DEVICE_SERIAL_SERIAL_IO_HANDLER_WIN_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "device/serial/serial_io_handler.h"
+
+namespace device {
+
+class SerialIoHandlerWin : public SerialIoHandler,
+ public base::MessageLoopForIO::IOHandler {
+ protected:
+ // SerialIoHandler implementation.
+ virtual void ReadImpl() OVERRIDE;
+ virtual void WriteImpl() OVERRIDE;
+ virtual void CancelReadImpl() OVERRIDE;
+ virtual void CancelWriteImpl() OVERRIDE;
+ virtual bool Flush() const OVERRIDE;
+ virtual serial::DeviceControlSignalsPtr GetControlSignals() const OVERRIDE;
+ virtual bool SetControlSignals(
+ const serial::HostControlSignals& control_signals) OVERRIDE;
+ virtual bool ConfigurePort(const serial::ConnectionOptions& options) OVERRIDE;
+ virtual serial::ConnectionInfoPtr GetPortInfo() const OVERRIDE;
+ virtual bool PostOpen() OVERRIDE;
+
+ private:
+ friend class SerialIoHandler;
+
+ SerialIoHandlerWin();
+ virtual ~SerialIoHandlerWin();
+
+ // base::MessageLoopForIO::IOHandler implementation.
+ virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error) OVERRIDE;
+
+ // Context used for asynchronous WaitCommEvent calls.
+ scoped_ptr<base::MessageLoopForIO::IOContext> comm_context_;
+
+ // Context used for overlapped reads.
+ scoped_ptr<base::MessageLoopForIO::IOContext> read_context_;
+
+ // Context used for overlapped writes.
+ scoped_ptr<base::MessageLoopForIO::IOContext> write_context_;
+
+ // Asynchronous event mask state
+ DWORD event_mask_;
+
+ // Indicates if a pending read is waiting on initial data arrival via
+ // WaitCommEvent, as opposed to waiting on actual ReadFile completion
+ // after a corresponding WaitCommEvent has completed.
+ bool is_comm_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerialIoHandlerWin);
+};
+
+} // namespace device
+
+#endif // DEVICE_SERIAL_SERIAL_IO_HANDLER_WIN_H_
diff --git a/device/serial/serial_service_impl.cc b/device/serial/serial_service_impl.cc
new file mode 100644
index 0000000..1d83e23
--- /dev/null
+++ b/device/serial/serial_service_impl.cc
@@ -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.
+
+#include "device/serial/serial_service_impl.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+
+namespace device {
+
+SerialServiceImpl::SerialServiceImpl() {
+}
+
+SerialServiceImpl::~SerialServiceImpl() {
+}
+
+// static
+void SerialServiceImpl::Create(
+ mojo::InterfaceRequest<serial::SerialService> request) {
+ mojo::BindToRequest(new SerialServiceImpl(), &request);
+}
+
+// static
+void SerialServiceImpl::CreateOnMessageLoop(
+ scoped_refptr<base::MessageLoopProxy> message_loop,
+ mojo::InterfaceRequest<serial::SerialService> request) {
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&SerialServiceImpl::Create, base::Passed(&request)));
+}
+
+void SerialServiceImpl::GetDevices(
+ const mojo::Callback<void(mojo::Array<serial::DeviceInfoPtr>)>& callback) {
+ if (!device_enumerator_)
+ device_enumerator_ = SerialDeviceEnumerator::Create();
+ callback.Run(device_enumerator_->GetDevices());
+}
+
+void SerialServiceImpl::OnConnectionError() {
+ delete this;
+}
+
+} // namespace device
diff --git a/device/serial/serial_service_impl.h b/device/serial/serial_service_impl.h
new file mode 100644
index 0000000..ff4cf4f
--- /dev/null
+++ b/device/serial/serial_service_impl.h
@@ -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.
+
+#ifndef DEVICE_SERIAL_SERIAL_SERVICE_IMPL_H_
+#define DEVICE_SERIAL_SERIAL_SERVICE_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "device/serial/serial.mojom.h"
+#include "device/serial/serial_device_enumerator.h"
+#include "mojo/public/cpp/bindings/interface_impl.h"
+
+namespace device {
+
+class SerialServiceImpl : public mojo::InterfaceImpl<serial::SerialService> {
+ public:
+ SerialServiceImpl();
+ virtual ~SerialServiceImpl();
+
+ static void Create(mojo::InterfaceRequest<serial::SerialService> request);
+ static void CreateOnMessageLoop(
+ scoped_refptr<base::MessageLoopProxy> message_loop,
+ mojo::InterfaceRequest<serial::SerialService> request);
+
+ // mojo::InterfaceImpl<SerialService> overrides.
+ virtual void GetDevices(const mojo::Callback<
+ void(mojo::Array<serial::DeviceInfoPtr>)>& callback) OVERRIDE;
+ virtual void OnConnectionError() OVERRIDE;
+
+ private:
+ scoped_ptr<SerialDeviceEnumerator> device_enumerator_;
+};
+
+} // namespace device
+
+#endif // DEVICE_SERIAL_SERIAL_SERVICE_IMPL_H_
diff --git a/device/serial/serial_service_unittest.cc b/device/serial/serial_service_unittest.cc
new file mode 100644
index 0000000..7e77c14
--- /dev/null
+++ b/device/serial/serial_service_unittest.cc
@@ -0,0 +1,53 @@
+// 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/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "device/serial/serial.mojom.h"
+#include "device/serial/serial_service_impl.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+class SerialServiceTest : public testing::Test, public mojo::ErrorHandler {
+ public:
+ SerialServiceTest() {}
+
+ void StoreDevices(mojo::Array<serial::DeviceInfoPtr> devices) {
+ devices_ = devices.Pass();
+ message_loop_.PostTask(FROM_HERE, run_loop_.QuitClosure());
+ }
+
+ virtual void OnConnectionError() OVERRIDE {
+ message_loop_.PostTask(FROM_HERE, run_loop_.QuitClosure());
+ FAIL() << "Connection error";
+ }
+
+ base::MessageLoop message_loop_;
+ base::RunLoop run_loop_;
+ mojo::Array<serial::DeviceInfoPtr> devices_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SerialServiceTest);
+};
+
+TEST_F(SerialServiceTest, GetDevices) {
+ mojo::InterfacePtr<serial::SerialService> service;
+ SerialServiceImpl::Create(mojo::Get(&service));
+ service.set_error_handler(this);
+ mojo::Array<serial::DeviceInfoPtr> result;
+ service->GetDevices(
+ base::Bind(&SerialServiceTest::StoreDevices, base::Unretained(this)));
+ run_loop_.Run();
+
+ // Because we're running on unknown hardware, only check that we received a
+ // non-null result.
+ EXPECT_TRUE(devices_);
+}
+
+} // namespace device
diff --git a/device/test/DEPS b/device/test/DEPS
new file mode 100644
index 0000000..26b3ad9
--- /dev/null
+++ b/device/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/embedder",
+]
diff --git a/device/test/run_all_unittests.cc b/device/test/run_all_unittests.cc
new file mode 100644
index 0000000..cd3adb5
--- /dev/null
+++ b/device/test/run_all_unittests.cc
@@ -0,0 +1,18 @@
+// 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/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "mojo/embedder/embedder.h"
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+
+ mojo::embedder::Init();
+ return base::LaunchUnitTests(
+ argc,
+ argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/device/usb/BUILD.gn b/device/usb/BUILD.gn
index f024965..d8a7d77 100644
--- a/device/usb/BUILD.gn
+++ b/device/usb/BUILD.gn
@@ -19,7 +19,7 @@
action("usb_device_ids") {
script = "//device/usb/tools/usb_ids.py"
- source_prereqs = [ source_ids ]
+ inputs = [ source_ids ]
outputs = [ generated_ids ]
args = [
"-i", rebase_path(source_ids, root_build_dir),