blob: d4d71bd9eb5de142d40e0326c04fcfae40dd58bc [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "extensions/browser/api/device_permissions_prompt.h"
#include "extensions/shell/browser/shell_extensions_api_client.h"
#include "extensions/shell/test/shell_apitest.h"
#include "extensions/test/extension_test_message_listener.h"
#include "services/device/public/cpp/hid/fake_hid_manager.h"
#include "services/device/public/cpp/hid/hid_report_descriptor.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/device/public/mojom/hid.mojom.h"
#include "services/service_manager/public/cpp/service_binding.h"
#if defined(OS_CHROMEOS)
#include "chromeos/dbus/permission_broker/fake_permission_broker_client.h"
#endif // defined(OS_CHROMEOS)
using base::ThreadTaskRunnerHandle;
using device::FakeHidManager;
using device::HidReportDescriptor;
const char* const kTestDeviceGuids[] = {"A", "B", "C", "D", "E"};
// These report descriptors define two devices with 8-byte input, output and
// feature reports. The first implements usage page 0xFF00 and has a single
// report without and ID. The second implements usage page 0xFF01 and has a
// single report with ID 1.
const uint8_t kReportDescriptor[] = {0x06, 0x00, 0xFF, 0x08, 0xA1, 0x01, 0x15,
0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
0x08, 0x08, 0x81, 0x02, 0x08, 0x91, 0x02,
0x08, 0xB1, 0x02, 0xC0};
const uint8_t kReportDescriptorWithIDs[] = {
0x06, 0x01, 0xFF, 0x08, 0xA1, 0x01, 0x15, 0x00, 0x26,
0xFF, 0x00, 0x85, 0x01, 0x75, 0x08, 0x95, 0x08, 0x08,
0x81, 0x02, 0x08, 0x91, 0x02, 0x08, 0xB1, 0x02, 0xC0};
namespace extensions {
class TestDevicePermissionsPrompt
: public DevicePermissionsPrompt,
public DevicePermissionsPrompt::Prompt::Observer {
public:
explicit TestDevicePermissionsPrompt(content::WebContents* web_contents)
: DevicePermissionsPrompt(web_contents) {}
~TestDevicePermissionsPrompt() override { prompt()->SetObserver(nullptr); }
void ShowDialog() override { prompt()->SetObserver(this); }
void OnDeviceAdded(size_t index, const base::string16& device_name) override {
OnDevicesChanged();
}
void OnDeviceRemoved(size_t index,
const base::string16& device_name) override {
OnDevicesChanged();
}
private:
void OnDevicesChanged() {
if (prompt()->multiple()) {
for (size_t i = 0; i < prompt()->GetDeviceCount(); ++i) {
prompt()->GrantDevicePermission(i);
}
prompt()->Dismissed();
} else {
for (size_t i = 0; i < prompt()->GetDeviceCount(); ++i) {
// Always choose the device whose serial number is "A".
if (prompt()->GetDeviceSerialNumber(i) == base::UTF8ToUTF16("A")) {
prompt()->GrantDevicePermission(i);
prompt()->Dismissed();
return;
}
}
}
}
};
class TestExtensionsAPIClient : public ShellExtensionsAPIClient {
public:
TestExtensionsAPIClient() : ShellExtensionsAPIClient() {}
std::unique_ptr<DevicePermissionsPrompt> CreateDevicePermissionsPrompt(
content::WebContents* web_contents) const override {
return std::make_unique<TestDevicePermissionsPrompt>(web_contents);
}
};
class HidApiTest : public ShellApiTest {
public:
HidApiTest() {
#if defined(OS_CHROMEOS)
// Required for DevicePermissionsPrompt:
chromeos::PermissionBrokerClient::InitializeFake();
#endif
// Because Device Service also runs in this process (browser process), we
// can set our binder to intercept requests for HidManager interface to it.
fake_hid_manager_ = std::make_unique<FakeHidManager>();
service_manager::ServiceBinding::OverrideInterfaceBinderForTesting(
device::mojom::kServiceName,
base::Bind(&FakeHidManager::Bind,
base::Unretained(fake_hid_manager_.get())));
}
~HidApiTest() override {
service_manager::ServiceBinding::ClearInterfaceBinderOverrideForTesting<
device::mojom::HidManager>(device::mojom::kServiceName);
#if defined(OS_CHROMEOS)
chromeos::PermissionBrokerClient::Shutdown();
#endif
}
void SetUpOnMainThread() override {
ShellApiTest::SetUpOnMainThread();
AddDevice(kTestDeviceGuids[0], 0x18D1, 0x58F0, false, "A");
AddDevice(kTestDeviceGuids[1], 0x18D1, 0x58F0, true, "B");
AddDevice(kTestDeviceGuids[2], 0x18D1, 0x58F1, false, "C");
}
void AddDevice(const std::string& device_guid,
int vendor_id,
int product_id,
bool report_id,
std::string serial_number) {
std::vector<uint8_t> report_descriptor;
if (report_id) {
report_descriptor.insert(
report_descriptor.begin(), kReportDescriptorWithIDs,
kReportDescriptorWithIDs + sizeof(kReportDescriptorWithIDs));
} else {
report_descriptor.insert(report_descriptor.begin(), kReportDescriptor,
kReportDescriptor + sizeof(kReportDescriptor));
}
std::vector<device::mojom::HidCollectionInfoPtr> collections;
bool has_report_id;
size_t max_input_report_size;
size_t max_output_report_size;
size_t max_feature_report_size;
HidReportDescriptor descriptor_parser(report_descriptor);
descriptor_parser.GetDetails(
&collections, &has_report_id, &max_input_report_size,
&max_output_report_size, &max_feature_report_size);
auto device = device::mojom::HidDeviceInfo::New(
device_guid, vendor_id, product_id, "Test Device", serial_number,
device::mojom::HidBusType::kHIDBusTypeUSB, report_descriptor,
std::move(collections), has_report_id, max_input_report_size,
max_output_report_size, max_feature_report_size, "");
fake_hid_manager_->AddDevice(std::move(device));
}
FakeHidManager* GetFakeHidManager() { return fake_hid_manager_.get(); }
protected:
std::unique_ptr<FakeHidManager> fake_hid_manager_;
};
IN_PROC_BROWSER_TEST_F(HidApiTest, HidApp) {
ASSERT_TRUE(RunAppTest("api_test/hid/api")) << message_;
}
IN_PROC_BROWSER_TEST_F(HidApiTest, OnDeviceAdded) {
ExtensionTestMessageListener load_listener("loaded", false);
ExtensionTestMessageListener result_listener("success", false);
result_listener.set_failure_message("failure");
ASSERT_TRUE(LoadApp("api_test/hid/add_event"));
ASSERT_TRUE(load_listener.WaitUntilSatisfied());
// Add a blocked device first so that the test will fail if a notification is
// received.
AddDevice(kTestDeviceGuids[3], 0x18D1, 0x58F1, false, "A");
AddDevice(kTestDeviceGuids[4], 0x18D1, 0x58F0, false, "A");
ASSERT_TRUE(result_listener.WaitUntilSatisfied());
EXPECT_EQ("success", result_listener.message());
}
IN_PROC_BROWSER_TEST_F(HidApiTest, OnDeviceRemoved) {
ExtensionTestMessageListener load_listener("loaded", false);
ExtensionTestMessageListener result_listener("success", false);
result_listener.set_failure_message("failure");
ASSERT_TRUE(LoadApp("api_test/hid/remove_event"));
ASSERT_TRUE(load_listener.WaitUntilSatisfied());
// Device C was not returned by chrome.hid.getDevices, the app will not get
// a notification.
GetFakeHidManager()->RemoveDevice(kTestDeviceGuids[2]);
// Device A was returned, the app will get a notification.
GetFakeHidManager()->RemoveDevice(kTestDeviceGuids[0]);
ASSERT_TRUE(result_listener.WaitUntilSatisfied());
EXPECT_EQ("success", result_listener.message());
}
IN_PROC_BROWSER_TEST_F(HidApiTest, GetUserSelectedDevices) {
ExtensionTestMessageListener open_listener("opened_device", false);
TestExtensionsAPIClient test_api_client;
ASSERT_TRUE(LoadApp("api_test/hid/get_user_selected_devices"));
ASSERT_TRUE(open_listener.WaitUntilSatisfied());
ExtensionTestMessageListener remove_listener("removed", false);
GetFakeHidManager()->RemoveDevice(kTestDeviceGuids[0]);
ASSERT_TRUE(remove_listener.WaitUntilSatisfied());
ExtensionTestMessageListener add_listener("added", false);
AddDevice(kTestDeviceGuids[0], 0x18D1, 0x58F0, true, "A");
ASSERT_TRUE(add_listener.WaitUntilSatisfied());
}
} // namespace extensions