blob: d374bf1e6271074683928994ca972f70cd013ebd [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/ui/hid/hid_chooser_controller.h"
#include <utility>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/hid/hid_chooser_context.h"
#include "chrome/browser/hid/hid_chooser_context_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/usb/usb_blocklist.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
HidChooserController::HidChooserController(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
content::HidChooser::Callback callback)
: ChooserController(render_frame_host,
IDS_HID_CHOOSER_PROMPT_ORIGIN,
IDS_HID_CHOOSER_PROMPT_EXTENSION_NAME),
filters_(std::move(filters)),
callback_(std::move(callback)),
requesting_origin_(render_frame_host->GetLastCommittedOrigin()),
embedding_origin_(
content::WebContents::FromRenderFrameHost(render_frame_host)
->GetMainFrame()
->GetLastCommittedOrigin()) {
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
auto* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
chooser_context_ =
HidChooserContextFactory::GetForProfile(profile)->AsWeakPtr();
DCHECK(chooser_context_);
chooser_context_->GetHidManager()->GetDevices(base::BindOnce(
&HidChooserController::OnGotDevices, weak_factory_.GetWeakPtr()));
// TODO(mattreynolds): Register to receive device added and removed events.
}
HidChooserController::~HidChooserController() {
if (callback_)
std::move(callback_).Run(std::vector<device::mojom::HidDeviceInfoPtr>());
}
bool HidChooserController::ShouldShowHelpButton() const {
return false;
}
base::string16 HidChooserController::GetNoOptionsText() const {
return l10n_util::GetStringUTF16(IDS_DEVICE_CHOOSER_NO_DEVICES_FOUND_PROMPT);
}
base::string16 HidChooserController::GetOkButtonLabel() const {
return l10n_util::GetStringUTF16(IDS_USB_DEVICE_CHOOSER_CONNECT_BUTTON_TEXT);
}
size_t HidChooserController::NumOptions() const {
return devices_.size();
}
base::string16 HidChooserController::GetOption(size_t index) const {
DCHECK_LT(index, devices_.size());
auto it = devices_.begin();
std::advance(it, index);
DCHECK_GT(it->second.size(), 0u);
auto& device = *it->second[0];
if (device.product_name.empty()) {
return l10n_util::GetStringFUTF16(
IDS_HID_CHOOSER_ITEM_WITHOUT_NAME,
base::ASCIIToUTF16(base::StringPrintf("0x%04x", device.vendor_id)),
base::ASCIIToUTF16(base::StringPrintf("0x%04x", device.product_id)));
}
return l10n_util::GetStringFUTF16(
IDS_HID_CHOOSER_ITEM_WITH_NAME, base::UTF8ToUTF16(device.product_name),
base::ASCIIToUTF16(base::StringPrintf("0x%04x", device.vendor_id)),
base::ASCIIToUTF16(base::StringPrintf("0x%04x", device.product_id)));
}
bool HidChooserController::IsPaired(size_t index) const {
DCHECK_LE(index, devices_.size());
if (!chooser_context_)
return false;
auto it = devices_.begin();
std::advance(it, index);
DCHECK_GT(it->second.size(), 0u);
for (auto& device : it->second) {
if (!chooser_context_->HasDevicePermission(requesting_origin_,
embedding_origin_, *device)) {
return false;
}
}
return true;
}
void HidChooserController::Select(const std::vector<size_t>& indices) {
// TODO(crbug.com/964041): Record metrics when an item is selected.
DCHECK_EQ(1u, indices.size());
size_t index = indices[0];
DCHECK_LT(index, devices_.size());
if (!chooser_context_) {
std::move(callback_).Run(std::vector<device::mojom::HidDeviceInfoPtr>());
return;
}
auto it = devices_.begin();
std::advance(it, index);
DCHECK_GT(it->second.size(), 0u);
for (auto& device : it->second) {
chooser_context_->GrantDevicePermission(requesting_origin_,
embedding_origin_, *device);
}
std::move(callback_).Run(std::move(it->second));
}
void HidChooserController::Cancel() {
// Called when the user presses the Cancel button in the chooser dialog.
// TODO(crbug.com/964041): Record metrics when the chooser dialog is canceled.
}
void HidChooserController::Close() {
// Called when the user dismisses the chooser by clicking outside the chooser
// dialog, or when the dialog closes without the user taking action.
// TODO(crbug.com/964041): Record metrics when the chooser dialog is closed.
}
void HidChooserController::OpenHelpCenterUrl() const {
NOTIMPLEMENTED();
}
void HidChooserController::OnGotDevices(
std::vector<device::mojom::HidDeviceInfoPtr> devices) {
for (auto& device : devices) {
if (ShouldExcludeDevice(*device))
continue;
if (FilterMatchesAny(*device)) {
// A single physical device may expose multiple HID interfaces, each
// represented by a HidDeviceInfo object. When a device exposes multiple
// HID interfaces, the HidDeviceInfo objects will share a common
// |physical_device_id|. Group these devices so that a single chooser item
// is shown for each physical device. If a device's physical device ID is
// empty, use its GUID instead.
const std::string& key = device->physical_device_id.empty()
? device->guid
: device->physical_device_id;
devices_[key].push_back(std::move(device));
}
}
if (view())
view()->OnOptionsInitialized();
}
bool HidChooserController::ShouldExcludeDevice(
const device::mojom::HidDeviceInfo& device) const {
// Do not pass the device to the chooser if it is on the USB blocklist.
if (UsbBlocklist::Get().IsExcluded({device.vendor_id, device.product_id, 0}))
return true;
// Do not pass the device to the chooser if it has a top-level collection with
// the FIDO usage page.
auto find_it =
std::find_if(device.collections.begin(), device.collections.end(),
[](const device::mojom::HidCollectionInfoPtr& c) {
return c->usage->usage_page == device::mojom::kPageFido;
});
return find_it != device.collections.end();
}
bool HidChooserController::FilterMatchesAny(
const device::mojom::HidDeviceInfo& device) const {
if (filters_.empty())
return true;
for (const auto& filter : filters_) {
if (filter->device_ids) {
if (filter->device_ids->is_vendor()) {
if (filter->device_ids->get_vendor() != device.vendor_id)
continue;
} else if (filter->device_ids->is_vendor_and_product()) {
const auto& vendor_and_product =
filter->device_ids->get_vendor_and_product();
if (vendor_and_product->vendor != device.vendor_id)
continue;
if (vendor_and_product->product != device.product_id)
continue;
}
}
if (filter->usage) {
if (filter->usage->is_page()) {
const uint16_t usage_page = filter->usage->get_page();
auto find_it =
std::find_if(device.collections.begin(), device.collections.end(),
[=](const device::mojom::HidCollectionInfoPtr& c) {
return usage_page == c->usage->usage_page;
});
if (find_it == device.collections.end())
continue;
} else if (filter->usage->is_usage_and_page()) {
const auto& usage_and_page = filter->usage->get_usage_and_page();
auto find_it = std::find_if(
device.collections.begin(), device.collections.end(),
[&usage_and_page](const device::mojom::HidCollectionInfoPtr& c) {
return usage_and_page->usage_page == c->usage->usage_page &&
usage_and_page->usage == c->usage->usage;
});
if (find_it == device.collections.end())
continue;
}
}
return true;
}
return false;
}