| // Copyright 2024 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/scanner/scanner_keyed_service.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/generative_ai_country_restrictions.h" |
| #include "base/check_deref.h" |
| #include "base/containers/span.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/string_view_util.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "components/drive/service/drive_api_service.h" |
| #include "components/drive/service/drive_service_interface.h" |
| #include "components/manta/manta_status.h" |
| #include "components/manta/proto/scanner.pb.h" |
| #include "components/manta/scanner_provider.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/account_capabilities.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "google_apis/common/auth_service.h" |
| #include "google_apis/common/request_sender.h" |
| #include "google_apis/drive/drive_api_url_generator.h" |
| #include "google_apis/gaia/core_account_id.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| |
| namespace { |
| |
| constexpr auto kTrafficAnnotation = |
| net::DefineNetworkTrafficAnnotation("chromeos_scanner", R"( |
| semantics { |
| sender: "ChromeOS Scanner" |
| description: |
| "Creates or mutates various Google entities - Docs, Sheets, " |
| "Contacts, etc. - for the user. These operations are specified by " |
| "responses from the Scanner service." |
| trigger: |
| "User selecting to create or mutate entities from the Scanner UI." |
| internal { |
| contacts { |
| email: "e14s-eng@google.com" |
| } |
| } |
| user_data { |
| type: USER_CONTENT |
| } |
| data: |
| "Files created by the Scanner service." |
| destination: GOOGLE_OWNED_SERVICE |
| last_reviewed: "2024-10-21" |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "No setting. Users must take explicit action to trigger the feature." |
| policy_exception_justification: |
| "Not implemented, not considered useful. This request is part of a " |
| "flow which is user-initiated." |
| } |
| )"); |
| |
| specialized_features::FeatureAccessConfig CreateFeatureAccessConfig() { |
| specialized_features::FeatureAccessConfig config; |
| config.settings_toggle_pref = ash::prefs::kScannerEnabled; |
| config.disabled_in_kiosk_mode = true; |
| config.consent_accepted_pref = ash::prefs::kScannerConsentDisclaimerAccepted; |
| |
| // Dogfood devices ignore all other checks. |
| // On actual launch, we will be using the ScannerUpdate flag instead of |
| // Dogfood which includes all extra checks. |
| if (base::FeatureList::IsEnabled(ash::features::kScannerDogfood)) { |
| return config; |
| } |
| |
| config.feature_flag = &ash::features::kScannerUpdate; |
| config.feature_management_flag = &ash::features::kFeatureManagementScanner; |
| config.capability_callback = |
| base::BindRepeating([](AccountCapabilities capabilities) { |
| return capabilities.can_use_manta_service(); |
| }); |
| config.country_codes = ash::GetGenerativeAiCountryAllowlist(); |
| return config; |
| } |
| |
| } // namespace |
| |
| ScannerKeyedService::ScannerKeyedService( |
| PrefService* pref_service, |
| signin::IdentityManager* identity_manager, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| std::unique_ptr<manta::ScannerProvider> scanner_provider, |
| specialized_features::FeatureAccessChecker::VariationsServiceCallback |
| variations_service_callback) |
| : identity_manager_(identity_manager), |
| access_checker_(CreateFeatureAccessConfig(), |
| /*prefs=*/pref_service, |
| /*identity_manager=*/identity_manager_, |
| std::move(variations_service_callback)), |
| scanner_provider_(std::move(scanner_provider)) { |
| if (identity_manager_ != nullptr) { |
| scoped_refptr<base::SequencedTaskRunner> blocking_task_runner = |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); |
| CoreAccountId account_id = |
| identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin); |
| |
| const GURL& base_url = GaiaUrls::GetInstance()->google_apis_origin_url(); |
| GURL base_thumbnail_url( |
| google_apis::DriveApiUrlGenerator::kBaseThumbnailUrlForProduction); |
| |
| drive_service_ = std::make_unique<drive::DriveAPIService>( |
| identity_manager_, url_loader_factory, blocking_task_runner.get(), |
| base_url, base_thumbnail_url, |
| /*custom_user_agent=*/"", kTrafficAnnotation); |
| drive_service_->Initialize(account_id); |
| |
| auto auth_service = std::make_unique<google_apis::AuthService>( |
| identity_manager_, account_id, url_loader_factory, |
| std::vector<std::string>{GaiaConstants::kContactsOAuth2Scope}); |
| request_sender_ = std::make_unique<google_apis::RequestSender>( |
| std::move(auth_service), url_loader_factory, blocking_task_runner, |
| /*custom_user_agent=*/"", kTrafficAnnotation); |
| } |
| } |
| |
| ScannerKeyedService::~ScannerKeyedService() = default; |
| |
| specialized_features::FeatureAccessFailureSet |
| ScannerKeyedService::CheckFeatureAccess() const { |
| return access_checker_.Check(); |
| } |
| |
| void ScannerKeyedService::FetchActionsForImage( |
| scoped_refptr<base::RefCountedMemory> jpeg_bytes, |
| manta::ScannerProvider::ScannerProtoResponseCallback callback) { |
| if (!scanner_provider_) { |
| // `scanner_provider_` can only be nullptr when there is no identity |
| // manager to instantiate the provider. |
| std::move(callback).Run( |
| nullptr, |
| manta::MantaStatus(manta::MantaStatusCode::kNoIdentityManager)); |
| return; |
| } |
| manta::proto::ScannerInput scanner_input; |
| scanner_input.set_image(std::string(base::as_string_view(*jpeg_bytes))); |
| scanner_provider_->Call(scanner_input, std::move(callback)); |
| } |
| |
| void ScannerKeyedService::FetchActionDetailsForImage( |
| scoped_refptr<base::RefCountedMemory> jpeg_bytes, |
| manta::proto::ScannerAction selected_action, |
| manta::ScannerProvider::ScannerProtoResponseCallback callback) { |
| if (!scanner_provider_) { |
| // `scanner_provider_` can only be nullptr when there is no identity |
| // manager to instantiate the provider. |
| std::move(callback).Run( |
| nullptr, |
| manta::MantaStatus(manta::MantaStatusCode::kNoIdentityManager)); |
| return; |
| } |
| manta::proto::ScannerInput scanner_input; |
| scanner_input.set_image(std::string(base::as_string_view(*jpeg_bytes))); |
| scanner_input.mutable_current_timestamp()->set_seconds( |
| base::Time::Now().InSecondsFSinceUnixEpoch()); |
| *scanner_input.mutable_selected_action() = std::move(selected_action); |
| scanner_provider_->Call(scanner_input, std::move(callback)); |
| } |
| |
| drive::DriveServiceInterface* ScannerKeyedService::GetDriveService() { |
| return drive_service_.get(); |
| } |
| |
| google_apis::RequestSender* ScannerKeyedService::GetGoogleApisRequestSender() { |
| return request_sender_.get(); |
| } |
| |
| void ScannerKeyedService::Shutdown() {} |