Merge from Chromium at DEPS revision 251904

This commit was generated by merge_to_master.py.

Change-Id: I1f9543259d7d2a57d81aa41a1b84f85837439d21
diff --git a/device/OWNERS b/device/OWNERS
index a83889f..f6716f4 100644
--- a/device/OWNERS
+++ b/device/OWNERS
@@ -1,3 +1,5 @@
 keybuk@chromium.org
 gdk@chromium.org
 miket@chromium.org
+rockot@chromium.org
+rpaquay@chromium.org
diff --git a/device/bluetooth/bluetooth.gyp b/device/bluetooth/bluetooth.gyp
index c5d8cad..c4012bd 100644
--- a/device/bluetooth/bluetooth.gyp
+++ b/device/bluetooth/bluetooth.gyp
@@ -16,6 +16,7 @@
         '../../net/net.gyp:net',
         '../../third_party/libxml/libxml.gyp:libxml',
         '../../ui/gfx/gfx.gyp:gfx',
+        '../../ui/gfx/gfx.gyp:gfx_geometry',
         '../../ui/ui.gyp:ui',
         'bluetooth_strings.gyp:device_bluetooth_strings',
       ],
@@ -38,6 +39,12 @@
         'bluetooth_device_mac.mm',
         'bluetooth_device_win.cc',
         'bluetooth_device_win.h',
+        'bluetooth_gatt_characteristic.cc',
+        'bluetooth_gatt_characteristic.h',
+        'bluetooth_gatt_descriptor.cc',
+        'bluetooth_gatt_descriptor.h',
+        'bluetooth_gatt_service.cc',
+        'bluetooth_gatt_service.h',
         'bluetooth_init_win.cc',
         'bluetooth_init_win.h',
         'bluetooth_out_of_band_pairing_data.h',
diff --git a/device/bluetooth/bluetooth_adapter.h b/device/bluetooth/bluetooth_adapter.h
index ed61586..24e9dcd 100644
--- a/device/bluetooth/bluetooth_adapter.h
+++ b/device/bluetooth/bluetooth_adapter.h
@@ -43,6 +43,12 @@
     virtual void AdapterPoweredChanged(BluetoothAdapter* adapter,
                                        bool powered) {}
 
+    // Called when the discoverability state of the  adapter |adapter| changes,
+    // when |discoverable| is true the adapter is discoverable by other devices,
+    // false means the adapter is not discoverable.
+    virtual void AdapterDiscoverableChanged(BluetoothAdapter* adapter,
+                                           bool discoverable) {}
+
     // Called when the discovering state of the adapter |adapter| changes,
     // when |discovering| is true the adapter is seeking new devices, false
     // means it is not.
@@ -91,6 +97,12 @@
   // The name of the adapter.
   virtual std::string GetName() const = 0;
 
+  // Set the human-readable name of the adapter to |name|. On success,
+  // |callback| will be called. On failure, |error_callback| will be called.
+  virtual void SetName(const std::string& name,
+                       const base::Closure& callback,
+                       const ErrorCallback& error_callback) = 0;
+
   // Indicates whether the adapter is initialized and ready to use.
   virtual bool IsInitialized() const = 0;
 
@@ -109,6 +121,17 @@
                           const base::Closure& callback,
                           const ErrorCallback& error_callback) = 0;
 
+  // Indicates whether the adapter radio is discoverable.
+  virtual bool IsDiscoverable() const = 0;
+
+  // Requests that the adapter change its discoverability state. If
+  // |discoverable| is true, then it will be discoverable by other Bluetooth
+  // devices. On successly changing the adapter's discoverability, |callback|
+  // will be called. On failure, |error_callback| will be called.
+  virtual void SetDiscoverable(bool discoverable,
+                               const base::Closure& callback,
+                               const ErrorCallback& error_callback) = 0;
+
   // Indicates whether the adapter is currently discovering new devices.
   virtual bool IsDiscovering() const = 0;
 
diff --git a/device/bluetooth/bluetooth_adapter_chromeos.cc b/device/bluetooth/bluetooth_adapter_chromeos.cc
index 319a6e9..0d962b9 100644
--- a/device/bluetooth/bluetooth_adapter_chromeos.cc
+++ b/device/bluetooth/bluetooth_adapter_chromeos.cc
@@ -79,6 +79,18 @@
   return properties->alias.value();
 }
 
+void BluetoothAdapterChromeOS::SetName(const std::string& name,
+                                       const base::Closure& callback,
+                                       const ErrorCallback& error_callback) {
+  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
+      GetProperties(object_path_)->alias.Set(
+          name,
+          base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     callback,
+                     error_callback));
+}
+
 bool BluetoothAdapterChromeOS::IsInitialized() const {
   return true;
 }
@@ -105,7 +117,31 @@
   DBusThreadManager::Get()->GetBluetoothAdapterClient()->
       GetProperties(object_path_)->powered.Set(
           powered,
-          base::Bind(&BluetoothAdapterChromeOS::OnSetPowered,
+          base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     callback,
+                     error_callback));
+}
+
+bool BluetoothAdapterChromeOS::IsDiscoverable() const {
+  if (!IsPresent())
+    return false;
+
+  BluetoothAdapterClient::Properties* properties =
+      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
+          GetProperties(object_path_);
+
+  return properties->discoverable.value();
+}
+
+void BluetoothAdapterChromeOS::SetDiscoverable(
+    bool discoverable,
+    const base::Closure& callback,
+    const ErrorCallback& error_callback) {
+  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
+      GetProperties(object_path_)->discoverable.Set(
+          discoverable,
+          base::Bind(&BluetoothAdapterChromeOS::OnSetDiscoverable,
                      weak_ptr_factory_.GetWeakPtr(),
                      callback,
                      error_callback));
@@ -185,6 +221,8 @@
 
   if (property_name == properties->powered.name())
     PoweredChanged(properties->powered.value());
+  else if (property_name == properties->discoverable.name())
+    DiscoverableChanged(properties->discoverable.value());
   else if (property_name == properties->discovering.name())
     DiscoveringChanged(properties->discovering.value());
 }
@@ -297,7 +335,7 @@
 
   VLOG(1) << object_path_.value() << ": using adapter.";
 
-  SetAdapterName();
+  SetDefaultAdapterName();
 
   BluetoothAdapterClient::Properties* properties =
       DBusThreadManager::Get()->GetBluetoothAdapterClient()->
@@ -307,6 +345,8 @@
 
   if (properties->powered.value())
     PoweredChanged(true);
+  if (properties->discoverable.value())
+    DiscoverableChanged(true);
   if (properties->discovering.value())
     DiscoveringChanged(true);
 
@@ -326,7 +366,7 @@
   }
 }
 
-void BluetoothAdapterChromeOS::SetAdapterName() {
+void BluetoothAdapterChromeOS::SetDefaultAdapterName() {
   std::string board = base::SysInfo::GetLsbReleaseBoard();
   std::string alias;
   if (board.substr(0, 6) == "stumpy") {
@@ -337,16 +377,7 @@
     alias = "Chromebook";
   }
 
-  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
-      GetProperties(object_path_)->alias.Set(
-          alias,
-          base::Bind(&BluetoothAdapterChromeOS::OnSetAlias,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-void BluetoothAdapterChromeOS::OnSetAlias(bool success) {
-  LOG_IF(WARNING, !success) << object_path_.value()
-                            << ": Failed to set adapter alias";
+  SetName(alias, base::Bind(&base::DoNothing), base::Bind(&base::DoNothing));
 }
 
 void BluetoothAdapterChromeOS::RemoveAdapter() {
@@ -361,6 +392,8 @@
 
   if (properties->powered.value())
     PoweredChanged(false);
+  if (properties->discoverable.value())
+    DiscoverableChanged(false);
   if (properties->discovering.value())
     DiscoveringChanged(false);
 
@@ -384,6 +417,11 @@
                     AdapterPoweredChanged(this, powered));
 }
 
+void BluetoothAdapterChromeOS::DiscoverableChanged(bool discoverable) {
+  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
+                    AdapterDiscoverableChanged(this, discoverable));
+}
+
 void BluetoothAdapterChromeOS::DiscoveringChanged(
     bool discovering) {
   FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
@@ -403,9 +441,25 @@
                     DeviceChanged(this, device));
 }
 
-void BluetoothAdapterChromeOS::OnSetPowered(const base::Closure& callback,
-                                            const ErrorCallback& error_callback,
-                                            bool success) {
+void BluetoothAdapterChromeOS::OnSetDiscoverable(
+    const base::Closure& callback,
+    const ErrorCallback& error_callback,
+    bool success) {
+  // Set the discoverable_timeout property to zero so the adapter remains
+  // discoverable forever.
+  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
+      GetProperties(object_path_)->discoverable_timeout.Set(
+          0,
+          base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     callback,
+                     error_callback));
+}
+
+void BluetoothAdapterChromeOS::OnPropertyChangeCompleted(
+    const base::Closure& callback,
+    const ErrorCallback& error_callback,
+    bool success) {
   if (success)
     callback.Run();
   else
diff --git a/device/bluetooth/bluetooth_adapter_chromeos.h b/device/bluetooth/bluetooth_adapter_chromeos.h
index 74beb35..b400ed3 100644
--- a/device/bluetooth/bluetooth_adapter_chromeos.h
+++ b/device/bluetooth/bluetooth_adapter_chromeos.h
@@ -40,6 +40,9 @@
       device::BluetoothAdapter::Observer* observer) OVERRIDE;
   virtual std::string GetAddress() const OVERRIDE;
   virtual std::string GetName() const OVERRIDE;
+  virtual void SetName(const std::string& name,
+                       const base::Closure& callback,
+                       const ErrorCallback& error_callback) OVERRIDE;
   virtual bool IsInitialized() const OVERRIDE;
   virtual bool IsPresent() const OVERRIDE;
   virtual bool IsPowered() const OVERRIDE;
@@ -47,6 +50,11 @@
       bool powered,
       const base::Closure& callback,
       const ErrorCallback& error_callback) OVERRIDE;
+  virtual bool IsDiscoverable() const OVERRIDE;
+  virtual void SetDiscoverable(
+      bool discoverable,
+      const base::Closure& callback,
+      const ErrorCallback& error_callback) OVERRIDE;
   virtual bool IsDiscovering() const OVERRIDE;
   virtual void StartDiscovering(
       const base::Closure& callback,
@@ -95,10 +103,8 @@
   // subsequently operate on that adapter until it is removed.
   void SetAdapter(const dbus::ObjectPath& object_path);
 
-  // Set the adapter name to one chosen from the system information, and method
-  // called by dbus:: on completion of the alias property change.
-  void SetAdapterName();
-  void OnSetAlias(bool success);
+  // Set the adapter name to one chosen from the system information.
+  void SetDefaultAdapterName();
 
   // Remove the currently tracked adapter. IsPresent() will return false after
   // this is called.
@@ -106,6 +112,7 @@
 
   // Announce to observers a change in the adapter state.
   void PoweredChanged(bool powered);
+  void DiscoverableChanged(bool discoverable);
   void DiscoveringChanged(bool discovering);
   void PresentChanged(bool present);
 
@@ -113,10 +120,15 @@
   // its D-Bus properties.
   void NotifyDeviceChanged(BluetoothDeviceChromeOS* device);
 
-  // Called by dbus:: on completion of the powered property change.
-  void OnSetPowered(const base::Closure& callback,
-                    const ErrorCallback& error_callback,
-                    bool success);
+  // Called by dbus:: on completion of the discoverable property change.
+  void OnSetDiscoverable(const base::Closure& callback,
+                         const ErrorCallback& error_callback,
+                         bool success);
+
+  // Called by dbus:: on completion of an adapter property change.
+  void OnPropertyChangeCompleted(const base::Closure& callback,
+                                 const ErrorCallback& error_callback,
+                                 bool success);
 
   // Called by dbus:: on completion of the D-Bus method call to start discovery.
   void OnStartDiscovery(const base::Closure& callback);
diff --git a/device/bluetooth/bluetooth_adapter_mac.h b/device/bluetooth/bluetooth_adapter_mac.h
index 7054acf..6639db0 100644
--- a/device/bluetooth/bluetooth_adapter_mac.h
+++ b/device/bluetooth/bluetooth_adapter_mac.h
@@ -47,6 +47,9 @@
   virtual void RemoveObserver(BluetoothAdapter::Observer* observer) OVERRIDE;
   virtual std::string GetAddress() const OVERRIDE;
   virtual std::string GetName() const OVERRIDE;
+  virtual void SetName(const std::string& name,
+                       const base::Closure& callback,
+                       const ErrorCallback& error_callback) OVERRIDE;
   virtual bool IsInitialized() const OVERRIDE;
   virtual bool IsPresent() const OVERRIDE;
   virtual bool IsPowered() const OVERRIDE;
@@ -54,6 +57,11 @@
       bool powered,
       const base::Closure& callback,
       const ErrorCallback& error_callback) OVERRIDE;
+  virtual bool IsDiscoverable() const OVERRIDE;
+  virtual void SetDiscoverable(
+      bool discoverable,
+      const base::Closure& callback,
+      const ErrorCallback& error_callback) OVERRIDE;
   virtual bool IsDiscovering() const OVERRIDE;
 
   virtual void StartDiscovering(
diff --git a/device/bluetooth/bluetooth_adapter_mac.mm b/device/bluetooth/bluetooth_adapter_mac.mm
index e15b894..502ed81 100644
--- a/device/bluetooth/bluetooth_adapter_mac.mm
+++ b/device/bluetooth/bluetooth_adapter_mac.mm
@@ -127,6 +127,12 @@
   return name_;
 }
 
+void BluetoothAdapterMac::SetName(const std::string& name,
+                                  const base::Closure& callback,
+                                  const ErrorCallback& error_callback) {
+  NOTIMPLEMENTED();
+}
+
 bool BluetoothAdapterMac::IsInitialized() const {
   return true;
 }
@@ -142,6 +148,19 @@
 void BluetoothAdapterMac::SetPowered(bool powered,
                                      const base::Closure& callback,
                                      const ErrorCallback& error_callback) {
+  NOTIMPLEMENTED();
+}
+
+bool BluetoothAdapterMac::IsDiscoverable() const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+void BluetoothAdapterMac::SetDiscoverable(
+    bool discoverable,
+    const base::Closure& callback,
+    const ErrorCallback& error_callback) {
+  NOTIMPLEMENTED();
 }
 
 bool BluetoothAdapterMac::IsDiscovering() const {
diff --git a/device/bluetooth/bluetooth_adapter_win.cc b/device/bluetooth/bluetooth_adapter_win.cc
index f6ca1d6..521afae 100644
--- a/device/bluetooth/bluetooth_adapter_win.cc
+++ b/device/bluetooth/bluetooth_adapter_win.cc
@@ -53,6 +53,12 @@
   return name_;
 }
 
+void BluetoothAdapterWin::SetName(const std::string& name,
+                                  const base::Closure& callback,
+                                  const ErrorCallback& error_callback) {
+  NOTIMPLEMENTED();
+}
+
 // TODO(youngki): Return true when |task_manager_| initializes the adapter
 // state.
 bool BluetoothAdapterWin::IsInitialized() const {
@@ -74,6 +80,18 @@
   task_manager_->PostSetPoweredBluetoothTask(powered, callback, error_callback);
 }
 
+bool BluetoothAdapterWin::IsDiscoverable() const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+void BluetoothAdapterWin::SetDiscoverable(
+    bool discoverable,
+    const base::Closure& callback,
+    const ErrorCallback& error_callback) {
+  NOTIMPLEMENTED();
+}
+
 bool BluetoothAdapterWin::IsDiscovering() const {
   return discovery_status_ == DISCOVERING ||
       discovery_status_ == DISCOVERY_STOPPING;
diff --git a/device/bluetooth/bluetooth_adapter_win.h b/device/bluetooth/bluetooth_adapter_win.h
index a017e29..773c6f3 100644
--- a/device/bluetooth/bluetooth_adapter_win.h
+++ b/device/bluetooth/bluetooth_adapter_win.h
@@ -39,11 +39,19 @@
   virtual void RemoveObserver(BluetoothAdapter::Observer* observer) OVERRIDE;
   virtual std::string GetAddress() const OVERRIDE;
   virtual std::string GetName() const OVERRIDE;
+  virtual void SetName(const std::string& name,
+                       const base::Closure& callback,
+                       const ErrorCallback& error_callback) OVERRIDE;
   virtual bool IsInitialized() const OVERRIDE;
   virtual bool IsPresent() const OVERRIDE;
   virtual bool IsPowered() const OVERRIDE;
   virtual void SetPowered(
-      bool powered,
+      bool discoverable,
+      const base::Closure& callback,
+      const ErrorCallback& error_callback) OVERRIDE;
+  virtual bool IsDiscoverable() const OVERRIDE;
+  virtual void SetDiscoverable(
+      bool discoverable,
       const base::Closure& callback,
       const ErrorCallback& error_callback) OVERRIDE;
   virtual bool IsDiscovering() const OVERRIDE;
diff --git a/device/bluetooth/bluetooth_chromeos_unittest.cc b/device/bluetooth/bluetooth_chromeos_unittest.cc
index dea1322..a1cc67d 100644
--- a/device/bluetooth/bluetooth_chromeos_unittest.cc
+++ b/device/bluetooth/bluetooth_chromeos_unittest.cc
@@ -28,6 +28,7 @@
   TestObserver(scoped_refptr<BluetoothAdapter> adapter)
       : present_changed_count_(0),
         powered_changed_count_(0),
+        discoverable_changed_count_(0),
         discovering_changed_count_(0),
         last_present_(false),
         last_powered_(false),
@@ -56,6 +57,13 @@
     last_powered_ = powered;
   }
 
+  virtual void AdapterDiscoverableChanged(BluetoothAdapter* adapter,
+                                          bool discoverable) OVERRIDE {
+    EXPECT_EQ(adapter_, adapter);
+
+    ++discoverable_changed_count_;
+  }
+
   virtual void AdapterDiscoveringChanged(BluetoothAdapter* adapter,
                                          bool discovering) OVERRIDE {
     EXPECT_EQ(adapter_, adapter);
@@ -99,6 +107,7 @@
 
   int present_changed_count_;
   int powered_changed_count_;
+  int discoverable_changed_count_;
   int discovering_changed_count_;
   bool last_present_;
   bool last_powered_;
@@ -264,7 +273,7 @@
     ASSERT_TRUE(adapter_.get() != NULL);
 
     if (base::MessageLoop::current() == NULL) {
-      base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+      base::MessageLoop message_loop;
       DiscoverDevices();
       return;
     }
@@ -507,8 +516,81 @@
   EXPECT_FALSE(adapter_->IsPowered());
 }
 
+TEST_F(BluetoothChromeOSTest, ChangeAdapterName) {
+  GetAdapter();
+
+  static const std::string new_name(".__.");
+
+  adapter_->SetName(
+      new_name,
+      base::Bind(&BluetoothChromeOSTest::Callback,
+                 base::Unretained(this)),
+      base::Bind(&BluetoothChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, callback_count_);
+  EXPECT_EQ(0, error_callback_count_);
+
+  EXPECT_EQ(new_name, adapter_->GetName());
+}
+
+TEST_F(BluetoothChromeOSTest, BecomeDiscoverable) {
+  GetAdapter();
+  ASSERT_FALSE(adapter_->IsDiscoverable());
+
+  // Install an observer; expect the AdapterDiscoverableChanged to be called
+  // with true, and IsDiscoverable() to return true.
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  adapter_->SetDiscoverable(
+      true,
+      base::Bind(&BluetoothChromeOSTest::Callback,
+                 base::Unretained(this)),
+      base::Bind(&BluetoothChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, callback_count_);
+  EXPECT_EQ(0, error_callback_count_);
+
+  EXPECT_EQ(1, observer.discoverable_changed_count_);
+
+  EXPECT_TRUE(adapter_->IsDiscoverable());
+}
+
+TEST_F(BluetoothChromeOSTest, BecomeNotDiscoverable) {
+  GetAdapter();
+  adapter_->SetDiscoverable(
+      true,
+      base::Bind(&BluetoothChromeOSTest::Callback,
+                 base::Unretained(this)),
+      base::Bind(&BluetoothChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, callback_count_);
+  EXPECT_EQ(0, error_callback_count_);
+  callback_count_ = 0;
+
+  ASSERT_TRUE(adapter_->IsDiscoverable());
+
+  // Install an observer; expect the AdapterDiscoverableChanged to be called
+  // with false, and IsDiscoverable() to return false.
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  adapter_->SetDiscoverable(
+      false,
+      base::Bind(&BluetoothChromeOSTest::Callback,
+                 base::Unretained(this)),
+      base::Bind(&BluetoothChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, callback_count_);
+  EXPECT_EQ(0, error_callback_count_);
+
+  EXPECT_EQ(1, observer.discoverable_changed_count_);
+
+  EXPECT_FALSE(adapter_->IsDiscoverable());
+}
+
 TEST_F(BluetoothChromeOSTest, StopDiscovery) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
 
   GetAdapter();
 
@@ -551,7 +633,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, StopDiscoveryAfterTwoStarts) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
 
   GetAdapter();
 
@@ -623,7 +705,7 @@
 
 TEST_F(BluetoothChromeOSTest, Discovery) {
   // Test a simulated discovery session.
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
 
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
   GetAdapter();
@@ -670,7 +752,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PoweredAndDiscovering) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
 
   GetAdapter();
   adapter_->SetPowered(
@@ -749,7 +831,7 @@
             devices[0]->GetAddress());
 
   // Verify the other device properties.
-  EXPECT_EQ(UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName),
+  EXPECT_EQ(base::UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName),
             devices[0]->GetName());
   EXPECT_EQ(BluetoothDevice::DEVICE_COMPUTER, devices[0]->GetDeviceType());
   EXPECT_TRUE(devices[0]->IsPaired());
@@ -805,7 +887,7 @@
   ASSERT_EQ(1U, devices.size());
   ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress,
             devices[0]->GetAddress());
-  ASSERT_EQ(UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName),
+  ASSERT_EQ(base::UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName),
             devices[0]->GetName());
 
   // Install an observer; expect the DeviceChanged method to be called when
@@ -823,7 +905,7 @@
   EXPECT_EQ(1, observer.device_changed_count_);
   EXPECT_EQ(devices[0], observer.last_device_);
 
-  EXPECT_EQ(UTF8ToUTF16(new_name), devices[0]->GetName());
+  EXPECT_EQ(base::UTF8ToUTF16(new_name), devices[0]->GetName());
 }
 
 TEST_F(BluetoothChromeOSTest, DeviceUuidsChanged) {
@@ -1169,7 +1251,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairAppleMouse) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1230,7 +1312,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairAppleKeyboard) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1292,7 +1374,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairMotorolaKeyboard) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1375,7 +1457,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairSonyHeadphones) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1438,7 +1520,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairPhone) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1498,7 +1580,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairWeirdDevice) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1559,7 +1641,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairUnpairableDeviceFails) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1602,7 +1684,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingFails) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1646,7 +1728,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingFailsAtConnection) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1703,7 +1785,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingRejectedAtPinCode) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1750,7 +1832,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingCancelledAtPinCode) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1797,7 +1879,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingRejectedAtPasskey) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1844,7 +1926,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingCancelledAtPasskey) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1891,7 +1973,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingRejectedAtConfirmation) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1938,7 +2020,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingCancelledAtConfirmation) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
@@ -1985,7 +2067,7 @@
 }
 
 TEST_F(BluetoothChromeOSTest, PairingCancelledInFlight) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
+  base::MessageLoop message_loop;
   fake_bluetooth_device_client_->SetSimulationIntervalMs(10);
 
   GetAdapter();
diff --git a/device/bluetooth/bluetooth_device.cc b/device/bluetooth/bluetooth_device.cc
index 7b6ee0d..7c100ec 100644
--- a/device/bluetooth/bluetooth_device.cc
+++ b/device/bluetooth/bluetooth_device.cc
@@ -24,17 +24,17 @@
 BluetoothDevice::~BluetoothDevice() {
 }
 
