| // 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 |