summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorreillyg <reillyg@chromium.org>2014-09-19 08:00:01 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-19 15:00:29 +0000
commit89b276fbcd87b3ce88be7b0a00df6d9266d90ad6 (patch)
tree737ef60a3a1a6191f4a62e9a5c3a368bb6c668a8 /apps
parentbfae635632bc062c0a56f5ed525390c82ed5c6cc (diff)
downloadchromium_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.gn5
-rw-r--r--apps/DEPS1
-rw-r--r--apps/apps.gypi4
-rw-r--r--apps/saved_devices_service.cc320
-rw-r--r--apps/saved_devices_service.h123
-rw-r--r--apps/saved_devices_service_factory.cc39
-rw-r--r--apps/saved_devices_service_factory.h35
-rw-r--r--apps/saved_devices_service_unittest.cc213
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",
]
diff --git a/apps/DEPS b/apps/DEPS
index e2f3d69..01144ad 100644
--- a/apps/DEPS
+++ b/apps/DEPS
@@ -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