-string16 BluetoothDevice::GetName() const {
+base::string16 BluetoothDevice::GetName() const {
   std::string name = GetDeviceName();
   if (!name.empty()) {
-    return UTF8ToUTF16(name);
+    return base::UTF8ToUTF16(name);
   } else {
     return GetAddressWithLocalizedDeviceTypeName();
   }
 }
 
-string16 BluetoothDevice::GetAddressWithLocalizedDeviceTypeName() const {
-  string16 address_utf16 = UTF8ToUTF16(GetAddress());
+base::string16 BluetoothDevice::GetAddressWithLocalizedDeviceTypeName() const {
+  base::string16 address_utf16 = base::UTF8ToUTF16(GetAddress());
   BluetoothDevice::DeviceType device_type = GetDeviceType();
   switch (device_type) {
     case DEVICE_COMPUTER:
diff --git a/device/bluetooth/bluetooth_device.h b/device/bluetooth/bluetooth_device.h
index aaf4e93..95fe683 100644
--- a/device/bluetooth/bluetooth_device.h
+++ b/device/bluetooth/bluetooth_device.h
@@ -188,7 +188,7 @@
   // Returns the name of the device suitable for displaying, this may
   // be a synthesized string containing the address and localized type name
   // if the device has no obtained name.
-  virtual string16 GetName() const;
+  virtual base::string16 GetName() const;
 
   // Returns the type of the device, limited to those we support or are
   // aware of, by decoding the bluetooth class information. The returned
@@ -369,7 +369,7 @@
  private:
   // Returns a localized string containing the device's bluetooth address and
   // a device type for display when |name_| is empty.
-  string16 GetAddressWithLocalizedDeviceTypeName() const;
+  base::string16 GetAddressWithLocalizedDeviceTypeName() const;
 };
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_gatt_characteristic.cc b/device/bluetooth/bluetooth_gatt_characteristic.cc
new file mode 100644
index 0000000..49fd3b3
--- /dev/null
+++ b/device/bluetooth/bluetooth_gatt_characteristic.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/bluetooth/bluetooth_gatt_characteristic.h"
+
+#include "base/logging.h"
+
+namespace device {
+
+BluetoothGattCharacteristic::BluetoothGattCharacteristic() {
+}
+
+BluetoothGattCharacteristic::~BluetoothGattCharacteristic() {
+}
+
+// static
+BluetoothGattCharacteristic* BluetoothGattCharacteristic::Create(
+    const bluetooth_utils::UUID& uuid,
+    const std::vector<uint8>& value,
+    Properties properties,
+    Permissions permissions) {
+  LOG(ERROR) << "Creating local GATT characteristics currently not supported.";
+  return NULL;
+}
+
+}  // namespace device
diff --git a/device/bluetooth/bluetooth_gatt_characteristic.h b/device/bluetooth/bluetooth_gatt_characteristic.h
new file mode 100644
index 0000000..00f7a88
--- /dev/null
+++ b/device/bluetooth/bluetooth_gatt_characteristic.h
@@ -0,0 +1,195 @@
+// 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_GATT_CHARACTERISTIC_H_
+#define DEVICE_BLUETOOTH_GATT_CHARACTERISTIC_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "device/bluetooth/bluetooth_utils.h"
+
+namespace device {
+
+class BluetoothGattDescriptor;
+class BluetoothGattService;
+
+// BluetoothGattCharacteristic represents a local or remote GATT characteristic.
+// A GATT characteristic is a basic data element used to construct a GATT
+// service. Hence, instances of a BluetoothGattCharacteristic are associated
+// with a BluetoothGattService. There are two ways in which this class is used:
+//
+//   1. To represent GATT characteristics that belong to a service hosted by a
+//      a remote device. In this case the characteristic will be constructed by
+//      the subsystem.
+//   2. To represent GATT characteristics that belong to a locally hosted
+//      service. To achieve this, users can construct instances of
+//      BluetoothGattCharacteristic directly and add it to the desired
+//      BluetoothGattService instance that represents a local service.
+class BluetoothGattCharacteristic {
+ public:
+  // Values representing the possible properties of a characteristic, which
+  // define how the characteristic can be used. Each of these properties serve
+  // a role as defined in the Bluetooth Specification.
+  // |kPropertyExtendedProperties| is a special property that, if present,
+  // indicates that there is a characteristic descriptor (namely the
+  // "Characteristic Extended Properties Descriptor" with UUID 0x2900) that
+  // contains additional properties pertaining to the characteristic.
+  enum Property {
+    kPropertyNone = 0,
+    kPropertyBroadcast = 1 << 0,
+    kPropertyRead = 1 << 1,
+    kPropertyWriteWithoutResponse = 1 << 2,
+    kPropertyWrite = 1 << 3,
+    kPropertyNotify = 1 << 4,
+    kPropertyIndicate = 1 << 5,
+    kPropertyAuthenticatedSignedWrites = 1 << 6,
+    kPropertyExtendedProperties = 1 << 7
+  };
+  typedef uint32 Properties;
+
+  // Values representing read, write, and encryption permissions for a
+  // characteristic's value. While attribute permissions for all GATT
+  // definitions have been set by the Bluetooth specification, characteristic
+  // value permissions are left up to the higher-level profile.
+  //
+  // Attribute permissions are distinct from the characteristic properties. For
+  // example, a characteristic may have the property |kPropertyRead| to make
+  // clients know that it is possible to read the characteristic value and have
+  // the permission |kPermissionReadEncrypted| to require a secure connection.
+  // It is up to the application to properly specify the permissions and
+  // properties for a local characteristic.
+  enum Permission {
+    kPermissionNone = 0,
+    kPermissionRead = 1 << 0,
+    kPermissionWrite = 1 << 1,
+    kPermissionReadEncrypted = 1 << 2,
+    kPermissionWriteEncrypted = 1 << 3
+  };
+  typedef uint32 Permissions;
+
+  // Interface for observing changes from a BluetoothGattCharacteristic.
+  // Properties of remote characteristics are received asynchonously. The
+  // Observer interface can be used to be notified when the initial values of a
+  // characteristic are received as well as when successive changes occur during
+  // its life cycle.
+  class Observer {
+   public:
+    // Called when the UUID of |characteristic| has changed.
+    virtual void UuidChanged(
+        BluetoothGattCharacteristic* characteristic,
+        const bluetooth_utils::UUID& uuid) {}
+
+    // Called when the current value of |characteristic| has changed.
+    virtual void ValueChanged(
+        BluetoothGattCharacteristic* characteristic,
+        const std::vector<uint8>& value) {}
+
+    // Called when the descriptors that are associated with |characteristic|
+    // have changed.
+    virtual void DescriptorsChanged(
+        BluetoothGattCharacteristic* characteristic,
+        const std::vector<BluetoothGattDescriptor*>& descriptors) {}
+  };
+
+  // The ErrorCallback is used by methods to asynchronously report errors.
+  typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+  // The ValueCallback is used to return the value of a remote characteristic
+  // upon a read request.
+  typedef base::Callback<void(const std::vector<uint8>&)> ValueCallback;
+
+  // Adds and removes observers for events on this GATT characteristic. If
+  // monitoring multiple characteristics, check the |characteristic| parameter
+  // of observer methods to determine which characteristic is issuing the event.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // Constructs a BluetoothGattCharacteristic that can be associated with a
+  // local GATT service when the adapter is in the peripheral role. To
+  // associate the returned characteristic with a service, add it to a local
+  // service by calling BluetoothGattService::AddCharacteristic.
+  //
+  // This method constructs a characteristic with UUID |uuid|, initial cached
+  // value |value|, properties |properties|, and permissions |permissions|.
+  // |value| will be cached and returned for read requests and automatically set
+  // for write requests by default, unless an instance of
+  // BluetoothGattService::Delegate has been provided to the associated
+  // BluetoothGattService instance, in which case the delegate will handle read
+  // and write requests.
+  //
+  // NOTE: Don't explicitly set |kPropertyExtendedProperties| in |properties|.
+  // Instead, create and add a BluetoothGattDescriptor that represents the
+  // "Characteristic Extended Properties" descriptor and this will automatically
+  // set the correspoding bit in the characteristic's properties field. If
+  // |properties| has |kPropertyExtendedProperties| set, it will be ignored.
+  static BluetoothGattCharacteristic* Create(const bluetooth_utils::UUID& uuid,
+                                             const std::vector<uint8>& value,
+                                             Properties properties,
+                                             Permissions permissions);
+
+  // The Bluetooth-specific UUID of the characteristic.
+  virtual const bluetooth_utils::UUID& GetUuid() const = 0;
+
+  // Returns true, if this characteristic is hosted locally. If false, then this
+  // instance represents a remote GATT characteristic.
+  virtual bool IsLocal() const = 0;
+
+  // Returns a pointer to the GATT service this characteristic belongs to.
+  virtual const BluetoothGattService* GetService() const = 0;
+
+  // Returns the list of GATT characteristic descriptors that provide more
+  // information about this characteristic.
+  virtual const std::vector<BluetoothGattDescriptor*>
+      GetDescriptors() const = 0;
+
+  // Adds a characteristic descriptor to the locally hosted characteristic
+  // represented by this instance. This method only makes sense for local
+  // characteristics and won't have an effect if this instance represents a
+  // remote GATT service and will return false. This method takes ownership
+  // of |descriptor|.
+  virtual bool AddDescriptor(BluetoothGattDescriptor* descriptor) = 0;
+
+  // For locally hosted characteristics, updates the characteristic's value.
+  // This will update the value that is visible to remote devices and send out
+  // any notifications and indications that have been configured. This method
+  // can be used in place of, and in conjunction with,
+  // BluetoothGattService::Delegate methods to send updates to remote devices,
+  // or simply to set update the cached value for read requests without having
+  // to implement the delegate methods.
+  //
+  // This method only makes sense for local characteristics and does nothing and
+  // returns false if this instance represents a remote characteristic.
+  virtual bool UpdateValue(const std::vector<uint8>& value) = 0;
+
+  // Sends a read request to a remote characteristic to read its value.
+  // |callback| is called to return the read value on success and
+  // |error_callback| is called for failures.
+  virtual void ReadRemoteCharacteristic(
+      const ValueCallback& callback,
+      const ErrorCallback& error_callback) = 0;
+
+  // Sends a write request to a remote characteristic, to modify the
+  // characteristic's value starting at offset |offset| with the new value
+  // |new_value|. |callback| is called to signal success and |error_callback|
+  // for failures. This method only applies to remote characteristics and will
+  // fail for those that are locally hosted.
+  virtual void WriteRemoteCharacteristic(
+      int offset,
+      const std::vector<uint8>& new_value,
+      const base::Closure& callback,
+      const ErrorCallback& error_callback) = 0;
+
+ protected:
+  BluetoothGattCharacteristic();
+  virtual ~BluetoothGattCharacteristic();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BluetoothGattCharacteristic);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_BLUETOOTH_GATT_CHARACTERISTIC_H_
diff --git a/device/bluetooth/bluetooth_gatt_descriptor.cc b/device/bluetooth/bluetooth_gatt_descriptor.cc
new file mode 100644
index 0000000..4746cad
--- /dev/null
+++ b/device/bluetooth/bluetooth_gatt_descriptor.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/bluetooth/bluetooth_gatt_descriptor.h"
+
+#include "base/logging.h"
+
+namespace device {
+
+using bluetooth_utils::UUID;
+
+const UUID BluetoothGattDescriptor::
+    kCharacteristicExtendedPropertiesUuid("0x2900");
+const UUID BluetoothGattDescriptor::
+    kCharacteristicUserDescriptionUuid("0x2901");
+const UUID BluetoothGattDescriptor::
+    kClientCharacteristicConfigurationUuid("0x2902");
+const UUID BluetoothGattDescriptor::
+    kServerCharacteristicConfigurationUuid("0x2903");
+const UUID BluetoothGattDescriptor::
+    kCharacteristicPresentationFormatUuid("0x2904");
+const UUID BluetoothGattDescriptor::
+    kCharacteristicAggregateFormatUuid("0x2905");
+
+BluetoothGattDescriptor::BluetoothGattDescriptor() {
+}
+
+BluetoothGattDescriptor::~BluetoothGattDescriptor() {
+}
+
+// static
+BluetoothGattDescriptor* BluetoothGattDescriptor::Create(
+    const bluetooth_utils::UUID& uuid,
+    const std::vector<uint8>& value) {
+  LOG(ERROR) << "Creating local GATT characteristic descriptors currently not "
+             << "supported.";
+  return NULL;
+}
+
+}  // namespace device
diff --git a/device/bluetooth/bluetooth_gatt_descriptor.h b/device/bluetooth/bluetooth_gatt_descriptor.h
new file mode 100644
index 0000000..9c0a1d2
--- /dev/null
+++ b/device/bluetooth/bluetooth_gatt_descriptor.h
@@ -0,0 +1,197 @@
+// 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_GATT_DESCRIPTOR_H_
+#define DEVICE_BLUETOOTH_GATT_DESCRIPTOR_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "device/bluetooth/bluetooth_utils.h"
+
+namespace device {
+
+class BluetoothGattCharacteristic;
+
+// BluetoothGattDescriptor represents a local or remote GATT characteristic
+// descriptor. A GATT characteristic descriptor provides further information
+// about a characteristic's value. They can be used to describe the
+// characteristic's features or to control certain behaviors.
+class BluetoothGattDescriptor {
+ public:
+  // The Bluetooth Specification declares several predefined descriptors that
+  // profiles can use. The following are definitions for the list of UUIDs
+  // and descriptions of the characteristic descriptors that they represent.
+  // Possible values for and further information on each descriptor can be found
+  // in Core v4.0, Volume 3, Part G, Section 3.3.3. All of these desciptors are
+  // optional and may not be present for a given characteristic.
+
+  // The "Characteristic Extended Properties" descriptor. This defines
+  // additional "Characteristic Properties" which cannot fit into the allocated
+  // single octet property field of a characteristic. The value is a bit field
+  // and the two predefined bits, as per Bluetooth Core Specification v4.0, are:
+  //
+  //    - Reliable Write: 0x0001
+  //    - Writable Auxiliaries: 0x0002
+  //
+  static const bluetooth_utils::UUID kCharacteristicExtendedPropertiesUuid;
+
+  // The "Characteristic User Description" descriptor defines a UTF-8 string of
+  // variable size that is a user textual description of the associated
+  // characteristic's value. There can be only one instance of this descriptor
+  // per characteristic. This descriptor can be written to if the "Writable
+  // Auxiliaries" bit of the Characteristic Properties (via the "Characteristic
+  // Extended Properties" descriptor) has been set.
+  static const bluetooth_utils::UUID kCharacteristicUserDescriptionUuid;
+
+  // The "Client Characteristic Configuration" descriptor defines how the
+  // characteristic may be configured by a specific client. A server-side
+  // instance of this descriptor exists for each client that has bonded with
+  // the server and the value can be read and written by that client only. As
+  // of Core v4.0, this descriptor is used by clients to set up notifications
+  // and indications from a characteristic. The value is a bit field and the
+  // predefined bits are:
+  //
+  //    - Default: 0x0000
+  //    - Notification: 0x0001
+  //    - Indication: 0x0002
+  //
+  static const bluetooth_utils::UUID kClientCharacteristicConfigurationUuid;
+
+  // The "Server Characteristic Configuration" descriptor defines how the
+  // characteristic may be configured for the server. There is one instance
+  // of this descriptor for all clients and setting the value of this descriptor
+  // affects its configuration for all clients. As of Core v4.0, this descriptor
+  // is used to set up the server to broadcast the characteristic value if
+  // advertising resources are available. The value is a bit field and the
+  // predefined bits are:
+  //
+  //    - Default: 0x0000
+  //    - Broadcast: 0x0001
+  //
+  static const bluetooth_utils::UUID kServerCharacteristicConfigurationUuid;
+
+  // The "Characteristic Presentation Format" declaration defines the format of
+  // the Characteristic Value. The value is composed of 7 octets which are
+  // divided into groups that represent different semantic meanings. For a
+  // detailed description of how the value of this descriptor should be
+  // interpreted, refer to Core v4.0, Volume 3, Part G, Section 3.3.3.5. If more
+  // than one declaration of this descriptor exists for a characteristic, then a
+  // "Characteristic Aggregate Format" descriptor must also exist for that
+  // characteristic.
+  static const bluetooth_utils::UUID kCharacteristicPresentationFormatUuid;
+
+  // The "Characteristic Aggregate Format" descriptor defines the format of an
+  // aggragated characteristic value. In GATT's underlying protocol, ATT, each
+  // attribute is identified by a handle that is unique for the hosting server.
+  // Multiple characteristics can share the same instance(s) of a
+  // "Characteristic Presentation Format" descriptor. The value of the
+  // "Characteristic Aggregate Format" descriptor contains a list of handles
+  // that each refer to a "Characteristic Presentation Format" descriptor that
+  // is used by that characteristic. Hence, exactly one instance of this
+  // descriptor must exist if more than one "Characteristic Presentation Format"
+  // descriptors exist for a characteristic.
+  //
+  // Applications that are using the device::Bluetooth API do not have access to
+  // the underlying handles and shouldn't use this descriptor to determine which
+  // "Characteristic Presentation Format" desciptors belong to a characteristic.
+  // The API will construct a BluetoothGattDescriptor object for each instance
+  // of "Characteristic Presentation Format" descriptor per instance of
+  // BluetoothGattCharacteristic that represents a remote characteristic.
+  // Similarly for local characteristics, implementations DO NOT need to create
+  // an instance of BluetoothGattDescriptor for this descriptor as this will be
+  // handled by the subsystem.
+  static const bluetooth_utils::UUID kCharacteristicAggregateFormatUuid;
+
+  // Interface for observing changes from a BluetoothGattDescriptor.
+  // Properties of remote characteristic desciptors are received asynchonously.
+  // The Observer interface can be used to be notified when the initial values
+  // of a characteristic descriptor are received as well as when successive
+  // changes occur during its life cycle.
+  class Observer {
+   public:
+    // Called when the UUID of |descriptor| has changed.
+    virtual void UuidChanged(
+        BluetoothGattDescriptor* descriptor,
+        const bluetooth_utils::UUID& uuid) {}
+
+    // Called when the current value of |descriptor| has changed.
+    virtual void ValueChanged(
+        BluetoothGattDescriptor* descriptor,
+        const std::vector<uint8>& value) {}
+  };
+
+  // The ErrorCallback is used by methods to asynchronously report errors.
+  typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+  // The ValueCallback is used to return the value of a remote characteristic
+  // descriptor upon a read request.
+  typedef base::Callback<void(const std::vector<uint8>&)> ValueCallback;
+
+  // Adds and removes observers for events on this GATT characteristic
+  // descriptor. If monitoring multiple descriptors, check the |descriptor|
+  // parameter of observer methods to determine which characteristic is issuing
+  // the event.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // Constructs a BluetoothGattDescriptor that can be associated with a local
+  // GATT characteristic when the adapter is in the peripheral role. To
+  // associate the returned descriptor with a characteristic, add it to a local
+  // characteristic by calling BluetoothGattCharacteristic::AddDescriptor.
+  //
+  // This method constructs a characteristic descriptor with UUID |uuid| and the
+  // initial cached value |value|. |value| will be cached and returned for read
+  // requests and automatically modified for write requests by default, unless
+  // an instance of BluetoothGattService::Delegate has been provided to the
+  // associated BluetoothGattService instance, in which case the delegate will
+  // handle the read and write requests.
+  //
+  // Currently, only custom UUIDs, |kCharacteristicDescriptionUuid|, and
+  // |kCharacteristicPresentationFormat| are supported for locally hosted
+  // descriptors. This method will return NULL if |uuid| is any one of the
+  // unsupported predefined descriptor UUIDs.
+  static BluetoothGattDescriptor* Create(const bluetooth_utils::UUID& uuid,
+                                         const std::vector<uint8>& value);
+
+  // The Bluetooth-specific UUID of the characteristic descriptor.
+  virtual const bluetooth_utils::UUID& GetUuid() const = 0;
+
+  // Returns true, if this characteristic descriptor is hosted locally. If
+  // false, then this instance represents a remote descriptor.
+  virtual bool IsLocal() const = 0;
+
+  // Returns a pointer to the GATT characteristic that this characteristic
+  // descriptor belongs to.
+  virtual const BluetoothGattCharacteristic* GetCharacteristic() const = 0;
+
+  // Sends a read request to a remote characteristic descriptor to read its
+  // value. |callback| is called to return the read value on success and
+  // |error_callback| is called for failures.
+  virtual void ReadRemoteDescriptor(const ValueCallback& callback,
+                                    const ErrorCallback& error_callback) = 0;
+
+  // Sends a write request to a remote characteristic descriptor, to modify the
+  // value of the descriptor starting at offset |offset| with the new value
+  // |new_value|. |callback| is called to signal success and |error_callback|
+  // for failures. This method only applies to remote descriptors and will fail
+  // for those that are locally hosted.
+  virtual void WriteRemoteDescriptor(
+      int offset,
+      const std::vector<uint8>& new_value,
+      const base::Closure& callback,
+      const ErrorCallback& error_callback) = 0;
+
+ protected:
+  BluetoothGattDescriptor();
+  virtual ~BluetoothGattDescriptor();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BluetoothGattDescriptor);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_BLUETOOTH_GATT_DESCRIPTOR_H_
diff --git a/device/bluetooth/bluetooth_gatt_service.cc b/device/bluetooth/bluetooth_gatt_service.cc
new file mode 100644
index 0000000..dccb06a
--- /dev/null
+++ b/device/bluetooth/bluetooth_gatt_service.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/bluetooth/bluetooth_gatt_service.h"
+
+#include "base/logging.h"
+
+namespace device {
+
+BluetoothGattService::BluetoothGattService() {
+}
+
+BluetoothGattService::~BluetoothGattService() {
+}
+
+// static
+BluetoothGattService* BluetoothGattService::Create(
+    const bluetooth_utils::UUID& uuid,
+    bool is_primary,
+    Delegate* delegate) {
+  LOG(ERROR) << "Creating local GATT services currently not supported.";
+  return NULL;
+}
+
+}  // namespace device
diff --git a/device/bluetooth/bluetooth_gatt_service.h b/device/bluetooth/bluetooth_gatt_service.h
new file mode 100644
index 0000000..963fdd5
--- /dev/null
+++ b/device/bluetooth/bluetooth_gatt_service.h
@@ -0,0 +1,226 @@
+// 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_GATT_SERVICE_H_
+#define DEVICE_BLUETOOTH_GATT_SERVICE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "device/bluetooth/bluetooth_utils.h"
+
+namespace device {
+
+class BluetoothGattCharacteristic;
+class BluetoothGattDescriptor;
+
+// BluetoothGattService represents a local or remote GATT service. A GATT
+// service is hosted by a peripheral and represents a collection of data in
+// the form of GATT characteristics and a set of included GATT services if this
+// service is what is called "a primary service".
+//
+// Instances of the BluetoothGattService class are used for two functions:
+//   1. To represent GATT attribute hierarchies that have been received from a
+//      remote Bluetooth GATT peripheral. Such BluetoothGattService instances
+//      are constructed and owned by a BluetoothDevice.
+//
+//   2. To represent a locally hosted GATT attribute hierarchy when the local
+//      adapter is used in the "peripheral" role. Such instances are meant to be
+//      constructed directly and registered. Once registered, a GATT attribute
+//      hierarchy will be visible to remote devices in the "central" role.
+class BluetoothGattService {
+ public:
+  // The Delegate class is used to send certain events that need to be handled
+  // when the device is in peripheral mode. The delegate handles read and write
+  // requests that are issued from remote clients.
+  class Delegate {
+   public:
+    // Callbacks used for communicating GATT request responses.
+    typedef base::Callback<void(const std::vector<uint8>)> ValueCallback;
+    typedef base::Closure ErrorCallback;
+
+    // Called when a remote device in the central role requests to read the
+    // value of the characteristic |characteristic| starting at offset |offset|.
+    // This method is only called if the characteristic was specified as
+    // readable and any authentication and authorization challanges were
+    // satisfied by the remote device.
+    //
+    // To respond to the request with success and return the requested value,
+    // the delegate must invoke |callback| with the value. Doing so will
+    // automatically update the value property of |characteristic|. To respond
+    // to the request with failure (e.g. if an invalid offset was given),
+    // delegates must invoke |error_callback|. If neither callback parameter is
+    // invoked, the request will time out and result in an error. Therefore,
+    // delegates MUST invoke either |callback| or |error_callback|.
+    virtual void OnCharacteristicReadRequest(
+        const BluetoothGattService* service,
+        const BluetoothGattCharacteristic* characteristic,
+        int offset,
+        const ValueCallback& callback,
+        const ErrorCallback& error_callback) = 0;
+
+    // Called when a remote device in the central role requests to write the
+    // value of the characteristic |characteristic| starting at offset |offset|.
+    // This method is only called if the characteristic was specified as
+    // writeable and any authentication and authorization challanges were
+    // satisfied by the remote device.
+    //
+    // To respond to the request with success the delegate must invoke
+    // |callback| with the new value of the characteristic. Doing so will
+    // automatically update the value property of |characteristic|. To respond
+    // to the request with failure (e.g. if an invalid offset was given),
+    // delegates must invoke |error_callback|. If neither callback parameter is
+    // invoked, the request will time out and result in an error. Therefore,
+    // delegates MUST invoke either |callback| or |error_callback|.
+    virtual void OnCharacteristicWriteRequest(
+        const BluetoothGattService* service,
+        const BluetoothGattCharacteristic* characteristic,
+        const std::vector<uint8>& value,
+        int offset,
+        const ValueCallback& callback,
+        const ErrorCallback& error_callback) = 0;
+
+    // Called when a remote device in the central role requests to read the
+    // value of the descriptor |descriptor| starting at offset |offset|.
+    // This method is only called if the characteristic was specified as
+    // readable and any authentication and authorization challanges were
+    // satisfied by the remote device.
+    //
+    // To respond to the request with success and return the requested value,
+    // the delegate must invoke |callback| with the value. Doing so will
+    // automatically update the value property of |descriptor|. To respond
+    // to the request with failure (e.g. if an invalid offset was given),
+    // delegates must invoke |error_callback|. If neither callback parameter is
+    // invoked, the request will time out and result in an error. Therefore,
+    // delegates MUST invoke either |callback| or |error_callback|.
+    virtual void OnDescriptorReadRequest(
+        const BluetoothGattService* service,
+        const BluetoothGattDescriptor* descriptor,
+        int offset,
+        const ValueCallback& callback,
+        const ErrorCallback& error_callback) = 0;
+
+    // Called when a remote device in the central role requests to write the
+    // value of the descriptor |descriptor| starting at offset |offset|.
+    // This method is only called if the characteristic was specified as
+    // writeable and any authentication and authorization challanges were
+    // satisfied by the remote device.
+    //
+    // To respond to the request with success the delegate must invoke
+    // |callback| with the new value of the descriptor. Doing so will
+    // automatically update the value property of |descriptor|. To respond
+    // to the request with failure (e.g. if an invalid offset was given),
+    // delegates must invoke |error_callback|. If neither callback parameter is
+    // invoked, the request will time out and result in an error. Therefore,
+    // delegates MUST invoke either |callback| or |error_callback|.
+    virtual void OnDescriptorWriteRequest(
+        const BluetoothGattService* service,
+        const BluetoothGattDescriptor* descriptor,
+        const std::vector<uint8>& value,
+        int offset,
+        const ValueCallback& callback,
+        const ErrorCallback& error_callback) = 0;
+  };
+
+  // Interface for observing changes from a BluetoothGattService. Properties
+  // of remote services are received asynchronously. The Observer interface can
+  // be used to be notified when the initial values of a service are received
+  // as well as when successive changes occur during its life cycle.
+  class Observer {
+   public:
+    // Called when the UUID of |service| have changed.
+    virtual void UuidChanged(
+        BluetoothGattService* service,
+        const bluetooth_utils::UUID& uuid) {}
+
+    // Called when the services included by |service| have changed.
+    virtual void IncludedServicesChanged(
+        BluetoothGattService* service,
+        const std::vector<BluetoothGattService*>& included_services) {}
+
+    // Called when the characteristics that belong to |service| have changed.
+    virtual void CharacteristicsChanged(
+        BluetoothGattService* service,
+        const std::vector<BluetoothGattCharacteristic*>& characteristics) {}
+  };
+
+  // The ErrorCallback is used by methods to asynchronously report errors.
+  typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+  // Constructs a BluetoothGattService that can be locally hosted when the local
+  // adapter is in the peripheral role. The resulting object can then be made
+  // available by calling the "Register" method. This method constructs a
+  // service with UUID |uuid|. Whether the constructed service is primary or
+  // secondary is determined by |is_primary|. |delegate| is used to send certain
+  // peripheral role events. If |delegate| is NULL, then this service will
+  // employ a default behavior when responding to read and write requests based
+  // on the cached value of its characteristics and descriptors at a given time.
+  static BluetoothGattService* Create(const bluetooth_utils::UUID& uuid,
+                                      bool is_primary,
+                                      Delegate* delegate);
+
+  // The Bluetooth-specific UUID of the service.
+  virtual const bluetooth_utils::UUID& GetUuid() const = 0;
+
+  // Returns true, if this service hosted locally. If false, then this service
+  // represents a remote GATT service.
+  virtual bool IsLocal() const = 0;
+
+  // Indicates whether the type of this service is primary or secondary. A
+  // primary service describes the primary function of the peripheral that
+  // hosts it, while a secondary service only makes sense in the presence of a
+  // primary service. A primary service may include other primary or secondary
+  // services.
+  virtual bool IsPrimary() const = 0;
+
+  // List of characteristics that belong to this service.
+  virtual const std::vector<BluetoothGattCharacteristic*>&
+      GetCharacteristics() const = 0;
+
+  // List of GATT services that are included by this service.
+  virtual const std::vector<BluetoothGattService*>&
+      GetIncludedServices() const = 0;
+
+  // Adds and removes observers for events on this GATT service. If monitoring
+  // multiple services, check the |service| parameter of observer methods to
+  // determine which service is issuing the event.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // Adds characteristics and included services to the local attribute hierarchy
+  // represented by this service. These methods only make sense for local
+  // services and won't have an effect if this instance represents a remote
+  // GATT service and will return false. While ownership of added
+  // characteristics are taken over by the service, ownership of an included
+  // service is not taken.
+  virtual bool AddCharacteristic(
+      BluetoothGattCharacteristic* characteristic) = 0;
+  virtual bool AddService(BluetoothGattService* service) = 0;
+
+  // Registers this GATT service. Calling Register will make this service and
+  // all of its associated attributes available on the local adapters GATT
+  // database and the service UUID will be advertised to nearby devices if the
+  // local adapter is discoverable. Call Unregister to make this service no
+  // longer available.
+  //
+  // These methods only make sense for services that are local and will hence
+  // fail if this instance represents a remote GATT service. |callback| is
+  // called to denote success and |error_callback| to denote failure.
+  virtual void Register(const base::Closure& callback,
+                        const ErrorCallback& error_callback) = 0;
+  virtual void Unregister(const base::Closure& callback,
+                          const ErrorCallback& error_callback) = 0;
+
+ protected:
+  BluetoothGattService();
+  virtual ~BluetoothGattService();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BluetoothGattService);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_BLUETOOTH_GATT_SERVICE_H_
diff --git a/device/bluetooth/bluetooth_profile_chromeos_unittest.cc b/device/bluetooth/bluetooth_profile_chromeos_unittest.cc
index 1792cda..dd7c945 100644
--- a/device/bluetooth/bluetooth_profile_chromeos_unittest.cc
+++ b/device/bluetooth/bluetooth_profile_chromeos_unittest.cc
@@ -31,8 +31,7 @@
 class BluetoothProfileChromeOSTest : public testing::Test {
  public:
   BluetoothProfileChromeOSTest()
-      : message_loop_(base::MessageLoop::TYPE_IO),
-        callback_count_(0),
+      : callback_count_(0),
         error_callback_count_(0),
         profile_callback_count_(0),
         connection_callback_count_(0),
@@ -102,7 +101,7 @@
   }
 
  protected:
-  base::MessageLoop message_loop_;
+  base::MessageLoopForIO message_loop_;
 
   FakeBluetoothProfileManagerClient* fake_bluetooth_profile_manager_client_;
   scoped_refptr<BluetoothAdapter> adapter_;
diff --git a/device/bluetooth/bluetooth_utils.cc b/device/bluetooth/bluetooth_utils.cc
index b1f9f78..df0c875 100644
--- a/device/bluetooth/bluetooth_utils.cc
+++ b/device/bluetooth/bluetooth_utils.cc
@@ -10,49 +10,103 @@
 #include "base/logging.h"
 #include "base/strings/string_util.h"
 
-namespace {
-static const char* kCommonUuidPostfix = "-0000-1000-8000-00805f9b34fb";
-static const char* kCommonUuidPrefix = "0000";
-static const int kUuidSize = 36;
-}  // namespace
-
 namespace device {
 namespace bluetooth_utils {
 
-std::string CanonicalUuid(std::string uuid) {
+namespace {
+
+const char* kCommonUuidPostfix = "-0000-1000-8000-00805f9b34fb";
+const char* kCommonUuidPrefix = "0000";
+const int kUuidSize = 36;
+
+// Returns the canonical, 128-bit canonical, and the format of the UUID
+// in |canonical|, |canonical_128|, and |format| based on |uuid|.
+void GetCanonicalUuid(std::string uuid,
+                      std::string* canonical,
+                      std::string* canonical_128,
+                      UUID::Format* format) {
+  // Initialize the values for the failure case.
+  canonical->clear();
+  canonical_128->clear();
+  *format = UUID::kFormatInvalid;
+
   if (uuid.empty())
-    return std::string();
+    return;
 
   if (uuid.size() < 11 && uuid.find("0x") == 0)
     uuid = uuid.substr(2);
 
   if (!(uuid.size() == 4 || uuid.size() == 8 || uuid.size() == 36))
-    return std::string();
+    return;
 
   if (uuid.size() == 4 || uuid.size() == 8) {
     for (size_t i = 0; i < uuid.size(); ++i) {
       if (!IsHexDigit(uuid[i]))
-        return std::string();
+        return;
     }
-
-    if (uuid.size() == 4)
-      return kCommonUuidPrefix + uuid + kCommonUuidPostfix;
-
-    return uuid + kCommonUuidPostfix;
+    if (uuid.size() == 4) {
+      canonical->assign(uuid);
+      canonical_128->assign(kCommonUuidPrefix + uuid + kCommonUuidPostfix);
+      *format = UUID::kFormat16Bit;
+      return;
+    }
+    canonical->assign(uuid);
+    canonical_128->assign(uuid + kCommonUuidPostfix);
+    *format = UUID::kFormat32Bit;
+    return;
   }
 
-  std::string uuid_result(uuid);
   for (int i = 0; i < kUuidSize; ++i) {
     if (i == 8 || i == 13 || i == 18 || i == 23) {
       if (uuid[i] != '-')
-        return std::string();
+        return;
     } else {
       if (!IsHexDigit(uuid[i]))
-        return std::string();
-      uuid_result[i] = tolower(uuid[i]);
+        return;
+      uuid[i] = tolower(uuid[i]);
     }
   }
-  return uuid_result;
+
+  canonical->assign(uuid);
+  canonical_128->assign(uuid);
+  *format = UUID::kFormat128Bit;
+}
+
+}  // namespace
+
+
+UUID::UUID(const std::string& uuid) {
+  GetCanonicalUuid(uuid, &value_, &canonical_value_, &format_);
+}
+
+UUID::UUID() : format_(kFormatInvalid) {
+}
+
+UUID::~UUID() {
+}
+
+bool UUID::IsValid() const {
+  return format_ != kFormatInvalid;
+}
+
+bool UUID::operator<(const UUID& uuid) const {
+  return canonical_value_ < uuid.canonical_value_;
+}
+
+bool UUID::operator==(const UUID& uuid) const {
+  return canonical_value_ == uuid.canonical_value_;
+}
+
+bool UUID::operator!=(const UUID& uuid) const {
+  return canonical_value_ != uuid.canonical_value_;
+}
+
+std::string CanonicalUuid(std::string uuid) {
+  std::string value;
+  std::string canonical_value;
+  UUID::Format format;
+  GetCanonicalUuid(uuid, &value, &canonical_value, &format);
+  return canonical_value;
 }
 
 }  // namespace bluetooth_utils
diff --git a/device/bluetooth/bluetooth_utils.h b/device/bluetooth/bluetooth_utils.h
index 0995160..44b52b1 100644
--- a/device/bluetooth/bluetooth_utils.h
+++ b/device/bluetooth/bluetooth_utils.h
@@ -12,6 +12,84 @@
 namespace device {
 namespace bluetooth_utils {
 
+// Opaque wrapper around a Bluetooth UUID. Instances of UUID represent the
+// 128-bit universally unique identifiers (UUIDs) of profiles and attributes
+// used in Bluetooth based communication, such as a peripheral's services,
+// characteristics, and characteristic descriptors. An instance are
+// constructed using a string representing 16, 32, or 128 bit UUID formats.
+class UUID {
+ public:
+  // Possible representation formats used during construction.
+  enum Format {
+    kFormatInvalid,
+    kFormat16Bit,
+    kFormat32Bit,
+    kFormat128Bit
+  };
+
+  // Single argument constructor. |uuid| can be a 16, 32, or 128 bit UUID
+  // represented as a 4, 8, or 36 character string with the following
+  // formats:
+  //   XXXX
+  //   0xXXXX
+  //   XXXXXXXX
+  //   0xXXXXXXXX
+  //   XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+  //
+  // 16 and 32 bit UUIDs will be internally converted to a 128 bit UUID using
+  // the base UUID defined in the Bluetooth specification, hence custom UUIDs
+  // should be provided in the 128-bit format. If |uuid| is in an unsupported
+  // format, the result might be invalid. Use IsValid to check for validity
+  // after construction.
+  explicit UUID(const std::string& uuid);
+  ~UUID();
+
+  // Returns true, if the UUID is in a valid canonical format.
+  bool IsValid() const;
+
+  // Returns the representation format of the UUID. This reflects the format
+  // that was provided during construction.
+  Format format() const { return format_; }
+
+  // Returns the value of the UUID as a string. The representation format is
+  // based on what was passed in during construction. For the supported sizes,
+  // this representation can have the following formats:
+  //   - 16 bit:  XXXX
+  //   - 32 bit:  XXXXXXXX
+  //   - 128 bit: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+  // where X is a lowercase hex digit.
+  const std::string& value() const { return value_; }
+
+  // Returns the underlying 128-bit value as a string in the following format:
+  //   XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+  // where X is a lowercase hex digit.
+  const std::string& canonical_value() const { return canonical_value_; }
+
+  // Permit sufficient comparison to allow a UUID to be used as a key in a
+  // std::map.
+  bool operator<(const UUID& uuid) const;
+
+  // Equality operators.
+  bool operator==(const UUID& uuid) const;
+  bool operator!=(const UUID& uuid) const;
+
+ private:
+  UUID();
+
+  // String representation of the UUID that was used during construction. For
+  // the supported sizes, this representation can have the following formats:
+  //   - 16 bit:  XXXX
+  //   - 32 bit:  XXXXXXXX
+  //   - 128 bit: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+  Format format_;
+  std::string value_;
+
+  // The 128-bit string representation of the UUID.
+  std::string canonical_value_;
+};
+
+// DEPRECATED. Use bluetooth_utils::UUID instead.
+//
 // Takes a 4, 8 or 36 character UUID, validates it and returns it in 36
 // character format with all hex digits lower case.  If |uuid| is invalid, the
 // empty string is returned.
diff --git a/device/bluetooth/bluetooth_utils_unittest.cc b/device/bluetooth/bluetooth_utils_unittest.cc
index 46d5f34..07c63b5 100644
--- a/device/bluetooth/bluetooth_utils_unittest.cc
+++ b/device/bluetooth/bluetooth_utils_unittest.cc
@@ -7,47 +7,100 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace device {
+namespace bluetooth_utils {
 
 TEST(BluetoothUtilsTest, CanonicalUuid) {
   // Does nothing for an already canonical UUID
   EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb",
-      bluetooth_utils::CanonicalUuid("00001101-0000-1000-8000-00805f9b34fb"));
+      CanonicalUuid("00001101-0000-1000-8000-00805f9b34fb"));
 
   // Rejects misformatted
-  EXPECT_EQ("", bluetooth_utils::CanonicalUuid("1101a"));
-  EXPECT_EQ("", bluetooth_utils::CanonicalUuid("Z101"));
-  EXPECT_EQ("", bluetooth_utils::CanonicalUuid("0000-1101"));
-  EXPECT_EQ("", bluetooth_utils::CanonicalUuid("0000Z101"));
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("0001101-0000-1000-8000-00805f9b34fb"));
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("Z0001101-0000-1000-8000-00805f9b34fb"));
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("00001101 0000-1000-8000-00805f9b34fb"));
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("00001101-0000:1000-8000-00805f9b34fb"));
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("00001101-0000-1000;8000-00805f9b34fb"));
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("00001101-0000-1000-8000000805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("1101a"));
+  EXPECT_EQ("", CanonicalUuid("Z101"));
+  EXPECT_EQ("", CanonicalUuid("0000-1101"));
+  EXPECT_EQ("", CanonicalUuid("0000Z101"));
+  EXPECT_EQ("", CanonicalUuid("0001101-0000-1000-8000-00805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("Z0001101-0000-1000-8000-00805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("00001101 0000-1000-8000-00805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("00001101-0000:1000-8000-00805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("00001101-0000-1000;8000-00805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("00001101-0000-1000-8000000805f9b34fb"));
 
   // Lower case
   EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb",
-      bluetooth_utils::CanonicalUuid("00001101-0000-1000-8000-00805F9B34FB"));
+      CanonicalUuid("00001101-0000-1000-8000-00805F9B34FB"));
 
   // Short to full
   EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb",
-      bluetooth_utils::CanonicalUuid("1101"));
+      CanonicalUuid("1101"));
   EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb",
-      bluetooth_utils::CanonicalUuid("0x1101"));
+      CanonicalUuid("0x1101"));
   EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb",
-      bluetooth_utils::CanonicalUuid("00001101"));
+      CanonicalUuid("00001101"));
   EXPECT_EQ("00001101-0000-1000-8000-00805f9b34fb",
-      bluetooth_utils::CanonicalUuid("0x00001101"));
+      CanonicalUuid("0x00001101"));
 
   // No 0x prefix on 36 character
-  EXPECT_EQ("",
-      bluetooth_utils::CanonicalUuid("0x00001101-0000-1000-8000-00805f9b34fb"));
+  EXPECT_EQ("", CanonicalUuid("0x00001101-0000-1000-8000-00805f9b34fb"));
 }
 
+TEST(BluetoothUtilsTest, UUID) {
+  const char kValid128Bit0[] = "12345678-1234-5678-9abc-def123456789";
+  const char kValid128Bit1[] = "00001101-0000-1000-8000-00805f9b34fb";
+  const char kInvalid36Char[] = "1234567-1234-5678-9abc-def123456789";
+  const char kInvalid4Char[] = "Z101";
+  const char kValid16Bit[] = "0x1101";
+  const char kValid32Bit[] = "00001101";
+
+  // Valid 128-bit custom UUID.
+  UUID uuid0(kValid128Bit0);
+  EXPECT_TRUE(uuid0.IsValid());
+  EXPECT_EQ(UUID::kFormat128Bit, uuid0.format());
+  EXPECT_EQ(uuid0.value(), uuid0.canonical_value());
+
+  // Valid 128-bit UUID.
+  UUID uuid1(kValid128Bit1);
+  EXPECT_TRUE(uuid1.IsValid());
+  EXPECT_EQ(UUID::kFormat128Bit, uuid1.format());
+  EXPECT_EQ(uuid1.value(), uuid1.canonical_value());
+
+  EXPECT_NE(uuid0, uuid1);
+
+  // Invalid 128-bit UUID.
+  UUID uuid2(kInvalid36Char);
+  EXPECT_FALSE(uuid2.IsValid());
+  EXPECT_EQ(UUID::kFormatInvalid, uuid2.format());
+  EXPECT_TRUE(uuid2.value().empty());
+  EXPECT_TRUE(uuid2.canonical_value().empty());
+
+  // Invalid 16-bit UUID.
+  UUID uuid3(kInvalid4Char);
+  EXPECT_FALSE(uuid3.IsValid());
+  EXPECT_EQ(UUID::kFormatInvalid, uuid3.format());
+  EXPECT_TRUE(uuid3.value().empty());
+  EXPECT_TRUE(uuid3.canonical_value().empty());
+
+  // Valid 16-bit UUID.
+  UUID uuid4(kValid16Bit);
+  EXPECT_TRUE(uuid4.IsValid());
+  EXPECT_EQ(UUID::kFormat16Bit, uuid4.format());
+  EXPECT_NE(uuid4.value(), uuid4.canonical_value());
+  EXPECT_EQ("1101", uuid4.value());
+  EXPECT_EQ(kValid128Bit1, uuid4.canonical_value());
+
+  // Valid 32-bit UUID.
+  UUID uuid5(kValid32Bit);
+  EXPECT_TRUE(uuid5.IsValid());
+  EXPECT_EQ(UUID::kFormat32Bit, uuid5.format());
+  EXPECT_NE(uuid5.value(), uuid5.canonical_value());
+  EXPECT_EQ("00001101", uuid5.value());
+  EXPECT_EQ(kValid128Bit1, uuid5.canonical_value());
+
+  // uuid4, uuid5, and uuid1 are equivalent.
+  EXPECT_EQ(uuid4, uuid5);
+  EXPECT_EQ(uuid1, uuid4);
+  EXPECT_EQ(uuid1, uuid5);
+}
+
+}  // namespace bluetooth_utils
 }  // namespace device
