Relanding addProfile/removeProfile patch with heapchecker fix.

This is a relanding of r197554 with heapchecker fix.

Some profile-dependent services depend on UI thread to clean up, so I had to add
UI message loop in bluetooth_event_router_unittest.cc to make sure that all the
objects are released at the end of test.

I verified with valgrind that this patch does not introduce any new memory leak.

BUG=229636

Review URL: https://chromiumcodereview.appspot.com/14569007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197741 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/api/bluetooth/bluetooth_api.cc b/chrome/browser/extensions/api/bluetooth/bluetooth_api.cc
index 307a2164..2057f37 100644
--- a/chrome/browser/extensions/api/bluetooth/bluetooth_api.cc
+++ b/chrome/browser/extensions/api/bluetooth/bluetooth_api.cc
@@ -20,6 +20,7 @@
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/bluetooth_out_of_band_pairing_data.h"
+#include "device/bluetooth/bluetooth_profile.h"
 #include "device/bluetooth/bluetooth_service_record.h"
 #include "device/bluetooth/bluetooth_socket.h"
 #include "device/bluetooth/bluetooth_utils.h"
@@ -50,6 +51,10 @@
 const char kInvalidUuid[] = "Invalid UUID";
 const char kPlatformNotSupported[] =
     "This operation is not supported on your platform";
+const char kProfileAlreadyRegistered[] =
+    "This profile has already been registered";
+const char kProfileNotFound[] = "Profile not found: invalid uuid";
+const char kProfileRegistrationFailed[] = "Profile registration failed";
 const char kServiceDiscoveryFailed[] = "Service discovery failed";
 const char kSocketNotFoundError[] = "Socket not found: invalid socket id";
 const char kStartDiscoveryFailed[] = "Starting discovery failed";
