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',
],