diff options
author | reillyg <reillyg@chromium.org> | 2014-09-19 08:00:01 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-19 15:00:29 +0000 |
commit | 89b276fbcd87b3ce88be7b0a00df6d9266d90ad6 (patch) | |
tree | 737ef60a3a1a6191f4a62e9a5c3a368bb6c668a8 /apps | |
parent | bfae635632bc062c0a56f5ed525390c82ed5c6cc (diff) | |
download | chromium_src-89b276fbcd87b3ce88be7b0a00df6d9266d90ad6.zip chromium_src-89b276fbcd87b3ce88be7b0a00df6d9266d90ad6.tar.gz chromium_src-89b276fbcd87b3ce88be7b0a00df6d9266d90ad6.tar.bz2 |
Add a service to track devices selected by the user.
apps::SavedDevicesService tracks USB devices that have been selected by
the user in the context of a given extension. Devices that can be
identified accurately after they have been reconnected because they have
a serial number are written out to ExtensionPrefs. All others are only
remembered until they are disconnected.
A new OnDisconnect observer function has been added to UsbDevice to
enable this monitoring.
BUG=346953
Review URL: https://codereview.chromium.org/580963002
Cr-Commit-Position: refs/heads/master@{#295708}
Diffstat (limited to 'apps')
-rw-r--r-- | apps/BUILD.gn | 5 | ||||
-rw-r--r-- | apps/DEPS | 1 | ||||
-rw-r--r-- | apps/apps.gypi | 4 | ||||
-rw-r--r-- | apps/saved_devices_service.cc | 320 | ||||
-rw-r--r-- | apps/saved_devices_service.h | 123 | ||||
-rw-r--r-- | apps/saved_devices_service_factory.cc | 39 | ||||
-rw-r--r-- | apps/saved_devices_service_factory.h | 35 | ||||
-rw-r--r-- | apps/saved_devices_service_unittest.cc | 213 |
8 files changed, 740 insertions, 0 deletions
diff --git a/apps/BUILD.gn b/apps/BUILD.gn index 25700b6..d78cf49 100644 --- a/apps/BUILD.gn +++ b/apps/BUILD.gn @@ -26,6 +26,10 @@ static_library("apps") { "launcher.cc", "launcher.h", "metrics_names.h", + "saved_devices_service.cc", + "saved_devices_service.h", + "saved_devices_service_factory.cc", + "saved_devices_service_factory.h", "saved_files_service.cc", "saved_files_service.h", "saved_files_service_factory.cc", @@ -41,6 +45,7 @@ static_library("apps") { "//chrome/browser/extensions", "//chrome/common/extensions/api:api", "//components/web_modal", + "//device/usb", "//skia", ] @@ -9,6 +9,7 @@ include_rules = [ "+components/sessions", "+components/user_manager", "+components/web_modal", + "+device/usb", "+extensions", "+net/base", "+skia/ext", diff --git a/apps/apps.gypi b/apps/apps.gypi index 35248b5..789b7a2 100644 --- a/apps/apps.gypi +++ b/apps/apps.gypi @@ -45,6 +45,10 @@ 'launcher.cc', 'launcher.h', 'metrics_names.h', + 'saved_devices_service.cc', + 'saved_devices_service.h', + 'saved_devices_service_factory.cc', + 'saved_devices_service_factory.h', 'saved_files_service.cc', 'saved_files_service.h', 'saved_files_service_factory.cc', diff --git a/apps/saved_devices_service.cc b/apps/saved_devices_service.cc new file mode 100644 index 0000000..df7db1d --- /dev/null +++ b/apps/saved_devices_service.cc @@ -0,0 +1,320 @@ +// 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 "apps/saved_devices_service.h" + +#include <set> +#include <vector> + +#include "apps/saved_devices_service_factory.h" +#include "base/basictypes.h" +#include "base/values.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "device/usb/usb_device.h" +#include "device/usb/usb_device_handle.h" +#include "extensions/browser/extension_host.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/extension_util.h" +#include "extensions/browser/notification_types.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/permissions/permission_set.h" +#include "extensions/common/permissions/permissions_data.h" + +namespace apps { + +using device::UsbDevice; +using device::UsbDeviceHandle; +using extensions::APIPermission; +using extensions::Extension; +using extensions::ExtensionHost; +using extensions::ExtensionPrefs; + +namespace { + +// Preference keys + +// The device that the app has permission to access. +const char kDevices[] = "devices"; + +// The type of device saved. +const char kDeviceType[] = "type"; + +// Type identifier for USB devices. +const char kDeviceTypeUsb[] = "usb"; + +// The vendor ID of the device that the app had permission to access. +const char kDeviceVendorId[] = "vendor_id"; + +// The product ID of the device that the app had permission to access. +const char kDeviceProductId[] = "product_id"; + +// The serial number of the device that the app has permission to access. +const char kDeviceSerialNumber[] = "serial_number"; + +// Persists a SavedDeviceEntry in ExtensionPrefs. +void AddSavedDeviceEntry(Profile* profile, + const std::string& extension_id, + const SavedDeviceEntry& device) { + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile); + ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices); + base::ListValue* devices = update.Get(); + if (!devices) { + devices = update.Create(); + } + + base::Value* device_entry = device.ToValue(); + DCHECK(devices->Find(*device_entry) == devices->end()); + devices->Append(device_entry); +} + +// Clears all SavedDeviceEntry for the app from ExtensionPrefs. +void ClearSavedDeviceEntries(ExtensionPrefs* prefs, + const std::string& extension_id) { + prefs->UpdateExtensionPref(extension_id, kDevices, NULL); +} + +// Returns all SavedDeviceEntries for the app. +std::vector<SavedDeviceEntry> GetSavedDeviceEntries( + ExtensionPrefs* prefs, + const std::string& extension_id) { + std::vector<SavedDeviceEntry> result; + const base::ListValue* devices = NULL; + if (!prefs->ReadPrefAsList(extension_id, kDevices, &devices)) { + return result; + } + + for (base::ListValue::const_iterator it = devices->begin(); + it != devices->end(); + ++it) { + const base::DictionaryValue* device_entry = NULL; + if (!(*it)->GetAsDictionary(&device_entry)) { + continue; + } + int vendor_id; + if (!device_entry->GetIntegerWithoutPathExpansion(kDeviceVendorId, + &vendor_id) || + vendor_id < 0 || vendor_id > UINT16_MAX) { + continue; + } + int product_id; + if (!device_entry->GetIntegerWithoutPathExpansion(kDeviceProductId, + &product_id) || + product_id < 0 || product_id > UINT16_MAX) { + continue; + } + base::string16 serial_number; + if (!device_entry->GetStringWithoutPathExpansion(kDeviceSerialNumber, + &serial_number)) { + continue; + } + + result.push_back(SavedDeviceEntry(vendor_id, product_id, serial_number)); + } + return result; +} + +} // namespace + +SavedDeviceEntry::SavedDeviceEntry(uint16_t vendor_id, + uint16_t product_id, + const base::string16& serial_number) + : vendor_id(vendor_id), + product_id(product_id), + serial_number(serial_number) { +} + +base::Value* SavedDeviceEntry::ToValue() const { + base::DictionaryValue* device_entry_dict = new base::DictionaryValue(); + device_entry_dict->SetStringWithoutPathExpansion(kDeviceType, kDeviceTypeUsb); + device_entry_dict->SetIntegerWithoutPathExpansion(kDeviceVendorId, vendor_id); + device_entry_dict->SetIntegerWithoutPathExpansion(kDeviceProductId, + product_id); + device_entry_dict->SetStringWithoutPathExpansion(kDeviceSerialNumber, + serial_number); + return device_entry_dict; +} + +bool SavedDevicesService::SavedDevices::IsRegistered( + scoped_refptr<UsbDevice> device) const { + if (ephemeral_devices_.find(device) != ephemeral_devices_.end()) { + return true; + } + + bool have_serial_number = false; + base::string16 serial_number; + for (std::vector<SavedDeviceEntry>::const_iterator it = + persistent_devices_.begin(); + it != persistent_devices_.end(); + ++it) { + if (it->vendor_id != device->vendor_id()) { + continue; + } + if (it->product_id != device->product_id()) { + continue; + } + if (!have_serial_number) { + scoped_refptr<UsbDeviceHandle> device_handle = device->Open(); + if (!device_handle.get()) { + break; + } + if (!device_handle->GetSerial(&serial_number)) { + break; + } + have_serial_number = true; + } + if (it->serial_number != serial_number) { + continue; + } + return true; + } + return false; +} + +void SavedDevicesService::SavedDevices::RegisterDevice( + scoped_refptr<device::UsbDevice> device, + base::string16* serial_number) { + if (serial_number) { + for (std::vector<SavedDeviceEntry>::const_iterator it = + persistent_devices_.begin(); + it != persistent_devices_.end(); + ++it) { + if (it->vendor_id != device->vendor_id()) { + continue; + } + if (it->product_id != device->product_id()) { + continue; + } + if (it->serial_number == *serial_number) { + return; + } + } + SavedDeviceEntry device_entry = SavedDeviceEntry( + device->vendor_id(), device->product_id(), *serial_number); + persistent_devices_.push_back(device_entry); + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(AddSavedDeviceEntry, profile_, extension_id_, device_entry)); + } else { + // Without a serial number a device cannot be reliably identified when it + // is reconnected so such devices are only remembered until disconnect. + // Register an observer here so that this set doesn't grow undefinitely. + ephemeral_devices_.insert(device); + device->AddObserver(this); + } +} + +SavedDevicesService::SavedDevices::SavedDevices(Profile* profile, + const std::string& extension_id) + : profile_(profile), extension_id_(extension_id) { + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); + persistent_devices_ = GetSavedDeviceEntries(prefs, extension_id_); +} + +SavedDevicesService::SavedDevices::~SavedDevices() { + // Only ephemeral devices have an observer registered. + for (std::set<scoped_refptr<UsbDevice> >::iterator it = + ephemeral_devices_.begin(); + it != ephemeral_devices_.end(); + ++it) { + (*it)->RemoveObserver(this); + } +} + +void SavedDevicesService::SavedDevices::OnDisconnect( + scoped_refptr<UsbDevice> device) { + // Permission for an ephemeral device lasts only as long as the device is + // plugged in. + ephemeral_devices_.erase(device); + device->RemoveObserver(this); +} + +SavedDevicesService::SavedDevicesService(Profile* profile) : profile_(profile) { + registrar_.Add(this, + extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::NotificationService::AllSources()); + registrar_.Add(this, + chrome::NOTIFICATION_APP_TERMINATING, + content::NotificationService::AllSources()); +} + +SavedDevicesService::~SavedDevicesService() { + for (std::map<std::string, SavedDevices*>::iterator it = + extension_id_to_saved_devices_.begin(); + it != extension_id_to_saved_devices_.end(); + ++it) { + delete it->second; + } +} + +// static +SavedDevicesService* SavedDevicesService::Get(Profile* profile) { + return SavedDevicesServiceFactory::GetForProfile(profile); +} + +SavedDevicesService::SavedDevices* SavedDevicesService::GetOrInsert( + const std::string& extension_id) { + SavedDevices* saved_devices = Get(extension_id); + if (saved_devices) { + return saved_devices; + } + + saved_devices = new SavedDevices(profile_, extension_id); + extension_id_to_saved_devices_[extension_id] = saved_devices; + return saved_devices; +} + +std::vector<SavedDeviceEntry> SavedDevicesService::GetAllDevices( + const std::string& extension_id) const { + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); + return GetSavedDeviceEntries(prefs, extension_id); +} + +void SavedDevicesService::Clear(const std::string& extension_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + ClearSavedDeviceEntries(ExtensionPrefs::Get(profile_), extension_id); + std::map<std::string, SavedDevices*>::iterator it = + extension_id_to_saved_devices_.find(extension_id); + if (it != extension_id_to_saved_devices_.end()) { + delete it->second; + extension_id_to_saved_devices_.erase(it); + } +} + +void SavedDevicesService::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: { + Clear(content::Details<ExtensionHost>(details)->extension_id()); + break; + } + + case chrome::NOTIFICATION_APP_TERMINATING: { + // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular + // as all extension hosts will be destroyed as a result of shutdown. + registrar_.RemoveAll(); + break; + } + } +} + +SavedDevicesService::SavedDevices* SavedDevicesService::Get( + const std::string& extension_id) const { + DCHECK(thread_checker_.CalledOnValidThread()); + std::map<std::string, SavedDevices*>::const_iterator it = + extension_id_to_saved_devices_.find(extension_id); + if (it != extension_id_to_saved_devices_.end()) { + return it->second; + } + + return NULL; +} + +} // namespace apps diff --git a/apps/saved_devices_service.h b/apps/saved_devices_service.h new file mode 100644 index 0000000..52a1874 --- /dev/null +++ b/apps/saved_devices_service.h @@ -0,0 +1,123 @@ +// 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 APPS_SAVED_DEVICES_SERVICE_H_ +#define APPS_SAVED_DEVICES_SERVICE_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/threading/thread_checker.h" +#include "components/keyed_service/core/keyed_service.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "device/usb/usb_device.h" + +class Profile; + +namespace base { +class Value; +} + +namespace extensions { +class Extension; +} + +namespace apps { + +// Represents a device that a user has given an app permission to access. It +// will be persisted to disk (in the Preferences file) and so should remain +// serializable. +struct SavedDeviceEntry { + SavedDeviceEntry(uint16_t vendor_id, + uint16_t product_id, + const base::string16& serial_number); + + base::Value* ToValue() const; + + // The vendor ID of this device. + uint16_t vendor_id; + + // The product ID of this device. + uint16_t product_id; + + // The serial number (possibly alphanumeric) of this device. + base::string16 serial_number; +}; + +// Tracks the devices that apps have retained access to both while running and +// when suspended. +class SavedDevicesService : public KeyedService, + public content::NotificationObserver { + public: + // Tracks the devices that a particular extension has retained access to. + // Unlike SavedDevicesService the functions of this class can be called from + // the FILE thread. + class SavedDevices : device::UsbDevice::Observer { + public: + bool IsRegistered(scoped_refptr<device::UsbDevice> device) const; + void RegisterDevice(scoped_refptr<device::UsbDevice> device, + /* optional */ base::string16* serial_number); + + private: + friend class SavedDevicesService; + + SavedDevices(Profile* profile, const std::string& extension_id); + virtual ~SavedDevices(); + + // device::UsbDevice::Observer + virtual void OnDisconnect(scoped_refptr<device::UsbDevice> device) OVERRIDE; + + Profile* profile_; + const std::string extension_id_; + + // Devices with serial numbers are written to the prefs file. + std::vector<SavedDeviceEntry> persistent_devices_; + // Other devices are ephemeral devices and cleared when the extension host + // is destroyed. + std::set<scoped_refptr<device::UsbDevice> > ephemeral_devices_; + + DISALLOW_COPY_AND_ASSIGN(SavedDevices); + }; + + explicit SavedDevicesService(Profile* profile); + virtual ~SavedDevicesService(); + + static SavedDevicesService* Get(Profile* profile); + + // Returns the SavedDevices for |extension_id|, creating it if necessary. + SavedDevices* GetOrInsert(const std::string& extension_id); + + std::vector<SavedDeviceEntry> GetAllDevices( + const std::string& extension_id) const; + + // Clears the SavedDevices for |extension_id|. + void Clear(const std::string& extension_id); + + private: + // content::NotificationObserver. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Returns the SavedDevices for |extension_id| or NULL if one does not exist. + SavedDevices* Get(const std::string& extension_id) const; + + std::map<std::string, SavedDevices*> extension_id_to_saved_devices_; + Profile* profile_; + content::NotificationRegistrar registrar_; + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SavedDevicesService); +}; + +} // namespace apps + +#endif // APPS_SAVED_DEVICES_SERVICE_H_ diff --git a/apps/saved_devices_service_factory.cc b/apps/saved_devices_service_factory.cc new file mode 100644 index 0000000..1153eac --- /dev/null +++ b/apps/saved_devices_service_factory.cc @@ -0,0 +1,39 @@ +// 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 "apps/saved_devices_service_factory.h" + +#include "apps/saved_devices_service.h" +#include "chrome/browser/profiles/profile.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" + +namespace apps { + +// static +SavedDevicesService* SavedDevicesServiceFactory::GetForProfile( + Profile* profile) { + return static_cast<SavedDevicesService*>( + GetInstance()->GetServiceForBrowserContext(profile, true)); +} + +// static +SavedDevicesServiceFactory* SavedDevicesServiceFactory::GetInstance() { + return Singleton<SavedDevicesServiceFactory>::get(); +} + +SavedDevicesServiceFactory::SavedDevicesServiceFactory() + : BrowserContextKeyedServiceFactory( + "SavedDevicesService", + BrowserContextDependencyManager::GetInstance()) { +} + +SavedDevicesServiceFactory::~SavedDevicesServiceFactory() { +} + +KeyedService* SavedDevicesServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* profile) const { + return new SavedDevicesService(static_cast<Profile*>(profile)); +} + +} // namespace apps diff --git a/apps/saved_devices_service_factory.h b/apps/saved_devices_service_factory.h new file mode 100644 index 0000000..d1b39a1 --- /dev/null +++ b/apps/saved_devices_service_factory.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef APPS_SAVED_DEVICES_SERVICE_FACTORY_H_ +#define APPS_SAVED_DEVICES_SERVICE_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +class Profile; + +namespace apps { + +class SavedDevicesService; + +// BrowserContextKeyedServiceFactory for SavedDevicesService. +class SavedDevicesServiceFactory : public BrowserContextKeyedServiceFactory { + public: + static SavedDevicesService* GetForProfile(Profile* profile); + + static SavedDevicesServiceFactory* GetInstance(); + + private: + SavedDevicesServiceFactory(); + virtual ~SavedDevicesServiceFactory(); + friend struct DefaultSingletonTraits<SavedDevicesServiceFactory>; + + virtual KeyedService* BuildServiceInstanceFor( + content::BrowserContext* profile) const OVERRIDE; +}; + +} // namespace apps + +#endif // APPS_SAVED_DEVICES_SERVICE_FACTORY_H_ diff --git a/apps/saved_devices_service_unittest.cc b/apps/saved_devices_service_unittest.cc new file mode 100644 index 0000000..b2ed544 --- /dev/null +++ b/apps/saved_devices_service_unittest.cc @@ -0,0 +1,213 @@ +// 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 "apps/saved_devices_service.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/values_test_util.h" +#include "chrome/browser/extensions/test_extension_environment.h" +#include "chrome/test/base/testing_profile.h" +#include "device/usb/usb_device.h" +#include "device/usb/usb_device_handle.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/common/extension.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace apps { + +namespace { + +using device::UsbDevice; +using device::UsbDeviceHandle; +using device::UsbEndpointDirection; +using device::UsbTransferCallback; +using testing::Return; + +class MockUsbDeviceHandle : public UsbDeviceHandle { + public: + MockUsbDeviceHandle(const std::string& serial_number) + : UsbDeviceHandle(), serial_number_(serial_number) {} + + MOCK_CONST_METHOD0(GetDevice, scoped_refptr<UsbDevice>()); + MOCK_METHOD0(Close, void()); + + MOCK_METHOD10(ControlTransfer, + void(UsbEndpointDirection direction, + TransferRequestType request_type, + TransferRecipient recipient, + uint8 request, + uint16 value, + uint16 index, + net::IOBuffer* buffer, + size_t length, + unsigned int timeout, + const UsbTransferCallback& callback)); + + MOCK_METHOD6(BulkTransfer, + void(UsbEndpointDirection direction, + uint8 endpoint, + net::IOBuffer* buffer, + size_t length, + unsigned int timeout, + const UsbTransferCallback& callback)); + + MOCK_METHOD6(InterruptTransfer, + void(UsbEndpointDirection direction, + uint8 endpoint, + net::IOBuffer* buffer, + size_t length, + unsigned int timeout, + const UsbTransferCallback& callback)); + + MOCK_METHOD8(IsochronousTransfer, + void(UsbEndpointDirection direction, + uint8 endpoint, + net::IOBuffer* buffer, + size_t length, + unsigned int packets, + unsigned int packet_length, + unsigned int timeout, + const UsbTransferCallback& callback)); + + MOCK_METHOD0(ResetDevice, bool()); + MOCK_METHOD1(ClaimInterface, bool(int interface_number)); + MOCK_METHOD1(ReleaseInterface, bool(int interface_number)); + MOCK_METHOD2(SetInterfaceAlternateSetting, + bool(int interface_number, int alternate_setting)); + MOCK_METHOD1(GetManufacturer, bool(base::string16* manufacturer)); + MOCK_METHOD1(GetProduct, bool(base::string16* product)); + + bool GetSerial(base::string16* serial) OVERRIDE { + if (serial_number_.empty()) { + return false; + } + + *serial = base::UTF8ToUTF16(serial_number_); + return true; + } + + private: + virtual ~MockUsbDeviceHandle() {} + + const std::string serial_number_; +}; + +class MockUsbDevice : public UsbDevice { + public: + MockUsbDevice(const std::string& serial_number, uint32 unique_id) + : UsbDevice(0, 0, unique_id), serial_number_(serial_number) {} + + MOCK_METHOD1(Close, bool(scoped_refptr<UsbDeviceHandle>)); +#if defined(OS_CHROMEOS) + MOCK_METHOD2(RequestUsbAccess, void(int, const base::Callback<void(bool)>&)); +#endif + MOCK_METHOD0(GetConfiguration, const device::UsbConfigDescriptor&()); + + scoped_refptr<UsbDeviceHandle> Open() OVERRIDE { + return new MockUsbDeviceHandle(serial_number_); + } + + void NotifyDisconnect() { UsbDevice::NotifyDisconnect(); } + + private: + virtual ~MockUsbDevice() {} + + const std::string serial_number_; +}; +} + +class SavedDevicesServiceTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + testing::Test::SetUp(); + env_.GetExtensionPrefs(); // Force creation before adding extensions. + extension_ = env_.MakeExtension(*base::test::ParseJson( + "{" + " \"app\": {" + " \"background\": {" + " \"scripts\": [\"background.js\"]" + " }" + " }," + " \"permissions\": [" + " \"usb\"" + " ]" + "}")); + service_ = SavedDevicesService::Get(env_.profile()); + device0 = new MockUsbDevice("ABCDE", 0); + device1 = new MockUsbDevice("", 1); + device2 = new MockUsbDevice("12345", 2); + device3 = new MockUsbDevice("", 3); + } + + extensions::TestExtensionEnvironment env_; + const extensions::Extension* extension_; + SavedDevicesService* service_; + scoped_refptr<MockUsbDevice> device0; + scoped_refptr<MockUsbDevice> device1; + scoped_refptr<MockUsbDevice> device2; + scoped_refptr<MockUsbDevice> device3; +}; + +TEST_F(SavedDevicesServiceTest, RegisterDevices) { + SavedDevicesService::SavedDevices* saved_devices = + service_->GetOrInsert(extension_->id()); + + base::string16 serial_number(base::ASCIIToUTF16("ABCDE")); + saved_devices->RegisterDevice(device0, &serial_number); + saved_devices->RegisterDevice(device1, NULL); + + // This is necessary as writing out registered devices happens in a task on + // the UI thread. + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + + ASSERT_TRUE(saved_devices->IsRegistered(device0)); + ASSERT_TRUE(saved_devices->IsRegistered(device1)); + ASSERT_FALSE(saved_devices->IsRegistered(device2)); + ASSERT_FALSE(saved_devices->IsRegistered(device3)); + + std::vector<SavedDeviceEntry> device_entries = + service_->GetAllDevices(extension_->id()); + ASSERT_EQ(1U, device_entries.size()); + ASSERT_EQ(base::ASCIIToUTF16("ABCDE"), device_entries[0].serial_number); + + device1->NotifyDisconnect(); + + ASSERT_TRUE(saved_devices->IsRegistered(device0)); + ASSERT_FALSE(saved_devices->IsRegistered(device1)); + ASSERT_FALSE(saved_devices->IsRegistered(device2)); + ASSERT_FALSE(saved_devices->IsRegistered(device3)); + + service_->Clear(extension_->id()); + + // App is normally restarted, clearing its reference to the SavedDevices. + saved_devices = service_->GetOrInsert(extension_->id()); + ASSERT_FALSE(saved_devices->IsRegistered(device0)); + device_entries = service_->GetAllDevices(extension_->id()); + ASSERT_EQ(0U, device_entries.size()); +} + +TEST_F(SavedDevicesServiceTest, LoadPrefs) { + scoped_ptr<base::Value> prefs_value = base::test::ParseJson( + "[" + " {" + " \"product_id\": 0," + " \"serial_number\": \"ABCDE\"," + " \"type\": \"usb\"," + " \"vendor_id\": 0" + " }" + "]"); + env_.GetExtensionPrefs()->UpdateExtensionPref( + extension_->id(), "devices", prefs_value.release()); + + SavedDevicesService::SavedDevices* saved_devices = + service_->GetOrInsert(extension_->id()); + ASSERT_TRUE(saved_devices->IsRegistered(device0)); + ASSERT_FALSE(saved_devices->IsRegistered(device1)); + ASSERT_FALSE(saved_devices->IsRegistered(device2)); + ASSERT_FALSE(saved_devices->IsRegistered(device3)); +} + +} // namespace apps |