@@ -107,16 +112,106 @@
 
 namespace api {
 
-// TOOD(youngki): Implement.
+BluetoothAddProfileFunction::BluetoothAddProfileFunction() {
+}
+
 bool BluetoothAddProfileFunction::RunImpl() {
-  return false;
+  scoped_ptr<AddProfile::Params> params(AddProfile::Params::Create(*args_));
+  EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+  if (!BluetoothDevice::IsUUIDValid(params->profile.uuid)) {
+    SetError(kInvalidUuid);
+    return false;
+  }
+
+  uuid_ = device::bluetooth_utils::CanonicalUuid(params->profile.uuid);
+
+  if (GetEventRouter(profile())->HasProfile(uuid_)) {
+    SetError(kProfileAlreadyRegistered);
+    return false;
+  }
+
+  device::BluetoothProfile::Options options;
+  if (params->profile.name.get())
+    options.name = *params->profile.name.get();
+  if (params->profile.channel.get())
+    options.channel = *params->profile.channel.get();
+  if (params->profile.psm.get())
+    options.psm = *params->profile.psm.get();
+  if (params->profile.require_authentication.get()) {
+    options.require_authentication =
+        *params->profile.require_authentication.get();
+  }
+  if (params->profile.require_authorization.get()) {
+    options.require_authorization =
+        *params->profile.require_authorization.get();
+  }
+  if (params->profile.auto_connect.get())
+    options.auto_connect = *params->profile.auto_connect.get();
+  if (params->profile.version.get())
+    options.version = *params->profile.version.get();
+  if (params->profile.features.get())
+    options.features = *params->profile.features.get();
+
+  RegisterProfile(
+      options,
+      base::Bind(&BluetoothAddProfileFunction::OnProfileRegistered, this));
+
+  return true;
+}
+
+void BluetoothAddProfileFunction::RegisterProfile(
+    const device::BluetoothProfile::Options& options,
+    const device::BluetoothProfile::ProfileCallback& callback) {
+  device::BluetoothProfile::Register(uuid_, options, callback);
+}
+
+void BluetoothAddProfileFunction::OnProfileRegistered(
+    device::BluetoothProfile* bluetooth_profile) {
+  if (!bluetooth_profile) {
+    SetError(kProfileRegistrationFailed);
+    SendResponse(false);
+    return;
+  }
+
+  if (GetEventRouter(profile())->HasProfile(uuid_)) {
+    bluetooth_profile->Unregister();
+    SetError(kProfileAlreadyRegistered);
+    SendResponse(false);
+    return;
+  }
+
+  bluetooth_profile->SetConnectionCallback(
+      base::Bind(&ExtensionBluetoothEventRouter::DispatchConnectionEvent,
+                 base::Unretained(GetEventRouter(profile())),
+                 extension_id(),
+                 uuid_));
+  GetEventRouter(profile())->AddProfile(uuid_, bluetooth_profile);
+  SendResponse(true);
+}
+
+bool BluetoothRemoveProfileFunction::RunImpl() {
+  scoped_ptr<RemoveProfile::Params> params(
+      RemoveProfile::Params::Create(*args_));
+
+  if (!BluetoothDevice::IsUUIDValid(params->profile.uuid)) {
+    SetError(kInvalidUuid);
+    return false;
+  }
+
+  std::string uuid =
+      device::bluetooth_utils::CanonicalUuid(params->profile.uuid);
+
+  if (!GetEventRouter(profile())->HasProfile(uuid)) {
+    SetError(kProfileNotFound);
+    return false;
+  }
+
+  GetEventRouter(profile())->RemoveProfile(uuid);
+  return true;
 }
 
 // TODO(youngki): Implement.
-bool BluetoothRemoveProfileFunction::RunImpl() {
-  return false;
-}
-
 bool BluetoothGetProfilesFunction::DoWork(
     scoped_refptr<device::BluetoothAdapter> adapter) {
   scoped_ptr<GetProfiles::Params> params(GetProfiles::Params::Create(*args_));
@@ -273,7 +368,7 @@
 
     bluetooth::Socket result_socket;
     bluetooth::BluetoothDeviceToApiDevice(*device, &result_socket.device);
-    result_socket.service_uuid = service_uuid;
+    result_socket.profile.uuid = service_uuid;
     result_socket.id = socket_id;
     SetResult(result_socket.ToValue().release());
     SendResponse(true);
diff --git a/chrome/browser/extensions/api/bluetooth/bluetooth_api.h b/chrome/browser/extensions/api/bluetooth/bluetooth_api.h
index 525fdfe..5a8230f 100644
--- a/chrome/browser/extensions/api/bluetooth/bluetooth_api.h
+++ b/chrome/browser/extensions/api/bluetooth/bluetooth_api.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/extensions/event_router.h"
 #include "chrome/browser/profiles/profile_keyed_service.h"
 #include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_profile.h"
 
 namespace device {
 
@@ -60,20 +61,30 @@
  public:
   DECLARE_EXTENSION_FUNCTION("bluetooth.addProfile", BLUETOOTH_ADDPROFILE)
 
-  virtual bool RunImpl() OVERRIDE;
+  BluetoothAddProfileFunction();
 
  protected:
   virtual ~BluetoothAddProfileFunction() {}
+  virtual bool RunImpl() OVERRIDE;
+
+  virtual void RegisterProfile(
+      const device::BluetoothProfile::Options& options,
+      const device::BluetoothProfile::ProfileCallback& callback);
+
+ private:
+  void OnProfileRegistered(device::BluetoothProfile* bluetooth_profile);
+
+  std::string uuid_;
 };
 
-class BluetoothRemoveProfileFunction : public AsyncExtensionFunction {
+class BluetoothRemoveProfileFunction : public SyncExtensionFunction {
  public:
   DECLARE_EXTENSION_FUNCTION("bluetooth.removeProfile",
                              BLUETOOTH_REMOVEPROFILE)
-  virtual bool RunImpl() OVERRIDE;
 
  protected:
   virtual ~BluetoothRemoveProfileFunction() {}
+  virtual bool RunImpl() OVERRIDE;
 };
 
 class BluetoothGetProfilesFunction : public BluetoothExtensionFunction {
diff --git a/chrome/browser/extensions/api/bluetooth/bluetooth_apitest.cc b/chrome/browser/extensions/api/bluetooth/bluetooth_apitest.cc
index c2d6af5..28fa4d0 100644
--- a/chrome/browser/extensions/api/bluetooth/bluetooth_apitest.cc
+++ b/chrome/browser/extensions/api/bluetooth/bluetooth_apitest.cc
@@ -19,14 +19,17 @@
 #include "device/bluetooth/bluetooth_out_of_band_pairing_data.h"
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_profile.h"
 #include "device/bluetooth/test/mock_bluetooth_socket.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 using device::BluetoothAdapter;
 using device::BluetoothDevice;
 using device::BluetoothOutOfBandPairingData;
+using device::BluetoothProfile;
 using device::MockBluetoothAdapter;
 using device::MockBluetoothDevice;
+using device::MockBluetoothProfile;
 using extensions::Extension;
 
 namespace utils = extension_function_test_utils;
@@ -48,6 +51,8 @@
 
   virtual void SetUpOnMainThread() OVERRIDE {
     SetUpMockAdapter();
+    profile1_.reset(new testing::NiceMock<MockBluetoothProfile>());
+    profile2_.reset(new testing::NiceMock<MockBluetoothProfile>());
   }
 
   virtual void CleanUpOnMainThread() OVERRIDE {
@@ -78,6 +83,8 @@
   testing::StrictMock<MockBluetoothAdapter>* mock_adapter_;
   scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device1_;
   scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device2_;
+  scoped_ptr<testing::NiceMock<MockBluetoothProfile> > profile1_;
+  scoped_ptr<testing::NiceMock<MockBluetoothProfile> > profile2_;
 
   extensions::ExtensionBluetoothEventRouter* event_router() {
     return extensions::BluetoothAPI::Get(browser()->profile())
@@ -88,6 +95,29 @@
   scoped_refptr<Extension> empty_extension_;
 };
 
+class TestBluetoothAddProfileFunction
+    : public api::BluetoothAddProfileFunction {
+ public:
+  explicit TestBluetoothAddProfileFunction(BluetoothProfile* profile)
+      : BluetoothAddProfileFunction(), profile_(profile) {
+  }
+
+ protected:
+  virtual ~TestBluetoothAddProfileFunction() {
+  }
+
+  // BluetoothAddProfileFunction override.
+  virtual void RegisterProfile(
+      const device::BluetoothProfile::Options& options,
+      const device::BluetoothProfile::ProfileCallback& callback) {
+    callback.Run(profile_);
+  }
+
+ private:
+  // TestBluetoothAddProfileFunction does not own |profile_|.
+  BluetoothProfile* profile_;
+};
+
 // This is the canonical UUID for the short UUID 0010.
 static const char kOutOfBandPairingDataHash[] = "0123456789ABCDEh";
 static const char kOutOfBandPairingDataRandomizer[] = "0123456789ABCDEr";
@@ -141,15 +171,63 @@
 
 }  // namespace
 
-IN_PROC_BROWSER_TEST_F(BluetoothApiTest, AddProfile) {
-  // TODO(youngki): Implement it to test BluetoothAddProfileFunction.
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, Profiles) {
+  EXPECT_CALL(*profile1_, SetConnectionCallback(testing::_));
+  scoped_refptr<TestBluetoothAddProfileFunction> add_profile_function;
+  add_profile_function = setupFunction(
+      new TestBluetoothAddProfileFunction(profile1_.get()));
+  std::string error(
+      utils::RunFunctionAndReturnError(
+          add_profile_function,
+          "[{\"uuid\": \"1234\"}]",
+          browser()));
+  ASSERT_TRUE(error.empty());
+
+  // Registering the profile for the same uuid again will throw an error.
+  add_profile_function = setupFunction(
+      new TestBluetoothAddProfileFunction(profile2_.get()));
+  error = utils::RunFunctionAndReturnError(
+      add_profile_function,
+      "[{\"uuid\": \"1234\"}]",
+      browser());
+  ASSERT_FALSE(error.empty());
+
+  add_profile_function = setupFunction(
+      new TestBluetoothAddProfileFunction(profile2_.get()));
+  error = utils::RunFunctionAndReturnError(
+      add_profile_function,
+      "[{\"uuid\": \"5678\"}]",
+      browser());
+  ASSERT_TRUE(error.empty());
+
+  scoped_refptr<api::BluetoothRemoveProfileFunction> remove_profile_function;
+  remove_profile_function = setupFunction(
+      new api::BluetoothRemoveProfileFunction());
+  error = utils::RunFunctionAndReturnError(
+      remove_profile_function,
+      "[{\"uuid\": \"1234\"}]",
+      browser());
+  ASSERT_TRUE(error.empty());
+
+  remove_profile_function = setupFunction(
+      new api::BluetoothRemoveProfileFunction());
+  error = utils::RunFunctionAndReturnError(
+      remove_profile_function,
+      "[{\"uuid\": \"5678\"}]",
+      browser());
+  ASSERT_TRUE(error.empty());
+
+  // Removing the same profile again will throw an error.
+  remove_profile_function = setupFunction(
+      new api::BluetoothRemoveProfileFunction());
+  error = utils::RunFunctionAndReturnError(
+      remove_profile_function,
+      "[{\"uuid\": \"5678\"}]",
+      browser());
+  ASSERT_FALSE(error.empty());
 }
 
-IN_PROC_BROWSER_TEST_F(BluetoothApiTest, RemoveProfile) {
-  // TODO(youngki): Implement it to test BluetoothRemoveProfileFunction.
-}
-
-IN_PROC_BROWSER_TEST_F(BluetoothApiTest, OnAdapterStateChanged) {
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetAdapterState) {
   EXPECT_CALL(*mock_adapter_, GetAddress())
       .WillOnce(testing::Return(kAdapterAddress));
   EXPECT_CALL(*mock_adapter_, GetName())
@@ -262,7 +340,7 @@
   start_function = setupFunction(new api::BluetoothStartDiscoveryFunction);
   std::string error(
       utils::RunFunctionAndReturnError(start_function, "[]", browser()));
-  ASSERT_TRUE(!error.empty());
+  ASSERT_FALSE(error.empty());
 
   // Reset for a successful start
   SetUpMockAdapter();
@@ -276,7 +354,7 @@
   testing::Mock::VerifyAndClearExpectations(mock_adapter_);
   EXPECT_CALL(*mock_adapter_, StopDiscovering(testing::_, testing::_))
       .WillOnce(testing::Invoke(CallDiscoveryCallback));
-  // StopDiscovery success will remove the apapter that is no longer used.
+  // StopDiscovery success will remove the adapter that is no longer used.
   EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
   scoped_refptr<api::BluetoothStopDiscoveryFunction> stop_function;
   stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction);
@@ -289,7 +367,7 @@
   EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
   stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction);
   error = utils::RunFunctionAndReturnError(stop_function, "[]", browser());
-  ASSERT_TRUE(!error.empty());
+  ASSERT_FALSE(error.empty());
   SetUpMockAdapter();
 }
 
@@ -368,13 +446,15 @@
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
-IN_PROC_BROWSER_TEST_F(BluetoothApiTest, Events) {
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, OnAdapterStateChanged) {
   ResultCatcher catcher;
   catcher.RestrictToProfile(browser()->profile());
 
   // Load and wait for setup
   ExtensionTestMessageListener listener("ready", true);
-  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("bluetooth/events")));
+  ASSERT_TRUE(
+      LoadExtension(
+          test_data_dir_.AppendASCII("bluetooth/on_adapter_state_changed")));
   EXPECT_TRUE(listener.WaitUntilSatisfied());
 
   EXPECT_CALL(*mock_adapter_, GetAddress())
@@ -418,6 +498,29 @@
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, OnConnection) {
+  ResultCatcher catcher;
+  catcher.RestrictToProfile(browser()->profile());
+
+  // Load and wait for setup
+  ExtensionTestMessageListener listener("ready", true);
+  scoped_refptr<const Extension> extension(
+      LoadExtension(test_data_dir_.AppendASCII("bluetooth/on_connection")));
+  ASSERT_TRUE(extension.get());
+  EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+  scoped_refptr<device::MockBluetoothSocket> socket =
+      new device::MockBluetoothSocket();
+
+  event_router()->AddProfile("1234", profile1_.get());
+  event_router()->DispatchConnectionEvent(
+      extension->id(), "1234", device1_.get(), socket);
+
+  listener.Reply("go");
+  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+  event_router()->RemoveProfile("1234");
+}
+
 IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetProfiles) {
   ResultCatcher catcher;
   catcher.RestrictToProfile(browser()->profile());
diff --git a/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.cc b/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.cc
index bd106c5..02331e2 100644
--- a/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.cc
+++ b/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.cc
@@ -20,6 +20,7 @@
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_profile.h"
 #include "device/bluetooth/bluetooth_socket.h"
 
 namespace extensions {
@@ -43,6 +44,12 @@
   DLOG_IF(WARNING, socket_map_.size() != 0)
       << "Bluetooth sockets are still open.";
   socket_map_.clear();
+
+  for (BluetoothProfileMap::iterator iter = bluetooth_profile_map_.begin();
+       iter != bluetooth_profile_map_.end();
+       ++iter) {
+    iter->second->Unregister();
+  }
 }
 
 bool ExtensionBluetoothEventRouter::IsBluetoothSupported() const {
@@ -92,6 +99,26 @@
   return true;
 }
 
+void ExtensionBluetoothEventRouter::AddProfile(
+    const std::string& uuid,
+    device::BluetoothProfile* bluetooth_profile) {
+  DCHECK(!HasProfile(uuid));
+  bluetooth_profile_map_[uuid] = bluetooth_profile;
+}
+
+void ExtensionBluetoothEventRouter::RemoveProfile(const std::string& uuid) {
+  BluetoothProfileMap::iterator iter = bluetooth_profile_map_.find(uuid);
+  if (iter != bluetooth_profile_map_.end()) {
+    device::BluetoothProfile* bluetooth_profile = iter->second;
+    bluetooth_profile_map_.erase(iter);
+    bluetooth_profile->Unregister();
+  }
+}
+
+bool ExtensionBluetoothEventRouter::HasProfile(const std::string& uuid) const {
+  return bluetooth_profile_map_.find(uuid) != bluetooth_profile_map_.end();
+}
+
 scoped_refptr<device::BluetoothSocket>
 ExtensionBluetoothEventRouter::GetSocket(int id) {
   SocketMap::iterator socket_entry = socket_map_.find(id);
@@ -131,6 +158,28 @@
   ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass());
 }
 
