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, &registered_channel_id);
+      RegisterRfcommService(uuid, options, &registered_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, &registered_psm);
+  service_record_handle_ = RegisterL2capService(uuid, options, &registered_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),