#include "chrome/browser/extensions/api/document_scan/simple_scan_runner.h"
#include "base/base64.h"
#include "base/containers/contains.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.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 "chromeos/crosapi/mojom/document_scan.mojom.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 {
constexpr char kNoScannersAvailableError[] = "No scanners available";
constexpr char kScanImageError[] = "Failed to scan image";
constexpr char kUnsupportedMimeTypesError[] = "Unsupported MIME types";
constexpr char kVirtualPrinterUnavailableError[] =
"Virtual USB printer unavailable";
constexpr char kTestingMimeType[] = "testing";
constexpr char kVirtualUSBPrinter[] = "DavieV Virtual USB Printer (USB)";
constexpr char kScannerImageMimeTypePng[] = "image/png";
constexpr char kPngImageDataUrlPrefix[] = "data:image/png;base64,";
constexpr base::TimeDelta kSlowReadInterval = base::Milliseconds(500);
constexpr base::TimeDelta kReadInterval = base::Milliseconds(100);
constexpr char kMopriaProtocolName[] = "Mopria";
}
SimpleScanRunner::SimpleScanRunner(content::BrowserContext* browser_context,
scoped_refptr<const Extension> extension,
crosapi::mojom::DocumentScan* document_scan)
: browser_context_(browser_context),
extension_(std::move(extension)),
document_scan_(document_scan) {
CHECK(browser_context_);
CHECK(extension_);
}
SimpleScanRunner::~SimpleScanRunner() = default;
void SimpleScanRunner::Start(std::vector<std::string> mime_types,
SimpleScanCallback callback) {
CHECK(!callback_) << "scan call already in progress";
callback_ = std::move(callback);
mime_types_ = std::move(mime_types);
scanner_ids_.clear();
scanner_handle_ = "";
job_handle_ = "";
scan_data_.clear();
scan_result_ = crosapi::mojom::ScanFailureMode::kUnknown;
bool should_use_virtual_usb_printer = false;
if (base::Contains(mime_types_, kTestingMimeType)) {
should_use_virtual_usb_printer = true;
} else if (!base::Contains(mime_types_, kScannerImageMimeTypePng)) {
std::move(callback_).Run(std::nullopt, kUnsupportedMimeTypesError);
return;
}
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(browser_context_)
->GetScannerInfoList(
extension_id(),
ash::LorgnetteScannerManager::LocalScannerFilter::
kIncludeNetworkScanners,
ash::LorgnetteScannerManager::SecureScannerFilter::
kIncludeUnsecureScanners,
base::BindOnce(&SimpleScanRunner::OnSimpleScanListReceived,
weak_ptr_factory_.GetWeakPtr(),
should_use_virtual_usb_printer));
}
const ExtensionId& SimpleScanRunner::extension_id() const {
return extension_->id();
}
void SimpleScanRunner::OnSimpleScanListReceived(
bool force_virtual_usb_printer,
const std::optional<lorgnette::ListScannersResponse>& response) {
if (!response.has_value() || response->scanners().empty()) {
std::move(callback_).Run(std::nullopt, kNoScannersAvailableError);
return;
}
std::vector<const lorgnette::ScannerInfo*> scanners;
scanners.reserve(response->scanners().size());
for (const auto& scanner : response->scanners()) {
scanners.push_back(&scanner);
}
std::ranges::stable_sort(
scanners, std::less<>{}, [](const lorgnette::ScannerInfo* info) {
return std::tuple(
info->display_name() != kVirtualUSBPrinter,
info->connection_type() !=
lorgnette::ConnectionType::CONNECTION_USB,
!info->secure(),
info->protocol_type() != kMopriaProtocolName,
info->display_name());
});
if (force_virtual_usb_printer &&
scanners[0]->display_name() != kVirtualUSBPrinter) {
std::move(callback_).Run(std::nullopt, kVirtualPrinterUnavailableError);
return;
}
scanner_ids_.reserve(scanners.size());
for (const lorgnette::ScannerInfo* info : scanners) {
if (force_virtual_usb_printer &&
info->display_name() != kVirtualUSBPrinter) {
continue;
}
scanner_ids_.push_back(std::move(info->name()));
}
std::ranges::reverse(scanner_ids_);
OpenFirstScanner();
}
void SimpleScanRunner::OpenFirstScanner() {
if (scanner_ids_.empty()) {
std::move(callback_).Run(std::nullopt, kNoScannersAvailableError);
return;
}
std::string scanner_id = std::move(scanner_ids_.back());
scanner_ids_.pop_back();
document_scan_->OpenScanner(
extension_id(), std::move(scanner_id),
base::BindOnce(&SimpleScanRunner::OnOpenScannerResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void SimpleScanRunner::OnOpenScannerResponse(
crosapi::mojom::OpenScannerResponsePtr response) {
if (response->result != crosapi::mojom::ScannerOperationResult::kSuccess ||
!response->scanner_handle.has_value()) {
OpenFirstScanner();
return;
}
scanner_handle_ = std::move(response->scanner_handle.value());
auto options = crosapi::mojom::StartScanOptions::New();
options->format = kScannerImageMimeTypePng;
document_scan_->StartPreparedScan(
scanner_handle_, std::move(options),
base::BindOnce(&SimpleScanRunner::OnStartPreparedScanResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void SimpleScanRunner::OnStartPreparedScanResponse(
crosapi::mojom::StartPreparedScanResponsePtr response) {
if (response->result != crosapi::mojom::ScannerOperationResult::kSuccess ||
!response->job_handle.has_value()) {
document_scan_->CloseScanner(
scanner_handle_,
base::BindOnce(&SimpleScanRunner::OnCloseScannerResponse,
weak_ptr_factory_.GetWeakPtr()));
return;
}
job_handle_ = std::move(response->job_handle.value());
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SimpleScanRunner::ReadScanData,
weak_ptr_factory_.GetWeakPtr()),
kSlowReadInterval);
}
void SimpleScanRunner::ReadScanData() {
document_scan_->ReadScanData(
job_handle_, base::BindOnce(&SimpleScanRunner::OnReadScanDataResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void SimpleScanRunner::OnReadScanDataResponse(
crosapi::mojom::ReadScanDataResponsePtr response) {
if (response->result == crosapi::mojom::ScannerOperationResult::kSuccess) {
if (response->data.has_value() && response->data->size() > 0) {
scan_data_.insert(scan_data_.end(), response->data->begin(),
response->data->end());
}
base::TimeDelta delay =
(scan_data_.size() > 100) ? kReadInterval : kSlowReadInterval;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SimpleScanRunner::ReadScanData,
weak_ptr_factory_.GetWeakPtr()),
delay);
return;
}
if (response->result == crosapi::mojom::ScannerOperationResult::kEndOfData) {
if (response->data.has_value() && response->data->size() > 0) {
scan_data_.insert(scan_data_.end(), response->data->begin(),
response->data->end());
}
scan_result_ = crosapi::mojom::ScanFailureMode::kNoFailure;
}
document_scan_->CloseScanner(
scanner_handle_, base::BindOnce(&SimpleScanRunner::OnCloseScannerResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void SimpleScanRunner::OnCloseScannerResponse(
crosapi::mojom::CloseScannerResponsePtr) {
OnSimpleScanCompleted(scan_result_);
}
void SimpleScanRunner::OnSimpleScanCompleted(
crosapi::mojom::ScanFailureMode failure_mode) {
if (!scan_data_.size() ||
failure_mode != crosapi::mojom::ScanFailureMode::kNoFailure) {
std::move(callback_).Run(std::nullopt, kScanImageError);
return;
}
std::string image_base64 = base::Base64Encode(scan_data_);
api::document_scan::ScanResults scan_results;
scan_results.data_urls.push_back(kPngImageDataUrlPrefix +
std::move(image_base64));
scan_results.mime_type = kScannerImageMimeTypePng;
std::move(callback_).Run(std::move(scan_results), std::nullopt);
}
}