+void ExtensionBluetoothEventRouter::DispatchConnectionEvent(
+    const std::string& extension_id,
+    const std::string& uuid,
+    const device::BluetoothDevice* device,
+    scoped_refptr<device::BluetoothSocket> socket) {
+  if (!HasProfile(uuid))
+    return;
+
+  int socket_id = RegisterSocket(socket);
+  api::bluetooth::Socket result_socket;
+  api::bluetooth::BluetoothDeviceToApiDevice(*device, &result_socket.device);
+  result_socket.profile.uuid = uuid;
+  result_socket.id = socket_id;
+
+  scoped_ptr<ListValue> args(new ListValue());
+  args->Append(result_socket.ToValue().release());
+  scoped_ptr<Event> event(new Event(
+      extensions::event_names::kBluetoothOnConnection, args.Pass()));
+  ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
+      extension_id, event.Pass());
+}
+
 void ExtensionBluetoothEventRouter::AdapterPresentChanged(
     device::BluetoothAdapter* adapter, bool present) {
   if (adapter != adapter_) {
diff --git a/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.h b/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.h
index d8f8024..3225c76e 100644
--- a/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.h
+++ b/chrome/browser/extensions/api/bluetooth/bluetooth_event_router.h
@@ -17,6 +17,13 @@
 
 class Profile;
 
+namespace device {
+
+class BluetoothDevice;
+class BluetoothProfile;
+
+}  // namespace device
+
 namespace extensions {
 
 class ExtensionBluetoothEventRouter
@@ -47,6 +54,20 @@
   // the socket was found and released, false otherwise.
   bool ReleaseSocket(int id);
 
+  // Add the BluetoothProfile |bluetooth_profile| for use by the extension
+  // system. This class will hold onto the profile for its lifetime, or until
+  // RemoveProfile is called for the profile.
+  void AddProfile(const std::string& uuid,
+                  device::BluetoothProfile* bluetooth_profile);
+
+  // Unregister the BluetoothProfile corersponding to |uuid| and release the
+  // object from this class.
+  void RemoveProfile(const std::string& uuid);
+
+  // Returns true if the BluetoothProfile corresponding to |uuid| is already
+  // registered.
+  bool HasProfile(const std::string& uuid) const;
+
   // Get the BluetoothSocket corresponding to |id|.
   scoped_refptr<device::BluetoothSocket> GetSocket(int id);
 
@@ -63,6 +84,13 @@
       const char* event_name,
       const extensions::api::bluetooth::Device& device);
 
+  // Dispatch an event that takes a connection socket as a parameter to the
+  // extension that registered the profile that the socket has connected to.
+  void DispatchConnectionEvent(const std::string& extension_id,
+                               const std::string& uuid,
+                               const device::BluetoothDevice* device,
+                               scoped_refptr<device::BluetoothSocket> socket);
+
   // Override from device::BluetoothAdapter::Observer
   virtual void AdapterPresentChanged(device::BluetoothAdapter* adapter,
                                      bool present) OVERRIDE;
@@ -103,6 +131,10 @@
       DeviceList;
   DeviceList discovered_devices_;
 
+  // A map that maps uuids to the BluetoothProfile objects.
+  typedef std::map<std::string, device::BluetoothProfile*> BluetoothProfileMap;
+  BluetoothProfileMap bluetooth_profile_map_;
+
   base::WeakPtrFactory<ExtensionBluetoothEventRouter> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionBluetoothEventRouter);
diff --git a/chrome/browser/extensions/api/bluetooth/bluetooth_event_router_unittest.cc b/chrome/browser/extensions/api/bluetooth/bluetooth_event_router_unittest.cc
index eb77bd8..8c2599d 100644
--- a/chrome/browser/extensions/api/bluetooth/bluetooth_event_router_unittest.cc
+++ b/chrome/browser/extensions/api/bluetooth/bluetooth_event_router_unittest.cc
@@ -2,26 +2,110 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
 #include "chrome/browser/extensions/api/bluetooth/bluetooth_event_router.h"
+#include "chrome/browser/extensions/event_names.h"
+#include "chrome/browser/extensions/event_router.h"
+#include "chrome/browser/extensions/extension_system_factory.h"
+#include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_profile.h"
+#include "device/bluetooth/test/mock_bluetooth_socket.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+
+const char kAudioProfileUuid[] = "audio profile uuid";
+const char kHealthProfileUuid[] = "health profile uuid";
+
+class FakeEventRouter : public extensions::EventRouter {
+ public:
+  explicit FakeEventRouter(Profile* profile) : EventRouter(profile, NULL) {}
+
+  virtual void DispatchEventToExtension(
+      const std::string& extension_id,
+      scoped_ptr<extensions::Event> event) OVERRIDE {
+    extension_id_ = extension_id;
+    event_ = event.Pass();
+  }
+
+  std::string extension_id() const {
+    return extension_id_;
+  }
+
+  const extensions::Event* event() const {
+    return event_.get();
+  }
+
+ private:
+  std::string extension_id_;
+  scoped_ptr<extensions::Event> event_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeEventRouter);
+};
+
+class FakeExtensionSystem : public extensions::TestExtensionSystem {
+ public:
+  explicit FakeExtensionSystem(Profile* profile)
+      : extensions::TestExtensionSystem(profile) {}
+
+  virtual extensions::EventRouter* event_router() OVERRIDE {
+    if (!fake_event_router_)
+      fake_event_router_.reset(new FakeEventRouter(profile_));
+    return fake_event_router_.get();
+  }
+
+ private:
+  scoped_ptr<FakeEventRouter> fake_event_router_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeExtensionSystem);
+};
+
+ProfileKeyedService* BuildFakeExtensionSystem(
+    content::BrowserContext* profile) {
+  return new FakeExtensionSystem(static_cast<Profile*>(profile));
+}
+
+}  // namespace
+
 namespace extensions {
 
 class ExtensionBluetoothEventRouterTest : public testing::Test {
  public:
   ExtensionBluetoothEventRouterTest()
       : mock_adapter_(new testing::StrictMock<device::MockBluetoothAdapter>()),
-        router_(&test_profile_) {
+        test_profile_(new TestingProfile()),
+        router_(test_profile_.get()),
+        ui_thread_(content::BrowserThread::UI, &message_loop_) {
     router_.SetAdapterForTest(mock_adapter_);
   }
 
+  virtual void TearDown() OVERRIDE {
+    // Some profile-dependent services rely on UI thread to clean up. We make
+    // sure they are properly cleaned up by running the UI message loop until
+    // idle.
+    test_profile_.reset(NULL);
+    base::RunLoop run_loop;
+    run_loop.RunUntilIdle();
+  }
+
  protected:
   testing::StrictMock<device::MockBluetoothAdapter>* mock_adapter_;
-  TestingProfile test_profile_;
+  testing::NiceMock<device::MockBluetoothProfile> mock_audio_profile_;
+  testing::NiceMock<device::MockBluetoothProfile> mock_health_profile_;
+  scoped_ptr<TestingProfile> test_profile_;
   ExtensionBluetoothEventRouter router_;
+  MessageLoopForUI message_loop_;
+  content::TestBrowserThread ui_thread_;
 };
 
 TEST_F(ExtensionBluetoothEventRouterTest, BluetoothEventListener) {
@@ -40,4 +124,88 @@
   router_.OnListenerRemoved();
 }
 
+TEST_F(ExtensionBluetoothEventRouterTest, Profiles) {
+  EXPECT_FALSE(router_.HasProfile(kAudioProfileUuid));
+  EXPECT_FALSE(router_.HasProfile(kHealthProfileUuid));
+
+  router_.AddProfile(kAudioProfileUuid, &mock_audio_profile_);
+  router_.AddProfile(kHealthProfileUuid, &mock_health_profile_);
+  EXPECT_TRUE(router_.HasProfile(kAudioProfileUuid));
+  EXPECT_TRUE(router_.HasProfile(kHealthProfileUuid));
+
+  EXPECT_CALL(mock_audio_profile_, Unregister()).Times(1);
+  router_.RemoveProfile(kAudioProfileUuid);
+  EXPECT_FALSE(router_.HasProfile(kAudioProfileUuid));
+  EXPECT_TRUE(router_.HasProfile(kHealthProfileUuid));
+
+  // Make sure remaining profiles are unregistered in destructor.
+  EXPECT_CALL(mock_health_profile_, Unregister()).Times(1);
+  EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+}
+
+TEST_F(ExtensionBluetoothEventRouterTest, DispatchConnectionEvent) {
+  router_.AddProfile(kAudioProfileUuid, &mock_audio_profile_);
+
+  FakeExtensionSystem* fake_extension_system =
+      static_cast<FakeExtensionSystem*>(ExtensionSystemFactory::GetInstance()->
+          SetTestingFactoryAndUse(test_profile_.get(),
+                                  &BuildFakeExtensionSystem));
+
+  const char test_extension_id[] = "test extension id";
+  testing::NiceMock<device::MockBluetoothDevice> mock_device(
+      mock_adapter_, 0, "device name", "device address", true, false);
+  scoped_refptr<testing::NiceMock<device::MockBluetoothSocket> > mock_socket(
+      new testing::NiceMock<device::MockBluetoothSocket>());
+
+  router_.DispatchConnectionEvent(test_extension_id,
+                                  kAudioProfileUuid,
+                                  &mock_device,
+                                  mock_socket);
+
+  FakeEventRouter* fake_event_router =
+      static_cast<FakeEventRouter*>(fake_extension_system->event_router());
+
+  EXPECT_STREQ(test_extension_id, fake_event_router->extension_id().c_str());
+  EXPECT_STREQ(event_names::kBluetoothOnConnection,
+               fake_event_router->event()->event_name.c_str());
+
+  base::ListValue* event_args = fake_event_router->event()->event_args.get();
+  base::DictionaryValue* socket_value = NULL;
+  ASSERT_TRUE(event_args->GetDictionary(0, &socket_value));
+  int socket_id;
+  ASSERT_TRUE(socket_value->GetInteger("id", &socket_id));
+  EXPECT_EQ(mock_socket.get(), router_.GetSocket(socket_id).get());
+
+  base::DictionaryValue* profile_value = NULL;
+  ASSERT_TRUE(socket_value->GetDictionary("profile", &profile_value));
+  std::string profile_uuid;
+  ASSERT_TRUE(profile_value->GetString("uuid", &profile_uuid));
+  EXPECT_STREQ(kAudioProfileUuid, profile_uuid.c_str());
+
+  EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+  router_.ReleaseSocket(socket_id);
+}
+
+TEST_F(ExtensionBluetoothEventRouterTest, DoNotDispatchConnectionEvent) {
+  FakeExtensionSystem* fake_extension_system =
+      static_cast<FakeExtensionSystem*>(ExtensionSystemFactory::GetInstance()->
+          SetTestingFactoryAndUse(test_profile_.get(),
+                                  &BuildFakeExtensionSystem));
+  testing::NiceMock<device::MockBluetoothDevice> mock_device(
+      mock_adapter_, 0, "device name", "device address", true, false);
+  scoped_refptr<testing::NiceMock<device::MockBluetoothSocket> > mock_socket(
+      new testing::NiceMock<device::MockBluetoothSocket>());
+
+  // Connection event won't be dispatched for non-registered profiles.
+  router_.DispatchConnectionEvent("test extension id",
+                                  kAudioProfileUuid,
+                                  &mock_device,
+                                  mock_socket);
+  FakeEventRouter* fake_event_router =
+      static_cast<FakeEventRouter*>(fake_extension_system->event_router());
+  EXPECT_TRUE(fake_event_router->event() == NULL);
+
+  EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+}
+
 }  // namespace extensions
diff --git a/chrome/common/extensions/api/bluetooth.idl b/chrome/common/extensions/api/bluetooth.idl
index 53d0f57..a0e2edf 100644
--- a/chrome/common/extensions/api/bluetooth.idl
+++ b/chrome/common/extensions/api/bluetooth.idl
@@ -82,8 +82,8 @@
     // The remote Bluetooth device associated with this socket.
     Device device;
 
-    // The remote Bluetooth service associated with this socket.
-    DOMString serviceUuid;
+    // The remote Bluetooth profile associated with this socket.
+    Profile profile;
 
     // An identifier for this socket that should be used with the
     // read/write/disconnect methods.
diff --git a/chrome/test/data/extensions/api_test/bluetooth/on_adapter_state_changed/manifest.json b/chrome/test/data/extensions/api_test/bluetooth/on_adapter_state_changed/manifest.json
new file mode 100644
index 0000000..327b2b4d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/bluetooth/on_adapter_state_changed/manifest.json
@@ -0,0 +1,12 @@
+{
+  "manifest_version": 2,
+  "name": "Test Bluetooth OnAdapterStateChanged Event",
+  "version": "1.0",
+  "description": "Tests Bluetooth OnAdapterStateChanged Event",
+  "app": {
+    "background": {
+      "scripts": ["runtest.js"]
+    }
+  },
+  "permissions": ["bluetooth"]
+}
diff --git a/chrome/test/data/extensions/api_test/bluetooth/events/runtest.js b/chrome/test/data/extensions/api_test/bluetooth/on_adapter_state_changed/runtest.js
similarity index 100%
rename from chrome/test/data/extensions/api_test/bluetooth/events/runtest.js
rename to chrome/test/data/extensions/api_test/bluetooth/on_adapter_state_changed/runtest.js
diff --git a/chrome/test/data/extensions/api_test/bluetooth/events/manifest.json b/chrome/test/data/extensions/api_test/bluetooth/on_connection/manifest.json
similarity index 60%
rename from chrome/test/data/extensions/api_test/bluetooth/events/manifest.json
rename to chrome/test/data/extensions/api_test/bluetooth/on_connection/manifest.json
index d279105c..587a7c7 100644
--- a/chrome/test/data/extensions/api_test/bluetooth/events/manifest.json
+++ b/chrome/test/data/extensions/api_test/bluetooth/on_connection/manifest.json
@@ -1,8 +1,8 @@
 {
   "manifest_version": 2,
-  "name": "Test Bluetooth Events",
+  "name": "Test Bluetooth OnConnection Event",
   "version": "1.0",
-  "description": "Tests Bluetooth Events",
+  "description": "Tests Bluetooth OnConnection Event",
   "app": {
     "background": {
       "scripts": ["runtest.js"]
diff --git a/chrome/test/data/extensions/api_test/bluetooth/on_connection/runtest.js b/chrome/test/data/extensions/api_test/bluetooth/on_connection/runtest.js
new file mode 100644
index 0000000..fca8ec0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/bluetooth/on_connection/runtest.js
@@ -0,0 +1,28 @@
+// 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.
+
+var deviceName;
+var deviceAddress;
+var profileUuid;
+
+function testOnConnectionEvent() {
+  chrome.test.assertEq('d1', deviceName);
+  chrome.test.assertEq('11:12:13:14:15:16', deviceAddress);
+  chrome.test.assertEq('1234', profileUuid);
+
+  chrome.test.succeed();
+}
+
+chrome.bluetooth.onConnection.addListener(
+  function(socket) {
+    deviceName = socket.device.name;
+    deviceAddress = socket.device.address;
+    profileUuid = socket.profile.uuid;
+    chrome.bluetooth.disconnect({'socket': socket});
+  });
+
+chrome.test.sendMessage('ready',
+  function(message) {
+    chrome.test.runTests([testOnConnectionEvent]);
+  });
diff --git a/device/bluetooth/bluetooth_profile.h b/device/bluetooth/bluetooth_profile.h
index 466bdf2a..46200b3 100644
--- a/device/bluetooth/bluetooth_profile.h
+++ b/device/bluetooth/bluetooth_profile.h
@@ -12,8 +12,10 @@
 
 namespace device {
 
+class BluetoothDevice;
 class BluetoothProfileMac;
 class BluetoothSocket;
+class MockBluetoothProfile;
 
 // BluetoothProfile represents an implementation of either a client or server
 // of a particular specified profile (aka service or protocol in other
@@ -92,11 +94,14 @@
   // The socket will be closed when all references are released; none of the
   // BluetoothProfile, or BluetoothAdapter or BluetoothDevice objects are
   // guaranteed to hold a reference so this may outlive all of them.
-  typedef base::Callback<void(scoped_refptr<BluetoothSocket>)> SocketCallback;
-  virtual void SetConnectionCallback(const SocketCallback& callback) = 0;
+  typedef base::Callback<void(
+      const BluetoothDevice*,
+      scoped_refptr<BluetoothSocket>)> ConnectionCallback;
+  virtual void SetConnectionCallback(const ConnectionCallback& callback) = 0;
 
  private:
   friend class BluetoothProfileMac;
+  friend class MockBluetoothProfile;
 
   BluetoothProfile();
   virtual ~BluetoothProfile();
diff --git a/device/bluetooth/bluetooth_profile_mac.h b/device/bluetooth/bluetooth_profile_mac.h
index 34b6b34..0085efde 100644
--- a/device/bluetooth/bluetooth_profile_mac.h
+++ b/device/bluetooth/bluetooth_profile_mac.h
@@ -23,7 +23,8 @@
  public:
   // BluetoothProfile override.
   virtual void Unregister() OVERRIDE;
-  virtual void SetConnectionCallback(const SocketCallback& callback) OVERRIDE;
+  virtual void SetConnectionCallback(
+      const ConnectionCallback& callback) OVERRIDE;
 
   // Makes an outgoing connection to |device|.
   // This method runs |socket_callback_| with the socket and returns true if the
@@ -38,7 +39,7 @@
 
   const std::string uuid_;
   const std::string name_;
-  SocketCallback socket_callback_;
+  ConnectionCallback connection_callback_;
 };
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_profile_mac.mm b/device/bluetooth/bluetooth_profile_mac.mm
index ca50f06..c0658a9 100644
--- a/device/bluetooth/bluetooth_profile_mac.mm
+++ b/device/bluetooth/bluetooth_profile_mac.mm
@@ -59,8 +59,8 @@
 }
 
 void BluetoothProfileMac::SetConnectionCallback(
-    const SocketCallback& callback) {
-  socket_callback_ = callback;
+    const ConnectionCallback& callback) {
+  connection_callback_ = callback;
 }
 
 bool BluetoothProfileMac::Connect(IOBluetoothDevice* device) {
@@ -70,7 +70,8 @@
     scoped_refptr<BluetoothSocket> socket(
         BluetoothSocketMac::CreateBluetoothSocket(record));
     if (socket.get() != NULL) {
-      socket_callback_.Run(socket);
+      BluetoothDeviceMac device_mac(device);
+      connection_callback_.Run(&device_mac, socket);
       return true;
     }
   }
diff --git a/device/bluetooth/test/mock_bluetooth_profile.cc b/device/bluetooth/test/mock_bluetooth_profile.cc
new file mode 100644
index 0000000..bf2af214
--- /dev/null
+++ b/device/bluetooth/test/mock_bluetooth_profile.cc
@@ -0,0 +1,15 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/bluetooth/test/mock_bluetooth_profile.h"
+
+namespace device {
+
+MockBluetoothProfile::MockBluetoothProfile() {
+}
+
+MockBluetoothProfile::~MockBluetoothProfile() {
+}
+
+}  // namespace device
diff --git a/device/bluetooth/test/mock_bluetooth_profile.h b/device/bluetooth/test/mock_bluetooth_profile.h
new file mode 100644
index 0000000..92b7505
--- /dev/null
+++ b/device/bluetooth/test/mock_bluetooth_profile.h
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_PROFILE_H_
+#define DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_PROFILE_H_
+
+#include "device/bluetooth/bluetooth_profile.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace device {
+
+class MockBluetoothProfile : public BluetoothProfile {
+ public:
+  MockBluetoothProfile();
+  virtual ~MockBluetoothProfile();
+
+  MOCK_METHOD0(Unregister, void());
+  MOCK_METHOD1(SetConnectionCallback,
+               void(const BluetoothProfile::ConnectionCallback&));
+};
+
+}  // namespace device
+
+#endif  // DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_PROFILE_H_
diff --git a/device/device.gyp b/device/device.gyp
index 3f3d010c..6afb8a0 100644
--- a/device/device.gyp
+++ b/device/device.gyp
@@ -110,6 +110,8 @@
         'bluetooth/test/mock_bluetooth_adapter.h',
         'bluetooth/test/mock_bluetooth_device.cc',
         'bluetooth/test/mock_bluetooth_device.h',
+        'bluetooth/test/mock_bluetooth_profile.cc',
+        'bluetooth/test/mock_bluetooth_profile.h',
         'bluetooth/test/mock_bluetooth_socket.cc',
         'bluetooth/test/mock_bluetooth_socket.h',
       ],