diff --git a/device/bluetooth/test/mock_bluetooth_adapter.h b/device/bluetooth/test/mock_bluetooth_adapter.h
index d2c9878..0266f03 100644
--- a/device/bluetooth/test/mock_bluetooth_adapter.h
+++ b/device/bluetooth/test/mock_bluetooth_adapter.h
@@ -35,11 +35,20 @@
   MOCK_METHOD1(RemoveObserver, void(BluetoothAdapter::Observer*));
   MOCK_CONST_METHOD0(GetAddress, std::string());
   MOCK_CONST_METHOD0(GetName, std::string());
+  MOCK_METHOD3(SetName,
+               void(const std::string& name,
+                    const base::Closure& callback,
+                    const ErrorCallback& error_callback));
   MOCK_CONST_METHOD0(IsInitialized, bool());
   MOCK_CONST_METHOD0(IsPresent, bool());
   MOCK_CONST_METHOD0(IsPowered, bool());
   MOCK_METHOD3(SetPowered,
-               void(bool discovering,
+               void(bool powered,
+                    const base::Closure& callback,
+                    const ErrorCallback& error_callback));
+  MOCK_CONST_METHOD0(IsDiscoverable, bool());
+  MOCK_METHOD3(SetDiscoverable,
+               void(bool discoverable,
                     const base::Closure& callback,
                     const ErrorCallback& error_callback));
   MOCK_CONST_METHOD0(IsDiscovering, bool());
diff --git a/device/bluetooth/test/mock_bluetooth_device.cc b/device/bluetooth/test/mock_bluetooth_device.cc
index b4c6cfe..c1fe56f 100644
--- a/device/bluetooth/test/mock_bluetooth_device.cc
+++ b/device/bluetooth/test/mock_bluetooth_device.cc
@@ -33,7 +33,7 @@
   ON_CALL(*this, IsConnecting())
       .WillByDefault(testing::Return(false));
   ON_CALL(*this, GetName())
-      .WillByDefault(testing::Return(UTF8ToUTF16(name_)));
+      .WillByDefault(testing::Return(base::UTF8ToUTF16(name_)));
   ON_CALL(*this, ExpectingPinCode())
       .WillByDefault(testing::Return(false));
   ON_CALL(*this, ExpectingPasskey())
diff --git a/device/bluetooth/test/mock_bluetooth_device.h b/device/bluetooth/test/mock_bluetooth_device.h
index 6020579..de2907d 100644
--- a/device/bluetooth/test/mock_bluetooth_device.h
+++ b/device/bluetooth/test/mock_bluetooth_device.h
@@ -32,7 +32,7 @@
   MOCK_CONST_METHOD0(GetVendorID, uint16());
   MOCK_CONST_METHOD0(GetProductID, uint16());
   MOCK_CONST_METHOD0(GetDeviceID, uint16());
-  MOCK_CONST_METHOD0(GetName, string16());
+  MOCK_CONST_METHOD0(GetName, base::string16());
   MOCK_CONST_METHOD0(GetDeviceType, BluetoothDevice::DeviceType());
   MOCK_CONST_METHOD0(IsPaired, bool());
   MOCK_CONST_METHOD0(IsConnected, bool());
diff --git a/device/device_tests.gyp b/device/device_tests.gyp
index e0564b5..268b270 100644
--- a/device/device_tests.gyp
+++ b/device/device_tests.gyp
@@ -19,6 +19,7 @@
         'bluetooth/bluetooth.gyp:device_bluetooth_mocks',
         'nfc/nfc.gyp:device_nfc',
         'usb/usb.gyp:device_usb',
+        'hid/hid.gyp:device_hid',
       ],
       'sources': [
         'bluetooth/bluetooth_adapter_mac_unittest.mm',
@@ -33,6 +34,8 @@
         'nfc/nfc_chromeos_unittest.cc',
         'nfc/nfc_ndef_record_unittest.cc',
         'usb/usb_ids_unittest.cc',
+        'hid/hid_connection_unittest.cc',
+        'hid/hid_service_unittest.cc',
       ],
       'conditions': [
         ['chromeos==1', {
diff --git a/device/hid/DEPS b/device/hid/DEPS
new file mode 100644
index 0000000..6a2f02e
--- /dev/null
+++ b/device/hid/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+net/base",
+]
diff --git a/device/hid/hid.gyp b/device/hid/hid.gyp
new file mode 100644
index 0000000..c5c3684
--- /dev/null
+++ b/device/hid/hid.gyp
@@ -0,0 +1,47 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'device_hid',
+      'type': 'static_library',
+      'include_dirs': [
+        '../..',
+      ],
+      'conditions': [
+        ['OS=="linux"', {
+          'dependencies': [
+            '../../build/linux/system.gyp:udev',
+          ],
+        }],
+      ],
+      'sources': [
+        'hid_connection.cc',
+        'hid_connection.h',
+        'hid_connection_linux.cc',
+        'hid_connection_linux.h',
+        'hid_connection_mac.cc',
+        'hid_connection_mac.h',
+        'hid_connection_win.cc',
+        'hid_connection_win.h',
+        'hid_device_info.cc',
+        'hid_device_info.h',
+        'hid_service.cc',
+        'hid_service.h',
+        'hid_service_linux.cc',
+        'hid_service_linux.h',
+        'hid_service_mac.cc',
+        'hid_service_mac.h',
+        'hid_service_win.cc',
+        'hid_service_win.h',
+        'hid_utils_mac.cc',
+        'hid_utils_mac.h',
+      ],
+    },
+  ],
+}
diff --git a/device/hid/hid_connection.cc b/device/hid/hid_connection.cc
new file mode 100644
index 0000000..5678407
--- /dev/null
+++ b/device/hid/hid_connection.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_connection.h"
+
+namespace device {
+
+HidConnection::HidConnection(HidDeviceInfo device_info)
+    : device_info_(device_info) {}
+
+HidConnection::~HidConnection() {}
+
+const HidDeviceInfo& HidConnection::device_info() const {
+  return device_info_;
+}
+
+}  // namespace device
diff --git a/device/hid/hid_connection.h b/device/hid/hid_connection.h
new file mode 100644
index 0000000..27792c4
--- /dev/null
+++ b/device/hid/hid_connection.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_CONNECTION_H_
+#define DEVICE_HID_HID_CONNECTION_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "device/hid/hid_device_info.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace device {
+
+class HidConnection : public base::RefCountedThreadSafe<HidConnection> {
+ public:
+  typedef base::Callback<void(bool success, size_t size)> IOCallback;
+
+  virtual void Read(scoped_refptr<net::IOBuffer> buffer,
+                    size_t size,
+                    const IOCallback& callback) = 0;
+  virtual void Write(scoped_refptr<net::IOBuffer> buffer,
+                     size_t size,
+                     const IOCallback& callback) = 0;
+  virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                size_t size,
+                                const IOCallback& callback) = 0;
+  virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                 size_t size,
+                                 const IOCallback& callback) = 0;
+
+  const HidDeviceInfo& device_info() const;
+
+ protected:
+  friend class base::RefCountedThreadSafe<HidConnection>;
+  friend struct HidDeviceInfo;
+
+  HidConnection(HidDeviceInfo device_info);
+  virtual ~HidConnection();
+
+  const HidDeviceInfo device_info_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidConnection);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_CONNECTION_H_
diff --git a/device/hid/hid_connection_linux.cc b/device/hid/hid_connection_linux.cc
new file mode 100644
index 0000000..0722fab
--- /dev/null
+++ b/device/hid/hid_connection_linux.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_connection_linux.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libudev.h>
+#include <linux/hidraw.h>
+#include <string>
+
+#include "base/threading/thread_restrictions.h"
+#include "base/tuple.h"
+#include "device/hid/hid_service.h"
+#include "device/hid/hid_service_linux.h"
+
+
+namespace device {
+
+namespace {
+
+const char kHidrawSubsystem[] = "hidraw";
+
+}  // namespace
+
+HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info,
+                                       ScopedUdevDevicePtr udev_raw_device)
+    : HidConnection(device_info),
+      initialized_(false) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  udev_device* dev = udev_raw_device.get();
+  std::string dev_node;
+  if (!FindHidrawDevNode(dev, &dev_node)) {
+    LOG(ERROR) << "Cannot open HID device as hidraw device.";
+    return;
+  }
+
+  base::PlatformFileError error;
+
+  int flags = base::PLATFORM_FILE_OPEN |
+              base::PLATFORM_FILE_READ |
+              base::PLATFORM_FILE_WRITE |
+              base::PLATFORM_FILE_EXCLUSIVE_READ |
+              base::PLATFORM_FILE_EXCLUSIVE_WRITE;
+
+  base::PlatformFile device_file = base::CreatePlatformFile(
+      base::FilePath(dev_node),
+      flags,
+      NULL,
+      &error);
+  if (error || device_file <= 0) {
+    LOG(ERROR) << error;
+    if (device_file)
+      base::ClosePlatformFile(device_file);
+    return;
+  }
+  if (fcntl(device_file, F_SETFL, fcntl(device_file, F_GETFL) | O_NONBLOCK)) {
+    PLOG(ERROR) << "Failed to set non-blocking flag to device file.";
+    return;
+  }
+  device_file_ = device_file;
+
+  if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+      device_file_,
+      true,
+      base::MessageLoopForIO::WATCH_READ_WRITE,
+      &device_file_watcher_,
+      this)) {
+    LOG(ERROR) << "Cannot start watching file descriptor.";
+    return;
+  }
+
+  initialized_ = true;
+}
+
+HidConnectionLinux::~HidConnectionLinux() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  Disconnect();
+}
+
+void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_EQ(fd, device_file_);
+  DCHECK(initialized_);
+
+  uint8 buffer[1024] = {0};
+  int bytes = read(device_file_, buffer, 1024);
+  if (bytes < 0) {
+    if (errno == EAGAIN) {
+      return;
+    }
+    Disconnect();
+    return;
+  }
+  scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(bytes));
+  memcpy(io_buffer->data(), buffer, bytes);
+  input_reports_.push(std::make_pair(io_buffer, bytes));
+
+  ProcessReadQueue();
+}
+
+void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {}
+
+void HidConnectionLinux::Disconnect() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!initialized_)
+    return;
+
+  initialized_ = false;
+  device_file_watcher_.StopWatchingFileDescriptor();
+  close(device_file_);
+  while (!read_queue_.empty()) {
+    PendingRequest callback = read_queue_.front();
+    read_queue_.pop();
+    callback.c.Run(false, 0);
+  }
+}
+
+void HidConnectionLinux::Read(scoped_refptr<net::IOBuffer> buffer,
+                              size_t size,
+                              const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!initialized_) {
+    DCHECK(read_queue_.empty());
+    // There might be unread reports.
+    if (!input_reports_.empty()){
+      read_queue_.push(MakeTuple(buffer, size, callback));
+      ProcessReadQueue();
+    }
+    callback.Run(false, 0);
+    return;
+  } else {
+    read_queue_.push(MakeTuple(buffer, size, callback));
+    ProcessReadQueue();
+  }
+}
+
+void HidConnectionLinux::Write(scoped_refptr<net::IOBuffer> buffer,
+                               size_t size,
+                               const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!initialized_) {
+    callback.Run(false, 0);
+    return;
+  } else {
+    int bytes = write(device_file_, buffer->data(), size);
+    if (bytes < 0) {
+      Disconnect();
+      callback.Run(false, 0);
+    } else {
+      callback.Run(true, bytes);
+    }
+  }
+}
+
+void HidConnectionLinux::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                          size_t size,
+                                          const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!initialized_) {
+    callback.Run(false, 0);
+    return;
+  }
+  NOTIMPLEMENTED();
+}
+
+void HidConnectionLinux::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                           size_t size,
+                                           const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!initialized_) {
+    callback.Run(false, 0);
+    return;
+  }
+  NOTIMPLEMENTED();
+}
+
+void HidConnectionLinux::ProcessReadQueue() {
+  while(read_queue_.size() && input_reports_.size()) {
+    PendingRequest request = read_queue_.front();
+    read_queue_.pop();
+    PendingReport report = input_reports_.front();
+    if (report.second > request.b) {
+      request.c.Run(false, report.second);
+    } else {
+      memcpy(request.a->data(), report.first->data(), report.second);
+      input_reports_.pop();
+      request.c.Run(true, report.second);
+    }
+  }
+}
+
+bool HidConnectionLinux::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;
+  }
+
+  const char* parent_path = udev_device_get_devpath(parent);
+  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());
+    if (strncmp(parent_path,
+                udev_device_get_devpath(hid_dev.get()),
+                strlen(parent_path)) == 0 &&
+        raw_path) {
+      *result = raw_path;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+}  // namespace device
diff --git a/device/hid/hid_connection_linux.h b/device/hid/hid_connection_linux.h
new file mode 100644
index 0000000..37d6cb0
--- /dev/null
+++ b/device/hid/hid_connection_linux.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_CONNECTION_LINUX_H_
+#define DEVICE_HID_HID_CONNECTION_LINUX_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/platform_file.h"
+#include "base/tuple.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service_linux.h"
+#include "net/base/io_buffer.h"
+
+namespace device {
+
+class HidConnectionLinux : public HidConnection,
+                           public base::MessagePumpLibevent::Watcher {
+ public:
+  HidConnectionLinux(HidDeviceInfo device_info,
+                     ScopedUdevDevicePtr udev_raw_device);
+
+  virtual void Read(scoped_refptr<net::IOBuffer> buffer,
+                    size_t size,
+                    const IOCallback& callback) OVERRIDE;
+  virtual void Write(scoped_refptr<net::IOBuffer> buffer,
+                     size_t size,
+                     const IOCallback& callback) OVERRIDE;
+  virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                size_t size,
+                                const IOCallback& callback) OVERRIDE;
+  virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                 size_t size,
+                                 const IOCallback& callback) OVERRIDE;
+
+  bool initialized() const { return initialized_; }
+
+  // Implements base::MessagePumpLibevent::Watcher
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ private:
+  friend class base::RefCountedThreadSafe<HidConnectionLinux>;
+  virtual ~HidConnectionLinux();
+
+  static bool FindHidrawDevNode(udev_device* parent, std::string* result);
+
+  void ProcessReadQueue();
+  void Disconnect();
+
+  base::PlatformFile device_file_;
+  base::MessagePumpLibevent::FileDescriptorWatcher device_file_watcher_;
+
+  typedef std::pair<scoped_refptr<net::IOBuffer>, size_t> PendingReport;
+  typedef Tuple3<scoped_refptr<net::IOBuffer>, size_t, IOCallback>
+      PendingRequest;
+
+  std::queue<PendingReport> input_reports_;
+  std::queue<PendingRequest> read_queue_;
+
+  bool initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidConnectionLinux);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_CONNECTION_LINUX__
diff --git a/device/hid/hid_connection_mac.cc b/device/hid/hid_connection_mac.cc
new file mode 100644
index 0000000..bce7113
--- /dev/null
+++ b/device/hid/hid_connection_mac.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_connection_mac.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/mac/foundation_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/tuple.h"
+#include "device/hid/hid_service.h"
+#include "device/hid/hid_service_mac.h"
+#include "net/base/io_buffer.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+namespace device {
+
+HidConnectionMac::HidConnectionMac(HidServiceMac* service,
+                                   HidDeviceInfo device_info,
+                                   IOHIDDeviceRef device)
+    : HidConnection(device_info),
+      service_(service),
+      device_(device),
+      disconnected_(false) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  message_loop_ = base::MessageLoopProxy::current();
+
+  CFRetain(device);
+  inbound_buffer_.reset((uint8_t*) malloc(device_info.input_report_size + 1));
+  IOHIDDeviceRegisterInputReportCallback(
+      device_.get(),
+      inbound_buffer_.get(),
+      device_info.input_report_size + 1,
+      &HidConnectionMac::InputReportCallback,
+      this);
+  IOHIDDeviceOpen(device_, kIOHIDOptionsTypeNone);
+}
+HidConnectionMac::~HidConnectionMac() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  while (read_queue_.size()) {
+    read_queue_.front().c.Run(false, 0);
+    read_queue_.pop();
+  }
+
+  IOHIDDeviceClose(device_, kIOHIDOptionsTypeNone);
+}
+
+void HidConnectionMac::InputReportCallback(void * context,
+                                           IOReturn result,
+                                           void * sender,
+                                           IOHIDReportType type,
+                                           uint32_t reportID,
+                                           uint8_t * report,
+                                           CFIndex reportLength) {
+  HidConnectionMac* connection = reinterpret_cast<HidConnectionMac*>(context);
+  size_t length = reportLength + (reportID != 0);
+  scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(length));
+  if (reportID) {
+    buffer->data()[0] = reportID;
+    memcpy(buffer->data() + 1, report, reportLength);
+  } else {
+    memcpy(buffer->data(), report, reportLength);
+  }
+  connection->message_loop_->PostTask(
+      FROM_HERE,
+      base::Bind(&HidConnectionMac::ProcessInputReport,
+                 connection,
+                 type,
+                 buffer,
+                 length));
+}
+
+void HidConnectionMac::ProcessReadQueue() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  while(read_queue_.size() && input_reports_.size()) {
+    PendingRead read = read_queue_.front();
+    read_queue_.pop();
+    PendingReport report = input_reports_.front();
+
+    if (read.b < report.second) {
+      read.c.Run(false, report.second);
+    } else {
+      memcpy(read.a->data(), report.first->data(), report.second);
+      input_reports_.pop();
+      read.c.Run(true, report.second);
+    }
+  }
+}
+
+void HidConnectionMac::ProcessInputReport(IOHIDReportType type,
+                                          scoped_refptr<net::IOBuffer> report,
+                                          CFIndex reportLength) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  input_reports_.push(std::make_pair(report, reportLength));
+  ProcessReadQueue();
+}
+
+void HidConnectionMac::WriteReport(IOHIDReportType type,
+                                   scoped_refptr<net::IOBuffer> buffer,
+                                   size_t size,
+                                   const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (disconnected_ || !device_) {
+    callback.Run(false, 0);
+    return;
+  }
+  const unsigned char* data_to_send =
+      reinterpret_cast<const unsigned char*>(buffer->data());
+  size_t length_to_send = size;
+  if (data_to_send[0] == 0x0) {
+      /* Not using numbered Reports.
+       Don't send the report number. */
+      ++data_to_send;
+      --length_to_send;
+  }
+  IOReturn res = IOHIDDeviceSetReport(device_.get(),
+                                      type,
+                                      buffer->data()[0], /* Report ID*/
+                                      data_to_send,
+                                      length_to_send);
+  if (res != kIOReturnSuccess) {
+    callback.Run(false, 0);
+  } else {
+    callback.Run(true, size);
+  }
+}
+
+void HidConnectionMac::Read(scoped_refptr<net::IOBuffer> buffer,
+                            size_t size,
+                            const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (disconnected_ || !device_) {
+    callback.Run(false, 0);
+    return;
+  }
+  read_queue_.push(MakeTuple(buffer, size, callback));
+  ProcessReadQueue();
+}
+
+void HidConnectionMac::Write(scoped_refptr<net::IOBuffer> buffer,
+                             size_t size,
+                             const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  WriteReport(kIOHIDReportTypeOutput, buffer, size, callback);
+}
+
+void HidConnectionMac::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                         size_t size,
+                                         const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  WriteReport(kIOHIDReportTypeFeature, buffer, size, callback);
+}
+
+void HidConnectionMac::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                        size_t size,
+                                        const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (disconnected_ || !device_ || device_info_.feature_report_size == 0) {
+    callback.Run(false, 0);
+    return;
+  }
+
+  if (device_info_.feature_report_size != 0 &&
+      device_info_.feature_report_size != size) {
+    callback.Run(false, 0);
+    return;
+  }
+
+  CFIndex len = device_info_.feature_report_size;
+  IOReturn res = IOHIDDeviceGetReport(device_,
+                                      kIOHIDReportTypeFeature,
+                                      0,
+                                      (uint8_t*) buffer->data(),
+                                      &len);
+  if (res == kIOReturnSuccess)
+    callback.Run(true, len);
+  else
+    callback.Run(false, 0);
+}
+
+}  // namespace device
diff --git a/device/hid/hid_connection_mac.h b/device/hid/hid_connection_mac.h
new file mode 100644
index 0000000..730eeae
--- /dev/null
+++ b/device/hid/hid_connection_mac.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_CONNECTION_MAC_H_
+#define DEVICE_HID_HID_CONNECTION_MAC_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/tuple.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service_mac.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace device {
+
+class HidConnectionMac : public HidConnection {
+ public:
+  HidConnectionMac(HidServiceMac* service,
+                   HidDeviceInfo device_info,
+                   IOHIDDeviceRef device);
+
+  virtual void Read(scoped_refptr<net::IOBuffer> buffer,
+                    size_t size,
+                    const IOCallback& callback) OVERRIDE;
+  virtual void Write(scoped_refptr<net::IOBuffer> buffer,
+                     size_t size,
+                     const IOCallback& callback) OVERRIDE;
+  virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                size_t size,
+                                const IOCallback& callback) OVERRIDE;
+  virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                 size_t size,
+                                 const IOCallback& callback) OVERRIDE;
+
+ private:
+  virtual ~HidConnectionMac();
+
+  static void InputReportCallback(void * context,
+                                  IOReturn result,
+                                  void * sender,
+                                  IOHIDReportType type,
+                                  uint32_t reportID,
+                                  uint8_t * report,
+                                  CFIndex reportLength);
+  void ProcessReadQueue();
+  void ProcessInputReport(IOHIDReportType type,
+                          scoped_refptr<net::IOBuffer> report,
+                          CFIndex reportLength);
+
+  void WriteReport(IOHIDReportType type,
+                   scoped_refptr<net::IOBuffer> buffer,
+                   size_t size,
+                   const IOCallback& callback);
+
+  HidServiceMac* service_;
+  scoped_refptr<base::MessageLoopProxy> message_loop_;
+  base::ScopedCFTypeRef<IOHIDDeviceRef> device_;
+  scoped_ptr_malloc<uint8_t> inbound_buffer_;
+  bool disconnected_;
+
+  typedef std::pair<scoped_refptr<net::IOBuffer>, size_t> PendingReport;
+  std::queue<PendingReport> input_reports_;
+  typedef Tuple3<scoped_refptr<net::IOBuffer>, size_t, IOCallback> PendingRead;
+  std::queue<PendingRead> read_queue_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidConnectionMac);
+};
+
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_CONNECTION_MAC_H_
diff --git a/device/hid/hid_connection_unittest.cc b/device/hid/hid_connection_unittest.cc
new file mode 100644
index 0000000..14a2ef9
--- /dev/null
+++ b/device/hid/hid_connection_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_service.h"
+#include "net/base/io_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+namespace {
+
+using net::IOBuffer;
+
+const int kUSBLUFADemoVID = 0x03eb;
+const int kUSBLUFADemoPID = 0x204f;
+const uint64_t kReport = 0x0903a65d030f8ec9ULL;
+
+int g_read_times = 0;
+void Read(scoped_refptr<HidConnection> conn);
+
+void OnRead(scoped_refptr<HidConnection> conn,
+            scoped_refptr<net::IOBuffer> buffer,
+            bool success,
+            size_t bytes) {
+  EXPECT_TRUE(success);
+  if (success) {
+    g_read_times++;
+    EXPECT_EQ(8U, bytes);
+    if (bytes == 8) {
+      uint64_t* data = reinterpret_cast<uint64_t*>(buffer->data());
+      EXPECT_EQ(kReport, *data);
+    } else {
+      base::MessageLoop::current()->Quit();
+    }
+  } else {
+    LOG(ERROR) << "~";
+    g_read_times++;
+  }
+
+  if (g_read_times < 3){
+    base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(Read, conn));
+  } else {
+    base::MessageLoop::current()->Quit();
+  }
+}
+
+void Read(scoped_refptr<HidConnection> conn) {
+  scoped_refptr<IOBuffer> buffer(new IOBuffer(8));
+  conn->Read(buffer, 8, base::Bind(OnRead, conn, buffer));
+}
+
+void OnWriteNormal(bool success,
+                   size_t bytes) {
+  ASSERT_TRUE(success);
+  base::MessageLoop::current()->Quit();
+}
+
+void WriteNormal(scoped_refptr<HidConnection> conn) {
+  scoped_refptr<IOBuffer> buffer(new IOBuffer(8));
+  *(int64_t*)buffer->data() = kReport;
+
+  conn->Write(buffer, 8, base::Bind(OnWriteNormal));
+}
+
+}  // namespace
+
+class HidConnectionTest : public testing::Test {
+ protected:
+  virtual void SetUp() OVERRIDE {
+    message_loop_.reset(new base::MessageLoopForIO());
+    service_.reset(HidService::CreateInstance());
+    ASSERT_TRUE(service_);
+
+    std::vector<HidDeviceInfo> devices;
+    service_->GetDevices(&devices);
+    for (std::vector<HidDeviceInfo>::iterator it = devices.begin();
+        it != devices.end();
+        ++it) {
+      if (it->vendor_id == kUSBLUFADemoVID &&
+          it->product_id == kUSBLUFADemoPID) {
+        device_id_ = it->device_id;
+        return;
+      }
+    }
+  }
+
+  virtual void TearDown() OVERRIDE {
+    service_.reset(NULL);
+    message_loop_.reset(NULL);
+  }
+
+  std::string device_id_;
+  scoped_ptr<base::MessageLoopForIO> message_loop_;
+  scoped_ptr<HidService> service_;
+};
+
+TEST_F(HidConnectionTest, Create) {
+  scoped_refptr<HidConnection> connection = service_->Connect(device_id_);
+  ASSERT_TRUE(connection || device_id_.empty());
+}
+
+TEST_F(HidConnectionTest, Read) {
+  scoped_refptr<HidConnection> connection = service_->Connect(device_id_);
+
+  if (!device_id_.empty()) {
+    ASSERT_TRUE(connection);
+    message_loop_->PostTask(FROM_HERE, base::Bind(Read, connection));
+    message_loop_->Run();
+  }
+}
+
+TEST_F(HidConnectionTest, Write) {
+  scoped_refptr<HidConnection> connection = service_->Connect(device_id_);
+
+  if (!device_id_.empty()) {
+    ASSERT_TRUE(connection);
+    message_loop_->PostTask(FROM_HERE, base::Bind(WriteNormal, connection));
+    message_loop_->Run();
+  }
+}
+
+}  // namespace device
diff --git a/device/hid/hid_connection_win.cc b/device/hid/hid_connection_win.cc
new file mode 100644
index 0000000..bbb158f
--- /dev/null
+++ b/device/hid/hid_connection_win.cc
@@ -0,0 +1,279 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_connection_win.h"
+
+#include <cstring>
+
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "device/hid/hid_service.h"
+#include "device/hid/hid_service_win.h"
+#include "net/base/io_buffer.h"
+
+#if defined(OS_WIN)
+
+#define INITGUID
+
+#include <windows.h>
+#include <hidclass.h>
+
+extern "C" {
+
+#include <hidsdi.h>
+
+}
+
+#include <setupapi.h>
+#include <winioctl.h>
+#include "base/win/scoped_handle.h"
+
+#endif  // defined(OS_WIN)
+
+namespace device {
+
+HidConnectionWin::PendingTransfer::PendingTransfer(
+    scoped_refptr<HidConnectionWin> conn,
+    scoped_refptr<net::IOBuffer> target,
+    scoped_refptr<net::IOBuffer> receiving,
+    bool is_input,
+    IOCallback callback)
+    : conn_(conn),
+      is_input_(is_input),
+      target_(target),
+      receiving_(receiving),
+      callback_(callback),
+      event_(CreateEvent(NULL, FALSE, FALSE, NULL)) {
+  memset(&overlapped_, 0, sizeof(OVERLAPPED));
+  overlapped_.hEvent = event_.Get();
+}
+HidConnectionWin::PendingTransfer::~PendingTransfer() {
+  base::MessageLoop::current()->RemoveDestructionObserver(this);
+}
+
+void HidConnectionWin::PendingTransfer::TakeResultFromWindowsAPI(BOOL result) {
+  if (result || GetLastError() != ERROR_IO_PENDING) {
+    conn_->OnTransferFinished(this);
+  } else {
+    base::MessageLoop::current()->AddDestructionObserver(this);
+    AddRef();
+    watcher_.StartWatching(event_.Get(), this);
+  }
+}
+
+void HidConnectionWin::PendingTransfer::OnObjectSignaled(HANDLE event_handle) {
+  conn_->OnTransferFinished(this);
+  Release();
+}
+
+void HidConnectionWin::PendingTransfer::WillDestroyCurrentMessageLoop() {
+  watcher_.StopWatching();
+  conn_->OnTransferCanceled(this);
+}
+
+void HidConnectionWin::OnTransferFinished(
+    scoped_refptr<PendingTransfer> transfer) {
+  DWORD bytes_transfered;
+  transfers_.erase(transfer);
+  if (GetOverlappedResult(file_,
+                          transfer->GetOverlapped(),
+                          &bytes_transfered,
+                          FALSE)) {
+    if (transfer->is_input_ && !device_info_.has_report_id) {
+      // Move one byte forward.
+      --bytes_transfered;
+      memcpy(transfer->target_->data(),
+             transfer->receiving_->data() + 1,
+             bytes_transfered);
+    }
+    transfer->callback_.Run(true, bytes_transfered);
+  } else {
+    transfer->callback_.Run(false, 0);
+  }
+}
+
+void HidConnectionWin::OnTransferCanceled(
+    scoped_refptr<PendingTransfer> transfer) {
+  transfers_.erase(transfer);
+  transfer->callback_.Run(false, 0);
+}
+
+HidConnectionWin::HidConnectionWin(HidDeviceInfo device_info)
+    : HidConnection(device_info),
+      available_(false) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  file_.Set(CreateFileA(device_info.device_id.c_str(),
+                        GENERIC_WRITE | GENERIC_READ,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE,
+                        NULL,
+                        OPEN_EXISTING,
+                        FILE_FLAG_OVERLAPPED,
+                        NULL));
+  available_ = file_.IsValid();
+}
+
+HidConnectionWin::~HidConnectionWin() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  CancelIo(file_.Get());
+}
+
+void HidConnectionWin::Read(scoped_refptr<net::IOBuffer> buffer,
+                            size_t size,
+                            const HidConnection::IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  size_t report_size = device_info_.input_report_size;
+  if (report_size == 0) {
+    // The device does not supoort input reports.
+    callback.Run(false, 0);
+    return;
+  }
+
+  if (size + !device_info_.has_report_id < report_size) {
+    // Buffer too short.
+    callback.Run(false, 0);
+    return;
+  }
+
+  scoped_refptr<net::IOBuffer> expanded_buffer;
+  if (!device_info_.has_report_id) {
+    ++size;
+    expanded_buffer = new net::IOBuffer(static_cast<int>(size));
+  }
+
+  scoped_refptr<PendingTransfer> transfer(
+      new PendingTransfer(this, buffer, expanded_buffer, true, callback));
+  transfers_.insert(transfer);
+  transfer->TakeResultFromWindowsAPI(ReadFile(file_.Get(),
+                                              device_info_.has_report_id ?
+                                                  buffer->data() :
+                                                  expanded_buffer->data(),
+                                              static_cast<DWORD>(size),
+                                              NULL,
+                                              transfer->GetOverlapped()));
+}
+
+void HidConnectionWin::Write(scoped_refptr<net::IOBuffer> buffer,
+                             size_t size,
+                             const HidConnection::IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  size_t report_size = device_info_.output_report_size;
+  if (report_size == 0) {
+    // The device does not supoort output reports.
+    callback.Run(false, 0);
+    return;
+  }
+
+  if (size + !device_info_.has_report_id > report_size) {
+    // Size of report too long.
+    callback.Run(false, 0);
+    return;
+  }
+
+  scoped_refptr<net::IOBuffer> expanded_buffer;
+  if (!device_info_.has_report_id) {
+    expanded_buffer = new net::IOBuffer(
+        static_cast<int>(device_info_.output_report_size));
+    memset(expanded_buffer->data(), 0, device_info_.output_report_size);
+    memcpy(expanded_buffer->data() + 1,
+           buffer->data(),
+           size);
+    size++;
+  }
+
+  scoped_refptr<PendingTransfer> transfer(
+      new PendingTransfer(this, buffer, expanded_buffer, false, callback));
+  transfers_.insert(transfer);
+  transfer->TakeResultFromWindowsAPI(
+      WriteFile(file_.Get(),
+                device_info_.has_report_id ?
+                    buffer->data() : expanded_buffer->data(),
+                static_cast<DWORD>(device_info_.output_report_size),
+                NULL,
+                transfer->GetOverlapped()));
+}
+
+void HidConnectionWin::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                        size_t size,
+                                        const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  size_t report_size = device_info_.feature_report_size;
+  if (report_size == 0) {
+    // The device does not supoort input reports.
+    callback.Run(false, 0);
+    return;
+  }
+
+  if (size + !device_info_.has_report_id < report_size) {
+    // Buffer too short.
+    callback.Run(false, 0);
+    return;
+  }
+
+  scoped_refptr<net::IOBuffer> expanded_buffer;
+  if (!device_info_.has_report_id) {
+    ++size;
+    expanded_buffer = new net::IOBuffer(static_cast<int>(size));
+  }
+
+  scoped_refptr<PendingTransfer> transfer(
+      new PendingTransfer(this, buffer, expanded_buffer, true, callback));
+  transfers_.insert(transfer);
+  transfer->TakeResultFromWindowsAPI(
+      DeviceIoControl(file_.Get(),
+                      IOCTL_HID_GET_FEATURE,
+                      NULL,
+                      0,
+                      device_info_.has_report_id ?
+                          buffer->data() :
+                          expanded_buffer->data(),
+                      static_cast<DWORD>(size),
+                      NULL,
+                      transfer->GetOverlapped()));
+}
+
+void HidConnectionWin::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                         size_t size,
+                                         const IOCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  size_t report_size = device_info_.feature_report_size;
+  if (report_size == 0) {
+    // The device does not supoort output reports.
+    callback.Run(false, 0);
+    return;
+  }
+
+  if (size + !device_info_.has_report_id > report_size) {
+    // Size of report too long.
+    callback.Run(false, 0);
+    return;
+  }
+
+  scoped_refptr<net::IOBuffer> expanded_buffer;
+  if (!device_info_.has_report_id) {
+    expanded_buffer = new net::IOBuffer(
+        static_cast<int>(device_info_.feature_report_size));
+    memset(expanded_buffer->data(), 0, device_info_.feature_report_size);
+    memcpy(expanded_buffer->data() + 1,
+           buffer->data(),
+           size);
+    size++;
+  }
+
+  scoped_refptr<PendingTransfer> transfer(
+      new PendingTransfer(this, buffer, expanded_buffer, false, callback));
+  transfer->TakeResultFromWindowsAPI(
+      DeviceIoControl(file_.Get(),
+                      IOCTL_HID_SET_FEATURE,
+                      device_info_.has_report_id ?
+                          buffer->data() :
+                          expanded_buffer->data(),
+                      static_cast<DWORD>(device_info_.output_report_size),
+                      NULL,
+                      0,
+                      NULL,
+                      transfer->GetOverlapped()));
+}
+
+}  // namespace device
diff --git a/device/hid/hid_connection_win.h b/device/hid/hid_connection_win.h
new file mode 100644
index 0000000..6cbb9e9
--- /dev/null
+++ b/device/hid/hid_connection_win.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_CONNECTION_WIN_H_
+#define DEVICE_HID_HID_CONNECTION_WIN_H_
+
+#include <set>
+#include <windows.h>
+
+#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/object_watcher.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_device_info.h"
+#include "net/base/io_buffer.h"
+
+namespace device {
+
+class HidConnectionWin : public HidConnection {
+ public:
+  struct PendingTransfer : public base::RefCounted<PendingTransfer>,
+                           public base::win::ObjectWatcher::Delegate,
+                           public base::MessageLoop::DestructionObserver {
+   public:
+    PendingTransfer(scoped_refptr<HidConnectionWin> conn,
+                    scoped_refptr<net::IOBuffer> target,
+                    scoped_refptr<net::IOBuffer> receiving,
+                    bool is_input,
+                    IOCallback callback);
+
+    void TakeResultFromWindowsAPI(BOOL result);
+
+    OVERLAPPED* GetOverlapped() { return &overlapped_; }
+
+    // Implements base::win::ObjectWatcher::Delegate.
+    virtual void OnObjectSignaled(HANDLE object) OVERRIDE;
+
+    // Implements base::MessageLoop::DestructionObserver
+    virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+
+   private:
+    friend class base::RefCounted<PendingTransfer>;
+    friend class HidConnectionWin;
+
+    virtual ~PendingTransfer();
+
+    scoped_refptr<HidConnectionWin> conn_;
+    bool is_input_;
+    scoped_refptr<net::IOBuffer> target_;
+    scoped_refptr<net::IOBuffer> receiving_;
+    IOCallback callback_;
+    OVERLAPPED overlapped_;
+    base::win::ScopedHandle event_;
+    base::win::ObjectWatcher watcher_;
+
+    DISALLOW_COPY_AND_ASSIGN(PendingTransfer);
+  };
+
+  HidConnectionWin(HidDeviceInfo device_info);
+
+  virtual void Read(scoped_refptr<net::IOBuffer> buffer,
+                    size_t size,
+                    const IOCallback& callback) OVERRIDE;
+  virtual void Write(scoped_refptr<net::IOBuffer> buffer,
+                     size_t size,
+                     const IOCallback& callback) OVERRIDE;
+  virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                size_t size,
+                                const IOCallback& callback) OVERRIDE;
+  virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+                                 size_t size,
+                                 const IOCallback& callback) OVERRIDE;
+
+  void OnTransferFinished(scoped_refptr<PendingTransfer> transfer);
+  void OnTransferCanceled(scoped_refptr<PendingTransfer> transfer);
+
+  bool available() const { return available_; }
+
+ private:
+  ~HidConnectionWin();
+
+  base::win::ScopedHandle file_;
+  std::set<scoped_refptr<PendingTransfer> > transfers_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidConnectionWin);
+
+  bool available_;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_CONNECTION_WIN_H_
diff --git a/device/hid/hid_device_info.cc b/device/hid/hid_device_info.cc
new file mode 100644
index 0000000..34ea686
--- /dev/null
+++ b/device/hid/hid_device_info.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_device_info.h"
+
+namespace device {
+
+HidDeviceInfo::HidDeviceInfo()
+    : bus_type(kHIDBusTypeUSB),
+      vendor_id(0),
+      product_id(0),
+      input_report_size(0),
+      output_report_size(0),
+      feature_report_size(0),
+      usage_page(0),
+      usage(0),
+      has_report_id(false) {}
+
+HidDeviceInfo::~HidDeviceInfo() {}
+
+}  // namespace device
diff --git a/device/hid/hid_device_info.h b/device/hid/hid_device_info.h
new file mode 100644
index 0000000..b665944
--- /dev/null
+++ b/device/hid/hid_device_info.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_DEVICE_INFO_H_
+#define DEVICE_HID_HID_DEVICE_INFO_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace device {
+
+enum HidBusType {
+  kHIDBusTypeUSB = 0,
+  kHIDBusTypeBluetooth = 1,
+};
+
+struct HidDeviceInfo {
+  HidDeviceInfo();
+  ~HidDeviceInfo();
+
+  std::string device_id;
+
+  HidBusType bus_type;
+  uint16 vendor_id;
+  uint16 product_id;
+
+  size_t input_report_size;
+  size_t output_report_size;
+  size_t feature_report_size;
+
+  uint16 usage_page;
+  uint16 usage;
+  bool has_report_id;
+
+  std::string product_name;
+  std::string serial_number;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_DEVICE_INFO_H_
diff --git a/device/hid/hid_service.cc b/device/hid/hid_service.cc
new file mode 100644
index 0000000..973e901
--- /dev/null
+++ b/device/hid/hid_service.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_service.h"
+
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "device/hid/hid_device_info.h"
+
+#if defined(OS_LINUX)
+#include "device/hid/hid_service_linux.h"
+#elif defined(OS_MACOSX)
+#include "device/hid/hid_service_mac.h"
+#else
+#include "device/hid/hid_service_win.h"
+#endif
+
+namespace device {
+
+namespace {
+
+// The instance will be reset when message loop destroys.
+base::LazyInstance<scoped_ptr<HidService> >::Leaky g_hid_service_ptr =
+    LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+HidService::HidService() : initialized_(false) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(thread_checker_.CalledOnValidThread());
+  base::MessageLoop::current()->AddDestructionObserver(this);
+}
+
+HidService::~HidService() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  base::MessageLoop::current()->RemoveDestructionObserver(this);
+}
+
+void HidService::WillDestroyCurrentMessageLoop() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  g_hid_service_ptr.Get().reset(NULL);
+}
+
+void HidService::GetDevices(std::vector<HidDeviceInfo>* devices) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  STLClearObject(devices);
+  for (DeviceMap::iterator it = devices_.begin();
+      it != devices_.end();
+      ++it) {
+    devices->push_back(it->second);
+  }
+}
+
+// Fills in the device info struct of the given device_id.
+bool HidService::GetInfo(std::string device_id, HidDeviceInfo* info) const {
+  DeviceMap::const_iterator it = devices_.find(device_id);
+  if (it == devices_.end())
+    return false;
+  *info = it->second;
+  return true;
+}
+
+void HidService::AddDevice(HidDeviceInfo info) {
+  if (!ContainsKey(devices_, info.device_id)) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    devices_[info.device_id] = info;
+  }
+}
+
+void HidService::RemoveDevice(std::string device_id) {
+  if (ContainsKey(devices_, device_id)) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    devices_.erase(device_id);
+  }
+}
+
+HidService* HidService::CreateInstance() {
+#if defined(OS_LINUX)
+    return new HidServiceLinux();
+#elif defined(OS_MACOSX)
+    return new HidServiceMac();
+#elif defined(OS_WIN)
+    return new HidServiceWin();
+#else
+    return NULL;
+#endif
+}
+
+HidService* HidService::GetInstance() {
+  if (!g_hid_service_ptr.Get().get()){
+    scoped_ptr<HidService> service(CreateInstance());
+
+    if (service && service->initialized())
+      g_hid_service_ptr.Get().reset(service.release());
+  }
+  return g_hid_service_ptr.Get().get();
+}
+
+}  // namespace device
diff --git a/device/hid/hid_service.h b/device/hid/hid_service.h
new file mode 100644
index 0000000..2589220
--- /dev/null
+++ b/device/hid/hid_service.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_SERVICE_H_
+#define DEVICE_HID_HID_SERVICE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread_checker.h"
+#include "build/build_config.h"
+#include "device/hid/hid_device_info.h"
+
+namespace device {
+
+namespace {
+
+class HidServiceContainer;
+
+}  // namespace
+
+class HidConnection;
+class HidService;
+
+class HidService : public base::MessageLoop::DestructionObserver {
+ public:
+  // Must be called on FILE thread.
+  static HidService* GetInstance();
+
+  // Enumerates and returns a list of device identifiers.
+  virtual void GetDevices(std::vector<HidDeviceInfo>* devices);
+
+  // Fills in the device info struct of the given device_id.
+  // Returns true if succeed.
+  // Returns false if the device_id is invalid, with info untouched.
+  bool GetInfo(std::string device_id, HidDeviceInfo* info) const;
+
+  virtual scoped_refptr<HidConnection> Connect(
+      std::string platform_device_id) = 0;
+
+  // Implements base::MessageLoop::DestructionObserver
+  virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+  // Gets whether the HidService have been successfully initialized.
+  bool initialized() const { return initialized_; }
+
+ protected:
+  friend class HidServiceContainer;
+  friend struct base::DefaultDeleter<HidService>;
+  friend class HidConnectionTest;
+
+  HidService();
+  virtual ~HidService();
+
+  static HidService* CreateInstance();
+
+  virtual void AddDevice(HidDeviceInfo info);
+  virtual void RemoveDevice(std::string platform_device_id);
+
+  typedef std::map<std::string, HidDeviceInfo> DeviceMap;
+  DeviceMap devices_;
+
+  bool initialized_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidService);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_SERVICE_H_
diff --git a/device/hid/hid_service_linux.cc b/device/hid/hid_service_linux.cc
new file mode 100644
index 0000000..f02a550
--- /dev/null
+++ b/device/hid/hid_service_linux.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <libudev.h>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "base/platform_file.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/threading/thread_restrictions.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_connection_linux.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service_linux.h"
+
+namespace device {
+
+namespace {
+
+const char kUdevName[] = "udev";
+const char kUdevActionAdd[] = "add";
+const char kUdevActionRemove[] = "remove";
+const char kHIDSubSystem[] = "hid";
+
+const char kHIDID[] = "HID_ID";
+const char kHIDName[] = "HID_NAME";
+const char kHIDUnique[] = "HID_UNIQ";
+
+} // namespace
+
+HidServiceLinux::HidServiceLinux() {
+  udev_.reset(udev_new());
+  if (!udev_) {
+    LOG(ERROR) << "Failed to create udev.";
+    return;
+  }
+  monitor_.reset(udev_monitor_new_from_netlink(udev_.get(), kUdevName));
+  if (!monitor_) {
+    LOG(ERROR) << "Failed to create udev monitor.";
+    return;
+  }
+  int ret = udev_monitor_filter_add_match_subsystem_devtype(
+      monitor_.get(),
+      kHIDSubSystem,
+      NULL);
+  if (ret != 0) {
+    LOG(ERROR) << "Failed to add udev monitor filter.";
+    return;
+  }
+
+  ret = udev_monitor_enable_receiving(monitor_.get());
+  if (ret != 0) {
+    LOG(ERROR) << "Failed to start udev monitoring.";
+    return;
+  }
+
+  monitor_fd_ = udev_monitor_get_fd(monitor_.get());
+  if (monitor_fd_ <= 0) {
+    LOG(ERROR) << "Failed to start udev monitoring.";
+    return;
+  }
+
+  if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+      monitor_fd_,
+      true,
+      base::MessageLoopForIO::WATCH_READ,
+      &monitor_watcher_,
+      this))
+    return;
+
+  Enumerate();
+}
+
+HidServiceLinux::~HidServiceLinux() {
+  monitor_watcher_.StopWatchingFileDescriptor();
+  close(monitor_fd_);
+}
+
+void HidServiceLinux::Enumerate() {
+  scoped_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate(
+      udev_enumerate_new(udev_.get()));
+
+  if (!enumerate) {
+    LOG(ERROR) << "Failed to enumerate devices.";
+    return;
+  }
+
+  if (udev_enumerate_add_match_subsystem(enumerate.get(), kHIDSubSystem)) {
+    LOG(ERROR) << "Failed to enumerate devices.";
+    return;
+  }
+
+  if (udev_enumerate_scan_devices(enumerate.get()) != 0) {
+    LOG(ERROR) << "Failed to enumerate devices.";
+    return;
+  }
+
+  // This list is managed by |enumerate|.
+  udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get());
+  for (udev_list_entry* i = devices; i != NULL;
+      i = udev_list_entry_get_next(i)) {
+    ScopedUdevDevicePtr hid_dev(
+        udev_device_new_from_syspath(udev_.get(), udev_list_entry_get_name(i)));
+    if (hid_dev) {
+      PlatformDeviceAdd(hid_dev.get());
+    }
+  }
+
+  initialized_ = true;
+}
+
+void HidServiceLinux::PlatformDeviceAdd(udev_device* device) {
+  if (!device)
+    return;
+
+  const char* device_id = udev_device_get_syspath(device);
+  if (!device_id)
+    return;
+
+
+  HidDeviceInfo device_info;
+  device_info.device_id = device_id;
+
+  uint32 int_property = 0;
+  const char* str_property = NULL;
+
+  const char* hid_id = udev_device_get_property_value(device, kHIDID);
+  if (!hid_id)
+    return;
+
+  std::vector<std::string> parts;
+  base::SplitString(hid_id, ':', &parts);
+  if (parts.size() != 3) {
+    return;
+  }
+
+  if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) {
+    device_info.vendor_id = int_property;
+  }
+
+  if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) {
+    device_info.product_id = int_property;
+  }
+
+  str_property = udev_device_get_property_value(device, kHIDUnique);
+  if (str_property != NULL)
+    device_info.serial_number = str_property;
+
+  str_property = udev_device_get_property_value(device, kHIDName);
+  if (str_property != NULL)
+    device_info.product_name = str_property;
+
+  AddDevice(device_info);
+}
+
+void HidServiceLinux::PlatformDeviceRemove(udev_device* raw_dev) {
+  // The returned the device is not referenced.
+  udev_device* hid_dev =
+      udev_device_get_parent_with_subsystem_devtype(raw_dev, "hid", NULL);
+
+  if (!hid_dev)
+    return;
+
+  const char* device_id = NULL;
+  device_id = udev_device_get_syspath(hid_dev);
+  if (device_id == NULL)
+    return;
+
+  RemoveDevice(device_id);
+}
+
+scoped_refptr<HidConnection> HidServiceLinux::Connect(std::string device_id) {
+  if (!ContainsKey(devices_, device_id))
+    return NULL;
+  ScopedUdevDevicePtr hid_device(
+      udev_device_new_from_syspath(udev_.get(), device_id.c_str()));
+  if (hid_device) {
+    scoped_refptr<HidConnectionLinux> connection =
+        new HidConnectionLinux(devices_[device_id], hid_device.Pass());
+    if (connection->initialized())
+      return connection;
+  }
+  return NULL;
+}
+
+void HidServiceLinux::OnFileCanReadWithoutBlocking(int fd) {
+  DCHECK_EQ(monitor_fd_, fd);
+
+  ScopedUdevDevicePtr dev(udev_monitor_receive_device(monitor_.get()));
+  if (!dev)
+    return;
+
+  std::string action(udev_device_get_action(dev.get()));
+  if (action == kUdevActionAdd) {
+    PlatformDeviceAdd(dev.get());
+  } else if (action == kUdevActionRemove) {
+    PlatformDeviceRemove(dev.get());
+  }
+}
+
+void HidServiceLinux::OnFileCanWriteWithoutBlocking(int fd) {}
+
+} // namespace dev
diff --git a/device/hid/hid_service_linux.h b/device/hid/hid_service_linux.h
new file mode 100644
index 0000000..ba58fad
--- /dev/null
+++ b/device/hid/hid_service_linux.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_SERVICE_LINUX_H_
+#define DEVICE_HID_HID_SERVICE_LINUX_H_
+
+#include <libudev.h>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_pump_libevent.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service.h"
+
+namespace device {
+
+class HidConnection;
+
+template<typename T, void func(T*)>
+void DiscardReturnType(T* arg) { func(arg); }
+template<typename T, T* func(T*)>
+void DiscardReturnType(T* arg) { func(arg); }
+
+template<typename T, void func(T*)>
+struct Deleter {
+  void operator()(T* enumerate) const {
+    if (enumerate != NULL)
+      func(enumerate);
+  }
+};
+
+typedef Deleter<
+    udev_enumerate,
+    DiscardReturnType<udev_enumerate, udev_enumerate_unref> >
+    UdevEnumerateDeleter;
+typedef Deleter<
+    udev_device,
+    DiscardReturnType<udev_device, udev_device_unref> > UdevDeviceDeleter;
+typedef Deleter<udev, DiscardReturnType<udev, udev_unref> > UdevDeleter;
+typedef Deleter<
+    udev_monitor,
+    DiscardReturnType<udev_monitor, udev_monitor_unref> > UdevMonitorDeleter;
+
+typedef scoped_ptr<udev_device, UdevDeviceDeleter> ScopedUdevDevicePtr;
+typedef scoped_ptr<udev_enumerate, UdevEnumerateDeleter> ScopedUdevEnumeratePtr;
+
+class HidServiceLinux : public HidService,
+                        public base::MessagePumpLibevent::Watcher {
+ public:
+  HidServiceLinux();
+
+  virtual scoped_refptr<HidConnection> Connect(std::string device_id) OVERRIDE;
+
+  // Implements base::MessagePumpLibevent::Watcher
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ private:
+  virtual ~HidServiceLinux();
+
+  void Enumerate();
+  void PlatformDeviceAdd(udev_device* device);
+  void PlatformDeviceRemove(udev_device* raw_dev);
+
+  scoped_ptr<udev, UdevDeleter> udev_;
+  scoped_ptr<udev_monitor, UdevMonitorDeleter> monitor_;
+  int monitor_fd_;
+  base::MessagePumpLibevent::FileDescriptorWatcher monitor_watcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidServiceLinux);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_SERVICE_LINUX_H_
diff --git a/device/hid/hid_service_mac.cc b/device/hid/hid_service_mac.cc
new file mode 100644
index 0000000..12da7cc
--- /dev/null
+++ b/device/hid/hid_service_mac.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_service_mac.h"
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_connection_mac.h"
+#include "device/hid/hid_utils_mac.h"
+#include "net/base/io_buffer.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+namespace device {
+
+class HidServiceMac;
+
+HidServiceMac::HidServiceMac() : enumeration_runloop_init_(true, false) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault,
+                                            kIOHIDOptionsTypeNone));
+  if (!hid_manager_ref_ ||
+      CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
+    LOG(ERROR) << "Failed to initialize HidManager";
+    return;
+  }
+  CFRetain(hid_manager_ref_);
+
+  // Register for plug/unplug notifications.
+  IOHIDManagerSetDeviceMatching(hid_manager_ref_.get(), NULL);
+  IOHIDManagerRegisterDeviceMatchingCallback(
+      hid_manager_ref_.get(),
+      &HidServiceMac::AddDeviceCallback,
+      this);
+  IOHIDManagerRegisterDeviceRemovalCallback(
+      hid_manager_ref_.get(),
+      &HidServiceMac::RemoveDeviceCallback,
+      this);
+
+  // Blocking operation to enumerate all the pre-existing devices.
+  Enumerate();
+
+  // Save the owner message loop.
+  message_loop_ = base::MessageLoopProxy::current();
+
+  // Start a thread to monitor HID device changes.
+  // The reason to create a new thread is that by default the only thread in the
+  // browser process having a CFRunLoop is the UI thread. We do not want to
+  // run potentially blocking routines on it.
+  enumeration_runloop_thread_.reset(
+      new base::Thread("HidService Device Enumeration Thread"));
+
+  base::Thread::Options options;
+  options.message_loop_type = base::MessageLoop::TYPE_UI;
+  enumeration_runloop_thread_->StartWithOptions(options);
+  enumeration_runloop_thread_->message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&HidServiceMac::ScheduleRunLoop, base::Unretained(this)));
+
+  enumeration_runloop_init_.Wait();
+  initialized_ = true;
+}
+
+HidServiceMac::~HidServiceMac() {
+  enumeration_runloop_thread_->message_loop()->PostTask(
+        FROM_HERE,
+        base::Bind(&HidServiceMac::UnscheduleRunLoop, base::Unretained(this)));
+
+  enumeration_runloop_thread_->Stop();
+}
+
+void HidServiceMac::ScheduleRunLoop() {
+  enumeration_runloop_ = CFRunLoopGetCurrent();
+
+  IOHIDManagerScheduleWithRunLoop(
+      hid_manager_ref_,
+      enumeration_runloop_,
+      kCFRunLoopDefaultMode);
+
+  IOHIDManagerOpen(hid_manager_ref_, kIOHIDOptionsTypeNone);
+
+  enumeration_runloop_init_.Signal();
+}
+
+void HidServiceMac::UnscheduleRunLoop() {
+  IOHIDManagerUnscheduleFromRunLoop(
+        hid_manager_ref_,
+        enumeration_runloop_,
+        kCFRunLoopDefaultMode);
+
+  IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
+
+}
+
+HidServiceMac* HidServiceMac::InstanceFromContext(void* context) {
+  return reinterpret_cast<HidServiceMac*>(context);
+}
+
+void HidServiceMac::AddDeviceCallback(void* context,
+                                      IOReturn result,
+                                      void* sender,
+                                      IOHIDDeviceRef ref) {
+  HidServiceMac* service = InstanceFromContext(context);
+
+  // Takes ownership of ref. Will be released inside PlatformDeviceAdd.
+  CFRetain(ref);
+  service->message_loop_->PostTask(
+      FROM_HERE,
+      base::Bind(&HidServiceMac::PlatformAddDevice,
+                 base::Unretained(service),
+                 base::Unretained(ref)));
+}
+
+void HidServiceMac::RemoveDeviceCallback(void* context,
+                                         IOReturn result,
+                                         void* sender,
+                                         IOHIDDeviceRef ref) {
+  HidServiceMac* service = InstanceFromContext(context);
+
+  // Takes ownership of ref. Will be released inside PlatformDeviceRemove.
+  CFRetain(ref);
+  service->message_loop_->PostTask(
+      FROM_HERE,
+      base::Bind(&HidServiceMac::PlatformRemoveDevice,
+                 base::Unretained(service),
+                 base::Unretained(ref)));
+}
+
+IOHIDDeviceRef HidServiceMac::FindDevice(std::string id) {
+  base::ScopedCFTypeRef<CFSetRef> devices(
+      IOHIDManagerCopyDevices(hid_manager_ref_));
+  CFIndex count = CFSetGetCount(devices);
+  scoped_ptr<IOHIDDeviceRef[]> device_refs(new IOHIDDeviceRef[count]);
+  CFSetGetValues(devices, (const void **)(device_refs.get()));
+
+  for (CFIndex i = 0; i < count; i++) {
+    int32_t int_property = 0;
+    if (GetHidIntProperty(device_refs[i], CFSTR(kIOHIDLocationIDKey),
+                       &int_property)) {
+      if (id == base::HexEncode(&int_property, sizeof(int_property))) {
+        return device_refs[i];
+      }
+    }
+  }
+
+  return NULL;
+}
+
+void HidServiceMac::Enumerate() {
+  base::ScopedCFTypeRef<CFSetRef> devices(
+      IOHIDManagerCopyDevices(hid_manager_ref_));
+  CFIndex count = CFSetGetCount(devices);
+  scoped_ptr<IOHIDDeviceRef[]> device_refs(new IOHIDDeviceRef[count]);
+  CFSetGetValues(devices, (const void **)(device_refs.get()));
+
+  for (CFIndex i = 0; i < count; i++) {
+    // Takes ownership. Will be released inside PlatformDeviceAdd.
+    CFRetain(device_refs[i]);
+    PlatformAddDevice(device_refs[i]);
+  }
+}
+
+void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef raw_ref) {
+  HidDeviceInfo device;
+  int32_t int_property = 0;
+  std::string str_property;
+
+  // Auto-release.
+  base::ScopedCFTypeRef<IOHIDDeviceRef> ref(raw_ref);
+
+  // Unique identifier for HID device.
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDLocationIDKey), &int_property)) {
+    device.device_id = base::HexEncode(&int_property, sizeof(int_property));
+  } else {
+    // Not an available device.
+    return;
+  }
+
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDVendorIDKey), &int_property)) {
+    device.vendor_id = int_property;
+  }
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDProductIDKey), &int_property)) {
+    device.product_id = int_property;
+  }
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDPrimaryUsageKey), &int_property)) {
+    device.usage = int_property;
+  }
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDPrimaryUsagePageKey), &int_property)) {
+    device.usage_page = int_property;
+  }
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDMaxInputReportSizeKey),
+                        &int_property)) {
+    device.input_report_size = int_property;
+  }
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDMaxOutputReportSizeKey),
+                        &int_property)) {
+    device.output_report_size = int_property;
+  }
+  if (GetHidIntProperty(ref, CFSTR(kIOHIDMaxFeatureReportSizeKey),
+                     &int_property)) {
+    device.feature_report_size = int_property;
+  }
+  if (GetHidStringProperty(ref, CFSTR(kIOHIDProductKey), &str_property)) {
+    device.product_name = str_property;
+  }
+  if (GetHidStringProperty(ref, CFSTR(kIOHIDSerialNumberKey), &str_property)) {
+    device.serial_number = str_property;
+  }
+  HidService::AddDevice(device);
+}
+
+void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef raw_ref) {
+  std::string device_id;
+  int32_t int_property = 0;
+  // Auto-release.
+  base::ScopedCFTypeRef<IOHIDDeviceRef> ref(raw_ref);
+  if (!GetHidIntProperty(ref, CFSTR(kIOHIDLocationIDKey), &int_property)) {
+    return;
+  }
+  device_id = base::HexEncode(&int_property, sizeof(int_property));
+  HidService::RemoveDevice(device_id);
+}
+
+scoped_refptr<HidConnection>
+HidServiceMac::Connect(std::string device_id) {
+  if (!ContainsKey(devices_, device_id))
+    return NULL;
+
+  IOHIDDeviceRef ref = FindDevice(device_id);
+  if (ref == NULL)
+    return NULL;
+  return scoped_refptr<HidConnection>(
+      new HidConnectionMac(this, devices_[device_id], ref));
+}
+
+}  // namespace device
diff --git a/device/hid/hid_service_mac.h b/device/hid/hid_service_mac.h
new file mode 100644
index 0000000..9351d1e
--- /dev/null
+++ b/device/hid/hid_service_mac.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_SERVICE_MAC_H_
+#define DEVICE_HID_HID_SERVICE_MAC_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/mac/foundation_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "build/build_config.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+namespace device {
+
+class HidConnection;
+class HidService;
+
+class HidServiceMac : public HidService {
+ public:
+  HidServiceMac();
+
+  virtual scoped_refptr<HidConnection> Connect(std::string device_id) OVERRIDE;
+
+ private:
+  virtual ~HidServiceMac();
+
+  void ScheduleRunLoop();
+  void UnscheduleRunLoop();
+
+  // Device changing callbacks.
+  static void AddDeviceCallback(void* context,
+                                IOReturn result,
+                                void* sender,
+                                IOHIDDeviceRef ref);
+  static void RemoveDeviceCallback(void* context,
+                                   IOReturn result,
+                                   void* sender,
+                                   IOHIDDeviceRef ref);
+  static HidServiceMac* InstanceFromContext(void* context);
+
+  IOHIDDeviceRef FindDevice(std::string id);
+
+  void Enumerate();
+
+  void PlatformAddDevice(IOHIDDeviceRef ref);
+  void PlatformRemoveDevice(IOHIDDeviceRef ref);
+
+  // The message loop this object belongs to.
+  scoped_refptr<base::MessageLoopProxy> message_loop_;
+
+  // Platform HID Manager
+  base::ScopedCFTypeRef<IOHIDManagerRef> hid_manager_ref_;
+
+  // Enumeration thread.
+  scoped_ptr<base::Thread> enumeration_runloop_thread_;
+  CFRunLoopRef enumeration_runloop_;
+  base::WaitableEvent enumeration_runloop_init_;
+
+  bool available_;
+
+  DISALLOW_COPY_AND_ASSIGN(HidServiceMac);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_SERVICE_MAC_H_
diff --git a/device/hid/hid_service_unittest.cc b/device/hid/hid_service_unittest.cc
new file mode 100644
index 0000000..1fa1c0a
--- /dev/null
+++ b/device/hid/hid_service_unittest.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_service.h"
+#include "net/base/io_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+TEST(HidServiceTest, Create) {
+  base::MessageLoopForIO message_loop;
+  HidService* service = HidService::GetInstance();
+  ASSERT_TRUE(service);
+
+  std::vector<HidDeviceInfo> devices;
+  service->GetDevices(&devices);
+
+  for (std::vector<HidDeviceInfo>::iterator it = devices.begin();
+      it != devices.end();
+      ++it) {
+    ASSERT_TRUE(!it->device_id.empty());
+  }
+}
+
+}  // namespace device
diff --git a/device/hid/hid_service_win.cc b/device/hid/hid_service_win.cc
new file mode 100644
index 0000000..cf9db15
--- /dev/null
+++ b/device/hid/hid_service_win.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_service_win.h"
+
+#include <cstdlib>
+#include <string>
+
+#include "base/callback_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_connection_win.h"
+#include "device/hid/hid_service.h"
+#include "net/base/io_buffer.h"
+
+#if defined(OS_WIN)
+
+#define INITGUID
+
+#include <windows.h>
+#include <hidclass.h>
+
+extern "C" {
+
+#include <hidsdi.h>
+#include <hidpi.h>
+
+}
+
+#include <setupapi.h>
+#include <winioctl.h>
+#include "base/win/scoped_handle.h"
+
+#endif  // defined(OS_WIN)
+
+// Setup API is required to enumerate HID devices.
+#pragma comment(lib, "setupapi.lib")
+#pragma comment(lib, "hid.lib")
+
+namespace device {
+namespace {
+
+const char kHIDClass[] = "HIDClass";
+
+}  // namespace
+
+HidServiceWin::HidServiceWin() {
+  initialized_ = Enumerate();
+}
+HidServiceWin::~HidServiceWin() {}
+
+bool HidServiceWin::Enumerate() {
+  BOOL res;
+  HDEVINFO device_info_set;
+  SP_DEVINFO_DATA devinfo_data;
+  SP_DEVICE_INTERFACE_DATA device_interface_data;
+
+  memset(&devinfo_data, 0, sizeof(SP_DEVINFO_DATA));
+  devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
+  device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+
+  device_info_set = SetupDiGetClassDevs(
+      &GUID_DEVINTERFACE_HID,
+      NULL,
+      NULL,
+      DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+  if (device_info_set == INVALID_HANDLE_VALUE)
+    return false;
+
+  for (int device_index = 0;
+      SetupDiEnumDeviceInterfaces(device_info_set,
+                                  NULL,
+                                  &GUID_DEVINTERFACE_HID,
+                                  device_index,
+                                  &device_interface_data);
+      device_index++) {
+    DWORD required_size = 0;
+
+    // Determime the required size of detail struct.
+    SetupDiGetDeviceInterfaceDetailA(device_info_set,
+                                     &device_interface_data,
+                                     NULL,
+                                     0,
+                                     &required_size,
+                                     NULL);
+
+    scoped_ptr_malloc<SP_DEVICE_INTERFACE_DETAIL_DATA_A>
+        device_interface_detail_data(
+            reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_A*>(
+                malloc(required_size)));
+    device_interface_detail_data->cbSize =
+        sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
+
+    // Get the detailed data for this device.
+    res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
+                                           &device_interface_data,
+                                           device_interface_detail_data.get(),
+                                           required_size,
+                                           NULL,
+                                           NULL);
+    if (!res)
+      continue;
+
+    // Enumerate device info. Looking for Setup Class "HIDClass".
+    for (DWORD i = 0;
+        SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);
+        i++) {
+      char class_name[256] = {0};
+      res = SetupDiGetDeviceRegistryPropertyA(device_info_set,
+                                              &devinfo_data,
+                                              SPDRP_CLASS,
+                                              NULL,
+                                              (PBYTE) class_name,
+                                              sizeof(class_name) - 1,
+                                              NULL);
+      if (!res)
+        break;
+      if (memcmp(class_name, kHIDClass, sizeof(kHIDClass)) == 0) {
+        char driver_name[256] = {0};
+        // Get bounded driver.
+        res = SetupDiGetDeviceRegistryPropertyA(device_info_set,
+                                                &devinfo_data,
+                                                SPDRP_DRIVER,
+                                                NULL,
+                                                (PBYTE) driver_name,
+                                                sizeof(driver_name) - 1,
+                                                NULL);
+        if (res) {
+          // Found the drive.
+          break;
+        }
+      }
+    }
+
+    if (!res)
+      continue;
+
+    PlatformAddDevice(device_interface_detail_data->DevicePath);
+  }
+
+  return true;
+}
+
+void HidServiceWin::PlatformAddDevice(std::string device_path) {
+  HidDeviceInfo device_info;
+  device_info.device_id = device_path;
+
+  // Try to open the device.
+  base::win::ScopedHandle device_handle(
+      CreateFileA(device_path.c_str(),
+                  0,
+                  FILE_SHARE_READ | FILE_SHARE_WRITE,
+                  NULL,
+                  OPEN_EXISTING,
+                  FILE_FLAG_OVERLAPPED,
+                  0));
+  if (!device_handle.IsValid())
+    return;
+
+  // Get VID/PID pair.
+  HIDD_ATTRIBUTES attrib = {0};
+  attrib.Size = sizeof(HIDD_ATTRIBUTES);
+  if (!HidD_GetAttributes(device_handle.Get(), &attrib))
+    return;
+
+  device_info.vendor_id = attrib.VendorID;
+  device_info.product_id = attrib.ProductID;
+
+  for (ULONG i = 32;
+      HidD_SetNumInputBuffers(device_handle.Get(), i);
+      i <<= 1);
+
+  // Get usage and usage page (optional).
+  PHIDP_PREPARSED_DATA preparsed_data;
+  if (HidD_GetPreparsedData(device_handle.Get(), &preparsed_data) &&
+      preparsed_data) {
+    HIDP_CAPS capabilities;
+    if (HidP_GetCaps(preparsed_data, &capabilities) == HIDP_STATUS_SUCCESS) {
+      device_info.usage = capabilities.Usage;
+      device_info.usage_page = capabilities.UsagePage;
+      device_info.input_report_size = capabilities.InputReportByteLength;
+      device_info.output_report_size = capabilities.OutputReportByteLength;
+      device_info.feature_report_size = capabilities.FeatureReportByteLength;
+    }
+    // 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);
+      }
+    }
+    HidD_FreePreparsedData(preparsed_data);
+  }
+
+  // Get the serial number
+  wchar_t str_property[512] = { 0 };
+  if (HidD_GetSerialNumberString(device_handle.Get(),
+                                 str_property,
+                                 sizeof(str_property))) {
+    device_info.serial_number = base::SysWideToUTF8(str_property);
+  }
+
+  if (HidD_GetProductString(device_handle.Get(),
+                            str_property,
+                            sizeof(str_property))) {
+    device_info.product_name = base::SysWideToUTF8(str_property);
+  }
+
+  HidService::AddDevice(device_info);
+}
+
+void HidServiceWin::PlatformRemoveDevice(std::string device_path) {
+  HidService::RemoveDevice(device_path);
+}
+
+void HidServiceWin::GetDevices(std::vector<HidDeviceInfo>* devices) {
+  Enumerate();
+  HidService::GetDevices(devices);
+}
+
+scoped_refptr<HidConnection> HidServiceWin::Connect(std::string device_id) {
+  if (!ContainsKey(devices_, device_id)) return NULL;
+  scoped_refptr<HidConnectionWin> connection(
+      new HidConnectionWin(devices_[device_id]));
+  if (!connection->available()) {
+    LOG_GETLASTERROR(ERROR) << "Failed to open device.";
+    return NULL;
+  }
+  return connection;
+}
+
+}  // namespace device
diff --git a/device/hid/hid_service_win.h b/device/hid/hid_service_win.h
new file mode 100644
index 0000000..428420e
--- /dev/null
+++ b/device/hid/hid_service_win.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_SERVICE_WIN_H_
+#define DEVICE_HID_HID_SERVICE_WIN_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service.h"
+
+namespace device {
+
+class HidConnection;
+class HidService;
+
+class HidServiceWin : public HidService {
+ public:
+  HidServiceWin();
+
+  virtual void GetDevices(std::vector<HidDeviceInfo>* devices) OVERRIDE;
+
+  virtual scoped_refptr<HidConnection> Connect(std::string device_id) OVERRIDE;
+
+ private:
+  virtual ~HidServiceWin();
+
+  bool Enumerate();
+  void PlatformAddDevice(std::string device_path);
+  void PlatformRemoveDevice(std::string device_path);
+
+  DISALLOW_COPY_AND_ASSIGN(HidServiceWin);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_SERVICE_WIN_H_
diff --git a/device/hid/hid_utils_mac.cc b/device/hid/hid_utils_mac.cc
new file mode 100644
index 0000000..588041a
--- /dev/null
+++ b/device/hid/hid_utils_mac.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/hid/hid_utils_mac.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+
+#if defined(OS_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDManager.h>
+#endif
+
+namespace device {
+
+bool GetHidIntProperty(IOHIDDeviceRef device,
+                       CFStringRef key,
+                       int32_t* result) {
+  CFNumberRef ref = base::mac::CFCast<CFNumberRef>(
+      IOHIDDeviceGetProperty(device, key));
+  return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result);
+}
+
+bool GetHidStringProperty(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
new file mode 100644
index 0000000..f1349ae
--- /dev/null
+++ b/device/hid/hid_utils_mac.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_HID_HID_UTILS_MAC_H_
+#define DEVICE_HID_HID_UTILS_MAC_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+
+#include <IOKit/hid/IOHIDManager.h>
+
+namespace device {
+
+bool GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key, int32_t* result);
+
+bool GetHidStringProperty(IOHIDDeviceRef device,
+                       CFStringRef key,
+                       std::string* result);
+
+}  // namespace device
+
+#endif  // DEVICE_HID_HID_UTILS_MAC_H_
diff --git a/device/nfc/nfc.gyp b/device/nfc/nfc.gyp
index d5e519b..11e4308 100644
--- a/device/nfc/nfc.gyp
+++ b/device/nfc/nfc.gyp
@@ -12,6 +12,7 @@
       'type': 'static_library',
       'dependencies': [
         '../../base/base.gyp:base',
+        '../../url/url.gyp:url_lib',
       ],
       'sources': [
         'nfc_adapter.cc',
@@ -22,12 +23,20 @@
         'nfc_adapter_factory.h',
         'nfc_ndef_record.cc',
         'nfc_ndef_record.h',
+        'nfc_ndef_record_utils_chromeos.cc',
+        'nfc_ndef_record_utils_chromeos.h',
         'nfc_peer.cc',
         'nfc_peer.h',
+        'nfc_peer_chromeos.cc',
+        'nfc_peer_chromeos.h',
         'nfc_tag.cc',
         'nfc_tag.h',
+        'nfc_tag_chromeos.cc',
+        'nfc_tag_chromeos.h',
         'nfc_tag_technology.cc',
-        'nfc_tag_technology.h'
+        'nfc_tag_technology.h',
+        'nfc_tag_technology_chromeos.cc',
+        'nfc_tag_technology_chromeos.h'
       ],
       'conditions': [
         ['chromeos==1', {
diff --git a/device/nfc/nfc_adapter.cc b/device/nfc/nfc_adapter.cc
index 1bef83d..520d4f3 100644
--- a/device/nfc/nfc_adapter.cc
+++ b/device/nfc/nfc_adapter.cc
@@ -48,4 +48,50 @@
   return NULL;
 }
 
+void NfcAdapter::SetTag(const std::string& identifier, NfcTag* tag) {
+  if (GetTag(identifier)) {
+    VLOG(1) << "Tag object for tag \"" << identifier << "\" already exists.";
+    return;
+  }
+  tags_[identifier] = tag;
+}
+
+void NfcAdapter::SetPeer(const std::string& identifier, NfcPeer* peer) {
+  if (GetPeer(identifier)) {
+    VLOG(1) << "Peer object for peer \"" << identifier << "\" already exists.";
+    return;
+  }
+  peers_[identifier] = peer;
+}
+
+NfcTag* NfcAdapter::RemoveTag(const std::string& identifier) {
+  TagsMap::iterator iter = tags_.find(identifier);
+  if (iter == tags_.end()) {
+    VLOG(1) << "Tag with identifier \"" << identifier << "\" not found.";
+    return NULL;
+  }
+  NfcTag* tag = iter->second;
+  tags_.erase(iter);
+  return tag;
+}
+
+NfcPeer* NfcAdapter::RemovePeer(const std::string& identifier) {
+  PeersMap::iterator iter = peers_.find(identifier);
+  if (iter == peers_.end()) {
+    VLOG(1) << "Peer object for peer \"" << identifier << "\" not found.";
+    return NULL;
+  }
+  NfcPeer* peer = iter->second;
+  peers_.erase(iter);
+  return peer;
+}
+
+void NfcAdapter::ClearTags() {
+  tags_.clear();
+}
+
+void NfcAdapter::ClearPeers() {
+  peers_.clear();
+}
+
 }  // namespace device
diff --git a/device/nfc/nfc_adapter.h b/device/nfc/nfc_adapter.h
index 373f4cc..455b142 100644
--- a/device/nfc/nfc_adapter.h
+++ b/device/nfc/nfc_adapter.h
@@ -181,10 +181,27 @@
   typedef std::map<const std::string, NfcPeer*> PeersMap;
   typedef std::map<const std::string, NfcTag*> TagsMap;
 
+  // Set the given tag or peer for |identifier|. If a tag or peer for
+  // |identifier| already exists, these methods won't do anything.
+  void SetTag(const std::string& identifier, NfcTag* tag);
+  void SetPeer(const std::string& identifier, NfcPeer* peer);
+
+  // Removes the tag or peer for |identifier| and returns the removed object.
+  // Returns NULL, if no tag or peer for |identifier| was found.
+  NfcTag* RemoveTag(const std::string& identifier);
+  NfcPeer* RemovePeer(const std::string& identifier);
+
+  // Clear the peer and tag maps. These methods won't delete the tag and peer
+  // objects, however after the call to these methods, the peers and tags won't
+  // be returned via calls to GetPeers and GetTags.
+  void ClearTags();
+  void ClearPeers();
+
+ private:
+  // Peers and tags that are managed by this adapter.
   PeersMap peers_;
   TagsMap tags_;
 
- private:
   DISALLOW_COPY_AND_ASSIGN(NfcAdapter);
 };
 
diff --git a/device/nfc/nfc_adapter_chromeos.cc b/device/nfc/nfc_adapter_chromeos.cc
index 071b080..374e8ac 100644
--- a/device/nfc/nfc_adapter_chromeos.cc
+++ b/device/nfc/nfc_adapter_chromeos.cc
@@ -9,19 +9,25 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
-#include "device/nfc/nfc_peer.h"
-#include "device/nfc/nfc_tag.h"
+#include "device/nfc/nfc_peer_chromeos.h"
+#include "device/nfc/nfc_tag_chromeos.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
 namespace chromeos {
 
+namespace {
+
+typedef std::vector<dbus::ObjectPath> ObjectPathVector;
+
+}  // namespace
+
 NfcAdapterChromeOS::NfcAdapterChromeOS()
     : weak_ptr_factory_(this) {
   DBusThreadManager::Get()->GetNfcAdapterClient()->AddObserver(this);
   DBusThreadManager::Get()->GetNfcDeviceClient()->AddObserver(this);
   DBusThreadManager::Get()->GetNfcTagClient()->AddObserver(this);
 
-  const std::vector<dbus::ObjectPath>& object_paths =
+  const ObjectPathVector& object_paths =
       DBusThreadManager::Get()->GetNfcAdapterClient()->GetAdapters();
   if (!object_paths.empty()) {
     VLOG(1) << object_paths.size() << " NFC adapter(s) available.";
@@ -126,9 +132,9 @@
 
   // There may still be other adapters present on the system. Set the next
   // available adapter as the current one.
-  const std::vector<dbus::ObjectPath>& object_paths =
+  const ObjectPathVector& object_paths =
       DBusThreadManager::Get()->GetNfcAdapterClient()->GetAdapters();
-  for (std::vector<dbus::ObjectPath>::const_iterator iter =
+  for (ObjectPathVector::const_iterator iter =
           object_paths.begin();
        iter != object_paths.end(); ++iter) {
     // The removed object will still be available until the call to
@@ -155,35 +161,91 @@
 }
 
 void NfcAdapterChromeOS::DeviceAdded(const dbus::ObjectPath& object_path) {
+  if (!IsPresent())
+    return;
+
+  if (GetPeer(object_path.value()))
+    return;
+
   VLOG(1) << "NFC device found: " << object_path.value();
-  // TODO(armansito): Implement device logic.
+
+  // Check to see if the device belongs to this adapter.
+  const ObjectPathVector& devices =
+      DBusThreadManager::Get()->GetNfcDeviceClient()->
+          GetDevicesForAdapter(object_path_);
+  bool device_found = false;
+  for (ObjectPathVector::const_iterator iter = devices.begin();
+       iter != devices.end(); ++iter) {
+    if (*iter == object_path) {
+      device_found = true;
+      break;
+    }
+  }
+  if (!device_found) {
+    VLOG(1) << "Found peer device does not belong to the current adapter.";
+    return;
+  }
+
+  // Create the peer object.
+  NfcPeerChromeOS* peer_chromeos = new NfcPeerChromeOS(object_path);
+  SetPeer(object_path.value(), peer_chromeos);
+  FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_,
+                    PeerFound(this, peer_chromeos));
 }
 
 void NfcAdapterChromeOS::DeviceRemoved(const dbus::ObjectPath& object_path) {
   VLOG(1) << "NFC device lost: " << object_path.value();
-  // TODO(armansito): Implement device logic.
-}
-
-void NfcAdapterChromeOS::DevicePropertyChanged(
-    const dbus::ObjectPath& object_path,
-    const std::string& property_name) {
-  // TODO(armansito): Implement device logic.
+  device::NfcPeer* peer = RemovePeer(object_path.value());
+  if (!peer) {
+    VLOG(1) << "Removed peer device does not belong to the current adapter.";
+    return;
+  }
+  FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, PeerLost(this, peer));
+  delete peer;
 }
 
 void NfcAdapterChromeOS::TagAdded(const dbus::ObjectPath& object_path) {
+  if (!IsPresent())
+    return;
+
+  if (GetTag(object_path.value()))
+    return;
+
   VLOG(1) << "NFC tag found: " << object_path.value();
-  // TODO(armansito): Implement tag logic.
+
+  // Check to see if the tag belongs to this adapter.
+  const std::vector<dbus::ObjectPath>& tags =
+      DBusThreadManager::Get()->GetNfcTagClient()->
+          GetTagsForAdapter(object_path_);
+  bool tag_found = false;
+  for (std::vector<dbus::ObjectPath>::const_iterator iter = tags.begin();
+       iter != tags.end(); ++iter) {
+    if (*iter == object_path) {
+      tag_found = true;
+      break;
+    }
+  }
+  if (!tag_found) {
+    VLOG(1) << "Found tag does not belong to the current adapter.";
+    return;
+  }
+
+  // Create the tag object.
+  NfcTagChromeOS* tag_chromeos = new NfcTagChromeOS(object_path);
+  SetTag(object_path.value(), tag_chromeos);
+  FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_,
+                    TagFound(this, tag_chromeos));
 }
 
 void NfcAdapterChromeOS::TagRemoved(const dbus::ObjectPath& object_path) {
-  VLOG(1) << "NFC tag found: " << object_path.value();
-  // TODO(armansito): Implement tag logic.
-}
-
-void NfcAdapterChromeOS::TagPropertyChanged(
-    const dbus::ObjectPath& object_path,
-    const std::string& property_name) {
-  // TODO(armansito): Implement tag logic.
+  VLOG(1) << "NFC tag lost : " << object_path.value();
+  device::NfcTag* tag = RemoveTag(object_path.value());
+  if (!tag) {
+    VLOG(1) << "Removed tag does not belong to the current adapter.";
+    return;
+  }
+  FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, TagLost(this, tag));
+  delete tag;
 }
 
 void NfcAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) {
@@ -200,9 +262,35 @@
   if (properties->polling.value())
     PollingChanged(true);
 
-  // TODO(armansito): Create device::NfcPeer and device::NfcTag instances for
-  // all peers and tags that exist, once they have been implemented for
-  // ChromeOS.
+  // Create peer objects for peers that were added before the adapter was set.
+  const ObjectPathVector& devices =
+      DBusThreadManager::Get()->GetNfcDeviceClient()->
+          GetDevicesForAdapter(object_path_);
+  for (ObjectPathVector::const_iterator iter = devices.begin();
+       iter != devices.end(); ++iter) {
+    const dbus::ObjectPath& object_path = *iter;
+    if (GetPeer(object_path.value()))
+      continue;
+    NfcPeerChromeOS* peer_chromeos = new NfcPeerChromeOS(object_path);
+    SetPeer(object_path.value(), peer_chromeos);
+    FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_,
+                      PeerFound(this, peer_chromeos));
+  }
+
+  // Create tag objects for tags that were added before the adapter was set.
+  const std::vector<dbus::ObjectPath>& tags =
+      DBusThreadManager::Get()->GetNfcTagClient()->
+          GetTagsForAdapter(object_path_);
+  for (std::vector<dbus::ObjectPath>::const_iterator iter = tags.begin();
+       iter != tags.end(); ++iter) {
+    const dbus::ObjectPath& object_path = *iter;
+    if (GetTag(object_path.value()))
+      continue;
+    NfcTagChromeOS* tag_chromeos = new NfcTagChromeOS(object_path);
+    SetTag(object_path.value(), tag_chromeos);
+    FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_,
+                      TagFound(this, tag_chromeos));
+  }
 }
 
 void NfcAdapterChromeOS::RemoveAdapter() {
@@ -219,22 +307,26 @@
 
   // Copy the tags and peers here and clear the original containers so that
   // GetPeers and GetTags return no values during the *Removed observer calls.
-  PeersMap peers = peers_;
-  TagsMap tags = tags_;
-  peers_.clear();
-  tags_.clear();
+  PeerList peers;
+  TagList tags;
+  GetPeers(&peers);
+  GetTags(&tags);
+  ClearPeers();
+  ClearTags();
 
-  for (PeersMap::iterator iter = peers_.begin();
-       iter != peers_.end(); ++iter) {
+  for (PeerList::iterator iter = peers.begin();
+       iter != peers.end(); ++iter) {
+    device::NfcPeer* peer = *iter;
     FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_,
-                      PeerLost(this, iter->second));
-    delete iter->second;
+                      PeerLost(this, peer));
+    delete peer;
   }
