| // 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.h" |
| |
| #include <map> |
| #include <set> |
| |
| #include "base/lazy_instance.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "device/usb/usb_context.h" |
| #include "device/usb/usb_device_impl.h" |
| #include "device/usb/usb_error.h" |
| #include "third_party/libusb/src/libusb/libusb.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| base::LazyInstance<scoped_ptr<UsbService> >::Leaky g_usb_service_instance = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| typedef struct libusb_device* PlatformUsbDevice; |
| typedef struct libusb_context* PlatformUsbContext; |
| |
| class UsbServiceImpl : public UsbService, |
| private base::MessageLoop::DestructionObserver { |
| public: |
| explicit UsbServiceImpl( |
| PlatformUsbContext context, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner); |
| ~UsbServiceImpl() override; |
| |
| private: |
| // device::UsbService implementation |
| scoped_refptr<UsbDevice> GetDeviceById(uint32 unique_id) override; |
| void GetDevices(std::vector<scoped_refptr<UsbDevice>>* devices) override; |
| |
| // base::MessageLoop::DestructionObserver implementation. |
| void WillDestroyCurrentMessageLoop() override; |
| |
| // Enumerate USB devices from OS and update devices_ map. |
| void RefreshDevices(); |
| |
| scoped_refptr<UsbContext> context_; |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; |
| |
| // TODO(reillyg): Figure out a better solution. |
| uint32 next_unique_id_; |
| |
| // The map from unique IDs to UsbDevices. |
| typedef std::map<uint32, scoped_refptr<UsbDeviceImpl> > DeviceMap; |
| DeviceMap devices_; |
| |
| // The map from PlatformUsbDevices to UsbDevices. |
| typedef std::map<PlatformUsbDevice, scoped_refptr<UsbDeviceImpl> > |
| PlatformDeviceMap; |
| PlatformDeviceMap platform_devices_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UsbServiceImpl); |
| }; |
| |
| 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); |
| RefreshDevices(); |
| |
| for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) { |
| devices->push_back(it->second); |
| } |
| } |
| |
| void UsbServiceImpl::WillDestroyCurrentMessageLoop() { |
| DCHECK(CalledOnValidThread()); |
| g_usb_service_instance.Get().reset(NULL); |
| } |
| |
| 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) { |
| base::MessageLoop::current()->AddDestructionObserver(this); |
| } |
| |
| UsbServiceImpl::~UsbServiceImpl() { |
| base::MessageLoop::current()->RemoveDestructionObserver(this); |
| for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) { |
| it->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])) { |
| libusb_device_descriptor descriptor; |
| const int rv = |
| libusb_get_device_descriptor(platform_devices[i], &descriptor); |
| // This test is needed. A valid vendor/produce pair is required. |
| if (rv != LIBUSB_SUCCESS) { |
| VLOG(1) << "Failed to get device descriptor: " |
| << ConvertPlatformUsbErrorToString(rv); |
| continue; |
| } |
| |
| 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_devices[i], |
| descriptor.idVendor, |
| descriptor.idProduct, |
| unique_id)); |
| platform_devices_[platform_devices[i]] = new_device; |
| devices_[unique_id] = new_device; |
| connected_devices.insert(new_device.get()); |
| } else { |
| connected_devices.insert(platform_devices_[platform_devices[i]].get()); |
| } |
| } |
| |
| // Find disconnected devices. |
| for (PlatformDeviceMap::iterator it = platform_devices_.begin(); |
| it != platform_devices_.end(); |
| ++it) { |
| if (!ContainsKey(connected_devices, it->second.get())) { |
| disconnected_devices.push_back(it->first); |
| devices_.erase(it->second->unique_id()); |
| it->second->OnDisconnect(); |
| } |
| } |
| |
| // Remove disconnected devices from platform_devices_. |
| for (size_t i = 0; i < disconnected_devices.size(); ++i) { |
| // UsbDevice will be destroyed after this. The corresponding |
| // PlatformUsbDevice will be unref'ed during this process. |
| platform_devices_.erase(disconnected_devices[i]); |
| } |
| |
| libusb_free_device_list(platform_devices, true); |
| } |
| |
| // static |
| UsbService* UsbService::GetInstance( |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { |
| UsbService* instance = g_usb_service_instance.Get().get(); |
| if (!instance) { |
| PlatformUsbContext context = NULL; |
| |
| const int rv = libusb_init(&context); |
| if (rv != LIBUSB_SUCCESS) { |
| VLOG(1) << "Failed to initialize libusb: " |
| << ConvertPlatformUsbErrorToString(rv); |
| return NULL; |
| } |
| if (!context) |
| return NULL; |
| |
| instance = new UsbServiceImpl(context, ui_task_runner); |
| g_usb_service_instance.Get().reset(instance); |
| } |
| return instance; |
| } |
| |
| // static |
| void UsbService::SetInstanceForTest(UsbService* instance) { |
| g_usb_service_instance.Get().reset(instance); |
| } |
| |
| } // namespace device |