// Copyright 2022 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/ash/crosapi/document_scan_ash.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/crosapi/document_scan_ash_type_converters.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_manager.h"
#include "chromeos/ash/components/dbus/lorgnette/lorgnette_service.pb.h"
#include "components/user_manager/user_manager.h"
namespace crosapi {
namespace {
Profile* GetProfile() {
if (!user_manager::UserManager::IsInitialized() ||
!user_manager::UserManager::Get()->IsUserLoggedIn()) {
return nullptr;
}
return ProfileManager::GetPrimaryUserProfile();
}
void GetScannerNamesAdapter(DocumentScanAsh::GetScannerNamesCallback callback,
std::vector<std::string> scanner_names) {
std::move(callback).Run(scanner_names);
}
// Supports the static_cast() in ProtobufResultToMojoResult() below.
static_assert(lorgnette::SCAN_FAILURE_MODE_NO_FAILURE ==
static_cast<int>(mojom::ScanFailureMode::kNoFailure));
static_assert(lorgnette::SCAN_FAILURE_MODE_UNKNOWN ==
static_cast<int>(mojom::ScanFailureMode::kUnknown));
static_assert(lorgnette::SCAN_FAILURE_MODE_DEVICE_BUSY ==
static_cast<int>(mojom::ScanFailureMode::kDeviceBusy));
static_assert(lorgnette::SCAN_FAILURE_MODE_ADF_JAMMED ==
static_cast<int>(mojom::ScanFailureMode::kAdfJammed));
static_assert(lorgnette::SCAN_FAILURE_MODE_ADF_EMPTY ==
static_cast<int>(mojom::ScanFailureMode::kAdfEmpty));
static_assert(lorgnette::SCAN_FAILURE_MODE_FLATBED_OPEN ==
static_cast<int>(mojom::ScanFailureMode::kFlatbedOpen));
static_assert(lorgnette::SCAN_FAILURE_MODE_IO_ERROR ==
static_cast<int>(mojom::ScanFailureMode::kIoError));
mojom::ScanFailureMode ProtobufResultToMojoResult(
lorgnette::ScanFailureMode failure_mode) {
// The static_assert() checks above make this cast safe.
return static_cast<mojom::ScanFailureMode>(failure_mode);
}
// Wrapper around `data` that allows this to be a WeakPtr.
struct ScanResult {
public:
ScanResult() = default;
ScanResult(const ScanResult&) = delete;
ScanResult& operator=(const ScanResult&) = delete;
~ScanResult() = default;
base::WeakPtr<ScanResult> AsWeakPtr() {
return weak_ptr_factory.GetWeakPtr();
}
std::optional<std::string> data;
private:
base::WeakPtrFactory<ScanResult> weak_ptr_factory{this};
};
void OnPageReceived(base::WeakPtr<ScanResult> scan_result,
std::string scanned_image,
uint32_t /*page_number*/) {
if (!scan_result)
return;
// Take only the first page of the scan.
if (scan_result->data.has_value())
return;
scan_result->data = std::move(scanned_image);
}
// As a standalone function, this will always run `callback`. If this was a
// DocumentScanAsh method instead, then that method bound to a
// base::WeakPtr<DocumentScanAsh> may sometimes not run `callback`.
void OnScanCompleted(DocumentScanAsh::ScanFirstPageCallback callback,
std::unique_ptr<ScanResult> scan_result,
lorgnette::ScanFailureMode failure_mode) {
std::move(callback).Run(ProtobufResultToMojoResult(failure_mode),
std::move(scan_result->data));
}
void GetScannerListAdapter(
DocumentScanAsh::GetScannerListCallback callback,
const std::optional<lorgnette::ListScannersResponse>& response_in) {
if (!response_in) {
auto response_out = mojom::GetScannerListResponse::New();
response_out->result = mojom::ScannerOperationResult::kInternalError;
std::move(callback).Run(std::move(response_out));
return;
}
std::move(callback).Run(
mojom::GetScannerListResponse::From(response_in.value()));
}
void OpenScannerAdapter(
const std::string& scanner_id,
DocumentScanAsh::OpenScannerCallback callback,
const std::optional<lorgnette::OpenScannerResponse>& response_in) {
if (!response_in) {
auto response_out = mojom::OpenScannerResponse::New();
response_out->scanner_id = scanner_id;
response_out->result = mojom::ScannerOperationResult::kInternalError;
std::move(callback).Run(std::move(response_out));
return;
}
std::move(callback).Run(
mojom::OpenScannerResponse::From(response_in.value()));
}
void CloseScannerAdapter(
const std::string& scanner_handle,
DocumentScanAsh::CloseScannerCallback callback,
const std::optional<lorgnette::CloseScannerResponse>& response_in) {
if (!response_in) {
auto response_out = mojom::CloseScannerResponse::New();
response_out->scanner_handle = scanner_handle;
response_out->result = mojom::ScannerOperationResult::kInternalError;
std::move(callback).Run(std::move(response_out));
return;
}
std::move(callback).Run(
mojom::CloseScannerResponse::From(response_in.value()));
}
void StartPreparedScanAdapter(
const std::string& scanner_handle,
DocumentScanAsh::StartPreparedScanCallback callback,
const std::optional<lorgnette::StartPreparedScanResponse>& response_in) {
if (!response_in) {
auto response = mojom::StartPreparedScanResponse::New();
response->result = mojom::ScannerOperationResult::kInternalError;
response->scanner_handle = scanner_handle;
std::move(callback).Run(std::move(response));
return;
}
std::move(callback).Run(
mojom::StartPreparedScanResponse::From(response_in.value()));
}
void ReadScanDataAdapter(
const std::string& job_handle,
DocumentScanAsh::ReadScanDataCallback callback,
const std::optional<lorgnette::ReadScanDataResponse>& response_in) {
if (!response_in) {
auto response = mojom::ReadScanDataResponse::New();
response->result = mojom::ScannerOperationResult::kInternalError;
response->job_handle = job_handle;
std::move(callback).Run(std::move(response));
return;
}
std::move(callback).Run(
mojom::ReadScanDataResponse::From(response_in.value()));
}
void SetOptionsAdapter(
const std::string& scanner_handle,
std::vector<std::string> option_names,
std::vector<std::string> invalid_option_names,
DocumentScanAsh::SetOptionsCallback callback,
const std::optional<lorgnette::SetOptionsResponse>& response_in) {
if (!response_in) {
auto response = mojom::SetOptionsResponse::New();
response->scanner_handle = scanner_handle;
for (const std::string& option_name : option_names) {
auto result = mojom::SetOptionResult::New();
result->name = option_name;
result->result = mojom::ScannerOperationResult::kInternalError;
response->results.emplace_back(std::move(result));
}
std::move(callback).Run(std::move(response));
return;
}
lorgnette::SetOptionsResponse response = response_in.value();
for (const std::string& invalid_name : invalid_option_names) {
(*response.mutable_results())[invalid_name] =
lorgnette::OperationResult::OPERATION_RESULT_WRONG_TYPE;
}
std::move(callback).Run(mojom::SetOptionsResponse::From(response));
}
void GetOptionGroupsAdapter(
const std::string& scanner_handle,
DocumentScanAsh::GetOptionGroupsCallback callback,
const std::optional<lorgnette::GetCurrentConfigResponse>& response_in) {
if (!response_in) {
auto response = mojom::GetOptionGroupsResponse::New();
response->result = mojom::ScannerOperationResult::kInternalError;
response->scanner_handle = scanner_handle;
std::move(callback).Run(std::move(response));
return;
}
std::move(callback).Run(
mojom::GetOptionGroupsResponse::From(response_in.value()));
}
void CancelScanAdapter(
const std::string& job_handle,
DocumentScanAsh::CancelScanCallback callback,
const std::optional<lorgnette::CancelScanResponse>& response_in) {
if (!response_in) {
auto response = mojom::CancelScanResponse::New();
response->job_handle = job_handle;
response->result = mojom::ScannerOperationResult::kInternalError;
std::move(callback).Run(std::move(response));
return;
}
std::move(callback).Run(mojom::CancelScanResponse::From(response_in.value()));
}
} // namespace
DocumentScanAsh::DocumentScanAsh() = default;
DocumentScanAsh::~DocumentScanAsh() = default;
void DocumentScanAsh::BindReceiver(
mojo::PendingReceiver<mojom::DocumentScan> pending_receiver) {
receivers_.Add(this, std::move(pending_receiver));
}
void DocumentScanAsh::GetScannerNames(GetScannerNamesCallback callback) {
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->GetScannerNames(
base::BindOnce(GetScannerNamesAdapter, std::move(callback)));
}
void DocumentScanAsh::ScanFirstPage(const std::string& scanner_name,
ScanFirstPageCallback callback) {
lorgnette::ScanSettings settings;
settings.set_color_mode(lorgnette::MODE_COLOR); // Hardcoded for now.
auto scan_result = std::make_unique<ScanResult>();
auto scan_result_weak_ptr = scan_result->AsWeakPtr();
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->Scan(scanner_name, settings, base::NullCallback(),
base::BindRepeating(&OnPageReceived, scan_result_weak_ptr),
base::BindOnce(&OnScanCompleted, std::move(callback),
std::move(scan_result)));
}
void DocumentScanAsh::GetScannerList(const std::string& client_id,
mojom::ScannerEnumFilterPtr filter,
GetScannerListCallback callback) {
using LocalScannerFilter = ash::LorgnetteScannerManager::LocalScannerFilter;
using SecureScannerFilter = ash::LorgnetteScannerManager::SecureScannerFilter;
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->GetScannerInfoList(
client_id,
filter->local ? LocalScannerFilter::kLocalScannersOnly
: LocalScannerFilter::kIncludeNetworkScanners,
filter->secure ? SecureScannerFilter::kSecureScannersOnly
: SecureScannerFilter::kIncludeUnsecureScanners,
base::BindOnce(&GetScannerListAdapter, std::move(callback)));
}
void DocumentScanAsh::OpenScanner(const std::string& client_id,
const std::string& scanner_id,
OpenScannerCallback callback) {
lorgnette::OpenScannerRequest request;
request.mutable_scanner_id()->set_connection_string(scanner_id);
request.set_client_id(client_id);
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->OpenScanner(
std::move(request),
base::BindOnce(&OpenScannerAdapter, scanner_id, std::move(callback)));
}
void DocumentScanAsh::CloseScanner(const std::string& scanner_handle,
CloseScannerCallback callback) {
lorgnette::CloseScannerRequest request;
request.mutable_scanner()->set_token(scanner_handle);
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->CloseScanner(std::move(request),
base::BindOnce(&CloseScannerAdapter, scanner_handle,
std::move(callback)));
}
void DocumentScanAsh::StartPreparedScan(const std::string& scanner_handle,
mojom::StartScanOptionsPtr options,
StartPreparedScanCallback callback) {
lorgnette::StartPreparedScanRequest request;
request.mutable_scanner()->set_token(scanner_handle);
request.set_image_format(options->format);
if (options->max_read_size) {
request.set_max_read_size(*options->max_read_size);
}
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->StartPreparedScan(
request, base::BindOnce(&StartPreparedScanAdapter, scanner_handle,
std::move(callback)));
}
void DocumentScanAsh::ReadScanData(const std::string& job_handle,
ReadScanDataCallback callback) {
lorgnette::ReadScanDataRequest request;
request.mutable_job_handle()->set_token(job_handle);
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->ReadScanData(request, base::BindOnce(&ReadScanDataAdapter, job_handle,
std::move(callback)));
}
void DocumentScanAsh::SetOptions(const std::string& scanner_handle,
std::vector<mojom::OptionSettingPtr> options,
SetOptionsCallback callback) {
lorgnette::SetOptionsRequest request;
request.mutable_scanner()->set_token(scanner_handle);
// Keep track of all of the option names. This is used if we don't get a
// valid response from the backend. All of these options will get sent back
// to the caller with an error result.
std::vector<std::string> option_names;
// Separately, keep track of any invalid options names (where the type
// specified for the value does not equal the type of the option). These
// options will get sent back to the caller with an appropriate error result.
std::vector<std::string> invalid_option_names;
for (const mojom::OptionSettingPtr& option_request : options) {
option_names.emplace_back(option_request->name);
auto maybe_option =
option_request.To<std::optional<lorgnette::ScannerOption>>();
if (maybe_option.has_value()) {
*request.add_options() = maybe_option.value();
} else {
invalid_option_names.emplace_back(option_request->name);
}
}
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->SetOptions(request, base::BindOnce(&SetOptionsAdapter, scanner_handle,
option_names, invalid_option_names,
std::move(callback)));
}
void DocumentScanAsh::GetOptionGroups(const std::string& scanner_handle,
GetOptionGroupsCallback callback) {
lorgnette::GetCurrentConfigRequest request;
request.mutable_scanner()->set_token(scanner_handle);
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->GetCurrentConfig(request,
base::BindOnce(&GetOptionGroupsAdapter, scanner_handle,
std::move(callback)));
}
void DocumentScanAsh::CancelScan(const std::string& job_handle,
CancelScanCallback callback) {
lorgnette::CancelScanRequest request;
request.mutable_job_handle()->set_token(job_handle);
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(GetProfile())
->CancelScan(request, base::BindOnce(&CancelScanAdapter, job_handle,
std::move(callback)));
}
} // namespace crosapi