File: screen_ai_install_state.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (208 lines) | stat: -rw-r--r-- 6,003 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 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 138.0, the library has the new `GetMaxImageDimension` API function.
const char kMinExpectedVersion[] = "138.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