diff options
author | noamsml@chromium.org <noamsml@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-02 01:25:53 +0000 |
---|---|---|
committer | noamsml@chromium.org <noamsml@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-02 01:25:53 +0000 |
commit | ca387296b9249ab106632996d9f37b2342ccf6bb (patch) | |
tree | 0897bf420ce5573707804dcf0d6ffb361c4b8241 | |
parent | 3f016cabc869fd57e80dea68f399ccdbc3e9eddb (diff) | |
download | chromium_src-ca387296b9249ab106632996d9f37b2342ccf6bb.zip chromium_src-ca387296b9249ab106632996d9f37b2342ccf6bb.tar.gz chromium_src-ca387296b9249ab106632996d9f37b2342ccf6bb.tar.bz2 |
[Privet API] onCloudDeviceStateChagned notifies of state changes for mDNS devices
onCloudDeviceStateChanged will now notify users when the state of an mDNS device
has changed.
BUG=383167
NOTRY=true
Review URL: https://codereview.chromium.org/333243006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280932 0039d316-1c4b-4281-b951-d872f2087c98
7 files changed, 308 insertions, 9 deletions
diff --git a/chrome/browser/extensions/api/gcd_private/gcd_private_api.cc b/chrome/browser/extensions/api/gcd_private/gcd_private_api.cc index b17b984..5d3dba1 100644 --- a/chrome/browser/extensions/api/gcd_private/gcd_private_api.cc +++ b/chrome/browser/extensions/api/gcd_private/gcd_private_api.cc @@ -9,6 +9,7 @@ #include "chrome/browser/local_discovery/cloud_device_list.h" #include "chrome/browser/local_discovery/cloud_print_printer_list.h" #include "chrome/browser/local_discovery/gcd_constants.h" +#include "chrome/browser/local_discovery/privet_device_lister_impl.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/signin/signin_manager_factory.h" @@ -18,14 +19,25 @@ namespace extensions { -using extensions::api::gcd_private::GCDDevice; +namespace gcd_private = api::gcd_private; namespace { +scoped_ptr<Event> MakeCloudDeviceStateChangedEvent( + bool available, + const gcd_private::GCDDevice& device) { + scoped_ptr<base::ListValue> params = + gcd_private::OnCloudDeviceStateChanged::Create(available, device); + scoped_ptr<Event> event(new Event( + gcd_private::OnCloudDeviceStateChanged::kEventName, params.Pass())); + return event.Pass(); +} + const int kNumRequestsNeeded = 2; const char kIDPrefixCloudPrinter[] = "cloudprint:"; const char kIDPrefixGcd[] = "gcd:"; +const char kIDPrefixMdns[] = "mdns:"; GcdPrivateAPI::GCDApiFlowFactoryForTests* g_gcd_api_flow_factory = NULL; @@ -54,7 +66,10 @@ scoped_ptr<local_discovery::GCDApiFlow> MakeGCDApiFlow(Profile* profile) { } // namespace GcdPrivateAPI::GcdPrivateAPI(content::BrowserContext* context) - : browser_context_(context) { + : num_device_listeners_(0), browser_context_(context) { + DCHECK(browser_context_); + EventRouter::Get(context)->RegisterObserver( + this, gcd_private::OnCloudDeviceStateChanged::kEventName); } GcdPrivateAPI::~GcdPrivateAPI() { @@ -66,6 +81,74 @@ GcdPrivateAPI::GetFactoryInstance() { return g_factory.Pointer(); } +void GcdPrivateAPI::OnListenerAdded(const EventListenerInfo& details) { + num_device_listeners_++; + + if (num_device_listeners_ == 1) { + service_discovery_client_ = + local_discovery::ServiceDiscoverySharedClient::GetInstance(); + privet_device_lister_.reset(new local_discovery::PrivetDeviceListerImpl( + service_discovery_client_.get(), this)); + privet_device_lister_->Start(); + } + + for (GCDDeviceMap::iterator i = known_devices_.begin(); + i != known_devices_.end(); + i++) { + EventRouter::Get(browser_context_)->DispatchEventToExtension( + details.extension_id, + MakeCloudDeviceStateChangedEvent(true, *i->second)); + } +} + +void GcdPrivateAPI::OnListenerRemoved(const EventListenerInfo& details) { + num_device_listeners_--; + + if (num_device_listeners_ == 0) { + privet_device_lister_.reset(); + service_discovery_client_ = NULL; + } +} + +void GcdPrivateAPI::DeviceChanged( + bool added, + const std::string& name, + const local_discovery::DeviceDescription& description) { + linked_ptr<gcd_private::GCDDevice> device(new gcd_private::GCDDevice); + device->setup_type = gcd_private::SETUP_TYPE_MDNS; + device->id_string = kIDPrefixMdns + name; + device->device_type = description.type; + device->device_name = description.name; + device->device_description = description.description; + if (!description.id.empty()) + device->cloud_id.reset(new std::string(description.id)); + + known_devices_[device->id_string] = device; + + EventRouter::Get(browser_context_) + ->BroadcastEvent(MakeCloudDeviceStateChangedEvent(true, *device)); +} + +void GcdPrivateAPI::DeviceRemoved(const std::string& name) { + GCDDeviceMap::iterator found = known_devices_.find(kIDPrefixMdns + name); + linked_ptr<gcd_private::GCDDevice> device = found->second; + known_devices_.erase(found); + + EventRouter::Get(browser_context_) + ->BroadcastEvent(MakeCloudDeviceStateChangedEvent(false, *device)); +} + +void GcdPrivateAPI::DeviceCacheFlushed() { + for (GCDDeviceMap::iterator i = known_devices_.begin(); + i != known_devices_.end(); + i++) { + EventRouter::Get(browser_context_) + ->BroadcastEvent(MakeCloudDeviceStateChangedEvent(false, *i->second)); + } + + known_devices_.clear(); +} + // static void GcdPrivateAPI::SetGCDApiFlowFactoryForTests( GCDApiFlowFactoryForTests* factory) { @@ -122,11 +205,11 @@ void GcdPrivateGetCloudDeviceListFunction::CheckListingDone() { return; } - std::vector<linked_ptr<GCDDevice> > devices; + std::vector<linked_ptr<gcd_private::GCDDevice> > devices; for (DeviceList::iterator i = devices_.begin(); i != devices_.end(); i++) { - linked_ptr<GCDDevice> device(new GCDDevice); - device->setup_type = extensions::api::gcd_private::SETUP_TYPE_CLOUD; + linked_ptr<gcd_private::GCDDevice> device(new gcd_private::GCDDevice); + device->setup_type = gcd_private::SETUP_TYPE_CLOUD; if (i->type == local_discovery::kGCDTypePrinter) { device->id_string = kIDPrefixCloudPrinter + i->id; } else { @@ -141,8 +224,7 @@ void GcdPrivateGetCloudDeviceListFunction::CheckListingDone() { devices.push_back(device); } - results_ = extensions::api::gcd_private::GetCloudDeviceList::Results::Create( - devices); + results_ = gcd_private::GetCloudDeviceList::Results::Create(devices); SendResponse(true); Release(); diff --git a/chrome/browser/extensions/api/gcd_private/gcd_private_api.h b/chrome/browser/extensions/api/gcd_private/gcd_private_api.h index d8c6f46..61f0db6 100644 --- a/chrome/browser/extensions/api/gcd_private/gcd_private_api.h +++ b/chrome/browser/extensions/api/gcd_private/gcd_private_api.h @@ -5,16 +5,22 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_GCD_PRIVATE_GCD_PRIVATE_API_H_ #define CHROME_BROWSER_EXTENSIONS_API_GCD_PRIVATE_GCD_PRIVATE_API_H_ +#include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "chrome/browser/extensions/chrome_extension_function.h" #include "chrome/browser/local_discovery/cloud_device_list_delegate.h" #include "chrome/browser/local_discovery/gcd_api_flow.h" +#include "chrome/browser/local_discovery/privet_device_lister.h" +#include "chrome/browser/local_discovery/service_discovery_shared_client.h" #include "chrome/common/extensions/api/gcd_private.h" #include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/browser/event_router.h" namespace extensions { -class GcdPrivateAPI : public BrowserContextKeyedAPI { +class GcdPrivateAPI : public BrowserContextKeyedAPI, + public EventRouter::Observer, + public local_discovery::PrivetDeviceLister::Delegate { public: class GCDApiFlowFactoryForTests { public: @@ -34,9 +40,30 @@ class GcdPrivateAPI : public BrowserContextKeyedAPI { private: friend class BrowserContextKeyedAPIFactory<GcdPrivateAPI>; + typedef std::map<std::string /* id_string */, + linked_ptr<api::gcd_private::GCDDevice> > GCDDeviceMap; + + // EventRouter::Observer implementation. + virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE; + virtual void OnListenerRemoved(const EventListenerInfo& details) OVERRIDE; + // BrowserContextKeyedAPI implementation. static const char* service_name() { return "GcdPrivateAPI"; } + // local_discovery::PrivetDeviceLister implementation. + virtual void DeviceChanged( + bool added, + const std::string& name, + const local_discovery::DeviceDescription& description) OVERRIDE; + virtual void DeviceRemoved(const std::string& name) OVERRIDE; + virtual void DeviceCacheFlushed() OVERRIDE; + + int num_device_listeners_; + scoped_refptr<local_discovery::ServiceDiscoverySharedClient> + service_discovery_client_; + scoped_ptr<local_discovery::PrivetDeviceLister> privet_device_lister_; + GCDDeviceMap known_devices_; + content::BrowserContext* const browser_context_; }; diff --git a/chrome/browser/extensions/api/gcd_private/gcd_private_apitest.cc b/chrome/browser/extensions/api/gcd_private/gcd_private_apitest.cc index 7e6ae8b..193a498 100644 --- a/chrome/browser/extensions/api/gcd_private/gcd_private_apitest.cc +++ b/chrome/browser/extensions/api/gcd_private/gcd_private_apitest.cc @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/bind.h" #include "base/command_line.h" #include "base/json/json_reader.h" +#include "base/message_loop/message_loop_proxy.h" #include "chrome/browser/extensions/api/gcd_private/gcd_private_api.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_service.h" @@ -11,6 +13,10 @@ #include "extensions/common/switches.h" #include "testing/gmock/include/gmock/gmock.h" +#if defined(ENABLE_MDNS) +#include "chrome/browser/local_discovery/test_service_discovery_client.h" +#endif // ENABLE_MDNS + namespace api = extensions::api; namespace { @@ -48,6 +54,84 @@ const char kGCDResponse[] = " \"maxRole\": \"owner\"" " }}]}"; +#if defined(ENABLE_MDNS) + +const uint8 kAnnouncePacket[] = { + // Header + 0x00, 0x00, // ID is zeroed out + 0x80, 0x00, // Standard query response, no error + 0x00, 0x00, // No questions (for simplicity) + 0x00, 0x05, // 5 RR (answers) + 0x00, 0x00, // 0 authority RRs + 0x00, 0x00, // 0 additional RRs + 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', 0x04, '_', + 't', 'c', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, + 0x00, 0x0c, // TYPE is PTR. + 0x00, 0x01, // CLASS is IN. + 0x00, 0x00, // TTL (4 bytes) is 32768 second. + 0x10, 0x00, 0x00, 0x0c, // RDLENGTH is 12 bytes. + 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', + 0xc0, 0x0c, 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', + 'c', 'e', 0xc0, 0x0c, 0x00, 0x10, // TYPE is TXT. + 0x00, 0x01, // CLASS is IN. + 0x00, 0x00, // TTL (4 bytes) is 32768 seconds. + 0x01, 0x00, 0x00, 0x41, // RDLENGTH is 69 bytes. + 0x03, 'i', 'd', '=', 0x10, 't', 'y', '=', 'S', 'a', + 'm', 'p', 'l', 'e', ' ', 'd', 'e', 'v', 'i', 'c', + 'e', 0x1e, 'n', 'o', 't', 'e', '=', 'S', 'a', 'm', + 'p', 'l', 'e', ' ', 'd', 'e', 'v', 'i', 'c', 'e', + ' ', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'i', + 'o', 'n', 0x0c, 't', 'y', 'p', 'e', '=', 'p', 'r', + 'i', 'n', 't', 'e', 'r', 0x09, 'm', 'y', 'S', 'e', + 'r', 'v', 'i', 'c', 'e', 0xc0, 0x0c, 0x00, 0x21, // Type is SRV + 0x00, 0x01, // CLASS is IN + 0x00, 0x00, // TTL (4 bytes) is 32768 second. + 0x10, 0x00, 0x00, 0x17, // RDLENGTH is 23 + 0x00, 0x00, 0x00, 0x00, 0x22, 0xb8, // port 8888 + 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', + 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0x09, 'm', 'y', + 'S', 'e', 'r', 'v', 'i', 'c', 'e', 0x05, 'l', 'o', + 'c', 'a', 'l', 0x00, 0x00, 0x01, // Type is A + 0x00, 0x01, // CLASS is IN + 0x00, 0x00, // TTL (4 bytes) is 32768 second. + 0x10, 0x00, 0x00, 0x04, // RDLENGTH is 4 + 0x01, 0x02, 0x03, 0x04, // 1.2.3.4 + 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', + 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0x00, 0x1C, // Type is AAAA + 0x00, 0x01, // CLASS is IN + 0x00, 0x00, // TTL (4 bytes) is 32768 second. + 0x10, 0x00, 0x00, 0x10, // RDLENGTH is 16 + 0x01, 0x02, 0x03, 0x04, // 1.2.3.4 + 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, + 0x03, 0x04, +}; + +const uint8 kGoodbyePacket[] = { + // Header + 0x00, 0x00, // ID is zeroed out + 0x80, 0x00, // Standard query response, RA, no error + 0x00, 0x00, // No questions (for simplicity) + 0x00, 0x02, // 1 RR (answers) + 0x00, 0x00, // 0 authority RRs + 0x00, 0x00, // 0 additional RRs + 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', 0x04, '_', 't', 'c', + 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0x00, 0x0c, // TYPE is PTR. + 0x00, 0x01, // CLASS is IN. + 0x00, 0x00, // TTL (4 bytes) is 0 seconds. + 0x00, 0x00, 0x00, 0x0c, // RDLENGTH is 12 bytes. + 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 0xc0, 0x0c, + 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 0xc0, 0x0c, + 0x00, 0x21, // Type is SRV + 0x00, 0x01, // CLASS is IN + 0x00, 0x00, // TTL (4 bytes) is 0 seconds. + 0x00, 0x00, 0x00, 0x17, // RDLENGTH is 23 + 0x00, 0x00, 0x00, 0x00, 0x22, 0xb8, // port 8888 + 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 0x05, 'l', + 'o', 'c', 'a', 'l', 0x00, +}; + +#endif // ENABLE_MDNS + // Sentinel value to signify the request should fail. const char kResponseValueFailure[] = "FAILURE"; @@ -105,10 +189,21 @@ class FakeGCDApiFlowFactory class GcdPrivateAPITest : public ExtensionApiTest { public: - GcdPrivateAPITest() {} + GcdPrivateAPITest() { +#if defined(ENABLE_MDNS) + test_service_discovery_client_ = + new local_discovery::TestServiceDiscoveryClient(); + test_service_discovery_client_->Start(); +#endif // ENABLE_MDNS + } protected: FakeGCDApiFlowFactory api_flow_factory_; + +#if defined(ENABLE_MDNS) + scoped_refptr<local_discovery::TestServiceDiscoveryClient> + test_service_discovery_client_; +#endif // ENABLE_MDNS }; IN_PROC_BROWSER_TEST_F(GcdPrivateAPITest, GetCloudList) { @@ -121,4 +216,44 @@ IN_PROC_BROWSER_TEST_F(GcdPrivateAPITest, GetCloudList) { EXPECT_TRUE(RunExtensionSubtest("gcd_private/api", "get_cloud_list.html")); } +#if defined(ENABLE_MDNS) + +IN_PROC_BROWSER_TEST_F(GcdPrivateAPITest, AddBefore) { + test_service_discovery_client_->SimulateReceive(kAnnouncePacket, + sizeof(kAnnouncePacket)); + + EXPECT_TRUE( + RunExtensionSubtest("gcd_private/api", "receive_new_device.html")); +} + +IN_PROC_BROWSER_TEST_F(GcdPrivateAPITest, AddAfter) { + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&local_discovery::TestServiceDiscoveryClient::SimulateReceive, + test_service_discovery_client_, + kAnnouncePacket, + sizeof(kAnnouncePacket)), + base::TimeDelta::FromSeconds(1)); + + EXPECT_TRUE( + RunExtensionSubtest("gcd_private/api", "receive_new_device.html")); +} + +IN_PROC_BROWSER_TEST_F(GcdPrivateAPITest, AddRemove) { + test_service_discovery_client_->SimulateReceive(kAnnouncePacket, + sizeof(kAnnouncePacket)); + + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&local_discovery::TestServiceDiscoveryClient::SimulateReceive, + test_service_discovery_client_, + kGoodbyePacket, + sizeof(kGoodbyePacket)), + base::TimeDelta::FromSeconds(1)); + + EXPECT_TRUE(RunExtensionSubtest("gcd_private/api", "remove_device.html")); +} + +#endif // ENABLE_MDNS + } // namespace diff --git a/chrome/test/data/extensions/api_test/gcd_private/api/receive_new_device.html b/chrome/test/data/extensions/api_test/gcd_private/api/receive_new_device.html new file mode 100644 index 0000000..5570a76 --- /dev/null +++ b/chrome/test/data/extensions/api_test/gcd_private/api/receive_new_device.html @@ -0,0 +1 @@ +<script src="receive_new_device.js"></script> diff --git a/chrome/test/data/extensions/api_test/gcd_private/api/receive_new_device.js b/chrome/test/data/extensions/api_test/gcd_private/api/receive_new_device.js new file mode 100644 index 0000000..2ccd688 --- /dev/null +++ b/chrome/test/data/extensions/api_test/gcd_private/api/receive_new_device.js @@ -0,0 +1,24 @@ +// 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. + +onload = function() { + chrome.test.runTests([ + function receiveNewDevice() { + chrome.gcdPrivate.onCloudDeviceStateChanged.addListener( + function(available, device) { + chrome.test.assertTrue(available); + + chrome.test.assertEq(device.setupType, "mdns"); + chrome.test.assertEq(device.idString, + "mdns:myService._privet._tcp.local"); + chrome.test.assertEq(device.deviceType, "printer"); + chrome.test.assertEq(device.deviceName, + "Sample device"); + chrome.test.assertEq(device.deviceDescription, + "Sample device description"); + + chrome.test.notifyPass(); + }) + }]); +}; diff --git a/chrome/test/data/extensions/api_test/gcd_private/api/remove_device.html b/chrome/test/data/extensions/api_test/gcd_private/api/remove_device.html new file mode 100644 index 0000000..ecbcf39a --- /dev/null +++ b/chrome/test/data/extensions/api_test/gcd_private/api/remove_device.html @@ -0,0 +1 @@ +<script src="remove_device.js"></script> diff --git a/chrome/test/data/extensions/api_test/gcd_private/api/remove_device.js b/chrome/test/data/extensions/api_test/gcd_private/api/remove_device.js new file mode 100644 index 0000000..072b79b --- /dev/null +++ b/chrome/test/data/extensions/api_test/gcd_private/api/remove_device.js @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +onload = function() { + chrome.test.runTests([ + function addRemoveDevice() { + var should_be_available = true; + chrome.gcdPrivate.onCloudDeviceStateChanged.addListener( + function(available, device) { + chrome.test.assertEq(available, should_be_available); + should_be_available = false; + + chrome.test.assertEq(device.setupType, "mdns"); + chrome.test.assertEq(device.idString, + "mdns:myService._privet._tcp.local"); + chrome.test.assertEq(device.deviceType, "printer"); + chrome.test.assertEq(device.deviceName, + "Sample device"); + chrome.test.assertEq(device.deviceDescription, + "Sample device description"); + + if (!available) { + // Only pass after device is removed + chrome.test.notifyPass(); + } + }) + }]); +}; |