blob: d789e702b68b64fe9bfbb38953015bc5ac94696a [file] [log] [blame]
// 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 "device/usb/usb_service_impl.h"
#include <set>
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/thread_task_runner_handle.h"
#include "device/usb/usb_error.h"
#if defined(OS_WIN)
#include <usbiodef.h>
#include "base/scoped_observer.h"
#include "device/core/device_monitor_win.h"
#endif // OS_WIN
namespace device {
#if defined(OS_WIN)
// This class lives on the application main thread so that it can listen for
// device change notification window messages. It registers for notifications
// regarding devices implementating the "UsbDevice" interface, which represents
// most of the devices the UsbService will enumerate.
class UsbServiceImpl::UIThreadHelper final
: private DeviceMonitorWin::Observer {
public:
UIThreadHelper(base::WeakPtr<UsbServiceImpl> usb_service)
: task_runner_(base::ThreadTaskRunnerHandle::Get()),
usb_service_(usb_service),
device_observer_(this) {}
~UIThreadHelper() {}
void Start() {
DeviceMonitorWin* device_monitor =
DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_USB_DEVICE);
if (device_monitor) {
device_observer_.Add(device_monitor);
}
}
private:
void OnDeviceAdded(const std::string& device_path) override {
task_runner_->PostTask(
FROM_HERE, base::Bind(&UsbServiceImpl::RefreshDevices, usb_service_));
}
void OnDeviceRemoved(const std::string& device_path) override {
task_runner_->PostTask(
FROM_HERE, base::Bind(&UsbServiceImpl::RefreshDevices, usb_service_));
}
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
base::WeakPtr<UsbServiceImpl> usb_service_;
ScopedObserver<DeviceMonitorWin, DeviceMonitorWin::Observer> device_observer_;
};
#endif
// static
UsbService* UsbServiceImpl::Create(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
PlatformUsbContext context = NULL;
const int rv = libusb_init(&context);
if (rv != LIBUSB_SUCCESS) {
VLOG(1) << "Failed to initialize libusb: "
<< ConvertPlatformUsbErrorToString(rv);
return nullptr;
}
if (!context) {
return nullptr;
}
return new UsbServiceImpl(context, ui_task_runner);
}
scoped_refptr<UsbDevice> UsbServiceImpl::GetDeviceById(uint32 unique_id) {
DCHECK(CalledOnValidThread());
RefreshDevices();
DeviceMap::iterator it = devices_.find(unique_id);
if (it != devices_.end()) {
return it->second;
}
return NULL;
}
void UsbServiceImpl::GetDevices(
std::vector<scoped_refptr<UsbDevice> >* devices) {
DCHECK(CalledOnValidThread());
STLClearObject(devices);
if (!hotplug_enabled_) {
RefreshDevices();
}
for (const auto& map_entry : devices_) {
devices->push_back(map_entry.second);
}
}
UsbServiceImpl::UsbServiceImpl(
PlatformUsbContext context,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
: context_(new UsbContext(context)),
ui_task_runner_(ui_task_runner),
next_unique_id_(0),
hotplug_enabled_(false),
weak_factory_(this) {
task_runner_ = base::ThreadTaskRunnerHandle::Get();
int rv = libusb_hotplug_register_callback(
context_->context(),
static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY,
LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
&UsbServiceImpl::HotplugCallback, this, &hotplug_handle_);
if (rv == LIBUSB_SUCCESS) {
hotplug_enabled_ = true;
} else {
#if defined(OS_WIN)
ui_thread_helper_ = new UIThreadHelper(weak_factory_.GetWeakPtr());
ui_task_runner_->PostTask(FROM_HERE,
base::Bind(&UIThreadHelper::Start,
base::Unretained(ui_thread_helper_)));
#endif // OS_WIN
}
}
UsbServiceImpl::~UsbServiceImpl() {
if (hotplug_enabled_) {
libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
}
#if defined(OS_WIN)
if (ui_thread_helper_) {
ui_task_runner_->DeleteSoon(FROM_HERE, ui_thread_helper_);
}
#endif // OS_WIN
for (const auto& map_entry : devices_) {
map_entry.second->OnDisconnect();
}
}
void UsbServiceImpl::RefreshDevices() {
DCHECK(CalledOnValidThread());
libusb_device** platform_devices = NULL;
const ssize_t device_count =
libusb_get_device_list(context_->context(), &platform_devices);
if (device_count < 0) {
VLOG(1) << "Failed to get device list: "
<< ConvertPlatformUsbErrorToString(device_count);
}
std::set<UsbDevice*> connected_devices;
std::vector<PlatformUsbDevice> disconnected_devices;
// Populates new devices.
for (ssize_t i = 0; i < device_count; ++i) {
if (!ContainsKey(platform_devices_, platform_devices[i])) {
scoped_refptr<UsbDeviceImpl> new_device = AddDevice(platform_devices[i]);
if (new_device) {
connected_devices.insert(new_device.get());
}
} else {
connected_devices.insert(platform_devices_[platform_devices[i]].get());
}
}
// Find disconnected devices.
for (const auto& map_entry : platform_devices_) {
PlatformUsbDevice platform_device = map_entry.first;
scoped_refptr<UsbDeviceImpl> device = map_entry.second;
if (!ContainsKey(connected_devices, device.get())) {
disconnected_devices.push_back(platform_device);
devices_.erase(device->unique_id());
NotifyDeviceRemoved(device);
device->OnDisconnect();
}
}
// Remove disconnected devices from platform_devices_.
for (const PlatformUsbDevice& platform_device : disconnected_devices) {
// UsbDevice will be destroyed after this. The corresponding
// PlatformUsbDevice will be unref'ed during this process.
platform_devices_.erase(platform_device);
}
libusb_free_device_list(platform_devices, true);
}
scoped_refptr<UsbDeviceImpl> UsbServiceImpl::AddDevice(
PlatformUsbDevice platform_device) {
libusb_device_descriptor descriptor;
int rv = libusb_get_device_descriptor(platform_device, &descriptor);
if (rv == LIBUSB_SUCCESS) {
uint32 unique_id;
do {
unique_id = ++next_unique_id_;
} while (devices_.find(unique_id) != devices_.end());
scoped_refptr<UsbDeviceImpl> new_device(new UsbDeviceImpl(
context_, ui_task_runner_, platform_device, descriptor.idVendor,
descriptor.idProduct, unique_id));
platform_devices_[platform_device] = new_device;
devices_[unique_id] = new_device;
NotifyDeviceAdded(new_device);
return new_device;
} else {
VLOG(1) << "Failed to get device descriptor: "
<< ConvertPlatformUsbErrorToString(rv);
return nullptr;
}
}
// static
int LIBUSB_CALL UsbServiceImpl::HotplugCallback(libusb_context* context,
PlatformUsbDevice device,
libusb_hotplug_event event,
void* user_data) {
// It is safe to access the UsbServiceImpl* here because libusb takes a lock
// around registering, deregistering and calling hotplug callback functions
// and so guarantees that this function will not be called by the event
// processing thread after it has been deregistered.
UsbServiceImpl* self = reinterpret_cast<UsbServiceImpl*>(user_data);
switch (event) {
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
libusb_ref_device(device); // Released in OnDeviceAdded.
if (self->task_runner_->BelongsToCurrentThread()) {
self->OnDeviceAdded(device);
} else {
self->task_runner_->PostTask(
FROM_HERE, base::Bind(&UsbServiceImpl::OnDeviceAdded,
base::Unretained(self), device));
}
break;
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
libusb_ref_device(device); // Released in OnDeviceRemoved.
if (self->task_runner_->BelongsToCurrentThread()) {
self->OnDeviceRemoved(device);
} else {
self->task_runner_->PostTask(
FROM_HERE, base::Bind(&UsbServiceImpl::OnDeviceRemoved,
base::Unretained(self), device));
}
break;
default:
NOTREACHED();
}
return 0;
}
void UsbServiceImpl::OnDeviceAdded(PlatformUsbDevice platform_device) {
DCHECK(CalledOnValidThread());
DCHECK(!ContainsKey(platform_devices_, platform_device));
AddDevice(platform_device);
libusb_unref_device(platform_device);
}
void UsbServiceImpl::OnDeviceRemoved(PlatformUsbDevice platform_device) {
DCHECK(CalledOnValidThread());
PlatformDeviceMap::iterator it = platform_devices_.find(platform_device);
if (it != platform_devices_.end()) {
scoped_refptr<UsbDeviceImpl> device = it->second;
DeviceMap::iterator dev_it = devices_.find(device->unique_id());
if (dev_it != devices_.end()) {
devices_.erase(dev_it);
} else {
NOTREACHED();
}
platform_devices_.erase(it);
NotifyDeviceRemoved(device);
device->OnDisconnect();
} else {
NOTREACHED();
}
libusb_unref_device(platform_device);
}
} // namespace device