-  for (TagsMap::iterator iter = tags_.begin();
-       iter != tags_.end(); ++iter) {
+  for (TagList::iterator iter = tags.begin();
+       iter != tags.end(); ++iter) {
+    device::NfcTag* tag = *iter;
     FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_,
-                      TagLost(this, iter->second));
-    delete iter->second;
+                      TagLost(this, tag));
+    delete tag;
   }
 
   object_path_ = dbus::ObjectPath("");
@@ -270,7 +362,7 @@
     }
     callback.Run();
   } else {
-    LOG(WARNING) << "Failed to power up the NFC antenna radio.";
+    LOG(ERROR) << "Failed to power up the NFC antenna radio.";
     error_callback.Run();
   }
 }
@@ -283,8 +375,8 @@
     const ErrorCallback& error_callback,
     const std::string& error_name,
     const std::string& error_message) {
-  LOG(WARNING) << object_path_.value() << ": Failed to start polling: "
-               << error_name << ": " << error_message;
+  LOG(ERROR) << object_path_.value() << ": Failed to start polling: "
+             << error_name << ": " << error_message;
   error_callback.Run();
 }
 
@@ -296,8 +388,8 @@
     const ErrorCallback& error_callback,
     const std::string& error_name,
     const std::string& error_message) {
-  LOG(WARNING) << object_path_.value() << ": Failed to stop polling: "
-               << error_name << ": " << error_message;
+  LOG(ERROR) << object_path_.value() << ": Failed to stop polling: "
+             << error_name << ": " << error_message;
   error_callback.Run();
 }
 
