0

Add chrome.usbPrivate API for use by USB device WEBUI.

This private API exposes more information about connected USB devices
than the public chrome.usb API. It is designed for use by WEBUI
components that wish to display information about connected to devices
to the user such as the dialog box shown by the proposed
chrome.usb.getUserSelectedDevices API.

BUG=346953

Review URL: https://codereview.chromium.org/463493006

Cr-Commit-Position: refs/heads/master@{#289530}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289530 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
reillyg@chromium.org
2014-08-14 12:09:54 +00:00
parent ae47a8facf
commit cc3dd4244d
16 changed files with 455 additions and 44 deletions

@ -43,6 +43,7 @@ const char kDeviceModelCommand[] = "shell:getprop ro.product.model";
const char kDumpsysCommand[] = "shell:dumpsys window policy";
const char kListProcessesCommand[] = "shell:ps";
const char kInstalledChromePackagesCommand[] = "shell:pm list packages";
const char kDeviceManufacturer[] = "Test Manufacturer";
const char kDeviceModel[] = "Nexus 5";
const char kDeviceSerial[] = "Sample serial";
@ -254,6 +255,16 @@ class MockUsbDeviceHandle : public UsbDeviceHandle {
virtual bool ResetDevice() OVERRIDE { return true; }
virtual bool GetManufacturer(base::string16* manufacturer) OVERRIDE {
*manufacturer = base::UTF8ToUTF16(kDeviceManufacturer);
return true;
}
virtual bool GetProduct(base::string16* product) OVERRIDE {
*product = base::UTF8ToUTF16(kDeviceModel);
return true;
}
virtual bool GetSerial(base::string16* serial) OVERRIDE {
*serial = base::UTF8ToUTF16(kDeviceSerial);
return true;

@ -58,6 +58,8 @@ class USB_SERVICE_EXPORT UsbDeviceHandle
virtual bool SetInterfaceAlternateSetting(const int interface_number,
const int alternate_setting) = 0;
virtual bool ResetDevice() = 0;
virtual bool GetManufacturer(base::string16* manufacturer) = 0;
virtual bool GetProduct(base::string16* product) = 0;
virtual bool GetSerial(base::string16* serial) = 0;
// Async IO. Can be called on any thread.

@ -196,7 +196,7 @@ UsbDeviceHandleImpl::UsbDeviceHandleImpl(
context_(context) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(handle) << "Cannot create device with NULL handle.";
DCHECK(interfaces_) << "Unabled to list interfaces";
DCHECK(interfaces_) << "Unable to list interfaces";
}
UsbDeviceHandleImpl::~UsbDeviceHandleImpl() {
@ -372,62 +372,145 @@ bool UsbDeviceHandleImpl::ResetDevice() {
return rv == LIBUSB_SUCCESS;
}
bool UsbDeviceHandleImpl::GetSerial(base::string16* serial) {
bool UsbDeviceHandleImpl::GetSupportedLanguages() {
if (!languages_.empty()) {
return true;
}
// The 1-byte length field limits the descriptor to 256-bytes (128 uint16s).
uint16 languages[128];
int size = libusb_get_string_descriptor(
handle_,
0,
0,
reinterpret_cast<unsigned char*>(&languages[0]),
sizeof(languages));
if (size < 0) {
VLOG(1) << "Failed to get list of supported languages: "
<< ConvertErrorToString(size);
return false;
} else if (size < 2) {
VLOG(1) << "String descriptor zero has no header.";
return false;
// The first 2 bytes of the descriptor are the total length and type tag.
} else if ((languages[0] & 0xff) != size) {
VLOG(1) << "String descriptor zero size mismatch: " << (languages[0] & 0xff)
<< " != " << size;
return false;
} else if ((languages[0] >> 8) != LIBUSB_DT_STRING) {
VLOG(1) << "String descriptor zero is not a string descriptor.";
return false;
}
languages_.assign(languages[1], languages[(size - 2) / 2]);
return true;
}
bool UsbDeviceHandleImpl::GetStringDescriptor(uint8 string_id,
base::string16* string) {
if (!GetSupportedLanguages()) {
return false;
}
std::map<uint8, base::string16>::const_iterator it = strings_.find(string_id);
if (it != strings_.end()) {
*string = it->second;
return true;
}
for (size_t i = 0; i < languages_.size(); ++i) {
// Get the string using language ID.
uint16 language_id = languages_[i];
// The 1-byte length field limits the descriptor to 256-bytes (128 char16s).
base::char16 text[128];
int size =
libusb_get_string_descriptor(handle_,
string_id,
language_id,
reinterpret_cast<unsigned char*>(&text[0]),
sizeof(text));
if (size < 0) {
VLOG(1) << "Failed to get string descriptor " << string_id << " (langid "
<< language_id << "): " << ConvertErrorToString(size);
continue;
} else if (size < 2) {
VLOG(1) << "String descriptor " << string_id << " (langid " << language_id
<< ") has no header.";
continue;
// The first 2 bytes of the descriptor are the total length and type tag.
} else if ((text[0] & 0xff) != size) {
VLOG(1) << "String descriptor " << string_id << " (langid " << language_id
<< ") size mismatch: " << (text[0] & 0xff) << " != " << size;
continue;
} else if ((text[0] >> 8) != LIBUSB_DT_STRING) {
VLOG(1) << "String descriptor " << string_id << " (langid " << language_id
<< ") is not a string descriptor.";
continue;
}
*string = base::string16(text + 1, (size - 2) / 2);
strings_[string_id] = *string;
return true;
}
return false;
}
bool UsbDeviceHandleImpl::GetManufacturer(base::string16* manufacturer) {
DCHECK(thread_checker_.CalledOnValidThread());
PlatformUsbDevice device = libusb_get_device(handle_);
libusb_device_descriptor desc;
// This is a non-blocking call as libusb has the descriptor in memory.
const int rv = libusb_get_device_descriptor(device, &desc);
if (rv != LIBUSB_SUCCESS) {
VLOG(1) << "Failed to read device descriptor: " << ConvertErrorToString(rv);
return false;
}
if (desc.iSerialNumber == 0)
return false;
// Getting supported language ID.
uint16 langid[128] = {0};
int size =
libusb_get_string_descriptor(handle_,
0,
0,
reinterpret_cast<unsigned char*>(&langid[0]),
sizeof(langid));
if (size < 0) {
VLOG(1) << "Failed to get language IDs: " << ConvertErrorToString(size);
if (desc.iManufacturer == 0) {
return false;
}
int language_count = (size - 2) / 2;
return GetStringDescriptor(desc.iManufacturer, manufacturer);
}
for (int i = 1; i <= language_count; ++i) {
// Get the string using language ID.
base::char16 text[256] = {0};
size =
libusb_get_string_descriptor(handle_,
desc.iSerialNumber,
langid[i],
reinterpret_cast<unsigned char*>(&text[0]),
sizeof(text));
if (size < 0) {
VLOG(1) << "Failed to get serial number (langid " << langid[i] << "): "
<< ConvertErrorToString(size);
continue;
}
if (size <= 2)
continue;
if ((text[0] >> 8) != LIBUSB_DT_STRING)
continue;
if ((text[0] & 255) > size)
continue;
bool UsbDeviceHandleImpl::GetProduct(base::string16* product) {
DCHECK(thread_checker_.CalledOnValidThread());
PlatformUsbDevice device = libusb_get_device(handle_);
libusb_device_descriptor desc;
size = size / 2 - 1;
*serial = base::string16(text + 1, size);
return true;
// This is a non-blocking call as libusb has the descriptor in memory.
const int rv = libusb_get_device_descriptor(device, &desc);
if (rv != LIBUSB_SUCCESS) {
VLOG(1) << "Failed to read device descriptor: " << ConvertErrorToString(rv);
return false;
}
return false;
if (desc.iProduct == 0) {
return false;
}
return GetStringDescriptor(desc.iProduct, product);
}
bool UsbDeviceHandleImpl::GetSerial(base::string16* serial) {
DCHECK(thread_checker_.CalledOnValidThread());
PlatformUsbDevice device = libusb_get_device(handle_);
libusb_device_descriptor desc;
// This is a non-blocking call as libusb has the descriptor in memory.
const int rv = libusb_get_device_descriptor(device, &desc);
if (rv != LIBUSB_SUCCESS) {
VLOG(1) << "Failed to read device descriptor: " << ConvertErrorToString(rv);
return false;
}
if (desc.iSerialNumber == 0) {
return false;
}
return GetStringDescriptor(desc.iSerialNumber, serial);
}
void UsbDeviceHandleImpl::ControlTransfer(

@ -45,6 +45,8 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle {
const int interface_number,
const int alternate_setting) OVERRIDE;
virtual bool ResetDevice() OVERRIDE;
virtual bool GetManufacturer(base::string16* manufacturer) OVERRIDE;
virtual bool GetProduct(base::string16* product) OVERRIDE;
virtual bool GetSerial(base::string16* serial) OVERRIDE;
virtual void ControlTransfer(const UsbEndpointDirection direction,
const TransferRequestType request_type,
@ -123,6 +125,9 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle {
// the in-flight transfer set.
void TransferComplete(PlatformUsbTransferHandle transfer);
bool GetSupportedLanguages();
bool GetStringDescriptor(uint8 string_id, base::string16* string);
// Informs the object to drop internal references.
void InternalClose();
@ -132,6 +137,9 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle {
scoped_refptr<UsbConfigDescriptor> interfaces_;
std::vector<uint16> languages_;
std::map<uint8, base::string16> strings_;
typedef std::map<int, scoped_refptr<InterfaceClaimer> > ClaimedInterfaceMap;
ClaimedInterfaceMap claimed_interfaces_;

@ -32,9 +32,22 @@ TEST_F(UsbServiceTest, ClaimGadget) {
ASSERT_TRUE(gadget.get());
scoped_refptr<UsbDeviceHandle> handle = gadget->GetDevice()->Open();
base::string16 serial_utf16;
ASSERT_TRUE(handle->GetSerial(&serial_utf16));
ASSERT_EQ(gadget->GetSerial(), base::UTF16ToUTF8(serial_utf16));
base::string16 utf16;
ASSERT_TRUE(handle->GetManufacturer(&utf16));
ASSERT_EQ("Google Inc.", base::UTF16ToUTF8(utf16));
// Check again to make sure string descriptor caching works.
ASSERT_EQ("Google Inc.", base::UTF16ToUTF8(utf16));
ASSERT_TRUE(handle->GetProduct(&utf16));
ASSERT_EQ("Test Gadget (default state)", base::UTF16ToUTF8(utf16));
// Check again to make sure string descriptor caching works.
ASSERT_EQ("Test Gadget (default state)", base::UTF16ToUTF8(utf16));
ASSERT_TRUE(handle->GetSerial(&utf16));
ASSERT_EQ(gadget->GetSerial(), base::UTF16ToUTF8(utf16));
// Check again to make sure string descriptor caching works.
ASSERT_EQ(gadget->GetSerial(), base::UTF16ToUTF8(utf16));
}
TEST_F(UsbServiceTest, DisconnectAndReconnect) {

@ -90,6 +90,8 @@ class MockUsbDeviceHandle : public UsbDeviceHandle {
MOCK_METHOD1(ReleaseInterface, bool(const int interface_number));
MOCK_METHOD2(SetInterfaceAlternateSetting,
bool(const int interface_number, const int alternate_setting));
MOCK_METHOD1(GetManufacturer, bool(base::string16* manufacturer));
MOCK_METHOD1(GetProduct, bool(base::string16* product));
MOCK_METHOD1(GetSerial, bool(base::string16* serial));
virtual scoped_refptr<UsbDevice> GetDevice() const OVERRIDE {

@ -0,0 +1,4 @@
include_rules = [
"+components/usb_service",
"+device/usb",
]

@ -0,0 +1,172 @@
// 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 "extensions/browser/api/usb_private/usb_private_api.h"
#include <string>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h"
#include "components/usb_service/usb_device_filter.h"
#include "components/usb_service/usb_device_handle.h"
#include "components/usb_service/usb_service.h"
#include "device/usb/usb_ids.h"
#include "extensions/common/api/usb_private.h"
namespace usb_private = extensions::core_api::usb_private;
namespace GetDevices = usb_private::GetDevices;
namespace GetDeviceInfo = usb_private::GetDeviceInfo;
using usb_service::UsbDevice;
using usb_service::UsbDeviceFilter;
using usb_service::UsbDeviceHandle;
using usb_service::UsbService;
namespace {
const char kErrorInitService[] = "Failed to initialize USB service.";
const char kErrorNoDevice[] = "No such device.";
const char kErrorOpen[] = "Failed to open device.";
} // namespace
namespace extensions {
UsbPrivateGetDevicesFunction::UsbPrivateGetDevicesFunction() {
}
UsbPrivateGetDevicesFunction::~UsbPrivateGetDevicesFunction() {
}
bool UsbPrivateGetDevicesFunction::Prepare() {
parameters_ = GetDevices::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(parameters_.get());
return true;
}
void UsbPrivateGetDevicesFunction::AsyncWorkStart() {
UsbService* service = UsbService::GetInstance();
if (!service) {
CompleteWithError(kErrorInitService);
return;
}
std::vector<UsbDeviceFilter> filters;
filters.resize(parameters_->filters.size());
for (size_t i = 0; i < parameters_->filters.size(); ++i) {
UsbDeviceFilter& filter = filters[i];
const usb_private::DeviceFilter* filter_param =
parameters_->filters[i].get();
if (filter_param->vendor_id) {
filter.SetVendorId(*filter_param->vendor_id);
}
if (filter_param->product_id) {
filter.SetProductId(*filter_param->product_id);
}
if (filter_param->interface_class) {
filter.SetInterfaceClass(*filter_param->interface_class);
}
if (filter_param->interface_subclass) {
filter.SetInterfaceSubclass(*filter_param->interface_subclass);
}
if (filter_param->interface_protocol) {
filter.SetInterfaceProtocol(*filter_param->interface_protocol);
}
}
std::vector<scoped_refptr<UsbDevice> > devices;
service->GetDevices(&devices);
scoped_ptr<base::ListValue> result(new base::ListValue());
for (size_t i = 0; i < devices.size(); ++i) {
scoped_refptr<UsbDevice> device = devices[i];
bool matched = false;
if (filters.empty()) {
matched = true;
} else {
for (size_t j = 0; !matched && j < filters.size(); ++j) {
if (filters[j].Matches(device)) {
matched = true;
}
}
}
if (matched) {
result->Append(new base::FundamentalValue((int)device->unique_id()));
}
}
SetResult(result.release());
AsyncWorkCompleted();
}
UsbPrivateGetDeviceInfoFunction::UsbPrivateGetDeviceInfoFunction() {
}
UsbPrivateGetDeviceInfoFunction::~UsbPrivateGetDeviceInfoFunction() {
}
bool UsbPrivateGetDeviceInfoFunction::Prepare() {
parameters_ = GetDeviceInfo::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(parameters_.get());
return true;
}
void UsbPrivateGetDeviceInfoFunction::AsyncWorkStart() {
UsbService* service = UsbService::GetInstance();
if (!service) {
CompleteWithError(kErrorInitService);
return;
}
scoped_refptr<UsbDevice> device =
service->GetDeviceById(parameters_->device_id);
if (!device) {
CompleteWithError(kErrorNoDevice);
return;
}
usb_private::DeviceInfo device_info;
device_info.vendor_id = device->vendor_id();
device_info.product_id = device->product_id();
const char* name = device::UsbIds::GetVendorName(device_info.vendor_id);
if (name) {
device_info.vendor_name.reset(new std::string(name));
}
name = device::UsbIds::GetProductName(device_info.vendor_id,
device_info.product_id);
if (name) {
device_info.product_name.reset(new std::string(name));
}
scoped_refptr<UsbDeviceHandle> device_handle = device->Open();
if (!device_handle) {
CompleteWithError(kErrorOpen);
return;
}
base::string16 utf16;
if (device_handle->GetManufacturer(&utf16)) {
device_info.manufacturer_string.reset(
new std::string(base::UTF16ToUTF8(utf16)));
}
if (device_handle->GetProduct(&utf16)) {
device_info.product_string.reset(new std::string(base::UTF16ToUTF8(utf16)));
}
if (device_handle->GetSerial(&utf16)) {
device_info.serial_string.reset(new std::string(base::UTF16ToUTF8(utf16)));
}
SetResult(device_info.ToValue().release());
AsyncWorkCompleted();
}
} // namespace extensions

@ -0,0 +1,49 @@
// 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 EXTENSIONS_BROWSER_API_USB_USB_PRIVATE_API_H_
#define EXTENSIONS_BROWSER_API_USB_USB_PRIVATE_API_H_
#include "extensions/browser/api/usb/usb_api.h"
#include "extensions/common/api/usb_private.h"
namespace extensions {
class UsbPrivateGetDevicesFunction : public UsbAsyncApiFunction {
public:
DECLARE_EXTENSION_FUNCTION("usbPrivate.getDevices", USBPRIVATE_GETDEVICES)
UsbPrivateGetDevicesFunction();
virtual bool Prepare() OVERRIDE;
virtual void AsyncWorkStart() OVERRIDE;
protected:
virtual ~UsbPrivateGetDevicesFunction();
private:
scoped_ptr<extensions::core_api::usb_private::GetDevices::Params> parameters_;
};
class UsbPrivateGetDeviceInfoFunction : public UsbAsyncApiFunction {
public:
DECLARE_EXTENSION_FUNCTION("usbPrivate.getDeviceInfo",
USBPRIVATE_GETDEVICEINFO)
UsbPrivateGetDeviceInfoFunction();
virtual bool Prepare() OVERRIDE;
virtual void AsyncWorkStart() OVERRIDE;
protected:
virtual ~UsbPrivateGetDeviceInfoFunction();
private:
scoped_ptr<extensions::core_api::usb_private::GetDeviceInfo::Params>
parameters_;
};
} // namespace extensions
#endif // EXTENSIONS_BROWSER_API_USB_USB_API_H_

@ -932,6 +932,8 @@ enum HistogramValue {
FILESYSTEM_UNOBSERVEENTRY,
FILESYSTEM_GETOBSERVEDENTRIES,
BROWSINGDATA_REMOVESERVICEWORKERS,
USBPRIVATE_GETDEVICES,
USBPRIVATE_GETDEVICEINFO,
// Last entry: Add new entries above and ensure to update
// tools/metrics/histograms/histograms/histograms.xml.
ENUM_BOUNDARY

@ -23,6 +23,7 @@ generated_extensions_api("api") {
"storage.json",
"test.json",
"usb.idl",
"usb_private.idl",
]
root_namespace = "extensions::core_api::%(namespace)s"
impl_dir = "//extensions/browser/api"

@ -155,5 +155,9 @@
"usb": {
"dependencies": ["permission:usb"],
"contexts": ["blessed_extension"]
},
"usbPrivate": {
"channel": "dev",
"contexts": ["webui"]
}
}

@ -41,6 +41,7 @@
'storage.json',
'test.json',
'usb.idl',
'usb_private.idl',
],
}, {
# TODO: Eliminate these on Android. See crbug.com/305852.

@ -0,0 +1,55 @@
// 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.
// Use the <code>chrome.usbPrivate</code> API to interact with connected USB
// devices. This API provides private extensions to the <code>chrome.usb</code>
// API which should only be available to trusted pages.
namespace usbPrivate {
// Properties for matching devices. A device matches of any of its interfaces
// match the given properties. An empty dictionary matches any device.
dictionary DeviceFilter {
// Device-level matching criteria:
long? vendorId;
// Checked only if the vendorId matches.
long? productId;
// Per-interface matching criteria:
long? interfaceClass;
// Checked only if the interfaceClass matches.
long? interfaceSubclass;
// Checked only if the interfaceSubclass matches.
long? interfaceProtocol;
};
dictionary DeviceInfo {
long vendorId; // idVendor from the device
long productId; // idProduct from the device
// Vendor and product names from an internal database.
DOMString? vendorName;
DOMString? productName;
// iManufacturer, iProduct and iSerial strings from the device.
DOMString? manufacturerString;
DOMString? productString;
DOMString? serialString;
};
callback GetDevicesCallback = void (long[] deviceIds);
callback GetDeviceInfoCallback = void (DeviceInfo deviceInfo);
interface Functions {
// Lists USB devices matching any of the given filters.
// |filters|: The properties to search for on target devices.
// |callback|: Invoked with a list of device IDs on complete.
static void getDevices(DeviceFilter[] filters,
GetDevicesCallback callback);
// Gets basic display information about a device.
// |deviceId|: The device ID (from |getDevices|).
// |callback|: Invoked with |DeviceInfo| from the device.
static void getDeviceInfo(long deviceId, GetDeviceInfoCallback callback);
};
};

@ -370,6 +370,8 @@
'browser/api/usb/usb_api.h',
'browser/api/usb/usb_device_resource.cc',
'browser/api/usb/usb_device_resource.h',
'browser/api/usb_private/usb_private_api.cc',
'browser/api/usb_private/usb_private_api.h',
'browser/api_activity_monitor.h',
'browser/app_sorting.h',
'browser/blacklist_state.h',

@ -40547,6 +40547,8 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="871" label="FILESYSTEM_UNOBSERVEENTRY"/>
<int value="872" label="FILESYSTEM_GETOBSERVEDENTRIES"/>
<int value="873" label="BROWSINGDATA_REMOVESERVICEWORKERS"/>
<int value="874" label="USBPRIVATE_GETDEVICES"/>
<int value="875" label="USBPRIVATE_GETDEVICEINFO"/>
</enum>
<enum name="ExtensionInstallCause" type="int">