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/core/device_client.h"
9 #include "device/hid/hid_collection_info.h"
10 #include "device/hid/hid_connection.h"
11 #include "device/hid/hid_device_info.h"
12 #include "device/hid/hid_service.h"
13 #include "device/hid/hid_usage_and_page.h"
14 #include "extensions/browser/api/device_permissions_prompt.h"
15 #include "extensions/shell/browser/shell_extensions_api_client.h"
16 #include "extensions/shell/test/shell_apitest.h"
17 #include "extensions/test/extension_test_message_listener.h"
18 #include "net/base/io_buffer.h"
20 using base::ThreadTaskRunnerHandle
;
21 using device::HidCollectionInfo
;
22 using device::HidConnection
;
23 using device::HidDeviceId
;
24 using device::HidDeviceInfo
;
25 using device::HidService
;
26 using device::HidUsageAndPage
;
29 #if defined(OS_MACOSX)
30 const uint64_t kTestDeviceIds
[] = {1, 2, 3, 4, 5};
32 const char* kTestDeviceIds
[] = {"A", "B", "C", "D", "E"};
37 // These report descriptors define two devices with 8-byte input, output and
38 // feature reports. The first implements usage page 0xFF00 and has a single
39 // report without and ID. The second implements usage page 0xFF01 and has a
40 // single report with ID 1.
41 const uint8 kReportDescriptor
[] = {0x06, 0x00, 0xFF, 0x08, 0xA1, 0x01, 0x15,
42 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
43 0x08, 0x08, 0x81, 0x02, 0x08, 0x91, 0x02,
44 0x08, 0xB1, 0x02, 0xC0};
45 const uint8 kReportDescriptorWithIDs
[] = {
46 0x06, 0x01, 0xFF, 0x08, 0xA1, 0x01, 0x15, 0x00, 0x26,
47 0xFF, 0x00, 0x85, 0x01, 0x75, 0x08, 0x95, 0x08, 0x08,
48 0x81, 0x02, 0x08, 0x91, 0x02, 0x08, 0xB1, 0x02, 0xC0};
50 class MockHidConnection
: public HidConnection
{
52 MockHidConnection(scoped_refptr
<HidDeviceInfo
> device_info
)
53 : HidConnection(device_info
) {}
55 void PlatformClose() override
{}
57 void PlatformRead(const ReadCallback
& callback
) override
{
58 const char kResult
[] = "This is a HID input report.";
59 uint8_t report_id
= device_info()->has_report_id() ? 1 : 0;
60 scoped_refptr
<IOBuffer
> buffer(new IOBuffer(sizeof(kResult
)));
61 buffer
->data()[0] = report_id
;
62 memcpy(buffer
->data() + 1, kResult
, sizeof(kResult
) - 1);
63 ThreadTaskRunnerHandle::Get()->PostTask(
64 FROM_HERE
, base::Bind(callback
, true, buffer
, sizeof(kResult
)));
67 void PlatformWrite(scoped_refptr
<net::IOBuffer
> buffer
,
69 const WriteCallback
& callback
) override
{
70 const char kExpected
[] = "o-report"; // 8 bytes
72 if (size
== sizeof(kExpected
)) {
73 uint8_t report_id
= buffer
->data()[0];
74 uint8_t expected_report_id
= device_info()->has_report_id() ? 1 : 0;
75 if (report_id
== expected_report_id
) {
76 if (memcmp(buffer
->data() + 1, kExpected
, sizeof(kExpected
) - 1) == 0) {
81 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
82 base::Bind(callback
, result
));
85 void PlatformGetFeatureReport(uint8_t report_id
,
86 const ReadCallback
& callback
) override
{
87 const char kResult
[] = "This is a HID feature report.";
88 scoped_refptr
<IOBuffer
> buffer(new IOBuffer(sizeof(kResult
)));
90 if (device_info()->has_report_id()) {
91 buffer
->data()[offset
++] = report_id
;
93 memcpy(buffer
->data() + offset
, kResult
, sizeof(kResult
) - 1);
94 ThreadTaskRunnerHandle::Get()->PostTask(
96 base::Bind(callback
, true, buffer
, sizeof(kResult
) - 1 + offset
));
99 void PlatformSendFeatureReport(scoped_refptr
<net::IOBuffer
> buffer
,
101 const WriteCallback
& callback
) override
{
102 const char kExpected
[] = "The app is setting this HID feature report.";
104 if (size
== sizeof(kExpected
)) {
105 uint8_t report_id
= buffer
->data()[0];
106 uint8_t expected_report_id
= device_info()->has_report_id() ? 1 : 0;
107 if (report_id
== expected_report_id
&&
108 memcmp(buffer
->data() + 1, kExpected
, sizeof(kExpected
) - 1) == 0) {
112 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
113 base::Bind(callback
, result
));
117 ~MockHidConnection() override
{}
120 class MockHidService
: public HidService
{
122 MockHidService() : HidService() {
123 // Verify that devices are enumerated properly even when the first
124 // enumeration happens asynchronously.
125 ThreadTaskRunnerHandle::Get()->PostTask(
126 FROM_HERE
, base::Bind(&MockHidService::LazyFirstEnumeration
,
127 base::Unretained(this)));
130 void Connect(const HidDeviceId
& device_id
,
131 const ConnectCallback
& callback
) override
{
132 const auto& device_entry
= devices().find(device_id
);
133 scoped_refptr
<HidConnection
> connection
;
134 if (device_entry
!= devices().end()) {
135 connection
= new MockHidConnection(device_entry
->second
);
138 ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
139 base::Bind(callback
, connection
));
142 void LazyFirstEnumeration() {
143 AddDevice(kTestDeviceIds
[0], 0x18D1, 0x58F0, false);
144 AddDevice(kTestDeviceIds
[1], 0x18D1, 0x58F0, true);
145 AddDevice(kTestDeviceIds
[2], 0x18D1, 0x58F1, false);
146 FirstEnumerationComplete();
149 void AddDevice(const HidDeviceId
& device_id
,
153 std::vector
<uint8
> report_descriptor
;
155 report_descriptor
.insert(
156 report_descriptor
.begin(), kReportDescriptorWithIDs
,
157 kReportDescriptorWithIDs
+ sizeof(kReportDescriptorWithIDs
));
159 report_descriptor
.insert(report_descriptor
.begin(), kReportDescriptor
,
160 kReportDescriptor
+ sizeof(kReportDescriptor
));
162 HidService::AddDevice(new HidDeviceInfo(device_id
, vendor_id
, product_id
,
163 "Test Device", "A", kHIDBusTypeUSB
,
167 void RemoveDevice(const HidDeviceId
& device_id
) {
168 HidService::RemoveDevice(device_id
);
172 class TestDeviceClient
: public DeviceClient
{
174 TestDeviceClient() : DeviceClient() {}
175 ~TestDeviceClient() override
{}
177 MockHidService
& mock_service() { return hid_service_
; }
180 HidService
* GetHidService() override
{ return &hid_service_
; }
182 MockHidService hid_service_
;
185 } // namespace device
187 namespace extensions
{
189 class TestDevicePermissionsPrompt
190 : public DevicePermissionsPrompt
,
191 public DevicePermissionsPrompt::Prompt::Observer
{
193 TestDevicePermissionsPrompt(content::WebContents
* web_contents
)
194 : DevicePermissionsPrompt(web_contents
) {}
196 ~TestDevicePermissionsPrompt() override
{ prompt()->SetObserver(nullptr); }
198 void ShowDialog() override
{ prompt()->SetObserver(this); }
200 void OnDevicesChanged() override
{
201 for (size_t i
= 0; i
< prompt()->GetDeviceCount(); ++i
) {
202 prompt()->GrantDevicePermission(i
);
203 if (!prompt()->multiple()) {
207 prompt()->Dismissed();
211 class TestExtensionsAPIClient
: public ShellExtensionsAPIClient
{
213 TestExtensionsAPIClient() : ShellExtensionsAPIClient() {}
215 scoped_ptr
<DevicePermissionsPrompt
> CreateDevicePermissionsPrompt(
216 content::WebContents
* web_contents
) const override
{
217 return make_scoped_ptr(new TestDevicePermissionsPrompt(web_contents
));
221 class HidApiTest
: public ShellApiTest
{
223 void SetUpOnMainThread() override
{
224 ShellApiTest::SetUpOnMainThread();
225 device_client_
.reset(new device::TestDeviceClient());
229 scoped_ptr
<device::TestDeviceClient
> device_client_
;
232 IN_PROC_BROWSER_TEST_F(HidApiTest
, HidApp
) {
233 ASSERT_TRUE(RunAppTest("api_test/hid/api")) << message_
;
236 IN_PROC_BROWSER_TEST_F(HidApiTest
, OnDeviceAdded
) {
237 ExtensionTestMessageListener
load_listener("loaded", false);
238 ExtensionTestMessageListener
result_listener("success", false);
239 result_listener
.set_failure_message("failure");
241 ASSERT_TRUE(LoadApp("api_test/hid/add_event"));
242 ASSERT_TRUE(load_listener
.WaitUntilSatisfied());
244 // Add a blocked device first so that the test will fail if a notification is
246 device_client_
->mock_service().AddDevice(kTestDeviceIds
[3], 0x18D1, 0x58F1,
248 device_client_
->mock_service().AddDevice(kTestDeviceIds
[4], 0x18D1, 0x58F0,
250 ASSERT_TRUE(result_listener
.WaitUntilSatisfied());
251 EXPECT_EQ("success", result_listener
.message());
254 IN_PROC_BROWSER_TEST_F(HidApiTest
, OnDeviceRemoved
) {
255 ExtensionTestMessageListener
load_listener("loaded", false);
256 ExtensionTestMessageListener
result_listener("success", false);
257 result_listener
.set_failure_message("failure");
259 ASSERT_TRUE(LoadApp("api_test/hid/remove_event"));
260 ASSERT_TRUE(load_listener
.WaitUntilSatisfied());
262 // Device C was not returned by chrome.hid.getDevices, the app will not get
264 device_client_
->mock_service().RemoveDevice(kTestDeviceIds
[2]);
265 // Device A was returned, the app will get a notification.
266 device_client_
->mock_service().RemoveDevice(kTestDeviceIds
[0]);
267 ASSERT_TRUE(result_listener
.WaitUntilSatisfied());
268 EXPECT_EQ("success", result_listener
.message());
271 IN_PROC_BROWSER_TEST_F(HidApiTest
, GetUserSelectedDevices
) {
272 ExtensionTestMessageListener
open_listener("opened_device", false);
274 TestExtensionsAPIClient test_api_client
;
275 ASSERT_TRUE(LoadApp("api_test/hid/get_user_selected_devices"));
276 ASSERT_TRUE(open_listener
.WaitUntilSatisfied());
278 ExtensionTestMessageListener
remove_listener("removed", false);
279 device_client_
->mock_service().RemoveDevice(kTestDeviceIds
[0]);
280 ASSERT_TRUE(remove_listener
.WaitUntilSatisfied());
282 ExtensionTestMessageListener
add_listener("added", false);
283 device_client_
->mock_service().AddDevice(kTestDeviceIds
[0], 0x18D1, 0x58F0,
285 ASSERT_TRUE(add_listener
.WaitUntilSatisfied());
288 } // namespace extensions