diff --git a/device/nfc/nfc_adapter_chromeos.h b/device/nfc/nfc_adapter_chromeos.h
index d70a98d..3511e5d 100644
--- a/device/nfc/nfc_adapter_chromeos.h
+++ b/device/nfc/nfc_adapter_chromeos.h
@@ -60,14 +60,10 @@
   // NfcDeviceClient::Observer overrides.
   virtual void DeviceAdded(const dbus::ObjectPath& object_path) OVERRIDE;
   virtual void DeviceRemoved(const dbus::ObjectPath& object_path) OVERRIDE;
-  virtual void DevicePropertyChanged(const dbus::ObjectPath& object_path,
-                                     const std::string& property_name) OVERRIDE;
 
   // NfcTagClient::Observer overrides.
   virtual void TagAdded(const dbus::ObjectPath& object_path) OVERRIDE;
   virtual void TagRemoved(const dbus::ObjectPath& object_path) OVERRIDE;
-  virtual void TagPropertyChanged(const dbus::ObjectPath& object_path,
-                                  const std::string& property_name) OVERRIDE;
 
   // Set the tracked adapter to the one in |object_path|, this object will
   // subsequently operate on that adapter until it is removed.
diff --git a/device/nfc/nfc_chromeos_unittest.cc b/device/nfc/nfc_chromeos_unittest.cc
index dd94beb..672780f 100644
--- a/device/nfc/nfc_chromeos_unittest.cc
+++ b/device/nfc/nfc_chromeos_unittest.cc
@@ -2,23 +2,57 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/values.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_nfc_adapter_client.h"
+#include "chromeos/dbus/fake_nfc_device_client.h"
+#include "chromeos/dbus/fake_nfc_record_client.h"
+#include "chromeos/dbus/fake_nfc_tag_client.h"
 #include "device/nfc/nfc_adapter_chromeos.h"
+#include "device/nfc/nfc_ndef_record.h"
+#include "device/nfc/nfc_ndef_record_utils_chromeos.h"
+#include "device/nfc/nfc_peer.h"
+#include "device/nfc/nfc_tag.h"
+#include "device/nfc/nfc_tag_technology.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
 
 using device::NfcAdapter;
