blob: e44c8aaae9dfa932383b12cbf173ba387e8b79bb [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/usb/usb_service.h"
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "chrome/browser/usb/usb_device.h"
#include "third_party/libusb/src/libusb/libusb.h"
#if defined(OS_CHROMEOS)
#include "base/chromeos/chromeos_version.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/permission_broker_client.h"
#endif // defined(OS_CHROMEOS)
using std::vector;
// The UsbEventHandler works around a design flaw in the libusb interface. There
// is currently no way to signal to libusb that any caller into one of the event
// handler calls should return without handling any events.
class UsbEventHandler : public base::PlatformThread::Delegate {
public:
explicit UsbEventHandler(PlatformUsbContext context)
: running_(true), context_(context) {
base::PlatformThread::CreateNonJoinable(0, this);
}
virtual ~UsbEventHandler() {}
virtual void ThreadMain() OVERRIDE {
base::PlatformThread::SetName("UsbEventHandler");
DLOG(INFO) << "UsbEventHandler started.";
while (running_) {
libusb_handle_events(context_);
}
DLOG(INFO) << "UsbEventHandler shutting down.";
libusb_exit(context_);
delete this;
}
void Stop() {
running_ = false;
}
private:
bool running_;
PlatformUsbContext context_;
DISALLOW_EVIL_CONSTRUCTORS(UsbEventHandler);
};
UsbService::UsbService() {
libusb_init(&context_);
event_handler_ = new UsbEventHandler(context_);
}
UsbService::~UsbService() {}
void UsbService::Cleanup() {
event_handler_->Stop();
event_handler_ = NULL;
}
void UsbService::FindDevices(const uint16 vendor_id,
const uint16 product_id,
int interface_id,
vector<scoped_refptr<UsbDevice> >* devices,
const base::Callback<void()>& callback) {
DCHECK(event_handler_) << "FindDevices called after event handler stopped.";
#if defined(OS_CHROMEOS)
// ChromeOS builds on non-ChromeOS machines (dev) should not attempt to
// use permission broker.
if (base::chromeos::IsRunningOnChromeOS()) {
chromeos::PermissionBrokerClient* client =
chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
DCHECK(client) << "Could not get permission broker client.";
if (!client) {
callback.Run();
return;
}
client->RequestUsbAccess(vendor_id,
product_id,
interface_id,
base::Bind(&UsbService::FindDevicesImpl,
base::Unretained(this),
vendor_id,
product_id,
devices,
callback));
} else {
FindDevicesImpl(vendor_id, product_id, devices, callback, true);
}
#else
FindDevicesImpl(vendor_id, product_id, devices, callback, true);
#endif // defined(OS_CHROMEOS)
}
void UsbService::EnumerateDevices(
std::vector<scoped_refptr<UsbDevice> >* devices) {
devices->clear();
DeviceVector enumerated_devices;
EnumerateDevicesImpl(&enumerated_devices);
for (DeviceVector::iterator it = enumerated_devices.begin();
it != enumerated_devices.end(); ++it) {
PlatformUsbDevice device = it->device();
UsbDevice* const wrapper = LookupOrCreateDevice(device);
if (wrapper)
devices->push_back(wrapper);
}
}
void UsbService::FindDevicesImpl(const uint16 vendor_id,
const uint16 product_id,
vector<scoped_refptr<UsbDevice> >* devices,
const base::Callback<void()>& callback,
bool success) {
base::ScopedClosureRunner run_callback(callback);
devices->clear();
// If the permission broker was unable to obtain permission for the specified
// devices then there is no point in attempting to enumerate the devices. On
// platforms without a permission broker, we assume permission is granted.
if (!success)
return;
DeviceVector enumerated_devices;
EnumerateDevicesImpl(&enumerated_devices);
for (DeviceVector::iterator it = enumerated_devices.begin();
it != enumerated_devices.end(); ++it) {
PlatformUsbDevice device = it->device();
if (DeviceMatches(device, vendor_id, product_id)) {
UsbDevice* const wrapper = LookupOrCreateDevice(device);
if (wrapper)
devices->push_back(wrapper);
}
}
}
void UsbService::CloseDevice(scoped_refptr<UsbDevice> device) {
DCHECK(event_handler_) << "CloseDevice called after event handler stopped.";
PlatformUsbDevice platform_device = libusb_get_device(device->handle());
if (!ContainsKey(devices_, platform_device)) {
LOG(WARNING) << "CloseDevice called for device we're not tracking!";
return;
}
devices_.erase(platform_device);
libusb_close(device->handle());
}
UsbService::RefCountedPlatformUsbDevice::RefCountedPlatformUsbDevice(
PlatformUsbDevice device) : device_(device) {
libusb_ref_device(device_);
}
UsbService::RefCountedPlatformUsbDevice::RefCountedPlatformUsbDevice(
const RefCountedPlatformUsbDevice& other) : device_(other.device_) {
libusb_ref_device(device_);
}
UsbService::RefCountedPlatformUsbDevice::~RefCountedPlatformUsbDevice() {
libusb_unref_device(device_);
}
PlatformUsbDevice UsbService::RefCountedPlatformUsbDevice::device() {
return device_;
}
void UsbService::EnumerateDevicesImpl(DeviceVector* output) {
STLClearObject(output);
libusb_device** devices = NULL;
const ssize_t device_count = libusb_get_device_list(context_, &devices);
if (device_count < 0)
return;
for (int i = 0; i < device_count; ++i) {
libusb_device* device = devices[i];
libusb_ref_device(device);
output->push_back(RefCountedPlatformUsbDevice(device));
}
libusb_free_device_list(devices, true);
}
bool UsbService::DeviceMatches(PlatformUsbDevice device,
const uint16 vendor_id,
const uint16 product_id) {
libusb_device_descriptor descriptor;
if (libusb_get_device_descriptor(device, &descriptor))
return false;
return descriptor.idVendor == vendor_id && descriptor.idProduct == product_id;
}
UsbDevice* UsbService::LookupOrCreateDevice(PlatformUsbDevice device) {
if (!ContainsKey(devices_, device)) {
libusb_device_handle* handle = NULL;
if (libusb_open(device, &handle)) {
LOG(WARNING) << "Could not open device.";
return NULL;
}
UsbDevice* wrapper = new UsbDevice(this, handle);
devices_[device] = wrapper;
}
return devices_[device].get();
}