blob: c2dc08d0ccf58a230cb0c3b9338c8d8ed71fda38 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "chrome/browser/extensions/api/document_scan/scanner_discovery_runner.h"
#include "base/containers/contains.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/extensions_dialogs.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/extension.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/native_window_tracker/native_window_tracker.h"
namespace extensions {
namespace {
// Icon size for confirmation dialogs.
constexpr int kIconSize = 64;
// There is no easy way to interact with UI dialogs that are generated by Chrome
// itself, so we need to have a way to bypass this for testing.
std::optional<bool> g_discovery_confirmation_result = std::nullopt;
bool CanSkipConfirmation(content::BrowserContext* browser_context,
const ExtensionId& extension_id) {
const base::Value::List& list =
Profile::FromBrowserContext(browser_context)
->GetPrefs()
->GetList(prefs::kDocumentScanAPITrustedExtensions);
return base::Contains(list, base::Value(extension_id));
}
api::document_scan::OperationResult ToApiOperationResult(
lorgnette::OperationResult result) {
switch (result) {
default:
NOTREACHED();
case lorgnette::OPERATION_RESULT_UNKNOWN:
return api::document_scan::OperationResult::kUnknown;
case lorgnette::OPERATION_RESULT_SUCCESS:
return api::document_scan::OperationResult::kSuccess;
case lorgnette::OPERATION_RESULT_UNSUPPORTED:
return api::document_scan::OperationResult::kUnsupported;
case lorgnette::OPERATION_RESULT_CANCELLED:
return api::document_scan::OperationResult::kCancelled;
case lorgnette::OPERATION_RESULT_DEVICE_BUSY:
return api::document_scan::OperationResult::kDeviceBusy;
case lorgnette::OPERATION_RESULT_INVALID:
return api::document_scan::OperationResult::kInvalid;
case lorgnette::OPERATION_RESULT_WRONG_TYPE:
return api::document_scan::OperationResult::kWrongType;
case lorgnette::OPERATION_RESULT_EOF:
return api::document_scan::OperationResult::kEof;
case lorgnette::OPERATION_RESULT_ADF_JAMMED:
return api::document_scan::OperationResult::kAdfJammed;
case lorgnette::OPERATION_RESULT_ADF_EMPTY:
return api::document_scan::OperationResult::kAdfEmpty;
case lorgnette::OPERATION_RESULT_COVER_OPEN:
return api::document_scan::OperationResult::kCoverOpen;
case lorgnette::OPERATION_RESULT_IO_ERROR:
return api::document_scan::OperationResult::kIoError;
case lorgnette::OPERATION_RESULT_ACCESS_DENIED:
return api::document_scan::OperationResult::kAccessDenied;
case lorgnette::OPERATION_RESULT_NO_MEMORY:
return api::document_scan::OperationResult::kNoMemory;
case lorgnette::OPERATION_RESULT_UNREACHABLE:
return api::document_scan::OperationResult::kUnreachable;
case lorgnette::OPERATION_RESULT_MISSING:
return api::document_scan::OperationResult::kMissing;
case lorgnette::OPERATION_RESULT_INTERNAL_ERROR:
return api::document_scan::OperationResult::kInternalError;
}
}
api::document_scan::ConnectionType ToApiConnectionType(
lorgnette::ConnectionType type) {
switch (type) {
default:
NOTREACHED();
case lorgnette::ConnectionType::CONNECTION_UNSPECIFIED:
return api::document_scan::ConnectionType::kUnspecified;
case lorgnette::ConnectionType::CONNECTION_USB:
return api::document_scan::ConnectionType::kUsb;
case lorgnette::ConnectionType::CONNECTION_NETWORK:
return api::document_scan::ConnectionType::kNetwork;
}
}
} // namespace
ScannerDiscoveryRunner::ScannerDiscoveryRunner(
gfx::NativeWindow native_window,
content::BrowserContext* browser_context,
scoped_refptr<const Extension> extension)
: native_window_(native_window),
browser_context_(browser_context),
extension_(std::move(extension)) {
CHECK(extension_);
if (native_window_) {
native_window_tracker_ = ui::NativeWindowTracker::Create(native_window_);
}
}
ScannerDiscoveryRunner::~ScannerDiscoveryRunner() = default;
// static
void ScannerDiscoveryRunner::SetDiscoveryConfirmationResultForTesting(
bool result) {
g_discovery_confirmation_result = result;
}
void ScannerDiscoveryRunner::Start(bool approved,
api::document_scan::DeviceFilter filter,
GetScannerListCallback callback) {
CHECK(!callback_) << "discovery call already in progress";
CHECK(callback);
callback_ = std::move(callback);
filter_.emplace(std::move(filter));
if (approved || CanSkipConfirmation(browser_context_, extension_->id())) {
SendGetScannerListRequest();
return;
}
// If a test has set the confirmation result, go directly to the end handler
// instead of displaying the dialog.
if (g_discovery_confirmation_result) {
OnConfirmationDialogClosed(g_discovery_confirmation_result.value());
return;
}
ImageLoader::Get(browser_context_)
->LoadImageAtEveryScaleFactorAsync(
extension_.get(), gfx::Size(kIconSize, kIconSize),
base::BindOnce(&ScannerDiscoveryRunner::ShowScanDiscoveryDialog,
weak_ptr_factory_.GetWeakPtr()));
}
const ExtensionId& ScannerDiscoveryRunner::extension_id() const {
return extension_->id();
}
void ScannerDiscoveryRunner::ShowScanDiscoveryDialog(const gfx::Image& icon) {
// If the browser window was closed during API request handling, treat it the
// same as if the user denied the request.
if (native_window_tracker_ &&
native_window_tracker_->WasNativeWindowDestroyed()) {
OnConfirmationDialogClosed(false);
return;
}
ShowDocumentScannerDiscoveryConfirmationDialog(
native_window_, extension_->id(), base::UTF8ToUTF16(extension_->name()),
icon.AsImageSkia(),
base::BindOnce(&ScannerDiscoveryRunner::OnConfirmationDialogClosed,
weak_ptr_factory_.GetWeakPtr()));
}
void ScannerDiscoveryRunner::OnConfirmationDialogClosed(bool approved) {
if (approved) {
SendGetScannerListRequest();
return;
}
api::document_scan::GetScannerListResponse api_response;
api_response.result = api::document_scan::OperationResult::kAccessDenied;
std::move(callback_).Run(std::move(api_response));
}
void ScannerDiscoveryRunner::SendGetScannerListRequest() {
using LocalScannerFilter = ash::LorgnetteScannerManager::LocalScannerFilter;
using SecureScannerFilter = ash::LorgnetteScannerManager::SecureScannerFilter;
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(browser_context_)
->GetScannerInfoList(
extension_->id(),
filter_->local.value_or(false)
? LocalScannerFilter::kLocalScannersOnly
: LocalScannerFilter::kIncludeNetworkScanners,
filter_->secure.value_or(false)
? SecureScannerFilter::kSecureScannersOnly
: SecureScannerFilter::kIncludeUnsecureScanners,
base::BindOnce(&ScannerDiscoveryRunner::OnScannerListReceived,
weak_ptr_factory_.GetWeakPtr()));
}
void ScannerDiscoveryRunner::OnScannerListReceived(
const std::optional<lorgnette::ListScannersResponse>& response) {
if (!response.has_value()) {
api::document_scan::GetScannerListResponse api_response;
api_response.result = api::document_scan::OperationResult::kInternalError;
std::move(callback_).Run(std::move(api_response));
return;
}
api::document_scan::GetScannerListResponse api_response;
api_response.result = ToApiOperationResult(response->result());
for (const lorgnette::ScannerInfo& scanner : response->scanners()) {
api::document_scan::ScannerInfo& scanner_out =
api_response.scanners.emplace_back();
scanner_out.scanner_id = scanner.name();
scanner_out.name = scanner.display_name();
scanner_out.manufacturer = scanner.manufacturer();
scanner_out.model = scanner.model();
scanner_out.device_uuid = scanner.device_uuid();
scanner_out.connection_type =
ToApiConnectionType(scanner.connection_type());
scanner_out.secure = scanner.secure();
for (const std::string& format : scanner.image_format()) {
scanner_out.image_formats.emplace_back(format);
}
scanner_out.protocol_type = scanner.protocol_type();
}
std::move(callback_).Run(std::move(api_response));
}
} // namespace extensions