blob: 26aad5fdc6f5464f279c748a2588f8b9a1493dca [file] [log] [blame]
// Copyright 2025 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/screen_ai/screen_ai_service_handler_base.h"
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/process/process.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/screen_ai/screen_ai_install_state.h"
#include "chrome/browser/screen_ai/screen_ai_service_router.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/browser/service_process_host_passkeys.h"
#include "mojo/public/mojom/base/file_path.mojom.h"
#include "services/network/public/mojom/network_change_manager.mojom.h"
#include "services/screen_ai/public/cpp/utilities.h"
#if BUILDFLAG(IS_WIN)
#include "base/strings/utf_string_conversions.h"
#endif
namespace screen_ai {
bool IsModelFileContentReadable(base::File& file) {
if (!file.IsValid()) {
return false;
}
int file_size = file.GetLength();
if (!file_size) {
return false;
}
std::vector<uint8_t> buffer(file_size);
return file.ReadAndCheck(0, base::span(buffer));
}
ComponentFiles::ComponentFiles(
const base::FilePath& library_binary_path,
const base::FilePath::CharType* files_list_file_name)
: library_binary_path_(library_binary_path) {
base::FilePath component_folder = library_binary_path.DirName();
// Get the files list.
std::string file_content;
if (!base::ReadFileToString(component_folder.Append(files_list_file_name),
&file_content)) {
VLOG(0) << "Could not read list of files for " << files_list_file_name;
return;
}
std::vector<std::string> files_list = base::SplitString(
file_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (files_list.empty()) {
VLOG(0) << "Could not parse files list for " << files_list_file_name;
return;
}
for (auto& relative_file_path : files_list) {
// Ignore comment lines.
if (relative_file_path.empty() || relative_file_path[0] == '#') {
continue;
}
#if BUILDFLAG(IS_WIN)
base::FilePath relative_path(base::UTF8ToWide(relative_file_path));
#else
base::FilePath relative_path(relative_file_path);
#endif
const base::FilePath full_path = component_folder.Append(relative_path);
model_files_[relative_path] =
base::File(full_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!IsModelFileContentReadable(model_files_[relative_path])) {
VLOG(0) << "Could not open " << full_path;
model_files_.clear();
return;
}
}
}
ComponentFiles::~ComponentFiles() {
if (model_files_.empty()) {
return;
}
// Transfer ownership of the file handles to a thread that may block, and let
// them get destroyed there.
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(
[](base::flat_map<base::FilePath, base::File> model_files) {},
std::move(model_files_)));
}
std::unique_ptr<ComponentFiles> ComponentFiles::Load(
const base::FilePath::CharType* files_list_file_name) {
return std::make_unique<ComponentFiles>(
screen_ai::ScreenAIInstallState::GetInstance()
->get_component_binary_path(),
files_list_file_name);
}
ScreenAIServiceHandlerBase::ScreenAIServiceHandlerBase()
: screen_ai_service_shutdown_handler_(this) {}
ScreenAIServiceHandlerBase::~ScreenAIServiceHandlerBase() = default;
std::string ScreenAIServiceHandlerBase::GetMetricFullName(
std::string_view metric_name) const {
return base::StringPrintf("Accessibility.%s.Service.%s", GetServiceName(),
metric_name);
}
std::optional<bool> ScreenAIServiceHandlerBase::GetServiceState() {
if (GetAndRecordSuspendedState()) {
return false;
}
if (IsConnectionBound()) {
return true;
}
if (IsServiceEnabled()) {
return std::nullopt;
}
return false;
}
std::optional<bool> ScreenAIServiceHandlerBase::GetServiceStateAsync(
ServiceStateCallback callback) {
auto service_state = GetServiceState();
// If `service_state` has value, the service is already initialized or
// disabled.
if (service_state) {
std::move(callback).Run(*service_state);
} else {
// Put the request in queue and wait for ScreenAIServiceRouter to announce
// that library download state is changed.
pending_state_requests_.emplace_back(std::move(callback));
}
return service_state;
}
void ScreenAIServiceHandlerBase::OnLibraryAvailablityChanged(bool available) {
if (pending_state_requests_.empty()) {
return;
}
if (available) {
InitializeServiceIfNeeded();
} else {
CallPendingStatusRequests(false);
}
}
void ScreenAIServiceHandlerBase::ShuttingDownOnIdle() {
shutdown_handler_data_.shutdown_message_received = true;
}
bool ScreenAIServiceHandlerBase::GetAndRecordSuspendedState() {
base::UmaHistogramBoolean(GetMetricFullName("IsSuspended"),
shutdown_handler_data_.suspended);
return shutdown_handler_data_.suspended;
}
void ScreenAIServiceHandlerBase::OnScreenAIServiceDisconnected() {
screen_ai_service_factory_.reset();
CallPendingStatusRequests(false);
if (resource_monitor_) {
if (resource_monitor_->get_max_resident_memory_kb()) {
base::UmaHistogramMemoryMB(
GetMetricFullName("MaxMemoryLoad"),
resource_monitor_->get_max_resident_memory_kb() / 1000);
}
resource_monitor_.reset();
base::UmaHistogramMediumTimes(GetMetricFullName("LifeTime"),
base::TimeTicks::Now() - service_start_time_);
}
screen_ai_service_shutdown_handler_.reset();
if (shutdown_handler_data_.shutdown_message_received) {
if (shutdown_handler_data_.crash_count) {
base::UmaHistogramCounts100(GetMetricFullName("CrashCountBeforeResume"),
shutdown_handler_data_.crash_count);
}
shutdown_handler_data_.crash_count = 0;
return;
}
// Crashed!
shutdown_handler_data_.crash_count++;
shutdown_handler_data_.suspended = true;
base::TimeDelta suspense_time =
ScreenAIServiceRouter::SuggestedWaitTimeBeforeReAttempt(
shutdown_handler_data_.crash_count);
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ScreenAIServiceHandlerBase::ResetSuspend,
weak_ptr_factory_.GetWeakPtr()),
suspense_time);
VLOG(0) << "Service suspended due to crash for: " << suspense_time;
}
void ScreenAIServiceHandlerBase::CallPendingStatusRequests(bool successful) {
std::vector<ServiceStateCallback> requests;
pending_state_requests_.swap(requests);
for (auto& callback : requests) {
std::move(callback).Run(successful);
}
}
void ScreenAIServiceHandlerBase::LaunchIfNotRunning() {
ScreenAIInstallState::GetInstance()->SetLastUsageTime();
if (screen_ai_service_factory_.is_bound()) {
return;
}
auto* state_instance = ScreenAIInstallState::GetInstance();
// To have a smooth user experience, the callers of the service should ensure
// that the component is downloaded before promising it to the users and
// triggering its launch.
// If it is not done, the calling feature will receive no reply when it tries
// to use this service. However, they can detect it by using an on-disconnect
// handler.
if (!state_instance->IsComponentAvailable()) {
VLOG(0) << "ScreenAI service launch triggered when component is not "
"available.";
state_instance->DownloadComponent();
return;
}
if (GetAndRecordSuspendedState()) {
VLOG(0) << "ScreenAI service triggered while suspended.";
return;
}
base::FilePath binary_path = state_instance->get_component_binary_path();
#if BUILDFLAG(IS_WIN)
std::vector<base::FilePath> preload_libraries = {binary_path};
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
std::vector<std::string> extra_switches = {
base::StringPrintf("--%s=%s", screen_ai::GetBinaryPathSwitch(),
binary_path.MaybeAsASCII().c_str())};
#endif // BUILDFLAG(IS_WIN)
std::string process_name = base::StrCat({GetServiceName(), " Service"});
content::ServiceProcessHost::Launch(
screen_ai_service_factory_.BindNewPipeAndPassReceiver(),
content::ServiceProcessHost::Options()
.WithDisplayName(process_name)
#if BUILDFLAG(IS_WIN)
.WithPreloadedLibraries(
preload_libraries,
content::ServiceProcessHostPreloadLibraries::GetPassKey())
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
.WithExtraCommandLineSwitches(extra_switches)
#endif // BUILDFLAG(IS_WIN)
.WithProcessCallback(
base::BindOnce(&ScreenAIServiceHandlerBase::OnServiceLaunched,
weak_ptr_factory_.GetWeakPtr(), process_name))
.Pass());
shutdown_handler_data_.shutdown_message_received = false;
screen_ai_service_factory_->BindShutdownHandler(
screen_ai_service_shutdown_handler_.BindNewPipeAndPassRemote());
screen_ai_service_factory_.set_disconnect_handler(
base::BindOnce(&ScreenAIServiceHandlerBase::OnScreenAIServiceDisconnected,
weak_ptr_factory_.GetWeakPtr()));
}
void ScreenAIServiceHandlerBase::OnServiceLaunched(
const std::string& process_name,
const base::Process& process) {
// Post task to ensure that `resource_monitor_` is created after the service
// process host is registered.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ScreenAIServiceHandlerBase::CreateResourceMonitor,
weak_ptr_factory_.GetWeakPtr(), process_name));
service_start_time_ = base::TimeTicks::Now();
}
void ScreenAIServiceHandlerBase::CreateResourceMonitor(
const std::string& process_name) {
CHECK(!resource_monitor_);
resource_monitor_ = ResourceMonitor::CreateForProcess(process_name);
// Resource monitor creation may fail if the process name is not found in
// process registry. Since it is only used for metrics and does not affect
// user experience, do not crash if it fails.
// TODO(crbug.com/433201897): Based on how often this happens, consider
// finding a different solution.
base::UmaHistogramBoolean(GetMetricFullName("ResourceMonitorCreated"),
!!resource_monitor_);
}
void ScreenAIServiceHandlerBase::InitializeServiceIfNeeded() {
std::optional<bool> service_state = GetServiceState();
if (service_state) {
// Either service is already initialized or disabled.
CallPendingStatusRequests(*service_state);
return;
}
base::TimeTicks request_start_time = base::TimeTicks::Now();
LaunchIfNotRunning();
if (!screen_ai_service_factory_.is_bound()) {
SetLibraryLoadState(request_start_time, false);
return;
}
LoadModelFilesAndInitialize(request_start_time);
}
void ScreenAIServiceHandlerBase::SetLibraryLoadState(
base::TimeTicks request_start_time,
bool successful) {
base::TimeDelta elapsed_time = base::TimeTicks::Now() - request_start_time;
base::UmaHistogramBoolean(GetMetricFullName("Initialization"), successful);
base::UmaHistogramTimes(successful
? GetMetricFullName("InitializationTime.Success")
: GetMetricFullName("InitializationTime.Failure"),
elapsed_time);
CallPendingStatusRequests(successful);
if (!successful) {
ResetConnection();
}
}
bool ScreenAIServiceHandlerBase::IsConnectionBoundForTesting() {
return IsConnectionBound();
}
bool ScreenAIServiceHandlerBase::IsProcessRunningForTesting() {
return screen_ai_service_factory_.is_bound();
}
} // namespace screen_ai