+using device::NfcNdefMessage;
+using device::NfcNdefRecord;
+using device::NfcNdefTagTechnology;
+using device::NfcPeer;
+using device::NfcTag;
 
 namespace chromeos {
 
 namespace {
 
-class TestObserver : public NfcAdapter::Observer {
+// Callback passed to property structures.
+void OnPropertyChangedCallback(const std::string& property_name) {
+}
+
+// Callback passed to dbus::PropertyBase::Set.
+void OnSet(bool success) {
+}
+
+class TestObserver : public NfcAdapter::Observer,
+                     public NfcPeer::Observer,
+                     public NfcTag::Observer,
+                     public NfcNdefTagTechnology::Observer {
  public:
   TestObserver(scoped_refptr<NfcAdapter> adapter)
       : present_changed_count_(0),
         powered_changed_count_(0),
+        polling_changed_count_(0),
+        peer_records_received_count_(0),
+        tag_records_received_count_(0),
+        peer_count_(0),
+        tag_count_(0),
         adapter_(adapter) {
   }
 
@@ -38,8 +72,68 @@
     powered_changed_count_++;
   }
 
+  // NfcAdapter::Observer override.
+  virtual void AdapterPollingChanged(NfcAdapter* adapter,
+                                     bool powered) OVERRIDE {
+    EXPECT_EQ(adapter_, adapter);
+    polling_changed_count_++;
+  }
+
+  // NfcAdapter::Observer override.
+  virtual void PeerFound(NfcAdapter* adapter, NfcPeer* peer) OVERRIDE {
+    EXPECT_EQ(adapter_, adapter);
+    peer_count_++;
+    peer_identifier_ = peer->GetIdentifier();
+  }
+
+  // NfcAdapter::Observer override.
+  virtual void PeerLost(NfcAdapter* adapter, NfcPeer* peer) OVERRIDE {
+    EXPECT_EQ(adapter_, adapter);
+    EXPECT_EQ(peer_identifier_, peer->GetIdentifier());
+    peer_count_--;
+    peer_identifier_.clear();
+  }
+
+  // NfcAdapter::Observer override.
+  virtual void TagFound(NfcAdapter* adapter, NfcTag* tag) OVERRIDE {
+    EXPECT_EQ(adapter_, adapter);
+    tag_count_++;
+    tag_identifier_ = tag->GetIdentifier();
+  }
+
+  // NfcAdapter::Observer override.
+  virtual void TagLost(NfcAdapter* adapter, NfcTag* tag) OVERRIDE {
+    EXPECT_EQ(adapter_, adapter);
+    EXPECT_EQ(tag_identifier_, tag->GetIdentifier());
+    tag_count_--;
+    tag_identifier_.clear();
+  }
+
+  // NfcPeer::Observer override.
+  virtual void RecordReceived(
+      NfcPeer* peer, const NfcNdefRecord* record) OVERRIDE {
+    EXPECT_EQ(peer, adapter_->GetPeer(peer_identifier_));
+    EXPECT_EQ(peer_identifier_, peer->GetIdentifier());
+    peer_records_received_count_++;
+  }
+
+  // NfcNdefTagTechnology::Observer override.
+  virtual void RecordReceived(
+        NfcTag* tag, const NfcNdefRecord* record) OVERRIDE {
+    EXPECT_EQ(tag, adapter_->GetTag(tag_identifier_));
+    EXPECT_EQ(tag_identifier_, tag->GetIdentifier());
+    tag_records_received_count_++;
+  }
+
   int present_changed_count_;
   int powered_changed_count_;
+  int polling_changed_count_;
+  int peer_records_received_count_;
+  int tag_records_received_count_;
+  int peer_count_;
+  int tag_count_;
+  std::string peer_identifier_;
+  std::string tag_identifier_;
   scoped_refptr<NfcAdapter> adapter_;
 };
 
@@ -51,9 +145,16 @@
     DBusThreadManager::InitializeWithStub();
     fake_nfc_adapter_client_ = static_cast<FakeNfcAdapterClient*>(
         DBusThreadManager::Get()->GetNfcAdapterClient());
-    SetAdapter();
-    message_loop_.RunUntilIdle();
+    fake_nfc_device_client_ = static_cast<FakeNfcDeviceClient*>(
+        DBusThreadManager::Get()->GetNfcDeviceClient());
+    fake_nfc_record_client_ = static_cast<FakeNfcRecordClient*>(
+        DBusThreadManager::Get()->GetNfcRecordClient());
+    fake_nfc_tag_client_ = static_cast<FakeNfcTagClient*>(
+        DBusThreadManager::Get()->GetNfcTagClient());
 
+    fake_nfc_adapter_client_->EnablePairingOnPoll(false);
+    fake_nfc_device_client_->DisableSimulationTimeout();
+    fake_nfc_tag_client_->DisableSimulationTimeout();
     success_callback_count_ = 0;
     error_callback_count_ = 0;
   }
@@ -68,6 +169,7 @@
     adapter_ = new NfcAdapterChromeOS();
     ASSERT_TRUE(adapter_.get() != NULL);
     ASSERT_TRUE(adapter_->IsInitialized());
+    base::RunLoop().RunUntilIdle();
   }
 
   // Generic callbacks for success and error.
@@ -79,23 +181,36 @@
     error_callback_count_++;
   }
 
+  void ErrorCallbackWithParameters(const std::string& error_name,
+                                   const std::string& error_message) {
+    LOG(INFO) << "Error callback called: " << error_name << ", "
+              << error_message;
+    error_callback_count_++;
+  }
+
  protected:
+  // MessageLoop instance, used to simulate asynchronous behavior.
+  base::MessageLoop message_loop_;
+
   // Fields for storing the number of times SuccessCallback and ErrorCallback
   // have been called.
   int success_callback_count_;
   int error_callback_count_;
 
-  // A message loop to emulate asynchronous behavior.
-  base::MessageLoop message_loop_;
-
   // The NfcAdapter instance under test.
   scoped_refptr<NfcAdapter> adapter_;
 
   // The fake D-Bus client instances used for testing.
   FakeNfcAdapterClient* fake_nfc_adapter_client_;
+  FakeNfcDeviceClient* fake_nfc_device_client_;
+  FakeNfcRecordClient* fake_nfc_record_client_;
+  FakeNfcTagClient* fake_nfc_tag_client_;
 };
 
+// Tests that the adapter updates correctly to reflect the current "default"
+// adapter, when multiple adapters appear and disappear.
 TEST_F(NfcChromeOSTest, PresentChanged) {
+  SetAdapter();
   EXPECT_TRUE(adapter_->IsPresent());
 
   TestObserver observer(adapter_);
@@ -122,7 +237,9 @@
   EXPECT_FALSE(adapter_->IsPresent());
 }
 
+// Tests that the adapter correctly reflects the power state.
 TEST_F(NfcChromeOSTest, SetPowered) {
+  SetAdapter();
   TestObserver observer(adapter_);
   adapter_->AddObserver(&observer);
 
@@ -177,7 +294,9 @@
   EXPECT_EQ(2, error_callback_count_);
 }
 
+// Tests that the power state updates correctly when the adapter disappears.
 TEST_F(NfcChromeOSTest, PresentChangedWhilePowered) {
+  SetAdapter();
   TestObserver observer(adapter_);
   adapter_->AddObserver(&observer);
 
@@ -199,4 +318,546 @@
   EXPECT_FALSE(adapter_->IsPresent());
 }
 
+// Tests that peer and record objects are created for all peers and records
+// that already exist when the adapter is created.
+TEST_F(NfcChromeOSTest, PeersInitializedWhenAdapterCreated) {
+  // Set up the adapter client.
+  NfcAdapterClient::Properties* properties =
+      fake_nfc_adapter_client_->GetProperties(
+          dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0));
+  properties->powered.Set(true, base::Bind(&OnSet));
+
+  fake_nfc_adapter_client_->StartPollLoop(
+      dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0),
+      nfc_adapter::kModeInitiator,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, success_callback_count_);
+  EXPECT_TRUE(properties->powered.value());
+  EXPECT_TRUE(properties->polling.value());
+
+  // Start pairing simulation, which will add a fake device and fake records.
+  fake_nfc_device_client_->BeginPairingSimulation(0, 0);
+  base::RunLoop().RunUntilIdle();
+
+  // Create the adapter.
+  SetAdapter();
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  // Observer shouldn't have received any calls, as it got created AFTER the
+  // notifications were sent.
+  EXPECT_EQ(0, observer.present_changed_count_);
+  EXPECT_EQ(0, observer.powered_changed_count_);
+  EXPECT_EQ(0, observer.polling_changed_count_);
+  EXPECT_EQ(0, observer.peer_count_);
+
+  EXPECT_TRUE(adapter_->IsPresent());
+  EXPECT_TRUE(adapter_->IsPowered());
+  EXPECT_FALSE(adapter_->IsPolling());
+
+  NfcAdapter::PeerList peers;
+  adapter_->GetPeers(&peers);
+  EXPECT_EQ(static_cast<size_t>(1), peers.size());
+
+  NfcPeer* peer = peers[0];
+  const NfcNdefMessage& message = peer->GetNdefMessage();
+  EXPECT_EQ(static_cast<size_t>(3), message.records().size());
+}
+
+// Tests that tag and record objects are created for all tags and records that
+// already exist when the adapter is created.
+TEST_F(NfcChromeOSTest, TagsInitializedWhenAdapterCreated) {
+  const char kTestURI[] = "fake://path/for/testing";
+
+  // Set up the adapter client.
+  NfcAdapterClient::Properties* properties =
+      fake_nfc_adapter_client_->GetProperties(
+          dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0));
+  properties->powered.Set(true, base::Bind(&OnSet));
+
+  fake_nfc_adapter_client_->StartPollLoop(
+      dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0),
+      nfc_adapter::kModeInitiator,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, success_callback_count_);
+  EXPECT_TRUE(properties->powered.value());
+  EXPECT_TRUE(properties->polling.value());
+
+  // Add the fake tag.
+  fake_nfc_tag_client_->BeginPairingSimulation(0);
+  base::RunLoop().RunUntilIdle();
+
+  // Create a fake record.
+  base::DictionaryValue test_record_data;
+  test_record_data.SetString(nfc_record::kTypeProperty, nfc_record::kTypeUri);
+  test_record_data.SetString(nfc_record::kUriProperty, kTestURI);
+  fake_nfc_tag_client_->Write(
+      dbus::ObjectPath(FakeNfcTagClient::kTagPath),
+      test_record_data,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters,
+                 base::Unretained(this)));
+  EXPECT_EQ(2, success_callback_count_);
+
+  // Create the adapter.
+  SetAdapter();
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  // Observer shouldn't have received any calls, as it got created AFTER the
+  // notifications were sent.
+  EXPECT_EQ(0, observer.present_changed_count_);
+  EXPECT_EQ(0, observer.powered_changed_count_);
+  EXPECT_EQ(0, observer.polling_changed_count_);
+  EXPECT_EQ(0, observer.peer_count_);
+
+  EXPECT_TRUE(adapter_->IsPresent());
+  EXPECT_TRUE(adapter_->IsPowered());
+  EXPECT_FALSE(adapter_->IsPolling());
+
+  NfcAdapter::TagList tags;
+  adapter_->GetTags(&tags);
+  EXPECT_EQ(static_cast<size_t>(1), tags.size());
+
+  NfcTag* tag = tags[0];
+  const NfcNdefMessage& message = tag->GetNdefTagTechnology()->GetNdefMessage();
+  EXPECT_EQ(static_cast<size_t>(1), message.records().size());
+
+  const NfcNdefRecord* record = message.records()[0];
+  std::string uri;
+  EXPECT_TRUE(record->data().GetString(NfcNdefRecord::kFieldURI, &uri));
+  EXPECT_EQ(kTestURI, uri);
+}
+
+// Tests that the adapter correctly updates its state when polling is started
+// and stopped.
+TEST_F(NfcChromeOSTest, StartAndStopPolling) {
+  SetAdapter();
+  EXPECT_TRUE(adapter_->IsPresent());
+
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  // Start polling while not powered. Should fail.
+  EXPECT_FALSE(adapter_->IsPowered());
+  adapter_->StartPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(0, success_callback_count_);
+  EXPECT_EQ(1, error_callback_count_);
+  EXPECT_FALSE(adapter_->IsPolling());
+
+  // Start polling while powered. Should succeed.
+  adapter_->SetPowered(
+      true,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(1, success_callback_count_);
+  EXPECT_EQ(1, error_callback_count_);
+  EXPECT_TRUE(adapter_->IsPowered());
+
+  adapter_->StartPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(2, success_callback_count_);
+  EXPECT_EQ(1, error_callback_count_);
+  EXPECT_TRUE(adapter_->IsPolling());
+
+  // Start polling while already polling. Should fail.
+  adapter_->StartPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(2, success_callback_count_);
+  EXPECT_EQ(2, error_callback_count_);
+  EXPECT_TRUE(adapter_->IsPolling());
+
+  // Stop polling. Should succeed.
+  adapter_->StopPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(3, success_callback_count_);
+  EXPECT_EQ(2, error_callback_count_);
+  EXPECT_FALSE(adapter_->IsPolling());
+
+  // Stop polling while not polling. Should fail.
+  adapter_->StopPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(3, success_callback_count_);
+  EXPECT_EQ(3, error_callback_count_);
+  EXPECT_FALSE(adapter_->IsPolling());
+}
+
+// Tests a simple peer pairing simulation.
+TEST_F(NfcChromeOSTest, PeerTest) {
+  SetAdapter();
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  adapter_->SetPowered(
+      true,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  adapter_->StartPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(2, success_callback_count_);
+
+  EXPECT_TRUE(adapter_->IsPowered());
+  EXPECT_TRUE(adapter_->IsPolling());
+  EXPECT_EQ(0, observer.peer_count_);
+
+  // Add the fake device.
+  fake_nfc_device_client_->BeginPairingSimulation(0, -1);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(1, observer.peer_count_);
+  EXPECT_EQ(FakeNfcDeviceClient::kDevicePath, observer.peer_identifier_);
+
+  NfcPeer* peer = adapter_->GetPeer(observer.peer_identifier_);
+  CHECK(peer);
+  peer->AddObserver(&observer);
+
+  // Peer should have no records on it.
+  EXPECT_TRUE(peer->GetNdefMessage().records().empty());
+  EXPECT_EQ(0, observer.peer_records_received_count_);
+
+  // Make records visible.
+  fake_nfc_record_client_->SetDeviceRecordsVisible(true);
+  EXPECT_EQ(3, observer.peer_records_received_count_);
+  EXPECT_EQ(static_cast<size_t>(3), peer->GetNdefMessage().records().size());
+
+  // End the simulation. Peer should get removed.
+  fake_nfc_device_client_->EndPairingSimulation();
+  EXPECT_EQ(0, observer.peer_count_);
+  EXPECT_TRUE(observer.peer_identifier_.empty());
+
+  peer = adapter_->GetPeer(observer.peer_identifier_);
+  EXPECT_FALSE(peer);
+
+  // No record related notifications will be sent when a peer gets removed.
+  EXPECT_EQ(3, observer.peer_records_received_count_);
+}
+
+// Tests a simple tag pairing simulation.
+TEST_F(NfcChromeOSTest, TagTest) {
+  const char kTestURI[] = "fake://path/for/testing";
+
+  SetAdapter();
+  TestObserver observer(adapter_);
+  adapter_->AddObserver(&observer);
+
+  adapter_->SetPowered(
+      true,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  adapter_->StartPolling(
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(2, success_callback_count_);
+
+  EXPECT_TRUE(adapter_->IsPowered());
+  EXPECT_TRUE(adapter_->IsPolling());
+  EXPECT_EQ(0, observer.tag_count_);
+
+  // Add the fake tag.
+  fake_nfc_tag_client_->BeginPairingSimulation(0);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(1, observer.tag_count_);
+  EXPECT_EQ(FakeNfcTagClient::kTagPath, observer.tag_identifier_);
+
+  NfcTag* tag = adapter_->GetTag(observer.tag_identifier_);
+  CHECK(tag);
+  tag->AddObserver(&observer);
+  EXPECT_TRUE(tag->IsReady());
+  CHECK(tag->GetNdefTagTechnology());
+  tag->GetNdefTagTechnology()->AddObserver(&observer);
+
+  NfcNdefTagTechnology* tag_technology = tag->GetNdefTagTechnology();
+  EXPECT_TRUE(tag_technology->IsSupportedByTag());
+
+  // Tag should have no records on it.
+  EXPECT_TRUE(tag_technology->GetNdefMessage().records().empty());
+  EXPECT_EQ(0, observer.tag_records_received_count_);
+
+  // Set the tag record visible. By default the record has no content, so no
+  // NfcNdefMessage should be received.
+  fake_nfc_record_client_->SetTagRecordsVisible(true);
+  EXPECT_TRUE(tag_technology->GetNdefMessage().records().empty());
+  EXPECT_EQ(0, observer.tag_records_received_count_);
+  fake_nfc_record_client_->SetTagRecordsVisible(false);
+
+  // Write an NDEF record to the tag.
+  EXPECT_EQ(2, success_callback_count_);  // 2 for SetPowered and StartPolling.
+  EXPECT_EQ(0, error_callback_count_);
+
+  base::DictionaryValue record_data;
+  record_data.SetString(NfcNdefRecord::kFieldURI, kTestURI);
+  NfcNdefRecord written_record;
+  written_record.Populate(NfcNdefRecord::kTypeURI, &record_data);
+  NfcNdefMessage written_message;
+  written_message.AddRecord(&written_record);
+
+  tag_technology->WriteNdef(
+      written_message,
+      base::Bind(&NfcChromeOSTest::SuccessCallback,
+                 base::Unretained(this)),
+      base::Bind(&NfcChromeOSTest::ErrorCallback,
+                 base::Unretained(this)));
+  EXPECT_EQ(3, success_callback_count_);
+  EXPECT_EQ(0, error_callback_count_);
+
+  EXPECT_EQ(static_cast<size_t>(1),
+            tag_technology->GetNdefMessage().records().size());
+  EXPECT_EQ(1, observer.tag_records_received_count_);
+
+  NfcNdefRecord* received_record =
+      tag_technology->GetNdefMessage().records()[0];
+  EXPECT_EQ(NfcNdefRecord::kTypeURI, received_record->type());
+  std::string uri;
+  EXPECT_TRUE(received_record->data().GetString(
+      NfcNdefRecord::kFieldURI, &uri));
+  EXPECT_EQ(kTestURI, uri);
+
+  // End the simulation. Tag should get removed.
+  fake_nfc_tag_client_->EndPairingSimulation();
+  EXPECT_EQ(0, observer.tag_count_);
+  EXPECT_TRUE(observer.tag_identifier_.empty());
+
+  tag = adapter_->GetTag(observer.tag_identifier_);
+  EXPECT_FALSE(tag);
+
+  // No record related notifications will be sent when a tag gets removed.
+  EXPECT_EQ(1, observer.tag_records_received_count_);
+}
+
+// Unit tests for nfc_ndef_record_utils methods.
+TEST_F(NfcChromeOSTest, NfcNdefRecordToDBusAttributes) {
+  const char kText[] = "text";
+  const char kURI[] = "test://uri";
+  const char kEncoding[] = "encoding";
+  const char kLanguageCode[] = "en";
+  const char kMimeType[] = "mime-type";
+  const double kSize = 5;
+
+  // Text record.
+  base::DictionaryValue data;
+  data.SetString(NfcNdefRecord::kFieldText, kText);
+  data.SetString(NfcNdefRecord::kFieldLanguageCode, kLanguageCode);
+  data.SetString(NfcNdefRecord::kFieldEncoding, kEncoding);
+
+  scoped_ptr<NfcNdefRecord> record(new NfcNdefRecord());
+  ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data));
+
+  base::DictionaryValue result;
+  EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes(
+      record.get(), &result));
+
+  std::string string_value;
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kTypeProperty, &string_value));
+  EXPECT_EQ(nfc_record::kTypeText, string_value);
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kRepresentationProperty, &string_value));
+  EXPECT_EQ(kText, string_value);
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kLanguageProperty, &string_value));
+  EXPECT_EQ(kLanguageCode, string_value);
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kEncodingProperty, &string_value));
+  EXPECT_EQ(kEncoding, string_value);
+
+  // URI record.
+  data.Clear();
+  data.SetString(NfcNdefRecord::kFieldURI, kURI);
+  data.SetString(NfcNdefRecord::kFieldMimeType, kMimeType);
+  data.SetDouble(NfcNdefRecord::kFieldTargetSize, kSize);
+
+  record.reset(new NfcNdefRecord());
+  ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeURI, &data));
+
+  result.Clear();
+  EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes(
+      record.get(), &result));
+
+  EXPECT_TRUE(result.GetString(nfc_record::kTypeProperty, &string_value));
+  EXPECT_EQ(nfc_record::kTypeUri, string_value);
+  EXPECT_TRUE(result.GetString(nfc_record::kUriProperty, &string_value));
+  EXPECT_EQ(kURI, string_value);
+  EXPECT_TRUE(result.GetString(nfc_record::kMimeTypeProperty, &string_value));
+  EXPECT_EQ(kMimeType, string_value);
+  double double_value;
+  EXPECT_TRUE(result.GetDouble(nfc_record::kSizeProperty, &double_value));
+  EXPECT_EQ(kSize, double_value);
+
+  // SmartPoster record.
+  base::DictionaryValue* title = new base::DictionaryValue();
+  title->SetString(NfcNdefRecord::kFieldText, kText);
+  title->SetString(NfcNdefRecord::kFieldLanguageCode, kLanguageCode);
+  title->SetString(NfcNdefRecord::kFieldEncoding, kEncoding);
+
+  base::ListValue* titles = new base::ListValue();
+  titles->Append(title);
+  data.Set(NfcNdefRecord::kFieldTitles, titles);
+
+  record.reset(new NfcNdefRecord());
+  ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data));
+
+  result.Clear();
+  EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes(
+      record.get(), &result));
+
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kTypeProperty, &string_value));
+  EXPECT_EQ(nfc_record::kTypeSmartPoster, string_value);
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kRepresentationProperty, &string_value));
+  EXPECT_EQ(kText, string_value);
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kLanguageProperty, &string_value));
+  EXPECT_EQ(kLanguageCode, string_value);
+  EXPECT_TRUE(result.GetString(
+      nfc_record::kEncodingProperty, &string_value));
+  EXPECT_EQ(kEncoding, string_value);
+  EXPECT_TRUE(result.GetString(nfc_record::kUriProperty, &string_value));
+  EXPECT_EQ(kURI, string_value);
+  EXPECT_TRUE(result.GetString(nfc_record::kMimeTypeProperty, &string_value));
+  EXPECT_EQ(kMimeType, string_value);
+  EXPECT_TRUE(result.GetDouble(nfc_record::kSizeProperty, &double_value));
+  EXPECT_EQ(kSize, double_value);
+}
+
+TEST_F(NfcChromeOSTest, RecordPropertiesToNfcNdefRecord) {
+  const char kText[] = "text";
+  const char kURI[] = "test://uri";
+  const char kEncoding[] = "encoding";
+  const char kLanguageCode[] = "en";
+  const char kMimeType[] = "mime-type";
+  const uint32 kSize = 5;
+
+  FakeNfcRecordClient::Properties record_properties(
+      base::Bind(&OnPropertyChangedCallback));
+
+  // Text record.
+  record_properties.type.ReplaceValue(nfc_record::kTypeText);
+  record_properties.representation.ReplaceValue(kText);
+  record_properties.language.ReplaceValue(kLanguageCode);
+  record_properties.encoding.ReplaceValue(kEncoding);
+
+  scoped_ptr<NfcNdefRecord> record(new NfcNdefRecord());
+  EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
+      &record_properties, record.get()));
+  EXPECT_TRUE(record->IsPopulated());
+
+  std::string string_value;
+  EXPECT_EQ(NfcNdefRecord::kTypeText, record->type());
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldText, &string_value));
+  EXPECT_EQ(kText, string_value);
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldLanguageCode, &string_value));
+  EXPECT_EQ(kLanguageCode, string_value);
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldEncoding, &string_value));
+  EXPECT_EQ(kEncoding, string_value);
+
+  // URI record.
+  record_properties.representation.ReplaceValue("");
+  record_properties.language.ReplaceValue("");
+  record_properties.encoding.ReplaceValue("");
+
+  record_properties.type.ReplaceValue(nfc_record::kTypeUri);
+  record_properties.uri.ReplaceValue(kURI);
+  record_properties.mime_type.ReplaceValue(kMimeType);
+  record_properties.size.ReplaceValue(kSize);
+
+  record.reset(new NfcNdefRecord());
+  EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
+      &record_properties, record.get()));
+  EXPECT_TRUE(record->IsPopulated());
+
+  EXPECT_EQ(NfcNdefRecord::kTypeURI, record->type());
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldURI, &string_value));
+  EXPECT_EQ(kURI, string_value);
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldMimeType, &string_value));
+  EXPECT_EQ(kMimeType, string_value);
+  double double_value;
+  EXPECT_TRUE(record->data().GetDouble(
+      NfcNdefRecord::kFieldTargetSize, &double_value));
+  EXPECT_EQ(kSize, double_value);
+
+  // Contents not matching type.
+  record_properties.representation.ReplaceValue(kText);
+  record_properties.language.ReplaceValue(kLanguageCode);
+  record_properties.encoding.ReplaceValue(kEncoding);
+
+  record.reset(new NfcNdefRecord());
+  EXPECT_FALSE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
+      &record_properties, record.get()));
+  EXPECT_FALSE(record->IsPopulated());
+
+  // SmartPoster record.
+  record_properties.type.ReplaceValue(nfc_record::kTypeSmartPoster);
+  EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
+      &record_properties, record.get()));
+  EXPECT_TRUE(record->IsPopulated());
+
+  EXPECT_EQ(NfcNdefRecord::kTypeSmartPoster, record->type());
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldURI, &string_value));
+  EXPECT_EQ(kURI, string_value);
+  EXPECT_TRUE(record->data().GetString(
+      NfcNdefRecord::kFieldMimeType, &string_value));
+  EXPECT_EQ(kMimeType, string_value);
+  EXPECT_TRUE(record->data().GetDouble(
+      NfcNdefRecord::kFieldTargetSize, &double_value));
+  EXPECT_EQ(kSize, double_value);
+
+  const base::ListValue* titles = NULL;
+  EXPECT_TRUE(record->data().GetList(NfcNdefRecord::kFieldTitles, &titles));
+  EXPECT_EQ(static_cast<size_t>(1), titles->GetSize());
+  ASSERT_TRUE(titles);
+  const base::DictionaryValue* title = NULL;
+  EXPECT_TRUE(titles->GetDictionary(0, &title));
+  CHECK(title);
+
+  EXPECT_TRUE(title->GetString(NfcNdefRecord::kFieldText, &string_value));
+  EXPECT_EQ(kText, string_value);
+  EXPECT_TRUE(title->GetString(
+      NfcNdefRecord::kFieldLanguageCode, &string_value));
+  EXPECT_EQ(kLanguageCode, string_value);
+  EXPECT_TRUE(title->GetString(NfcNdefRecord::kFieldEncoding, &string_value));
+  EXPECT_EQ(kEncoding, string_value);
+}
+
 }  // namespace chromeos
diff --git a/device/nfc/nfc_ndef_record.cc b/device/nfc/nfc_ndef_record.cc
index ec13c2f..5b8bb99 100644
--- a/device/nfc/nfc_ndef_record.cc
+++ b/device/nfc/nfc_ndef_record.cc
@@ -7,6 +7,7 @@
 #include <map>
 
 #include "base/logging.h"
+#include "url/gurl.h"
 
 using base::DictionaryValue;
 using base::ListValue;
@@ -17,6 +18,23 @@
 
 typedef std::map<std::string, base::Value::Type> FieldValueMap;
 
+bool ValidateURI(const DictionaryValue* data) {
+  std::string uri;
+  if (!data->GetString(NfcNdefRecord::kFieldURI, &uri)) {
+    VLOG(1) << "No URI entry in data.";
+    return false;
+  }
+  DCHECK(!uri.empty());
+
+  // Use GURL to check validity.
+  GURL url(uri);
+  if (!url.is_valid()) {
+    LOG(ERROR) << "Invalid URI given: " << uri;
+    return false;
+  }
+  return true;
+}
+
 bool CheckFieldsAreValid(
     const FieldValueMap& required_fields,
     const FieldValueMap& optional_fields,
@@ -47,6 +65,12 @@
               << field_iter->second;
       return false;
     }
+    // Make sure that the value is non-empty, if the value is a string.
+    std::string string_value;
+    if (iter.value().GetAsString(&string_value) && string_value.empty()) {
+      VLOG(1) << "Empty value given for field of type string: " << iter.key();
+      return false;
+    }
   }
   // Check for required fields.
   if (required_count != required_fields.size()) {
@@ -63,13 +87,23 @@
   VLOG(1) << "Populating record with type \"Text\".";
   FieldValueMap required_fields;
   required_fields[NfcNdefRecord::kFieldText] = base::Value::TYPE_STRING;
+  required_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING;
+  required_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING;
   FieldValueMap optional_fields;
-  optional_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING;
-  optional_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING;
   if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
     VLOG(1) << "Failed to populate record.";
     return false;
   }
+
+  // Verify that the "Encoding" property has valid values.
+  std::string encoding;
+  if (!data->GetString(NfcNdefRecord::kFieldEncoding, &encoding)) {
+    if (encoding != NfcNdefRecord::kEncodingUtf8 ||
+        encoding != NfcNdefRecord::kEncodingUtf16) {
+      VLOG(1) << "Invalid \"Encoding\" value:" << encoding;
+      return false;
+    }
+  }
   return true;
 }
 
@@ -113,7 +147,7 @@
       }
     }
   }
-  return true;
+  return ValidateURI(data);
 }
 
 // Verifies that the contents of |data| conform to the fields of NDEF type
@@ -125,11 +159,13 @@
   FieldValueMap optional_fields;
   optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING;
   optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE;
+
+  // Allow passing TargetSize as an integer, but convert it to a double.
   if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
     VLOG(1) << "Failed to populate record.";
     return false;
   }
-  return true;
+  return ValidateURI(data);
 }
 
 }  // namespace
@@ -210,4 +246,15 @@
   records_.push_back(record);
 }
 
+bool NfcNdefMessage::RemoveRecord(NfcNdefRecord* record) {
+  for (RecordList::iterator iter = records_.begin();
+       iter != records_.end(); ++iter) {
+    if (*iter == record) {
+      records_.erase(iter);
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace device
diff --git a/device/nfc/nfc_ndef_record.h b/device/nfc/nfc_ndef_record.h
index 2be66b1..24cbc12 100644
--- a/device/nfc/nfc_ndef_record.h
+++ b/device/nfc/nfc_ndef_record.h
@@ -146,9 +146,15 @@
   const RecordList& records() const { return records_; }
 
   // Adds the NDEF record |record| to the sequence of records that this
-  // NdefMessage contains.
+  // NfcNdefMessage contains. This method simply adds the record to this message
+  // and does NOT take ownership of it.
   void AddRecord(NfcNdefRecord* record);
 
+  // Removes the NDEF record |record| from this message. Returns true, if the
+  // record was removed, otherwise returns false if |record| was not contained
+  // in this message.
+  bool RemoveRecord(NfcNdefRecord* record);
+
  private:
   // The NDEF records that are contained by this message.
   RecordList records_;
diff --git a/device/nfc/nfc_ndef_record_unittest.cc b/device/nfc/nfc_ndef_record_unittest.cc
index 00c2d2f..2b6ef48 100644
--- a/device/nfc/nfc_ndef_record_unittest.cc
+++ b/device/nfc/nfc_ndef_record_unittest.cc
@@ -21,7 +21,7 @@
 const char kTestMimeType[] = "test-mime-type";
 const uint32 kTestTargetSize = 0;
 const char kTestText[] = "test-text";
-const char kTestURI[] = "test-uri";
+const char kTestURI[] = "test://uri";
 
 }  // namespace
 
@@ -33,35 +33,32 @@
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data));
   EXPECT_FALSE(record->IsPopulated());
 
-  // Text field with incorrect entry. Should fail.
+  // Text field with incorrect type. Should fail.
   data.SetInteger(NfcNdefRecord::kFieldText, 0);
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data));
   EXPECT_FALSE(record->IsPopulated());
 
-  // Text field with correct entry. Should succeed.
+  // Text field with correct type but missing encoding and language.
+  // Should fail.
   data.SetString(NfcNdefRecord::kFieldText, kTestText);
-  EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data));
-  EXPECT_TRUE(record->IsPopulated());
-  EXPECT_EQ(NfcNdefRecord::kTypeText, record->type());
+  EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data));
+  EXPECT_FALSE(record->IsPopulated());
 
   // Populating a successfully populated record should fail.
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data));
 
-  // Recycle the record.
-  record.reset(new NfcNdefRecord());
-  EXPECT_FALSE(record->IsPopulated());
-
-  // Incorrect optional fields. Should fail.
+  // Incorrect type for language code.
   data.SetInteger(NfcNdefRecord::kFieldLanguageCode, 0);
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data));
   EXPECT_FALSE(record->IsPopulated());
 
+  // Correct type for language code, invalid encoding.
   data.SetString(NfcNdefRecord::kFieldLanguageCode, kTestLanguageCode);
   data.SetInteger(NfcNdefRecord::kFieldEncoding, 0);
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data));
   EXPECT_FALSE(record->IsPopulated());
 
-  // Optional fields are correct. Should succeed.
+  // All entries valid. Should succeed.
   data.SetString(NfcNdefRecord::kFieldEncoding, kTestEncoding);
   EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data));
   EXPECT_TRUE(record->IsPopulated());
@@ -88,12 +85,16 @@
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data));
   EXPECT_FALSE(record->IsPopulated());
 
-  // URI field with incorrect entry. Should fail.
+  // URI field with incorrect type. Should fail.
   data.SetInteger(NfcNdefRecord::kFieldURI, 0);
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data));
   EXPECT_FALSE(record->IsPopulated());
 
-  // URI field with correct entry. Should succeed.
+  // URI field with correct type but invalid format.
+  data.SetString(NfcNdefRecord::kFieldURI, "test.uri");
+  EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data));
+  EXPECT_FALSE(record->IsPopulated());
+
   data.SetString(NfcNdefRecord::kFieldURI, kTestURI);
   EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeURI, &data));
   EXPECT_TRUE(record->IsPopulated());
