910e62b5创建于 1月15日历史提交
// 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