blob: 495277c072e431dd8374d0b9103b86a18dcb2616 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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 <iterator>
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "extensions/browser/api/device_permissions_prompt.h"
#include "extensions/browser/api/hid/hid_device_manager.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/hid_report_descriptor.h"
#include "services/device/public/cpp/test/fake_hid_manager.h"
#include "services/device/public/mojom/hid.mojom.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/dbus/permission_broker/fake_permission_broker_client.h" // nogncheck
#endif // BUILDFLAG(IS_CHROMEOS)
namespace extensions {
namespace {
using ::device::FakeHidManager;
using ::device::HidReportDescriptor;
const char* const kTestDeviceGuids[] = {"A", "B", "C", "D", "E"};
const char* const kTestPhysicalDeviceIds[] = {"1", "2", "3", "4", "5"};
// 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, // Usage Page (Vendor Defined 0xFF00)
0x08, // Usage
0xA1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x08, // Report Count (8)
0x08, // Usage
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,
// No Null Position)
0x08, // Usage
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,
// No Null Position,Non-volatile)
0x08, // Usage
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred
// State,No Null Position,Non-volatile)
0xC0, // End Collection
};
const uint8_t kReportDescriptorWithIDs[] = {
0x06, 0x01, 0xFF, // Usage Page (Vendor Defined 0xFF01)
0x08, // Usage
0xA1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x85, 0x01, // Report ID (1)
0x75, 0x08, // Report Size (8)
0x95, 0x08, // Report Count (8)
0x08, // Usage
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,
// No Null Position)
0x08, // Usage
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,
// No Null Position,Non-volatile)
0x08, // Usage
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred
// State,No Null Position,Non-volatile)
0xC0, // End Collection
};
// Device IDs for the device granted permission by the manifest.
constexpr uint16_t kTestVendorId = 0x18D1;
constexpr uint16_t kTestProductId = 0x58F0;
} // namespace
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 OnDevicesInitialized() override {
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) == u"A") {
prompt()->GrantDevicePermission(i);
prompt()->Dismissed();
return;
}
}
}
}
void OnDeviceAdded(size_t index, const std::u16string& device_name) override {
}
void OnDeviceRemoved(size_t index,
const std::u16string& device_name) override {}
};
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 BUILDFLAG(IS_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>();
auto binder = base::BindRepeating(
&FakeHidManager::Bind, base::Unretained(fake_hid_manager_.get()));
HidDeviceManager::OverrideHidManagerBinderForTesting(binder);
DevicePermissionsPrompt::OverrideHidManagerBinderForTesting(binder);
}
~HidApiTest() override {
HidDeviceManager::OverrideHidManagerBinderForTesting(base::NullCallback());
#if BUILDFLAG(IS_CHROMEOS)
chromeos::PermissionBrokerClient::Shutdown();
#endif
}
void SetUpOnMainThread() override {
ShellApiTest::SetUpOnMainThread();
AddDevice(kTestDeviceGuids[0], kTestPhysicalDeviceIds[0], kTestVendorId,
kTestProductId, false, "A");
AddDevice(kTestDeviceGuids[1], kTestPhysicalDeviceIds[1], kTestVendorId,
kTestProductId, true, "B");
AddDevice(kTestDeviceGuids[2], kTestPhysicalDeviceIds[2], kTestVendorId,
kTestProductId + 1, false, "C");
}
void AddDevice(const std::string& device_guid,
const std::string& physical_device_id,
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(),
std::begin(kReportDescriptorWithIDs),
std::end(kReportDescriptorWithIDs));
} else {
report_descriptor.insert(report_descriptor.begin(),
std::begin(kReportDescriptor),
std::end(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, physical_device_id, 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,
/*device_path=*/"",
/*protected_input_report_ids=*/std::vector<uint8_t>{},
/*protected_output_report_ids=*/std::vector<uint8_t>{},
/*protected_feature_report_ids=*/std::vector<uint8_t>{});
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");
ExtensionTestMessageListener result_listener("success");
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], kTestPhysicalDeviceIds[3], kTestVendorId,
kTestProductId + 1, false, "A");
AddDevice(kTestDeviceGuids[4], kTestPhysicalDeviceIds[4], kTestVendorId,
kTestProductId, false, "A");
ASSERT_TRUE(result_listener.WaitUntilSatisfied());
EXPECT_EQ("success", result_listener.message());
}
IN_PROC_BROWSER_TEST_F(HidApiTest, OnDeviceRemoved) {
ExtensionTestMessageListener load_listener("loaded");
ExtensionTestMessageListener result_listener("success");
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());
}
namespace {
device::mojom::HidDeviceInfoPtr CreateDeviceWithOneCollection(
const std::string& guid) {
auto device_info = device::mojom::HidDeviceInfo::New();
device_info->guid = guid;
device_info->vendor_id = kTestVendorId;
device_info->product_id = kTestProductId;
auto collection = device::mojom::HidCollectionInfo::New();
collection->usage =
device::mojom::HidUsageAndPage::New(1, device::mojom::kPageVendor);
auto report = device::mojom::HidReportDescription::New();
collection->input_reports.push_back(std::move(report));
device_info->collections.push_back(std::move(collection));
return device_info;
}
device::mojom::HidDeviceInfoPtr CreateDeviceWithTwoCollections(
const std::string guid) {
auto device_info = CreateDeviceWithOneCollection(guid);
auto collection = device::mojom::HidCollectionInfo::New();
collection->usage =
device::mojom::HidUsageAndPage::New(2, device::mojom::kPageVendor);
collection->output_reports.push_back(
device::mojom::HidReportDescription::New());
device_info->collections.push_back(std::move(collection));
return device_info;
}
} // namespace
IN_PROC_BROWSER_TEST_F(HidApiTest, DeviceAddedChangedRemoved) {
constexpr char kTestGuid[] = "guid";
ExtensionTestMessageListener load_listener("loaded");
ExtensionTestMessageListener add_listener("added", ReplyBehavior::kWillReply);
ExtensionTestMessageListener change_listener("changed");
ExtensionTestMessageListener result_listener("success");
result_listener.set_failure_message("failure");
ASSERT_TRUE(LoadApp("api_test/hid/add_change_remove"));
ASSERT_TRUE(load_listener.WaitUntilSatisfied());
// Add a device with one collection.
GetFakeHidManager()->AddDevice(CreateDeviceWithOneCollection(kTestGuid));
ASSERT_TRUE(add_listener.WaitUntilSatisfied());
// Update the device info to add a second collection. No event is generated,
// so we will reply to the |add_listener| to signal to the test that the
// change is complete.
GetFakeHidManager()->ChangeDevice(CreateDeviceWithTwoCollections(kTestGuid));
add_listener.Reply("device info updated");
ASSERT_TRUE(change_listener.WaitUntilSatisfied());
// Remove the device.
GetFakeHidManager()->RemoveDevice(kTestGuid);
ASSERT_TRUE(result_listener.WaitUntilSatisfied());
EXPECT_EQ("success", result_listener.message());
}
} // namespace extensions