@@ -203,8 +204,10 @@
   EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data));
   EXPECT_FALSE(record->IsPopulated());
 
-  // Title value with valid "text" field.
+  // Title value with valid entries.
   title_value->SetString(NfcNdefRecord::kFieldText, kTestText);
+  title_value->SetString(NfcNdefRecord::kFieldLanguageCode, kTestLanguageCode);
+  title_value->SetString(NfcNdefRecord::kFieldEncoding, kTestEncoding);
 
   EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data));
   EXPECT_TRUE(record->IsPopulated());
@@ -233,6 +236,12 @@
   EXPECT_TRUE(const_dictionary_value->GetString(
       NfcNdefRecord::kFieldText, &string_value));
   EXPECT_EQ(kTestText, string_value);
+  EXPECT_TRUE(const_dictionary_value->GetString(
+      NfcNdefRecord::kFieldLanguageCode, &string_value));
+  EXPECT_EQ(kTestLanguageCode, string_value);
+  EXPECT_TRUE(const_dictionary_value->GetString(
+      NfcNdefRecord::kFieldEncoding, &string_value));
+  EXPECT_EQ(kTestEncoding, string_value);
 }
 
 }  // namespace device
diff --git a/device/nfc/nfc_ndef_record_utils_chromeos.cc b/device/nfc/nfc_ndef_record_utils_chromeos.cc
new file mode 100644
index 0000000..466cf38
--- /dev/null
+++ b/device/nfc/nfc_ndef_record_utils_chromeos.cc
@@ -0,0 +1,238 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/nfc/nfc_ndef_record_utils_chromeos.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/nfc/nfc_ndef_record.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using device::NfcNdefRecord;
+
+namespace chromeos {
+namespace nfc_ndef_record_utils {
+
+namespace {
+
+// Maps the NDEF type |type| as returned by neard to the corresponding
+// device::NfcNdefRecord::Type value.
+NfcNdefRecord::Type DBusRecordTypeValueToNfcNdefRecordType(
+    const std::string& type) {
+  if (type == nfc_record::kTypeSmartPoster)
+    return NfcNdefRecord::kTypeSmartPoster;
+  if (type == nfc_record::kTypeText)
+    return NfcNdefRecord::kTypeText;
+  if (type == nfc_record::kTypeUri)
+    return NfcNdefRecord::kTypeURI;
+  if (type == nfc_record::kTypeHandoverRequest)
+    return NfcNdefRecord::kTypeHandoverRequest;
+  if (type == nfc_record::kTypeHandoverSelect)
+    return NfcNdefRecord::kTypeHandoverSelect;
+  if (type == nfc_record::kTypeHandoverCarrier)
+    return NfcNdefRecord::kTypeHandoverCarrier;
+  return NfcNdefRecord::kTypeUnknown;
+}
+
+// Maps the NDEF type |type| given as a NFC C++ API enumeration to the
+// corresponding string value defined by neard.
+std::string NfcRecordTypeEnumToPropertyValue(NfcNdefRecord::Type type) {
+  switch (type) {
+    case NfcNdefRecord::kTypeSmartPoster:
+      return nfc_record::kTypeSmartPoster;
+    case NfcNdefRecord::kTypeText:
+      return nfc_record::kTypeText;
+    case NfcNdefRecord::kTypeURI:
+      return nfc_record::kTypeUri;
+    case NfcNdefRecord::kTypeHandoverRequest:
+      return nfc_record::kTypeHandoverRequest;
+    case NfcNdefRecord::kTypeHandoverSelect:
+      return nfc_record::kTypeHandoverSelect;
+    case NfcNdefRecord::kTypeHandoverCarrier:
+      return nfc_record::kTypeHandoverCarrier;
+    default:
+      return "";
+  }
+}
+
+// Maps the field name |field_name| given as defined in NfcNdefRecord to the
+// Neard Record D-Bus property name. This handles all fields except for
+// NfcNdefRecord::kFieldTitles and NfcNdefRecord::kFieldAction, which need
+// special handling.
+std::string NdefRecordFieldToDBusProperty(const std::string& field_name) {
+  if (field_name == NfcNdefRecord::kFieldEncoding)
+    return nfc_record::kEncodingProperty;
+  if (field_name == NfcNdefRecord::kFieldLanguageCode)
+    return nfc_record::kLanguageProperty;
+  if (field_name == NfcNdefRecord::kFieldText)
+    return nfc_record::kRepresentationProperty;
+  if (field_name == NfcNdefRecord::kFieldURI)
+    return nfc_record::kUriProperty;
+  if (field_name == NfcNdefRecord::kFieldMimeType)
+    return nfc_record::kMimeTypeProperty;
+  if (field_name == NfcNdefRecord::kFieldTargetSize)
+    return nfc_record::kSizeProperty;
+  return "";
+}
+
+std::string NfcNdefRecordActionValueToDBusActionValue(
+    const std::string& action) {
+  // TODO(armansito): Add bindings for values returned by neard to
+  // service_constants.h.
+  if (action == device::NfcNdefRecord::kSmartPosterActionDo)
+    return "Do";
+  if (action == device::NfcNdefRecord::kSmartPosterActionSave)
+    return "Save";
+  if (action == device::NfcNdefRecord::kSmartPosterActionOpen)
+    return "Edit";
+  return "";
+}
+
+std::string DBusActionValueToNfcNdefRecordActionValue(
+    const std::string& action) {
+  // TODO(armansito): Add bindings for values returned by neard to
+  // service_constants.h.
+  if (action == "Do")
+    return device::NfcNdefRecord::kSmartPosterActionDo;
+  if (action == "Save")
+    return device::NfcNdefRecord::kSmartPosterActionSave;
+  if (action == "Edit")
+    return device::NfcNdefRecord::kSmartPosterActionOpen;
+  return "";
+}
+
+
+// Translates the given dictionary of NDEF fields by recursively converting
+// each key in |record_data| to its corresponding Record property name defined
+// by the Neard D-Bus API. The output is stored in |out|. Returns false if an
+// error occurs.
+bool ConvertNdefFieldsToDBusAttributes(
+    const base::DictionaryValue& fields,
+    base::DictionaryValue* out) {
+  DCHECK(out);
+  for (base::DictionaryValue::Iterator iter(fields);
+       !iter.IsAtEnd(); iter.Advance()) {
+    // Special case the "titles" and "action" fields.
+    if (iter.key() == NfcNdefRecord::kFieldTitles) {
+      const base::ListValue* titles = NULL;
+      bool value_result = iter.value().GetAsList(&titles);
+      DCHECK(value_result);
+      DCHECK(titles->GetSize() != 0);
+      // TODO(armansito): For now, pick the first title in the list and write
+      // its contents directly to the top level of the field. This is due to an
+      // error in the Neard D-Bus API design. This code will need to be updated
+      // if the neard API changes to correct this.
+      const base::DictionaryValue* first_title = NULL;
+      value_result = titles->GetDictionary(0, &first_title);
+      DCHECK(value_result);
+      if (!ConvertNdefFieldsToDBusAttributes(*first_title, out)) {
+        LOG(ERROR) << "Invalid title field.";
+        return false;
+      }
+    } else if (iter.key() == NfcNdefRecord::kFieldAction) {
+      // The value of the action field needs to be translated.
+      std::string action_value;
+      bool value_result = iter.value().GetAsString(&action_value);
+      DCHECK(value_result);
+      std::string action =
+          NfcNdefRecordActionValueToDBusActionValue(action_value);
+      if (action.empty()) {
+        VLOG(1) << "Invalid action value: \"" << action_value << "\"";
+        return false;
+      }
+      out->SetString(nfc_record::kActionProperty, action);
+    } else {
+      std::string dbus_property = NdefRecordFieldToDBusProperty(iter.key());
+      if (dbus_property.empty()) {
+        LOG(ERROR) << "Invalid field: " << iter.key();
+        return false;
+      }
+      out->Set(dbus_property, iter.value().DeepCopy());
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+bool NfcNdefRecordToDBusAttributes(
+      const NfcNdefRecord* record,
+      base::DictionaryValue* out) {
+  DCHECK(record);
+  DCHECK(out);
+  if (!record->IsPopulated()) {
+    LOG(ERROR) << "Record is not populated.";
+    return false;
+  }
+  out->SetString(nfc_record::kTypeProperty,
+                 NfcRecordTypeEnumToPropertyValue(record->type()));
+  return ConvertNdefFieldsToDBusAttributes(record->data(), out);
+}
+
+bool RecordPropertiesToNfcNdefRecord(
+      const NfcRecordClient::Properties* properties,
+      device::NfcNdefRecord* out) {
+  if (out->IsPopulated()) {
+    LOG(ERROR) << "Record is already populated!";
+    return false;
+  }
+  NfcNdefRecord::Type type =
+      DBusRecordTypeValueToNfcNdefRecordType(properties->type.value());
+  if (type == NfcNdefRecord::kTypeUnknown) {
+    LOG(ERROR) << "Record type is unknown.";
+    return false;
+  }
+
+  // Extract each property.
+  base::DictionaryValue attributes;
+  if (!properties->uri.value().empty())
+    attributes.SetString(NfcNdefRecord::kFieldURI, properties->uri.value());
+  if (!properties->mime_type.value().empty()) {
+    attributes.SetString(NfcNdefRecord::kFieldMimeType,
+                         properties->mime_type.value());
+  }
+  if (properties->size.value() != 0) {
+    attributes.SetDouble(NfcNdefRecord::kFieldTargetSize,
+                         static_cast<double>(properties->size.value()));
+  }
+  std::string action_value =
+    DBusActionValueToNfcNdefRecordActionValue(properties->action.value());
+  if (!action_value.empty())
+    attributes.SetString(NfcNdefRecord::kFieldAction, action_value);
+
+  // The "representation", "encoding", and "language" properties will be stored
+  // differently, depending on whether the record type is "SmartPoster" or
+  // "Text".
+  {
+    scoped_ptr<base::DictionaryValue> text_attributes(
+        new base::DictionaryValue());
+    if (!properties->representation.value().empty()) {
+      text_attributes->SetString(NfcNdefRecord::kFieldText,
+                                 properties->representation.value());
+    }
+    if (!properties->encoding.value().empty()) {
+      text_attributes->SetString(NfcNdefRecord::kFieldEncoding,
+                                 properties->encoding.value());
+    }
+    if (!properties->language.value().empty()) {
+      text_attributes->SetString(NfcNdefRecord::kFieldLanguageCode,
+                                 properties->language.value());
+    }
+    if (!text_attributes->empty()) {
+      if (type == NfcNdefRecord::kTypeSmartPoster) {
+        base::ListValue* titles = new base::ListValue();
+        titles->Append(text_attributes.release());
+        attributes.Set(NfcNdefRecord::kFieldTitles, titles);
+      } else {
+        attributes.MergeDictionary(text_attributes.get());
+      }
+    }
+  }
+
+  // Populate the given record.
+  return out->Populate(type, &attributes);
+}
+
+}  // namespace nfc_ndef_record_utils
+}  // namespace chromeos
diff --git a/device/nfc/nfc_ndef_record_utils_chromeos.h b/device/nfc/nfc_ndef_record_utils_chromeos.h
new file mode 100644
index 0000000..2d96b97
--- /dev/null
+++ b/device/nfc/nfc_ndef_record_utils_chromeos.h
@@ -0,0 +1,36 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/values.h"
+#include "chromeos/dbus/nfc_record_client.h"
+
+#ifndef DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_
+#define DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_
+
+namespace device {
+class NfcNdefRecord;
+}  // namespace device;
+
+namespace chromeos {
+namespace nfc_ndef_record_utils {
+
+// Converts the NfcNdefRecord |record| to a dictionary that can be passed to
+// NfcDeviceClient::Push and NfcTagClient::Write and stores it in |out|.
+// Returns false, if an error occurs during conversion.
+bool NfcNdefRecordToDBusAttributes(
+    const device::NfcNdefRecord* record,
+    base::DictionaryValue* out);
+
+// Converts an NDEF record D-Bus properties structure to an NfcNdefRecord
+// instance by populating the instance passed in |out|. |out| must not be NULL
+// and must not be already populated. Returns false, if an error occurs during
+// conversion.
+bool RecordPropertiesToNfcNdefRecord(
+    const NfcRecordClient::Properties* properties,
+    device::NfcNdefRecord* out);
+
+}  // namespace nfc_ndef_record_utils
+}  // namespace chromeos
+
+#endif  // DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_
diff --git a/device/nfc/nfc_peer.h b/device/nfc/nfc_peer.h
index 1578738..a38192f 100644
--- a/device/nfc/nfc_peer.h
+++ b/device/nfc/nfc_peer.h
@@ -37,12 +37,12 @@
    public:
     virtual ~Observer() {}
 
-    // This method will be called when an NDEF message |message| from the peer
+    // This method will be called when an NDEF record |record| from the peer
     // device |peer| is received. Users can use this method to be notified of
     // new records on the device and when the initial set of records are
-    // received from it, if any.
-    virtual void RecordsReceived(NfcPeer* peer,
-                                 const NfcNdefMessage& message) {}
+    // received from it, if any. All records received from |peer| can be
+    // accessed by calling |peer->GetNdefMessage()|.
+    virtual void RecordReceived(NfcPeer* peer, const NfcNdefRecord* record) {}
   };
 
   // The ErrorCallback is used by methods to asynchronously report errors.
@@ -64,12 +64,12 @@
   // this only means that no records have yet been received from the device.
   // Users should use this method in conjunction with the Observer methods
   // to be notified when the records are ready.
-  virtual NfcNdefMessage GetNdefMessage() const = 0;
+  virtual const NfcNdefMessage& GetNdefMessage() const = 0;
 
   // Sends the NDEF records contained in |message| to the peer device. On
   // success, |callback| will be invoked. On failure, |error_callback| will be
   // invoked.
-  virtual void PushNdef(NfcNdefMessage* message,
+  virtual void PushNdef(const NfcNdefMessage& message,
                         const base::Closure& callback,
                         const ErrorCallback& error_callback) = 0;
 
diff --git a/device/nfc/nfc_peer_chromeos.cc b/device/nfc/nfc_peer_chromeos.cc
new file mode 100644
index 0000000..4ef1804
--- /dev/null
+++ b/device/nfc/nfc_peer_chromeos.cc
@@ -0,0 +1,194 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/nfc/nfc_peer_chromeos.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/nfc_device_client.h"
+#include "device/nfc/nfc_ndef_record_utils_chromeos.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using device::NfcNdefMessage;
+using device::NfcNdefRecord;
+
+namespace chromeos {
+
+namespace {
+
+typedef std::vector<dbus::ObjectPath> ObjectPathVector;
+
+}  // namespace
+
+NfcPeerChromeOS::NfcPeerChromeOS(const dbus::ObjectPath& object_path)
+    : object_path_(object_path),
+      weak_ptr_factory_(this) {
+  // Create record objects for all records that were received before.
+  const ObjectPathVector& records =
+      DBusThreadManager::Get()->GetNfcRecordClient()->
+          GetRecordsForDevice(object_path_);
+  for (ObjectPathVector::const_iterator iter = records.begin();
+       iter != records.end(); ++iter) {
+    AddRecord(*iter);
+  }
+  DBusThreadManager::Get()->GetNfcRecordClient()->AddObserver(this);
+}
+
+NfcPeerChromeOS::~NfcPeerChromeOS() {
+  DBusThreadManager::Get()->GetNfcRecordClient()->RemoveObserver(this);
+  STLDeleteValues(&records_);
+}
+
+void NfcPeerChromeOS::AddObserver(device::NfcPeer::Observer* observer) {
+  DCHECK(observer);
+  observers_.AddObserver(observer);
+}
+
+void NfcPeerChromeOS::RemoveObserver(device::NfcPeer::Observer* observer) {
+  DCHECK(observer);
+  observers_.RemoveObserver(observer);
+}
+
+std::string NfcPeerChromeOS::GetIdentifier() const {
+  return object_path_.value();
+}
+
+const NfcNdefMessage& NfcPeerChromeOS::GetNdefMessage() const {
+  return message_;
+}
+
+void NfcPeerChromeOS::PushNdef(const NfcNdefMessage& message,
+                               const base::Closure& callback,
+                               const ErrorCallback& error_callback) {
+  if (message.records().empty()) {
+    LOG(ERROR) << "Given NDEF message is empty. Cannot push it.";
+    error_callback.Run();
+    return;
+  }
+  // TODO(armansito): neard currently supports pushing only one NDEF record
+  // to a remote device and won't support multiple records until 0.15. Until
+  // then, report failure if |message| contains more than one record.
+  if (message.records().size() > 1) {
+    LOG(ERROR) << "Currently, pushing only 1 NDEF record is supported.";
+    error_callback.Run();
+    return;
+  }
+  const NfcNdefRecord* record = message.records()[0];
+  base::DictionaryValue attributes;
+  if (!nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes(
+          record, &attributes)) {
+    LOG(ERROR) << "Failed to extract NDEF record fields for NDEF push.";
+    error_callback.Run();
+    return;
+  }
+  DBusThreadManager::Get()->GetNfcDeviceClient()->Push(
+      object_path_,
+      attributes,
+      base::Bind(&NfcPeerChromeOS::OnPushNdef,
+                 weak_ptr_factory_.GetWeakPtr(),
+                 callback),
+      base::Bind(&NfcPeerChromeOS::OnPushNdefError,
+                 weak_ptr_factory_.GetWeakPtr(),
+                 error_callback));
+}
+
+void NfcPeerChromeOS::StartHandover(HandoverType handover_type,
+                                    const base::Closure& callback,
+                                    const ErrorCallback& error_callback) {
+  // TODO(armansito): Initiating handover with a peer is currently not
+  // supported. For now, return an error immediately.
+  LOG(ERROR) << "NFC Handover currently not supported.";
+  error_callback.Run();
+}
+
+void NfcPeerChromeOS::RecordAdded(const dbus::ObjectPath& object_path) {
+  // Don't create the record object yet. Instead, wait until all record
+  // properties have been received and contruct the object and notify observers
+  // then.
+  VLOG(1) << "Record added: " << object_path.value() << ". Waiting until "
+          << "all properties have been fetched to create record object.";
+}
+
+void NfcPeerChromeOS::RecordRemoved(const dbus::ObjectPath& object_path) {
+  NdefRecordMap::iterator iter = records_.find(object_path);
+  if (iter == records_.end())
+    return;
+  VLOG(1) << "Lost remote NDEF record object: " << object_path.value()
+          << ", removing record.";
+  NfcNdefRecord* record = iter->second;
+  message_.RemoveRecord(record);
+  delete record;
+  records_.erase(iter);
+}
+
+void NfcPeerChromeOS::RecordPropertiesReceived(
+    const dbus::ObjectPath& object_path) {
+  VLOG(1) << "Record properties received for: " << object_path.value();
+
+  // Check if the found record belongs to this device.
+  bool record_found = false;
+  const ObjectPathVector& records =
+      DBusThreadManager::Get()->GetNfcRecordClient()->
+          GetRecordsForDevice(object_path_);
+  for (ObjectPathVector::const_iterator iter = records.begin();
+       iter != records.end(); ++iter) {
+    if (*iter == object_path) {
+      record_found = true;
+      break;
+    }
+  }
+  if (!record_found) {
+    VLOG(1) << "Record \"" << object_path.value() << "\" doesn't belong to this"
+            << " device. Ignoring.";
+    return;
+  }
+
+  AddRecord(object_path);
+}
+
+void NfcPeerChromeOS::OnPushNdef(const base::Closure& callback) {
+  callback.Run();
+}
+
+void NfcPeerChromeOS::OnPushNdefError(const ErrorCallback& error_callback,
+                                      const std::string& error_name,
+                                      const std::string& error_message) {
+  LOG(ERROR) << object_path_.value() << ": Failed to Push NDEF message: "
+             << error_name << ": " << error_message;
+  error_callback.Run();
+}
+
+void NfcPeerChromeOS::AddRecord(const dbus::ObjectPath& object_path) {
+  // Ignore this call if an entry for this record already exists.
+  if (records_.find(object_path) != records_.end()) {
+    VLOG(1) << "Record object for remote \"" << object_path.value()
+            << "\" already exists.";
+    return;
+  }
+
+  NfcRecordClient::Properties* record_properties =
+      DBusThreadManager::Get()->GetNfcRecordClient()->
+          GetProperties(object_path);
+  DCHECK(record_properties);
+
+  NfcNdefRecord* record = new NfcNdefRecord();
+  if (!nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
+          record_properties, record)) {
+    LOG(ERROR) << "Failed to create record object for record with object "
+               << "path \"" << object_path.value() << "\"";
+    delete record;
+    return;
+  }
+
+  message_.AddRecord(record);
+  records_[object_path] = record;
+  FOR_EACH_OBSERVER(NfcPeer::Observer, observers_,
+                    RecordReceived(this, record));
+}
+
+}  // namespace chromeos
diff --git a/device/nfc/nfc_peer_chromeos.h b/device/nfc/nfc_peer_chromeos.h
new file mode 100644
index 0000000..eec8e4b
--- /dev/null
+++ b/device/nfc/nfc_peer_chromeos.h
@@ -0,0 +1,81 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_NFC_NFC_PEER_CHROMEOS_H_
+#define DEVICE_NFC_NFC_PEER_CHROMEOS_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "chromeos/dbus/nfc_record_client.h"
+#include "dbus/object_path.h"
+#include "device/nfc/nfc_ndef_record.h"
+#include "device/nfc/nfc_peer.h"
+
+namespace chromeos {
+
+// The NfcPeerChromeOS class implements NfcPeer for the Chrome OS platform.
+class NfcPeerChromeOS : public device::NfcPeer,
+                        public NfcRecordClient::Observer {
+ public:
+  // NfcPeer overrides.
+  virtual void AddObserver(device::NfcPeer::Observer* observer) OVERRIDE;
+  virtual void RemoveObserver(device::NfcPeer::Observer* observer) OVERRIDE;
+  virtual std::string GetIdentifier() const OVERRIDE;
+  virtual const device::NfcNdefMessage& GetNdefMessage() const OVERRIDE;
+  virtual void PushNdef(const device::NfcNdefMessage& message,
+                        const base::Closure& callback,
+                        const ErrorCallback& error_callback) OVERRIDE;
+  virtual void StartHandover(HandoverType handover_type,
+                             const base::Closure& callback,
+                             const ErrorCallback& error_callback) OVERRIDE;
+
+ private:
+  friend class NfcAdapterChromeOS;
+
+  // Mapping from D-Bus object paths to NfcNdefRecord objects.
+  typedef std::map<dbus::ObjectPath, device::NfcNdefRecord*> NdefRecordMap;
+
+  explicit NfcPeerChromeOS(const dbus::ObjectPath& object_path);
+  virtual ~NfcPeerChromeOS();
+
+  // NfcRecordClient::Observer overrides.
+  virtual void RecordAdded(const dbus::ObjectPath& object_path) OVERRIDE;
+  virtual void RecordRemoved(const dbus::ObjectPath& object_path) OVERRIDE;
+  virtual void RecordPropertiesReceived(
+      const dbus::ObjectPath& object_path) OVERRIDE;
+
+  // Called by dbus:: on completion of the D-Bus method call to push an NDEF.
+  void OnPushNdef(const base::Closure& callback);
+  void OnPushNdefError(const ErrorCallback& error_callback,
+                       const std::string& error_name,
+                       const std::string& error_message);
+
+  // Creates a record object for the record with object path |object_path| and
+  // notifies the observers, if a record object did not already exist for it.
+  void AddRecord(const dbus::ObjectPath& object_path);
+
+  // Object path of the peer that we are currently tracking.
+  dbus::ObjectPath object_path_;
+
+  // A map containing the NDEF records that were received from the peer.
+  NdefRecordMap records_;
+
+  // Message instance that contains pointers to all created records.
+  device::NfcNdefMessage message_;
+
+  // List of observers interested in event notifications from us.
+  ObserverList<device::NfcPeer::Observer> observers_;
+
+  // Note: This should remain the last member so it'll be destroyed and
+  // invalidate its weak pointers before any other members are destroyed.
+  base::WeakPtrFactory<NfcPeerChromeOS> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(NfcPeerChromeOS);
+};
+
+}  // namespace chromeos
+
+#endif  // DEVICE_NFC_NFC_PEER_CHROMEOS_H_
diff --git a/device/nfc/nfc_tag.h b/device/nfc/nfc_tag.h
index 650f37b..039fc78 100644
--- a/device/nfc/nfc_tag.h
+++ b/device/nfc/nfc_tag.h
@@ -31,7 +31,8 @@
     kTagType1,
     kTagType2,
     kTagType3,
-    kTagType4
+    kTagType4,
+    kTagTypeUnknown,
   };
 
   // NFC protocols that a tag can support. A tag will usually support only one
@@ -41,7 +42,8 @@
     kProtocolIsoDep,
     kProtocolJewel,
     kProtocolMifare,
-    kProtocolNfcDep
+    kProtocolNfcDep,
+    kProtocolUnknown
   };
 
   // Interface for observing changes from NFC tags.
@@ -49,12 +51,18 @@
    public:
     virtual ~Observer() {}
 
-    // This method will be called when an NDEF message |message|, stored on the
-    // NFC tag |tag| has been read. Although NDEF is the most common record
-    // storage format for NFC tags, not all tags support it. This method won't
-    // be called if there are no records on an NDEF compliant tag or if the tag
-    // doesn't support NDEF.
-    virtual void RecordsReceived(NfcTag* tag, const NfcNdefMessage& message) {}
+    // Called when the tag type has been determined.
+    virtual void TagTypeChanged(NfcTag* tag, TagType type) {}
+
+    // Called when the write access to the tag has been determined or changed.
+    virtual void TagWritePermissionChanged(NfcTag* tag, bool read_only) {}
+
+    // Called when the underlying NFC protocol has been determined.
+    virtual void TagSupportedProtocolChanged(NfcTag* tag, Protocol protocol) {}
+
+    // Called when all initial values of the tag properties have been received
+    // from the remote tag and |tag| is ready to use.
+    virtual void TagReady(NfcTag* tag) {}
   };
 
   virtual ~NfcTag();
@@ -81,6 +89,17 @@
   virtual NfcTagTechnology::TechnologyTypeMask
       GetSupportedTechnologies() const = 0;
 
+  // Returns true, if all tag properties have been received from the remote tag
+  // and this object is ready to use.
+  virtual bool IsReady() const = 0;
+
+  // Returns a pointer to the NDEF technology object that allows I/O on NDEF
+  // records. If NDEF is not supported by this tag, operations that are
+  // performed on the returned instance may not succeed. Users can determine
+  // support by calling NfcTagTechnology::IsSupportedByTag. The returned
+  // instance is owned by this tag.
+  virtual NfcNdefTagTechnology* GetNdefTagTechnology() = 0;
+
  protected:
   NfcTag();
 
