Replace StringBuffer with StringBuilder.
[chromium-blink-merge.git] / device / hid / hid_service_linux.cc
blob500f7b8c3b61f142f3cd06864c57cf9d94e7158a
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "device/hid/hid_service_linux.h"
7 #include <fcntl.h>
8 #include <limits>
9 #include <string>
11 #include "base/bind.h"
12 #include "base/files/file.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/location.h"
16 #include "base/scoped_observer.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "components/device_event_log/device_event_log.h"
22 #include "device/hid/device_monitor_linux.h"
23 #include "device/hid/hid_connection_linux.h"
24 #include "device/hid/hid_device_info_linux.h"
25 #include "device/udev_linux/scoped_udev.h"
27 #if defined(OS_CHROMEOS)
28 #include "base/sys_info.h"
29 #include "chromeos/dbus/dbus_thread_manager.h"
30 #include "chromeos/dbus/permission_broker_client.h"
31 #endif // defined(OS_CHROMEOS)
33 namespace device {
35 namespace {
37 const char kHidrawSubsystem[] = "hidraw";
38 const char kHIDID[] = "HID_ID";
39 const char kHIDName[] = "HID_NAME";
40 const char kHIDUnique[] = "HID_UNIQ";
41 const char kSysfsReportDescriptorKey[] = "report_descriptor";
43 } // namespace
45 struct HidServiceLinux::ConnectParams {
46 ConnectParams(scoped_refptr<HidDeviceInfoLinux> device_info,
47 const ConnectCallback& callback,
48 scoped_refptr<base::SingleThreadTaskRunner> task_runner,
49 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
50 : device_info(device_info),
51 callback(callback),
52 task_runner(task_runner),
53 file_task_runner(file_task_runner) {}
54 ~ConnectParams() {}
56 scoped_refptr<HidDeviceInfoLinux> device_info;
57 ConnectCallback callback;
58 scoped_refptr<base::SingleThreadTaskRunner> task_runner;
59 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner;
60 base::File device_file;
63 class HidServiceLinux::FileThreadHelper
64 : public DeviceMonitorLinux::Observer,
65 public base::MessageLoop::DestructionObserver {
66 public:
67 FileThreadHelper(base::WeakPtr<HidServiceLinux> service,
68 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
69 : observer_(this), service_(service), task_runner_(task_runner) {
70 DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance();
71 observer_.Add(monitor);
72 monitor->Enumerate(
73 base::Bind(&FileThreadHelper::OnDeviceAdded, base::Unretained(this)));
74 task_runner->PostTask(
75 FROM_HERE,
76 base::Bind(&HidServiceLinux::FirstEnumerationComplete, service_));
79 ~FileThreadHelper() override {
80 DCHECK(thread_checker_.CalledOnValidThread());
83 private:
84 // DeviceMonitorLinux::Observer:
85 void OnDeviceAdded(udev_device* device) override {
86 DCHECK(thread_checker_.CalledOnValidThread());
87 const char* device_path = udev_device_get_syspath(device);
88 if (!device_path) {
89 return;
91 HidDeviceId device_id = device_path;
93 const char* subsystem = udev_device_get_subsystem(device);
94 if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0) {
95 return;
98 const char* str_property = udev_device_get_devnode(device);
99 if (!str_property) {
100 return;
102 std::string device_node = str_property;
104 udev_device* parent = udev_device_get_parent(device);
105 if (!parent) {
106 return;
109 const char* hid_id = udev_device_get_property_value(parent, kHIDID);
110 if (!hid_id) {
111 return;
114 std::vector<std::string> parts;
115 base::SplitString(hid_id, ':', &parts);
116 if (parts.size() != 3) {
117 return;
120 uint32_t int_property = 0;
121 if (!HexStringToUInt(base::StringPiece(parts[1]), &int_property) ||
122 int_property > std::numeric_limits<uint16_t>::max()) {
123 return;
125 uint16_t vendor_id = int_property;
127 if (!HexStringToUInt(base::StringPiece(parts[2]), &int_property) ||
128 int_property > std::numeric_limits<uint16_t>::max()) {
129 return;
131 uint16_t product_id = int_property;
133 std::string serial_number;
134 str_property = udev_device_get_property_value(parent, kHIDUnique);
135 if (str_property != NULL) {
136 serial_number = str_property;
139 std::string product_name;
140 str_property = udev_device_get_property_value(parent, kHIDName);
141 if (str_property != NULL) {
142 product_name = str_property;
145 const char* parent_sysfs_path = udev_device_get_syspath(parent);
146 if (!parent_sysfs_path) {
147 return;
149 base::FilePath report_descriptor_path =
150 base::FilePath(parent_sysfs_path).Append(kSysfsReportDescriptorKey);
151 std::string report_descriptor_str;
152 if (!base::ReadFileToString(report_descriptor_path,
153 &report_descriptor_str)) {
154 return;
157 scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfoLinux(
158 device_id, device_node, vendor_id, product_id, product_name,
159 serial_number,
160 kHIDBusTypeUSB, // TODO(reillyg): Detect Bluetooth. crbug.com/443335
161 std::vector<uint8>(report_descriptor_str.begin(),
162 report_descriptor_str.end())));
164 task_runner_->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::AddDevice,
165 service_, device_info));
168 void OnDeviceRemoved(udev_device* device) override {
169 DCHECK(thread_checker_.CalledOnValidThread());
170 const char* device_path = udev_device_get_syspath(device);
171 if (device_path) {
172 task_runner_->PostTask(
173 FROM_HERE, base::Bind(&HidServiceLinux::RemoveDevice, service_,
174 std::string(device_path)));
178 // base::MessageLoop::DestructionObserver:
179 void WillDestroyCurrentMessageLoop() override {
180 DCHECK(thread_checker_.CalledOnValidThread());
181 base::MessageLoop::current()->RemoveDestructionObserver(this);
182 delete this;
185 base::ThreadChecker thread_checker_;
186 ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_;
188 // This weak pointer is only valid when checked on this task runner.
189 base::WeakPtr<HidServiceLinux> service_;
190 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
193 HidServiceLinux::HidServiceLinux(
194 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
195 : file_task_runner_(file_task_runner), weak_factory_(this) {
196 task_runner_ = base::ThreadTaskRunnerHandle::Get();
197 // The device watcher is passed a weak pointer back to this service so that it
198 // can be cleaned up after the service is destroyed however this weak pointer
199 // must be constructed on the this thread where it will be checked.
200 file_task_runner_->PostTask(
201 FROM_HERE, base::Bind(&HidServiceLinux::StartHelper,
202 weak_factory_.GetWeakPtr(), task_runner_));
205 // static
206 void HidServiceLinux::StartHelper(
207 base::WeakPtr<HidServiceLinux> weak_ptr,
208 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
209 // Helper is a message loop destruction observer and will delete itself when
210 // this thread's message loop is destroyed.
211 new FileThreadHelper(weak_ptr, task_runner);
214 void HidServiceLinux::Connect(const HidDeviceId& device_id,
215 const ConnectCallback& callback) {
216 DCHECK(thread_checker_.CalledOnValidThread());
218 const auto& map_entry = devices().find(device_id);
219 if (map_entry == devices().end()) {
220 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
221 return;
223 scoped_refptr<HidDeviceInfoLinux> device_info =
224 static_cast<HidDeviceInfoLinux*>(map_entry->second.get());
226 scoped_ptr<ConnectParams> params(new ConnectParams(
227 device_info, callback, task_runner_, file_task_runner_));
229 #if defined(OS_CHROMEOS)
230 if (base::SysInfo::IsRunningOnChromeOS()) {
231 chromeos::PermissionBrokerClient* client =
232 chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
233 DCHECK(client) << "Could not get permission broker client.";
234 if (client) {
235 client->RequestPathAccess(
236 device_info->device_node(), -1,
237 base::Bind(&HidServiceLinux::OnRequestPathAccessComplete,
238 base::Passed(&params)));
239 } else {
240 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
242 return;
244 #endif // defined(OS_CHROMEOS)
246 file_task_runner_->PostTask(
247 FROM_HERE,
248 base::Bind(&HidServiceLinux::OpenDevice, base::Passed(&params)));
251 HidServiceLinux::~HidServiceLinux() {
252 file_task_runner_->DeleteSoon(FROM_HERE, helper_.release());
255 #if defined(OS_CHROMEOS)
256 // static
257 void HidServiceLinux::OnRequestPathAccessComplete(
258 scoped_ptr<ConnectParams> params,
259 bool success) {
260 if (success) {
261 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner =
262 params->file_task_runner;
263 file_task_runner->PostTask(
264 FROM_HERE,
265 base::Bind(&HidServiceLinux::OpenDevice, base::Passed(&params)));
266 } else {
267 params->callback.Run(nullptr);
270 #endif // defined(OS_CHROMEOS)
272 // static
273 void HidServiceLinux::OpenDevice(scoped_ptr<ConnectParams> params) {
274 base::ThreadRestrictions::AssertIOAllowed();
275 scoped_refptr<base::SingleThreadTaskRunner> task_runner = params->task_runner;
276 base::FilePath device_path(params->device_info->device_node());
277 base::File& device_file = params->device_file;
278 int flags =
279 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE;
280 device_file.Initialize(device_path, flags);
281 if (!device_file.IsValid()) {
282 base::File::Error file_error = device_file.error_details();
284 if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) {
285 HID_LOG(EVENT)
286 << "Access denied opening device read-write, trying read-only.";
287 flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
288 device_file.Initialize(device_path, flags);
291 if (!device_file.IsValid()) {
292 HID_LOG(EVENT) << "Failed to open '" << params->device_info->device_node()
293 << "': "
294 << base::File::ErrorToString(device_file.error_details());
295 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr));
296 return;
299 int result = fcntl(device_file.GetPlatformFile(), F_GETFL);
300 if (result == -1) {
301 HID_PLOG(ERROR) << "Failed to get flags from the device file descriptor";
302 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr));
303 return;
306 result = fcntl(device_file.GetPlatformFile(), F_SETFL, result | O_NONBLOCK);
307 if (result == -1) {
308 HID_PLOG(ERROR) << "Failed to set the non-blocking flag on the device fd";
309 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr));
310 return;
313 task_runner->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::ConnectImpl,
314 base::Passed(&params)));
317 // static
318 void HidServiceLinux::ConnectImpl(scoped_ptr<ConnectParams> params) {
319 DCHECK(params->device_file.IsValid());
320 params->callback.Run(make_scoped_refptr(
321 new HidConnectionLinux(params->device_info, params->device_file.Pass(),
322 params->file_task_runner)));
325 } // namespace device