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.
6 #include "base/run_loop.h"
7 #include "base/thread_task_runner_handle.h"
8 #include "device/hid/hid_collection_info.h"
9 #include "device/hid/hid_connection.h"
10 #include "device/hid/hid_device_info.h"
11 #include "device/hid/hid_service.h"
12 #include "device/hid/hid_usage_and_page.h"
13 #include "extensions/shell/test/shell_apitest.h"
14 #include "extensions/test/extension_test_message_listener.h"
15 #include "net/base/io_buffer.h"
17 using base::ThreadTaskRunnerHandle
;
18 using device::HidCollectionInfo
;
19 using device::HidConnection
;
20 using device::HidDeviceId
;
21 using device::HidDeviceInfo
;
22 using device::HidService
;
23 using device::HidUsageAndPage
;
26 #if defined(OS_MACOSX)
27 const uint64_t kTestDeviceIds
[] = {1, 2, 3, 4, 5};
29 const char* kTestDeviceIds
[] = {"A", "B", "C", "D", "E"};
34 // These report descriptors define two devices with 8-byte input, output and
35 // feature reports. The first implements usage page 0xFF00 and has a single
36 // report without and ID. The second implements usage page 0xFF01 and has a
37 // single report with ID 1.
38 const uint8 kReportDescriptor
[] = {0x06, 0x00, 0xFF, 0x08, 0xA1, 0x01, 0x15,
39 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
40 0x08, 0x08, 0x81, 0x02, 0x08, 0x91, 0x02,
41 0x08, 0xB1, 0x02, 0xC0};
42 const uint8 kReportDescriptorWithIDs
[] = {
43 0x06, 0x01, 0xFF, 0x08, 0xA1, 0x01, 0x15, 0x00, 0x26,
44 0xFF, 0x00, 0x85, 0x01, 0x75, 0x08, 0x95, 0x08, 0x08,
45 0x81, 0x02, 0x08, 0x91, 0x02, 0x08, 0xB1, 0x02, 0xC0};
47 class MockHidConnection
: public HidConnection
{
49 MockHidConnection(scoped_refptr
<HidDeviceInfo
> device_info
)
50 : HidConnection(device_info
) {}
52 void PlatformClose() override
{}
54 void PlatformRead(const ReadCallback
& callback
) override
{
55 const char kResult
[] = "This is a HID input report.";
56 uint8_t report_id
= device_info()->has_report_id() ? 1 : 0;
57 scoped_refptr
<IOBuffer
> buffer(new IOBuffer(sizeof(kResult
)));
58 buffer
->data()[0] = report_id
;
59 memcpy(buffer
->data() + 1, kResult
, sizeof(kResult
) - 1);
60 ThreadTaskRunnerHandle::Get()->PostTask(
61 FROM_HERE
, base::Bind(callback
, true, buffer
, sizeof(kResult
)));
64 void PlatformWrite(scoped_refptr
<net::IOBuffer
> buffer
,
66 const WriteCallback
& callback
) override
{
67 const char kExpected
[] = "o-report"; // 8 bytes
69 if (size
== sizeof(kExpected
)) {
70 uint8_t report_id
= buffer
->data()[0];
71 uint8_t expected_report_id
= device_info()->has_report_id() ? 1 : 0;
72 if (report_id
== expected_report_id
) {
73 if (memcmp(buffer
->data() + 1, kExpected
, sizeof(kExpected
) - 1) == 0) {
78 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
79 base::Bind(callback
, result
));
82 void PlatformGetFeatureReport(uint8_t report_id
,
83 const ReadCallback
& callback
) override
{
84 const char kResult
[] = "This is a HID feature report.";
85 scoped_refptr
<IOBuffer
> buffer(new IOBuffer(sizeof(kResult
)));
87 if (device_info()->has_report_id()) {
88 buffer
->data()[offset
++] = report_id
;
90 memcpy(buffer
->data() + offset
, kResult
, sizeof(kResult
) - 1);
91 ThreadTaskRunnerHandle::Get()->PostTask(
93 base::Bind(callback
, true, buffer
, sizeof(kResult
) - 1 + offset
));
96 void PlatformSendFeatureReport(scoped_refptr
<net::IOBuffer
> buffer
,
98 const WriteCallback
& callback
) override
{
99 const char kExpected
[] = "The app is setting this HID feature report.";
101 if (size
== sizeof(kExpected
)) {
102 uint8_t report_id
= buffer
->data()[0];
103 uint8_t expected_report_id
= device_info()->has_report_id() ? 1 : 0;
104 if (report_id
== expected_report_id
&&
105 memcmp(buffer
->data() + 1, kExpected
, sizeof(kExpected
) - 1) == 0) {
109 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
110 base::Bind(callback
, result
));
114 ~MockHidConnection() override
{}
117 class MockHidService
: public HidService
{
119 MockHidService() : HidService() {
120 // Verify that devices are enumerated properly even when the first
121 // enumeration happens asynchronously.
122 ThreadTaskRunnerHandle::Get()->PostTask(
123 FROM_HERE
, base::Bind(&MockHidService::LazyFirstEnumeration
,
124 base::Unretained(this)));
127 void Connect(const HidDeviceId
& device_id
,
128 const ConnectCallback
& callback
) override
{
129 const auto& device_entry
= devices().find(device_id
);
130 scoped_refptr
<HidConnection
> connection
;
131 if (device_entry
!= devices().end()) {
132 connection
= new MockHidConnection(device_entry
->second
);
135 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
136 base::Bind(callback
, connection
));
139 void LazyFirstEnumeration() {
140 AddDevice(kTestDeviceIds
[0], 0x18D1, 0x58F0, false);
141 AddDevice(kTestDeviceIds
[1], 0x18D1, 0x58F0, true);
142 AddDevice(kTestDeviceIds
[2], 0x18D1, 0x58F1, false);
143 FirstEnumerationComplete();
146 void AddDevice(const HidDeviceId
& device_id
,
150 std::vector
<uint8
> report_descriptor
;
152 report_descriptor
.insert(
153 report_descriptor
.begin(), kReportDescriptorWithIDs
,
154 kReportDescriptorWithIDs
+ sizeof(kReportDescriptorWithIDs
));
156 report_descriptor
.insert(report_descriptor
.begin(), kReportDescriptor
,
157 kReportDescriptor
+ sizeof(kReportDescriptor
));
159 HidService::AddDevice(new HidDeviceInfo(device_id
, vendor_id
, product_id
,
160 "", "", kHIDBusTypeUSB
,
164 void RemoveDevice(const HidDeviceId
& device_id
) {
165 HidService::RemoveDevice(device_id
);
169 } // namespace device
171 namespace extensions
{
173 class HidApiTest
: public ShellApiTest
{
175 void SetUpOnMainThread() override
{
176 ShellApiTest::SetUpOnMainThread();
177 hid_service_
= new device::MockHidService();
178 HidService::SetInstanceForTest(hid_service_
);
182 device::MockHidService
* hid_service_
;
185 IN_PROC_BROWSER_TEST_F(HidApiTest
, HidApp
) {
186 ASSERT_TRUE(RunAppTest("api_test/hid/api")) << message_
;
189 IN_PROC_BROWSER_TEST_F(HidApiTest
, OnDeviceAdded
) {
190 ExtensionTestMessageListener
load_listener("loaded", false);
191 ExtensionTestMessageListener
result_listener("success", false);
192 result_listener
.set_failure_message("failure");
194 ASSERT_TRUE(LoadApp("api_test/hid/add_event"));
195 ASSERT_TRUE(load_listener
.WaitUntilSatisfied());
197 // Add a blocked device first so that the test will fail if a notification is
199 hid_service_
->AddDevice(kTestDeviceIds
[3], 0x18D1, 0x58F1, false);
200 hid_service_
->AddDevice(kTestDeviceIds
[4], 0x18D1, 0x58F0, false);
201 ASSERT_TRUE(result_listener
.WaitUntilSatisfied());
202 EXPECT_EQ("success", result_listener
.message());
205 IN_PROC_BROWSER_TEST_F(HidApiTest
, OnDeviceRemoved
) {
206 ExtensionTestMessageListener
load_listener("loaded", false);
207 ExtensionTestMessageListener
result_listener("success", false);
208 result_listener
.set_failure_message("failure");
210 ASSERT_TRUE(LoadApp("api_test/hid/remove_event"));
211 ASSERT_TRUE(load_listener
.WaitUntilSatisfied());
213 // Device C was not returned by chrome.hid.getDevices, the app will not get
215 hid_service_
->RemoveDevice(kTestDeviceIds
[2]);
216 // Device A was returned, the app will get a notification.
217 hid_service_
->RemoveDevice(kTestDeviceIds
[0]);
218 ASSERT_TRUE(result_listener
.WaitUntilSatisfied());
219 EXPECT_EQ("success", result_listener
.message());
222 } // namespace extensions