diff --git a/device/nfc/nfc_tag_chromeos.cc b/device/nfc/nfc_tag_chromeos.cc
new file mode 100644
index 0000000..e2390fa
--- /dev/null
+++ b/device/nfc/nfc_tag_chromeos.cc
@@ -0,0 +1,164 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/nfc/nfc_tag_chromeos.h"
+
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "device/nfc/nfc_tag_technology_chromeos.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using device::NfcTag;
+using device::NfcTagTechnology;
+using device::NfcNdefTagTechnology;
+
+namespace chromeos {
+
+namespace {
+
+// Converts an NFC tag type value returned by neard to a NfcTag::TagType enum
+// value.
+NfcTag::TagType DBusTypePropertyToTagType(const std::string& type) {
+  if (type == nfc_tag::kTagType1)
+    return NfcTag::kTagType1;
+  if (type == nfc_tag::kTagType2)
+    return NfcTag::kTagType2;
+  if (type == nfc_tag::kTagType3)
+    return NfcTag::kTagType3;
+  if (type == nfc_tag::kTagType4)
+    return NfcTag::kTagType4;
+  return NfcTag::kTagTypeUnknown;
+}
+
+// Converts an NFC tag protocol value returned by neard to a NfcTag::Protocol
+// enum value.
+NfcTag::Protocol DBusProtocolPropertyToTagProtocol(
+    const std::string& protocol) {
+  if (protocol == nfc_common::kProtocolFelica)
+    return NfcTag::kProtocolFelica;
+  if (protocol == nfc_common::kProtocolIsoDep)
+    return NfcTag::kProtocolIsoDep;
+  if (protocol == nfc_common::kProtocolJewel)
+    return NfcTag::kProtocolJewel;
+  if (protocol == nfc_common::kProtocolMifare)
+    return NfcTag::kProtocolMifare;
+  if (protocol == nfc_common::kProtocolNfcDep)
+    return NfcTag::kProtocolNfcDep;
+  return NfcTag::kProtocolUnknown;
+}
+
+}  // namespace
+
+NfcTagChromeOS::NfcTagChromeOS(const dbus::ObjectPath& object_path)
+    : object_path_(object_path),
+      is_ready_(false),
+      ndef_technology_(new NfcNdefTagTechnologyChromeOS(this)) {
+  DBusThreadManager::Get()->GetNfcTagClient()->AddObserver(this);
+}
+
+NfcTagChromeOS::~NfcTagChromeOS() {
+  DBusThreadManager::Get()->GetNfcTagClient()->RemoveObserver(this);
+}
+
+void NfcTagChromeOS::AddObserver(NfcTag::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void NfcTagChromeOS::RemoveObserver(NfcTag::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+std::string NfcTagChromeOS::GetIdentifier() const {
+  return object_path_.value();
+}
+
+NfcTag::TagType NfcTagChromeOS::GetType() const {
+  DCHECK(object_path_.IsValid());
+  return DBusTypePropertyToTagType(
+      DBusThreadManager::Get()->GetNfcTagClient()->
+          GetProperties(object_path_)->type.value());
+}
+
+bool NfcTagChromeOS::IsReadOnly() const {
+  DCHECK(object_path_.IsValid());
+  return DBusThreadManager::Get()->GetNfcTagClient()->
+      GetProperties(object_path_)->read_only.value();
+}
+
+NfcTag::Protocol NfcTagChromeOS::GetSupportedProtocol() const {
+  DCHECK(object_path_.IsValid());
+  return DBusProtocolPropertyToTagProtocol(
+      DBusThreadManager::Get()->GetNfcTagClient()->
+          GetProperties(object_path_)->protocol.value());
+}
+
+NfcTagTechnology::TechnologyTypeMask
+NfcTagChromeOS::GetSupportedTechnologies() const {
+  // Determine supported technologies based on the tag's protocol and
+  // type.
+  NfcTag::TagType type = GetType();
+  NfcTag::Protocol protocol = GetSupportedProtocol();
+  if (type == NfcTag::kTagTypeUnknown || protocol == kProtocolUnknown) {
+    VLOG(1) << "Tag type and protocol unknown.";
+    return 0;
+  }
+
+  NfcTagTechnology::TechnologyTypeMask technologies = 0;
+  technologies |= NfcTagTechnology::kTechnologyTypeNdef;
+  if (type == NfcTag::kTagType3) {
+    DCHECK(protocol == NfcTag::kProtocolFelica);
+    return technologies | NfcTagTechnology::kTechnologyTypeNfcF;
+  }
+
+  if (protocol == NfcTag::kProtocolIsoDep) {
+    DCHECK(type == NfcTag::kTagType4);
+    technologies |= NfcTagTechnology::kTechnologyTypeIsoDep;
+    // TODO(armansito): Neard doesn't provide enough information to determine
+    // if the underlying wave-form is type A or type B. For now, report
+    // neither.
+    return technologies;
+  }
+
+  return technologies | NfcTagTechnology::kTechnologyTypeNfcA;
+}
+
+bool NfcTagChromeOS::IsReady() const {
+  return is_ready_;
+}
+
+NfcNdefTagTechnology* NfcTagChromeOS::GetNdefTagTechnology() {
+  return ndef_technology_.get();
+}
+
+void NfcTagChromeOS::TagPropertyChanged(const dbus::ObjectPath& object_path,
+                                        const std::string& property_name) {
+  if (object_path != object_path_)
+    return;
+
+  NfcTagClient::Properties* properties =
+    DBusThreadManager::Get()->GetNfcTagClient()->GetProperties(object_path_);
+  DCHECK(properties);
+
+  if (property_name == properties->type.name()) {
+    FOR_EACH_OBSERVER(NfcTag::Observer, observers_,
+                      TagTypeChanged(this, GetType()));
+  } else if (property_name == properties->read_only.name()) {
+    FOR_EACH_OBSERVER(NfcTag::Observer, observers_,
+                      TagWritePermissionChanged(this, IsReadOnly()));
+  } else if (property_name == properties->protocol.name()) {
+    FOR_EACH_OBSERVER(
+        NfcTag::Observer, observers_,
+        TagSupportedProtocolChanged(this, GetSupportedProtocol()));
+  }
+}
+
+void NfcTagChromeOS::TagPropertiesReceived(
+    const dbus::ObjectPath& object_path) {
+  if (is_ready_ || object_path != object_path_)
+    return;
+
+  is_ready_ = true;
+  FOR_EACH_OBSERVER(NfcTag::Observer, observers_, TagReady(this));
+}
+
+}  // namespace chromeos
diff --git a/device/nfc/nfc_tag_chromeos.h b/device/nfc/nfc_tag_chromeos.h
new file mode 100644
index 0000000..5e4a723
--- /dev/null
+++ b/device/nfc/nfc_tag_chromeos.h
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_NFC_NFC_TAG_CHROMEOS_H_
+#define DEVICE_NFC_NFC_TAG_CHROMEOS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "chromeos/dbus/nfc_tag_client.h"
+#include "dbus/object_path.h"
+#include "device/nfc/nfc_tag.h"
+
+namespace chromeos {
+
+class NfcNdefTagTechnologyChromeOS;
+
+// The NfcTagChromeOS class implements device::NfcTag for the Chrome OS
+// platform.
+class NfcTagChromeOS : public device::NfcTag,
+                       public NfcTagClient::Observer {
+ public:
+  // device::NfcTag overrides.
+  virtual void AddObserver(device::NfcTag::Observer* observer) OVERRIDE;
+  virtual void RemoveObserver(device::NfcTag::Observer* observer) OVERRIDE;
+  virtual std::string GetIdentifier() const OVERRIDE;
+  virtual TagType GetType() const OVERRIDE;
+  virtual bool IsReadOnly() const OVERRIDE;
+  virtual device::NfcTag::Protocol GetSupportedProtocol() const OVERRIDE;
+  virtual device::NfcTagTechnology::TechnologyTypeMask
+      GetSupportedTechnologies() const OVERRIDE;
+  virtual bool IsReady() const OVERRIDE;
+  virtual device::NfcNdefTagTechnology* GetNdefTagTechnology() OVERRIDE;
+
+  // NfcTagClient::Observer overrides.
+  virtual void TagPropertyChanged(const dbus::ObjectPath& object_path,
+                                  const std::string& property_name) OVERRIDE;
+  virtual void TagPropertiesReceived(
+      const dbus::ObjectPath& object_path) OVERRIDE;
+
+  // Object path representing the remote tag object.
+  const dbus::ObjectPath& object_path() const { return object_path_; }
+
+ private:
+  friend class NfcAdapterChromeOS;
+
+  explicit NfcTagChromeOS(const dbus::ObjectPath& object_path);
+  virtual ~NfcTagChromeOS();
+
+  // Object path of the tag that we are currently tracking.
+  dbus::ObjectPath object_path_;
+
+  // Stores whether or not the initial set of properties have been received.
+  bool is_ready_;
+
+  // The NfcNdefTagTechnology instance that allows users to perform NDEF
+  // read and write on this tag.
+  scoped_ptr<NfcNdefTagTechnologyChromeOS> ndef_technology_;
+
+  // List of observers interested in event notifications from us.
+  ObserverList<device::NfcTag::Observer> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(NfcTagChromeOS);
+};
+
+}  // namespace chromeos
+
+#endif  // DEVICE_NFC_NFC_TAG_CHROMEOS_H_
diff --git a/device/nfc/nfc_tag_technology.cc b/device/nfc/nfc_tag_technology.cc
index fa2b66a..b07fb52 100644
--- a/device/nfc/nfc_tag_technology.cc
+++ b/device/nfc/nfc_tag_technology.cc
@@ -29,11 +29,4 @@
 NfcNdefTagTechnology::~NfcNdefTagTechnology() {
 }
 
-// static
-NfcNdefTagTechnology* NfcNdefTagTechnology::Create(NfcTag* tag) {
-  // TODO(armansito): Create and return platform-specific implementation
-  // instances here.
-  return NULL;
-}
-
 }  // namespace device
diff --git a/device/nfc/nfc_tag_technology.h b/device/nfc/nfc_tag_technology.h
index c5d1c19..35def12 100644
--- a/device/nfc/nfc_tag_technology.h
+++ b/device/nfc/nfc_tag_technology.h
@@ -15,7 +15,8 @@
 // NfcTagTechnology represents an NFC technology that allows a certain type of
 // I/O operation on an NFC tag. NFC tags can support a wide array of protocols.
 // The NfcTagTechnology hierarchy allows both raw and high-level I/O operations
-// on NFC tags.
+// on NFC tags. Do not create instances of these objects directly. Instead,
+// obtain a handle directly from an NfcTag object.
 class NfcTagTechnology {
  public:
   // The various I/O technologies that an NFC tag can support.
@@ -56,7 +57,15 @@
 };
 
 // NfcNdefTagTechnology allows reading and writing NDEF messages to a tag. This
-// is the most commonly used data exchange format in NFC.
+// is the most commonly used data exchange format in NFC. NDEF is a data
+// exchange format and is the top most layer of the protocol stack. NDEF itself
+// is not a protocol; data is typically formatted in a way that is defined by
+// the NDEF format and then transmitted via one of the underlying protocols.
+// Hence all tags are capable of NDEF data exchange, however, all tags don't
+// necessarily use NDEF to operate (e.g. a tag may contain a smart chip that
+// does data processing on ISO-DEP based APDUs and ignores NDEF). This is why,
+// even if a tag inherently supports NDEF, operations done via this class may
+// not necessarily succeed.
 class NfcNdefTagTechnology : public NfcTagTechnology {
  public:
   // The ErrorCallback is used by methods to asynchronously report errors.
@@ -64,6 +73,25 @@
 
   virtual ~NfcNdefTagTechnology();
 
+  // Interface for observing changes from NFC tags related to NDEF records.
+  class Observer {
+   public:
+    virtual ~Observer() {}
+
+    // This method will be called when an NDEF record |record|, stored on the
+    // NFC tag |tag| has been read. This method will be called multiple times
+    // as records are read from the tag or when the tag's records change (e.g.
+    // when the tag has been rewritten). All received records can be accessed by
+    // calling GetNdefMessage().
+    virtual void RecordReceived(NfcTag* tag, const NfcNdefRecord* record) {}
+  };
+
+  // Adds and removes observers for events on this NFC tag. If monitoring
+  // multiple tags, check the |tag| parameter of observer methods to determine
+  // which tag is issuing the event.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
   // NfcTagTechnology override.
   virtual bool IsSupportedByTag() const OVERRIDE;
 
@@ -72,22 +100,17 @@
   // means that no records have yet been received from the tag. Users should
   // use this method in conjunction with the NfcTag::Observer::RecordsReceived
   // method to be notified when the records are ready.
-  virtual NfcNdefMessage GetNdefMessage() const = 0;
+  virtual const NfcNdefMessage& GetNdefMessage() const = 0;
 
   // Writes the given NDEF message to the underlying tag, overwriting any
   // existing NDEF message on it. On success, |callback| will be invoked. On
   // failure, |error_callback| will be invoked. This method can fail, if the
   // underlying tag does not support NDEF as a technology.
-  virtual void WriteNdefMessage(const NfcNdefMessage& message,
-                                const base::Closure& callback,
-                                const ErrorCallback& error_callback) = 0;
+  virtual void WriteNdef(const NfcNdefMessage& message,
+                         const base::Closure& callback,
+                         const ErrorCallback& error_callback) = 0;
 
-  // Static factory method for constructing an instance. The ownership of the
-  // returned instance belongs to the caller. Returns NULL, if NFC is not
-  // supported on the current platform.
-  static NfcNdefTagTechnology* Create(NfcTag* tag);
-
- private:
+ protected:
   // Constructs a technology instance, where |tag| is the NFC tag that this
   // instance will operate on.
   explicit NfcNdefTagTechnology(NfcTag* tag);
diff --git a/device/nfc/nfc_tag_technology_chromeos.cc b/device/nfc/nfc_tag_technology_chromeos.cc
new file mode 100644
index 0000000..25d1b24
--- /dev/null
+++ b/device/nfc/nfc_tag_technology_chromeos.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/nfc/nfc_tag_technology_chromeos.h"
+
+#include "base/stl_util.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "device/nfc/nfc_ndef_record_utils_chromeos.h"
+#include "device/nfc/nfc_tag_chromeos.h"
+
+using device::NfcNdefMessage;
+using device::NfcNdefRecord;
+
+namespace chromeos {
+
+namespace {
+
+typedef std::vector<dbus::ObjectPath> ObjectPathVector;
+
+}  // namespace
+
+NfcNdefTagTechnologyChromeOS::NfcNdefTagTechnologyChromeOS(NfcTagChromeOS* tag)
+    : NfcNdefTagTechnology(tag),
+      object_path_(tag->object_path()),
+      weak_ptr_factory_(this) {
+  DCHECK(tag);
+  // Create record objects for all records that were received before.
+  const ObjectPathVector& records =
+      DBusThreadManager::Get()->GetNfcRecordClient()->
+          GetRecordsForTag(object_path_);
+  for (ObjectPathVector::const_iterator iter = records.begin();
+       iter != records.end(); ++iter) {
+    AddRecord(*iter);
+  }
+  DBusThreadManager::Get()->GetNfcRecordClient()->AddObserver(this);
+}
+
+NfcNdefTagTechnologyChromeOS::~NfcNdefTagTechnologyChromeOS() {
+  DBusThreadManager::Get()->GetNfcRecordClient()->RemoveObserver(this);
+  STLDeleteValues(&records_);
+}
+
+void NfcNdefTagTechnologyChromeOS::AddObserver(
+    NfcNdefTagTechnology::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void NfcNdefTagTechnologyChromeOS::RemoveObserver(
+    NfcNdefTagTechnology::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+const NfcNdefMessage& NfcNdefTagTechnologyChromeOS::GetNdefMessage() const {
+  return message_;
+}
+
+void NfcNdefTagTechnologyChromeOS::WriteNdef(
+    const device::NfcNdefMessage& message,
+    const base::Closure& callback,
+    const ErrorCallback& error_callback) {
+  if (message.records().empty()) {
+    LOG(ERROR) << "Given NDEF message is empty. Cannot write it.";
+    error_callback.Run();
+    return;
+  }
+  if (!tag()->IsReady()) {
+    LOG(ERROR) << "The tag is not ready yet: " << tag()->GetIdentifier();
+    error_callback.Run();
+    return;
+  }
+  // TODO(armansito): neard currently supports writing only one NDEF record
+  // to a tag and won't support multiple records until 0.15. Until then, report
+  // failure if |message| contains more than one record.
+  if (message.records().size() > 1) {
+    LOG(ERROR) << "Currently, writing only 1 NDEF record is supported.";
+    error_callback.Run();
+    return;
+  }
+  const NfcNdefRecord* record = message.records()[0];
+  base::DictionaryValue attributes;
+  if (!nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes(
+          record, &attributes)) {
+    LOG(ERROR) << "Failed to extract NDEF record fields for NDEF push.";
+    error_callback.Run();
+    return;
+  }
+  DBusThreadManager::Get()->GetNfcTagClient()->Write(
+      object_path_,
+      attributes,
+      base::Bind(&NfcNdefTagTechnologyChromeOS::OnWriteNdefMessage,
+                 weak_ptr_factory_.GetWeakPtr(), callback),
+      base::Bind(&NfcNdefTagTechnologyChromeOS::OnWriteNdefMessageError,
+                 weak_ptr_factory_.GetWeakPtr(), error_callback));
+}
+
+void NfcNdefTagTechnologyChromeOS::RecordAdded(
+    const dbus::ObjectPath& object_path) {
+  // Don't create the record object yet. Instead, wait until all record
+  // properties have been received and construct the object and notify observers
+  // then.
+  VLOG(1) << "Record added: " << object_path.value() << ". Waiting until "
+          "all properties have been fetched to create record object.";
+}
+
+void NfcNdefTagTechnologyChromeOS::RecordRemoved(
+    const dbus::ObjectPath& object_path) {
+  NdefRecordMap::iterator iter = records_.find(object_path);
+  if (iter == records_.end())
+    return;
+  VLOG(1) << "Lost remote NDEF record object: " << object_path.value()
+          << ", removing record.";
+  NfcNdefRecord* record = iter->second;
+  message_.RemoveRecord(record);
+  delete record;
+  records_.erase(iter);
+}
+
+void NfcNdefTagTechnologyChromeOS::RecordPropertiesReceived(
+    const dbus::ObjectPath& object_path) {
+  VLOG(1) << "Record properties received for: " << object_path.value();
+
+  // Check if the found record belongs to this tag.
+  bool record_found = false;
+  const ObjectPathVector& records =
+      DBusThreadManager::Get()->GetNfcRecordClient()->
+          GetRecordsForTag(object_path_);
+  for (ObjectPathVector::const_iterator iter = records.begin();
+       iter != records.end(); ++iter) {
+    if (*iter == object_path) {
+      record_found = true;
+      break;
+    }
+  }
+  if (!record_found) {
+    VLOG(1) << "Record \"" << object_path.value() << "\" doesn't belong to this"
+            << " tag. Ignoring.";
+    return;
+  }
+  AddRecord(object_path);
+}
+
+void NfcNdefTagTechnologyChromeOS::OnWriteNdefMessage(
+    const base::Closure& callback) {
+  callback.Run();
+}
+
+void NfcNdefTagTechnologyChromeOS::OnWriteNdefMessageError(
+    const ErrorCallback& error_callback,
+    const std::string& error_name,
+    const std::string& error_message) {
+  LOG(ERROR) << object_path_.value() << ": Failed to Push NDEF message: "
+             << error_name << ": " << error_message;
+  error_callback.Run();
+}
+
+void NfcNdefTagTechnologyChromeOS::AddRecord(
+    const dbus::ObjectPath& object_path) {
+  // Ignore this call if an entry for this record already exists.
+  if (records_.find(object_path) != records_.end()) {
+    VLOG(1) << "Record object for remote \"" << object_path.value()
+            << "\" already exists.";
+    return;
+  }
+
+  NfcRecordClient::Properties* record_properties =
+      DBusThreadManager::Get()->GetNfcRecordClient()->
+          GetProperties(object_path);
+  DCHECK(record_properties);
+
+  NfcNdefRecord* record = new NfcNdefRecord();
+  if (!nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
+          record_properties, record)) {
+    LOG(ERROR) << "Failed to create record object for record with object "
+               << "path \"" << object_path.value() << "\"";
+    delete record;
+    return;
+  }
+
+  message_.AddRecord(record);
+  records_[object_path] = record;
+  FOR_EACH_OBSERVER(NfcNdefTagTechnology::Observer, observers_,
+                    RecordReceived(tag(), record));
+}
+
+}  // namespace chromeos
diff --git a/device/nfc/nfc_tag_technology_chromeos.h b/device/nfc/nfc_tag_technology_chromeos.h
new file mode 100644
index 0000000..71b070e
--- /dev/null
+++ b/device/nfc/nfc_tag_technology_chromeos.h
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_NFC_NFC_TAG_TECHNOLOGY_CHROMEOS_H_
+#define DEVICE_NFC_NFC_TAG_TECHNOLOGY_CHROMEOS_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "chromeos/dbus/nfc_record_client.h"
+#include "device/nfc/nfc_tag_technology.h"
+
+namespace chromeos {
+
+class NfcTagChromeOS;
+
+// The NfcNdefTagTechnologyChromeOS class implements
+// device::NfcNdefTagTechnology for the Chrome OS platform. The lifetime of an
+// instance of this class must be tied to an instance of NfcTagChromeOS.
+// Instances of this class must never outlast the owning NfcTagChromeOS
+// instance.
+class NfcNdefTagTechnologyChromeOS : public device::NfcNdefTagTechnology,
+                                     public NfcRecordClient::Observer {
+ public:
+  virtual ~NfcNdefTagTechnologyChromeOS();
+
+  // device::NfcNdefTagTechnology overrides.
+  virtual void AddObserver(device::NfcNdefTagTechnology::Observer* observer)
+    OVERRIDE;
+  virtual void RemoveObserver(device::NfcNdefTagTechnology::Observer* observer)
+    OVERRIDE;
+  virtual const device::NfcNdefMessage& GetNdefMessage() const OVERRIDE;
+  virtual void WriteNdef(const device::NfcNdefMessage& message,
+                         const base::Closure& callback,
+                         const ErrorCallback& error_callback) OVERRIDE;
+
+  // NfcRecordClient::Observer overrides.
+  virtual void RecordAdded(const dbus::ObjectPath& object_path) OVERRIDE;
+  virtual void RecordRemoved(const dbus::ObjectPath& object_path) OVERRIDE;
+  virtual void RecordPropertiesReceived(
+      const dbus::ObjectPath& object_path) OVERRIDE;
+
+ private:
+  friend class NfcTagChromeOS;
+
+  // Mapping from D-Bus object paths to NfcNdefRecord objects.
+  typedef std::map<dbus::ObjectPath, device::NfcNdefRecord*> NdefRecordMap;
+
+  explicit NfcNdefTagTechnologyChromeOS(NfcTagChromeOS* tag);
+
+  // Called by dbus:: on completion of the D-Bus method call to write an NDEF.
+  void OnWriteNdefMessage(const base::Closure& callback);
+  void OnWriteNdefMessageError(const ErrorCallback& error_callback,
+                               const std::string& error_name,
+                               const std::string& error_message);
+
+  // Creates a record object for the record with object path |object_path| and
+  // notifies the observers, if a record object did not already exist for it.
+  void AddRecord(const dbus::ObjectPath& object_path);
+
+  // A map containing the NDEF records that were received from the tag.
+  NdefRecordMap records_;
+
+  // Message instance that contains pointers to all created records that are
+  // in |records_|. This is mainly used as the cached return value for
+  // GetNdefMessage().
+  device::NfcNdefMessage message_;
+
+  // List of observers interested in event notifications from us.
+  ObserverList<device::NfcNdefTagTechnology::Observer> observers_;
+
+  // D-Bus object path of the remote tag or device that this object operates
+  // on.
+  dbus::ObjectPath object_path_;
+
+  // Note: This should remain the last member so it'll be destroyed and
+  // invalidate its weak pointers before any other members are destroyed.
+  base::WeakPtrFactory<NfcNdefTagTechnologyChromeOS> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(NfcNdefTagTechnologyChromeOS);
+};
+
+}  // namespace chromeos
+
+#endif  // DEVICE_NFC_NFC_TAG_TECHNOLOGY_CHROMEOS_H_
diff --git a/device/serial/serial.gyp b/device/serial/serial.gyp
new file mode 100644
index 0000000..1f200b9
--- /dev/null
+++ b/device/serial/serial.gyp
@@ -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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'device_serial',
+      'type': 'static_library',
+      'include_dirs': [
+        '../..',
+      ],
+      'conditions': [
+        ['OS=="linux"', {
+          'dependencies': [
+            '../../build/linux/system.gyp:udev',
+          ],
+        }],
+      ],
+      'sources': [
+        'serial_device_enumerator.cc',
+        'serial_device_enumerator.h',
+        'serial_device_enumerator_linux.cc',
+        'serial_device_enumerator_linux.h',
+        'serial_device_enumerator_mac.cc',
+        'serial_device_enumerator_mac.h',
+        'serial_device_enumerator_win.cc',
+        'serial_device_enumerator_win.h',
+        'serial_device_info.cc',
+        'serial_device_info.h',
+      ],
+    },
+  ],
+}
diff --git a/device/serial/serial_device_enumerator.cc b/device/serial/serial_device_enumerator.cc
new file mode 100644
index 0000000..586d33d
--- /dev/null
+++ b/device/serial/serial_device_enumerator.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/serial/serial_device_enumerator.h"
+
+namespace device {
+
+SerialDeviceEnumerator::SerialDeviceEnumerator() {}
+
+SerialDeviceEnumerator::~SerialDeviceEnumerator() {}
+
+}  // namespace device
diff --git a/device/serial/serial_device_enumerator.h b/device/serial/serial_device_enumerator.h
new file mode 100644
index 0000000..33c8878
--- /dev/null
+++ b/device/serial/serial_device_enumerator.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_
+#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "device/serial/serial_device_info.h"
+
+namespace device {
+
+// Discovers and enumerates serial devices available to the host.
+class SerialDeviceEnumerator {
+ public:
+  static scoped_ptr<SerialDeviceEnumerator> Create();
+
+  SerialDeviceEnumerator();
+  virtual ~SerialDeviceEnumerator();
+
+  virtual void GetDevices(SerialDeviceInfoList* devices) = 0;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_
diff --git a/device/serial/serial_device_enumerator_linux.cc b/device/serial/serial_device_enumerator_linux.cc
new file mode 100644
index 0000000..48f1441e
--- /dev/null
+++ b/device/serial/serial_device_enumerator_linux.cc
@@ -0,0 +1,105 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/serial/serial_device_enumerator_linux.h"
+
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/strings/string_number_conversions.h"
+
+namespace device {
+
+namespace {
+
+const char kSerialSubsystem[] = "tty";
+
+const char kHostPathKey[] = "DEVNAME";
+const char kHostBusKey[] = "ID_BUS";
+const char kVendorIDKey[] = "ID_VENDOR_ID";
+const char kProductIDKey[] = "ID_MODEL_ID";
+const char kProductNameKey[] = "ID_MODEL";
+
+struct UdevEnumerateDeleter {
+  void operator()(udev_enumerate* enumerate) {
+    udev_enumerate_unref(enumerate);
+  }
+};
+
+struct UdevDeviceDeleter {
+  void operator()(udev_device* device) { udev_device_unref(device); }
+};
+
+typedef scoped_ptr<udev_enumerate, UdevEnumerateDeleter> ScopedUdevEnumeratePtr;
+typedef scoped_ptr<udev_device, UdevDeviceDeleter> ScopedUdevDevicePtr;
+}
+
+// static
+scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() {
+  return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorLinux());
+}
+
+SerialDeviceEnumeratorLinux::SerialDeviceEnumeratorLinux() {
+  udev_.reset(udev_new());
+}
+
+SerialDeviceEnumeratorLinux::~SerialDeviceEnumeratorLinux() {}
+
+void SerialDeviceEnumeratorLinux::GetDevices(SerialDeviceInfoList* devices) {
+  devices->clear();
+
+  ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get()));
+  if (!enumerate) {
+    LOG(ERROR) << "Serial device enumeration failed.";
+    return;
+  }
+  if (udev_enumerate_add_match_subsystem(enumerate.get(), kSerialSubsystem)) {
+    LOG(ERROR) << "Serial device enumeration failed.";
+    return;
+  }
+  if (udev_enumerate_scan_devices(enumerate.get())) {
+    LOG(ERROR) << "Serial device enumeration failed.";
+    return;
+  }
+
+  udev_list_entry* entry = udev_enumerate_get_list_entry(enumerate.get());
+  for (; entry != NULL; entry = udev_list_entry_get_next(entry)) {
+    ScopedUdevDevicePtr device(udev_device_new_from_syspath(
+        udev_.get(), udev_list_entry_get_name(entry)));
+    // TODO(rockot): There may be a better way to filter serial devices here,
+    // but it's not clear what that would be. Udev will list lots of virtual
+    // devices with no real endpoint to back them anywhere. The presence of
+    // a bus identifier (e.g., "pci" or "usb") seems to be a good heuristic
+    // for detecting actual devices.
+    const char* path =
+        udev_device_get_property_value(device.get(), kHostPathKey);
+    const char* bus = udev_device_get_property_value(device.get(), kHostBusKey);
+    if (path != NULL && bus != NULL) {
+      linked_ptr<SerialDeviceInfo> info(new SerialDeviceInfo());
+      info->path = std::string(path);
+
+      const char* vendor_id =
+          udev_device_get_property_value(device.get(), kVendorIDKey);
+      const char* product_id =
+          udev_device_get_property_value(device.get(), kProductIDKey);
+      const char* product_name =
+          udev_device_get_property_value(device.get(), kProductNameKey);
+
+      uint32 int_value;
+      if (vendor_id && base::HexStringToUInt(vendor_id, &int_value))
+        info->vendor_id.reset(new uint16(int_value));
+      if (product_id && base::HexStringToUInt(product_id, &int_value))
+        info->product_id.reset(new uint16(int_value));
+      if (product_name)
+        info->display_name.reset(new std::string(product_name));
+
+      devices->push_back(info);
+    }
+  }
+}
+
+void SerialDeviceEnumeratorLinux::UdevDeleter::operator()(udev* handle) {
+  udev_unref(handle);
+}
+
+}  // namespace device
diff --git a/device/serial/serial_device_enumerator_linux.h b/device/serial/serial_device_enumerator_linux.h
new file mode 100644
index 0000000..3a8d8a9
--- /dev/null
+++ b/device/serial/serial_device_enumerator_linux.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_DEVICE_ENUMERATOR_LINUX_H_
+#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_LINUX_H_
+
+#include <libudev.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "device/serial/serial_device_enumerator.h"
+#include "device/serial/serial_device_info.h"
+
+namespace device {
+
+// Discovers and enumerates serial devices available to the host.
+class SerialDeviceEnumeratorLinux : public SerialDeviceEnumerator {
+ public:
+  SerialDeviceEnumeratorLinux();
+  virtual ~SerialDeviceEnumeratorLinux();
+
+  // Implementation for SerialDeviceEnumerator.
+  virtual void GetDevices(SerialDeviceInfoList* devices) OVERRIDE;
+
+ private:
+  struct UdevDeleter {
+    void operator()(udev* handle);
+  };
+
+  scoped_ptr<udev, UdevDeleter> udev_;
+
+  DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorLinux);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_H_
diff --git a/device/serial/serial_device_enumerator_mac.cc b/device/serial/serial_device_enumerator_mac.cc
new file mode 100644
index 0000000..1719275
--- /dev/null
+++ b/device/serial/serial_device_enumerator_mac.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/serial/serial_device_enumerator_mac.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+
+namespace device {
+
+// static
+scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() {
+  return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorMac());
+}
+
+SerialDeviceEnumeratorMac::SerialDeviceEnumeratorMac() {}
+
+SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {}
+
+// TODO(rockot): Use IOKit to enumerate serial interfaces.
+void SerialDeviceEnumeratorMac::GetDevices(SerialDeviceInfoList* devices) {
+  const base::FilePath kDevRoot("/dev");
+  const int kFilesAndSymLinks =
+      base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS;
+
+  std::set<std::string> valid_patterns;
+  valid_patterns.insert("/dev/*Bluetooth*");
+  valid_patterns.insert("/dev/*Modem*");
+  valid_patterns.insert("/dev/*bluetooth*");
+  valid_patterns.insert("/dev/*modem*");
+  valid_patterns.insert("/dev/*serial*");
+  valid_patterns.insert("/dev/tty.*");
+  valid_patterns.insert("/dev/cu.*");
+
+  devices->clear();
+  base::FileEnumerator enumerator(kDevRoot, false, kFilesAndSymLinks);
+  do {
+    const base::FilePath next_device_path(enumerator.Next());
+    const std::string next_device = next_device_path.value();
+    if (next_device.empty())
+      break;
+
+    std::set<std::string>::const_iterator i = valid_patterns.begin();
+    for (; i != valid_patterns.end(); ++i) {
+      if (MatchPattern(next_device, *i)) {
+        linked_ptr<SerialDeviceInfo> info(new SerialDeviceInfo);
+        info->path = next_device;
+        devices->push_back(info);
+        break;
+      }
+    }
+  } while (true);
+}
+
+}  // namespace device
diff --git a/device/serial/serial_device_enumerator_mac.h b/device/serial/serial_device_enumerator_mac.h
new file mode 100644
index 0000000..d76b7bc
--- /dev/null
+++ b/device/serial/serial_device_enumerator_mac.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_MAC_H_
+#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_MAC_H_
+
+#include "device/serial/serial_device_enumerator.h"
+#include "device/serial/serial_device_info.h"
+
+namespace device {
+
+// Discovers and enumerates serial devices available to the host.
+class SerialDeviceEnumeratorMac : public SerialDeviceEnumerator {
+ public:
+  SerialDeviceEnumeratorMac();
+  virtual ~SerialDeviceEnumeratorMac();
+
+  // Implementation for SerialDeviceEnumerator.
+  virtual void GetDevices(SerialDeviceInfoList* devices) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorMac);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_MAC_H_
diff --git a/device/serial/serial_device_enumerator_win.cc b/device/serial/serial_device_enumerator_win.cc
new file mode 100644
index 0000000..e3bde29
--- /dev/null
+++ b/device/serial/serial_device_enumerator_win.cc
@@ -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.
+
+#include "device/serial/serial_device_enumerator_win.h"
+
+#include <windows.h>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/win/registry.h"
+
+namespace device {
+
+// static
+scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() {
+  return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorWin());
+}
+
+SerialDeviceEnumeratorWin::SerialDeviceEnumeratorWin() {}
+
+SerialDeviceEnumeratorWin::~SerialDeviceEnumeratorWin() {}
+
+// TODO(rockot): Query the system for more information than just device paths.
+// This may or may not require using a different strategy than scanning the
+// registry location below.
+void SerialDeviceEnumeratorWin::GetDevices(SerialDeviceInfoList* devices) {
+  devices->clear();
+
+  base::win::RegistryValueIterator iter_key(
+      HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM\\");
+  for (; iter_key.Valid(); ++iter_key) {
+    base::string16 value(iter_key.Value());
+    linked_ptr<SerialDeviceInfo> info(new SerialDeviceInfo);
+    info->path = WideToASCII(value);
+    devices->push_back(info);
+  }
+}
+
+}  // namespace device
diff --git a/device/serial/serial_device_enumerator_win.h b/device/serial/serial_device_enumerator_win.h
new file mode 100644
index 0000000..4d36c40
--- /dev/null
+++ b/device/serial/serial_device_enumerator_win.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_
+#define DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_
+
+#include "device/serial/serial_device_enumerator.h"
+#include "device/serial/serial_device_info.h"
+
+namespace device {
+
+// Discovers and enumerates serial devices available to the host.
+class SerialDeviceEnumeratorWin : public SerialDeviceEnumerator {
+ public:
+  SerialDeviceEnumeratorWin();
+  virtual ~SerialDeviceEnumeratorWin();
+
+  // Implementation for SerialDeviceEnumerator.
+  virtual void GetDevices(SerialDeviceInfoList* devices) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SerialDeviceEnumeratorWin);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_
diff --git a/device/serial/serial_device_info.cc b/device/serial/serial_device_info.cc
new file mode 100644
index 0000000..9c7284d
--- /dev/null
+++ b/device/serial/serial_device_info.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/serial/serial_device_info.h"
+
+namespace device {
+
+SerialDeviceInfo::SerialDeviceInfo() {}
+
+SerialDeviceInfo::~SerialDeviceInfo() {}
+
+}  // namespace device
diff --git a/device/serial/serial_device_info.h b/device/serial/serial_device_info.h
new file mode 100644
index 0000000..277bb07
--- /dev/null
+++ b/device/serial/serial_device_info.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_SERIAL_SERIAL_DEVICE_INFO_H_
+#define DEVICE_SERIAL_SERIAL_DEVICE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace device {
+
+struct SerialDeviceInfo {
+  SerialDeviceInfo();
+  ~SerialDeviceInfo();
+
+  std::string path;
+  scoped_ptr<uint16> vendor_id;
+  scoped_ptr<uint16> product_id;
+  scoped_ptr<std::string> display_name;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SerialDeviceInfo);
+};
+
+typedef std::vector<linked_ptr<SerialDeviceInfo> > SerialDeviceInfoList;
+
+}  // namespace device
+
+#endif  // DEVICE_SERIAL_SERIAL_DEVICE_INFO_H_