blob: 3ed27929c87a2be73fdea1db3787f409889a2227 [file] [log] [blame]
// 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/screen_ai/screen_ai_install_state.h"
#include <memory>
#include "base/check_is_test.h"
#include "base/cpu.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/screen_ai/pref_names.h"
#include "components/prefs/pref_service.h"
#include "services/screen_ai/public/cpp/utilities.h"
#include "ui/accessibility/accessibility_features.h"
namespace {
// From 140.0, the library has the new `SetOCRLightMode` API function.
const char kMinExpectedVersion[] = "140.0";
const int kScreenAICleanUpDelayInDays = 30;
bool IsDeviceCompatible() {
#if defined(ARCH_CPU_X86_FAMILY)
// Check if the CPU has the required instruction set to run the Screen AI
// library.
// TODO(crbug.com/381256355): Update when ScreenAI library is compatible with
// older CPUs.
static const bool device_compatible = base::CPU().has_sse42();
#elif BUILDFLAG(IS_LINUX)
// On Linux, the library is only built for X86 CPUs.
static constexpr bool device_compatible = false;
#else
static constexpr bool device_compatible = true;
#endif
return device_compatible;
}
} // namespace
namespace screen_ai {
// ScreenAIInstallState is created through ScreenAIDownloader and we expect on
// and only one of it exists during browser's life time.
ScreenAIInstallState* g_instance = nullptr;
// static
ScreenAIInstallState* ScreenAIInstallState::GetInstance() {
if (g_instance) {
return g_instance;
}
// `!g_instance` only happens in unit tests in which a browser instance is
// not created. Assert that this code path is only taken in tests.
CHECK_IS_TEST();
return ScreenAIInstallState::CreateForTesting();
}
// static
bool ScreenAIInstallState::VerifyLibraryVersion(const base::Version& version) {
base::Version min_version(kMinExpectedVersion);
CHECK(min_version.IsValid());
if (!version.IsValid()) {
VLOG(0) << "Cannot verify library version.";
return false;
}
if (version < min_version) {
VLOG(0) << "Version is expected to be at least " << kMinExpectedVersion
<< ", but it is: " << version;
return false;
}
return true;
}
ScreenAIInstallState::ScreenAIInstallState() {
CHECK_EQ(g_instance, nullptr);
g_instance = this;
}
ScreenAIInstallState::~ScreenAIInstallState() {
CHECK_NE(g_instance, nullptr);
g_instance = nullptr;
}
// static
bool ScreenAIInstallState::ShouldInstall(PrefService* local_state) {
bool device_compatible = IsDeviceCompatible();
base::UmaHistogramBoolean("Accessibility.ScreenAI.DeviceCompatible",
device_compatible);
if (!device_compatible) {
return false;
}
base::Time last_used_time =
local_state->GetTime(prefs::kScreenAILastUsedTimePrefName);
if (last_used_time.is_null()) {
return false;
}
if (base::Time::Now() >=
last_used_time + base::Days(kScreenAICleanUpDelayInDays)) {
local_state->ClearPref(prefs::kScreenAILastUsedTimePrefName);
return false;
}
return true;
}
void ScreenAIInstallState::AddObserver(
ScreenAIInstallState::Observer* observer) {
observers_.AddObserver(observer);
observer->StateChanged(state_);
// Adding an observer indicates that we need the component.
SetLastUsageTime();
DownloadComponent();
}
void ScreenAIInstallState::DownloadComponent() {
if (!features::IsScreenAIOCREnabled() &&
!features::IsScreenAIMainContentExtractionEnabled()) {
SetState(State::kDownloadFailed);
return;
}
if (MayTryDownload()) {
DownloadComponentInternal();
}
}
void ScreenAIInstallState::RemoveObserver(
ScreenAIInstallState::Observer* observer) {
observers_.RemoveObserver(observer);
}
void ScreenAIInstallState::SetComponentFolder(
const base::FilePath& component_folder) {
component_binary_path_ =
component_folder.Append(GetComponentBinaryFileName());
// A new component may be downloaded when an older version already exists and
// is ready to use. We don't need to set the state again and call the
// observers to tell this. If the older component is already in use, current
// session will continue using that and the new one will be used after next
// Chrome restart. Otherwise the new component will be used when a service
// request arrives as its path is stored in |component_binary_path_|.
if (state_ != State::kDownloaded) {
SetState(State::kDownloaded);
}
}
void ScreenAIInstallState::SetState(State state) {
if (state == state_) {
// `kDownloadFailed` state can be repeated as download can be retriggered.
// `kDownloading` can be repeated in ChromeOS tests that call
// LoginManagerTest::AddUser() and reset UserSessionInitializer.
DCHECK(state == State::kDownloadFailed || state == State::kDownloading);
return;
}
state_ = state;
for (ScreenAIInstallState::Observer& observer : observers_) {
observer.StateChanged(state_);
}
}
void ScreenAIInstallState::SetDownloadProgress(double progress) {
for (ScreenAIInstallState::Observer& observer : observers_) {
observer.DownloadProgressChanged(progress);
}
}
bool ScreenAIInstallState::IsComponentAvailable() {
return !get_component_binary_path().empty();
}
bool ScreenAIInstallState::MayTryDownload() {
switch (state_) {
case State::kNotDownloaded:
case State::kDownloadFailed:
return true;
case State::kDownloading:
case State::kDownloaded:
return false;
}
}
void ScreenAIInstallState::ResetForTesting() {
state_ = State::kNotDownloaded;
component_binary_path_.clear();
}
void ScreenAIInstallState::SetStateForTesting(State state) {
state_ = state;
for (ScreenAIInstallState::Observer& observer : observers_) {
observer.StateChanged(state_);
}
}
} // namespace screen_ai