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/browser/api/device_permissions_prompt.h"
14 #include "extensions/shell/browser/shell_extensions_api_client.h"
15 #include "extensions/shell/test/shell_apitest.h"
16 #include "extensions/test/extension_test_message_listener.h"
17 #include "net/base/io_buffer.h"
19 using base::ThreadTaskRunnerHandle
;
20 using device::HidCollectionInfo
;
21 using device::HidConnection
;
22 using device::HidDeviceId
;
23 using device::HidDeviceInfo
;
24 using device::HidService
;
25 using device::HidUsageAndPage
;
28 #if defined(OS_MACOSX)
29 const uint64_t kTestDeviceIds
[] = {1, 2, 3, 4, 5};
31 const char* kTestDeviceIds
[] = {"A", "B", "C", "D", "E"};
36 // These report descriptors define two devices with 8-byte input, output and
37 // feature reports. The first implements usage page 0xFF00 and has a single
38 // report without and ID. The second implements usage page 0xFF01 and has a
39 // single report with ID 1.
40 const uint8 kReportDescriptor
[] = {0x06, 0x00, 0xFF, 0x08, 0xA1, 0x01, 0x15,
41 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
42 0x08, 0x08, 0x81, 0x02, 0x08, 0x91, 0x02,
43 0x08, 0xB1, 0x02, 0xC0};
44 const uint8 kReportDescriptorWithIDs
[] = {
45 0x06, 0x01, 0xFF, 0x08, 0xA1, 0x01, 0x15, 0x00, 0x26,
46 0xFF, 0x00, 0x85, 0x01, 0x75, 0x08, 0x95, 0x08, 0x08,
47 0x81, 0x02, 0x08, 0x91, 0x02, 0x08, 0xB1, 0x02, 0xC0};
49 class MockHidConnection
: public HidConnection
{
51 MockHidConnection(scoped_refptr
<HidDeviceInfo
> device_info
)
52 : HidConnection(device_info
) {}
54 void PlatformClose() override
{}
56 void PlatformRead(const ReadCallback
& callback
) override
{
57 const char kResult
[] = "This is a HID input report.";
58 uint8_t report_id
= device_info()->has_report_id() ? 1 : 0;
59 scoped_refptr
<IOBuffer
> buffer(new IOBuffer(sizeof(kResult
)));
60 buffer
->data()[0] = report_id
;
61 memcpy(buffer
->data() + 1, kResult
, sizeof(kResult
) - 1);
62 ThreadTaskRunnerHandle::Get()->PostTask(
63 FROM_HERE
, base::Bind(callback
, true, buffer
, sizeof(kResult
)));
66 void PlatformWrite(scoped_refptr
<net::IOBuffer
> buffer
,
68 const WriteCallback
& callback
) override
{
69 const char kExpected
[] = "o-report"; // 8 bytes
71 if (size
== sizeof(kExpected
)) {
72 uint8_t report_id
= buffer
->data()[0];
73 uint8_t expected_report_id
= device_info()->has_report_id() ? 1 : 0;
74 if (report_id
== expected_report_id
) {
75 if (memcmp(buffer
->data() + 1, kExpected
, sizeof(kExpected
) - 1) == 0) {
80 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
81 base::Bind(callback
, result
));
84 void PlatformGetFeatureReport(uint8_t report_id
,
85 const ReadCallback
& callback
) override
{
86 const char kResult
[] = "This is a HID feature report.";
87 scoped_refptr
<IOBuffer
> buffer(new IOBuffer(sizeof(kResult
)));
89 if (device_info()->has_report_id()) {
90 buffer
->data()[offset
++] = report_id
;
92 memcpy(buffer
->data() + offset
, kResult
, sizeof(kResult
) - 1);
93 ThreadTaskRunnerHandle::Get()->PostTask(
95 base::Bind(callback
, true, buffer
, sizeof(kResult
) - 1 + offset
));
98 void PlatformSendFeatureReport(scoped_refptr
<net::IOBuffer
> buffer
,
100 const WriteCallback
& callback
) override
{
101 const char kExpected
[] = "The app is setting this HID feature report.";
103 if (size
== sizeof(kExpected
)) {
104 uint8_t report_id
= buffer
->data()[0];
105 uint8_t expected_report_id
= device_info()->has_report_id() ? 1 : 0;
106 if (report_id
== expected_report_id
&&
107 memcmp(buffer
->data() + 1, kExpected
, sizeof(kExpected
) - 1) == 0) {
111 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
112 base::Bind(callback
, result
));
116 ~MockHidConnection() override
{}
119 class MockHidService
: public HidService
{
121 MockHidService() : HidService() {
122 // Verify that devices are enumerated properly even when the first
123 // enumeration happens asynchronously.
124 ThreadTaskRunnerHandle::Get()->PostTask(
125 FROM_HERE
, base::Bind(&MockHidService::LazyFirstEnumeration
,
126 base::Unretained(this)));
129 void Connect(const HidDeviceId
& device_id
,
130 const ConnectCallback
& callback
) override
{
131 const auto& device_entry
= devices().find(device_id
);
132 scoped_refptr
<HidConnection
> connection
;
133 if (device_entry
!= devices().end()) {
134 connection
= new MockHidConnection(device_entry
->second
);
137 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
138 base::Bind(callback
, connection
));
141 void LazyFirstEnumeration() {
142 AddDevice(kTestDeviceIds
[0], 0x18D1, 0x58F0, false);
143 AddDevice(kTestDeviceIds
[1], 0x18D1, 0x58F0, true);
144 AddDevice(kTestDeviceIds
[2], 0x18D1, 0x58F1, false);
145 FirstEnumerationComplete();
148 void AddDevice(const HidDeviceId
& device_id
,
152 std::vector
<uint8
> report_descriptor
;
154 report_descriptor
.insert(
155 report_descriptor
.begin(), kReportDescriptorWithIDs
,
156 kReportDescriptorWithIDs
+ sizeof(kReportDescriptorWithIDs
));
158 report_descriptor
.insert(report_descriptor
.begin(), kReportDescriptor
,
159 kReportDescriptor
+ sizeof(kReportDescriptor
));
161 HidService::AddDevice(new HidDeviceInfo(device_id
, vendor_id
, product_id
,
162 "Test Device", "A", kHIDBusTypeUSB
,
166 void RemoveDevice(const HidDeviceId
& device_id
) {
167 HidService::RemoveDevice(device_id
);
171 } // namespace device
173 namespace extensions
{
175 class TestDevicePermissionsPrompt
176 : public DevicePermissionsPrompt
,
177 public DevicePermissionsPrompt::Prompt::Observer
{
179 TestDevicePermissionsPrompt(content::WebContents
* web_contents
)
180 : DevicePermissionsPrompt(web_contents
) {}
182 ~TestDevicePermissionsPrompt() override
{ prompt()->SetObserver(nullptr); }
184 void ShowDialog() override
{ prompt()->SetObserver(this); }
186 void OnDevicesChanged() override
{
187 for (size_t i
= 0; i
< prompt()->GetDeviceCount(); ++i
) {
188 prompt()->GrantDevicePermission(i
);
189 if (!prompt()->multiple()) {
193 prompt()->Dismissed();
197 class TestExtensionsAPIClient
: public ShellExtensionsAPIClient
{
199 TestExtensionsAPIClient() : ShellExtensionsAPIClient() {}
201 scoped_ptr
<DevicePermissionsPrompt
> CreateDevicePermissionsPrompt(
202 content::WebContents
* web_contents
) const override
{
203 return make_scoped_ptr(new TestDevicePermissionsPrompt(web_contents
));
207 class HidApiTest
: public ShellApiTest
{
209 void SetUpOnMainThread() override
{
210 ShellApiTest::SetUpOnMainThread();
211 hid_service_
= new device::MockHidService();
212 HidService::SetInstanceForTest(hid_service_
);
216 device::MockHidService
* hid_service_
;
219 IN_PROC_BROWSER_TEST_F(HidApiTest
, HidApp
) {
220 ASSERT_TRUE(RunAppTest("api_test/hid/api")) << message_
;
223 IN_PROC_BROWSER_TEST_F(HidApiTest
, OnDeviceAdded
) {
224 ExtensionTestMessageListener
load_listener("loaded", false);
225 ExtensionTestMessageListener
result_listener("success", false);
226 result_listener
.set_failure_message("failure");
228 ASSERT_TRUE(LoadApp("api_test/hid/add_event"));
229 ASSERT_TRUE(load_listener
.WaitUntilSatisfied());
231 // Add a blocked device first so that the test will fail if a notification is
233 hid_service_
->AddDevice(kTestDeviceIds
[3], 0x18D1, 0x58F1, false);
234 hid_service_
->AddDevice(kTestDeviceIds
[4], 0x18D1, 0x58F0, false);
235 ASSERT_TRUE(result_listener
.WaitUntilSatisfied());
236 EXPECT_EQ("success", result_listener
.message());
239 IN_PROC_BROWSER_TEST_F(HidApiTest
, OnDeviceRemoved
) {
240 ExtensionTestMessageListener
load_listener("loaded", false);
241 ExtensionTestMessageListener
result_listener("success", false);
242 result_listener
.set_failure_message("failure");
244 ASSERT_TRUE(LoadApp("api_test/hid/remove_event"));
245 ASSERT_TRUE(load_listener
.WaitUntilSatisfied());
247 // Device C was not returned by chrome.hid.getDevices, the app will not get
249 hid_service_
->RemoveDevice(kTestDeviceIds
[2]);
250 // Device A was returned, the app will get a notification.
251 hid_service_
->RemoveDevice(kTestDeviceIds
[0]);
252 ASSERT_TRUE(result_listener
.WaitUntilSatisfied());
253 EXPECT_EQ("success", result_listener
.message());
256 IN_PROC_BROWSER_TEST_F(HidApiTest
, GetUserSelectedDevices
) {
257 ExtensionTestMessageListener
open_listener("opened_device", false);
259 TestExtensionsAPIClient test_api_client
;
260 ASSERT_TRUE(LoadApp("api_test/hid/get_user_selected_devices"));
261 ASSERT_TRUE(open_listener
.WaitUntilSatisfied());
263 ExtensionTestMessageListener
remove_listener("removed", false);
264 hid_service_
->RemoveDevice(kTestDeviceIds
[0]);
265 ASSERT_TRUE(remove_listener
.WaitUntilSatisfied());
267 ExtensionTestMessageListener
add_listener("added", false);
268 hid_service_
->AddDevice(kTestDeviceIds
[0], 0x18D1, 0x58F0, true);
269 ASSERT_TRUE(add_listener
.WaitUntilSatisfied());
272 } // namespace extensions