blob: d437c576d98922fc188a0bdd77d1dbcf77c0369d [file] [log] [blame]
Avi Drissman8ba1bad2022-09-13 19:22:361// Copyright 2022 The Chromium Authors
Ramin Halavati26dcce22022-02-23 13:11:142// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Abigail Klein7a63c572024-02-28 20:45:095#include "services/screen_ai/screen_ai_service_impl.h"
Ramin Halavati26dcce22022-02-23 13:11:146
Ramin Halavati26679592025-03-13 07:24:577#include <algorithm>
Ramin Halavati55fdf4d2022-11-07 06:35:188#include <memory>
Ramin Halavati63d9c252024-07-29 06:59:179#include <optional>
Ramin Halavatifd712d8e2022-10-19 05:11:2710#include <utility>
Ramin Halavatib044504d2022-10-24 06:36:3411#include <vector>
Ramin Halavatifd712d8e2022-10-19 05:11:2712
13#include "base/check.h"
Ramin Halavatiad969ef2024-05-21 06:59:4714#include "base/check_is_test.h"
Tom Sepeze9b4dd552024-08-14 22:44:2915#include "base/compiler_specific.h"
Ramin Halavati65f96fb2025-02-24 16:45:1816#include "base/cpu.h"
Ramin Halavati55fdf4d2022-11-07 06:35:1817#include "base/functional/bind.h"
18#include "base/location.h"
Ramin Halavatifafff152022-12-05 09:39:3819#include "base/logging.h"
Kyungjun Lee9d755a02022-11-08 17:57:1120#include "base/metrics/histogram_functions.h"
Ramin Halavatida3ec442025-05-06 04:14:0321#include "base/numerics/safe_conversions.h"
Ramin Halavati5ff855c2022-04-27 16:30:4822#include "base/process/process.h"
Ramin Halavati65f96fb2025-02-24 16:45:1823#include "base/strings/stringprintf.h"
Ramin Halavati26679592025-03-13 07:24:5724#include "base/system/sys_info.h"
Sean Maher5b9af51f2022-11-21 15:32:4725#include "base/task/single_thread_task_runner.h"
Ramin Halavati55fdf4d2022-11-07 06:35:1826#include "base/task/thread_pool.h"
Ramin Halavatiaa67ee52025-04-25 05:15:5327#include "base/threading/sequence_bound.h"
Ramin Halavati65f96fb2025-02-24 16:45:1828#include "components/crash/core/common/crash_key.h"
Abigail Klein7a63c572024-02-28 20:45:0929#include "services/screen_ai/buildflags/buildflags.h"
Ramin Halavati4c47eb82024-12-03 19:06:4230#include "services/screen_ai/proto/chrome_screen_ai.pb.h"
Abigail Klein7a63c572024-02-28 20:45:0931#include "services/screen_ai/proto/main_content_extractor_proto_convertor.h"
32#include "services/screen_ai/proto/visual_annotator_proto_convertor.h"
Ramin Halavati4c47eb82024-12-03 19:06:4233#include "services/screen_ai/public/cpp/metrics.h"
Abigail Klein7a63c572024-02-28 20:45:0934#include "services/screen_ai/public/cpp/utilities.h"
Ramin Halavatieddadb62022-05-04 17:29:4935#include "ui/accessibility/accessibility_features.h"
Ramin Halavati4f904d0a2024-06-25 15:32:3236#include "ui/accessibility/ax_node.h"
37#include "ui/accessibility/ax_tree.h"
Ramin Halavati84231072022-08-17 08:02:2038#include "ui/accessibility/ax_tree_id.h"
Nektarios Paisiosf73d6972022-06-04 11:32:2539#include "ui/gfx/geometry/rect_f.h"
Ramin Halavati367352a2022-04-14 06:00:2340
Ramin Halavati361d0e42025-06-03 05:58:1241#if BUILDFLAG(IS_LINUX)
42#include "partition_alloc/buildflags.h"
43
44#if PA_BUILDFLAG( \
45 ENABLE_ALLOCATOR_SHIM_PARTITION_ALLOC_DISPATCH_WITH_ADVANCED_CHECKS_SUPPORT)
46#include "base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim_default_dispatch_to_partition_alloc_with_advanced_checks.h"
47#endif
48#endif
49
Ramin Halavatia9b50a102024-02-07 18:37:2350#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Abigail Klein7a63c572024-02-28 20:45:0951#include "services/screen_ai/screen_ai_library_wrapper_fake.h"
Ramin Halavati767b8fc2024-02-02 06:23:0852#else
Abigail Klein7a63c572024-02-28 20:45:0953#include "services/screen_ai/screen_ai_library_wrapper_impl.h"
Ramin Halavati767b8fc2024-02-02 06:23:0854#endif
55
Ramin Halavati69bf7752022-04-04 09:58:1556namespace screen_ai {
Ramin Halavati26dcce22022-02-23 13:11:1457
Ramin Halavati6d4fc2b2022-06-16 15:33:3558namespace {
59
Ramin Halavati2acf2362024-08-02 14:22:5360// How often it would be checked that the service is idle and can be shutdown.
Ramin Halavatibe45dde2025-05-01 04:19:1061// LINT.IfChange(kIdleCheckingDelay)
Ramin Halavati1520b702025-04-23 15:24:1862constexpr base::TimeDelta kIdleCheckingDelay = base::Seconds(3);
Ramin Halavatibe45dde2025-05-01 04:19:1063// LINT.ThenChange(//chrome/browser/screen_ai/optical_character_recognizer_browsertest.cc:kServiceIdleCheckingDelay)
Ramin Halavati6ff56412024-08-13 07:15:5464
Ramin Halavati4825419e2025-04-02 05:49:4665// How long to wait for a request to the library be responded, before assuming
66// that the library is not responsive.
Ramin Halavatiaa67ee52025-04-25 05:15:5367constexpr base::TimeDelta kMaxWaitForResponseTime = base::Seconds(10);
Ramin Halavati4825419e2025-04-02 05:49:4668
Ramin Halavatiad969ef2024-05-21 06:59:4769// These values are persisted to logs. Entries should not be renumbered and
70// numeric values should never be reused.
Ramin Halavati29cc1152024-10-27 17:30:2871// See `screen_ai_service.mojom` for more info.
Ramin Halavatib3fe2fe2025-03-20 16:53:0372// LINT.IfChange(OcrClientType)
Ramin Halavatiad969ef2024-05-21 06:59:4773enum class OcrClientTypeForMetrics {
74 kTest = 0,
75 kPdfViewer = 1,
76 kLocalSearch = 2,
77 kCameraApp = 3,
Ramin Halavati29cc1152024-10-27 17:30:2878 kNotUsed = 4, // Can be used for a new client.
Ramin Halavatiad969ef2024-05-21 06:59:4779 kMediaApp = 5,
Michelle Chen5192d522024-10-22 23:38:2280 kScreenshotTextDetection,
81 kMaxValue = kScreenshotTextDetection
Ramin Halavatiad969ef2024-05-21 06:59:4782};
Ramin Halavatib3fe2fe2025-03-20 16:53:0383// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:OcrClientType)
84
85// These values are persisted to logs. Entries should not be renumbered and
86// numeric values should never be reused.
87// See `screen_ai_service.mojom` for more info.
88// LINT.IfChange(MainContentExtractionClientType)
89enum class MainContentExtractionClientTypeForMetrics {
90 kTest = 0,
91 kReadingMode = 1,
92 kMainNode = 2,
93 kMahi = 3,
94 kMaxValue = kMahi
95};
96// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:MainContentExtractionClientType)
Ramin Halavatiad969ef2024-05-21 06:59:4797
98OcrClientTypeForMetrics GetClientType(mojom::OcrClientType client_type) {
99 switch (client_type) {
100 case mojom::OcrClientType::kTest:
101 CHECK_IS_TEST();
102 return OcrClientTypeForMetrics::kTest;
103 case mojom::OcrClientType::kPdfViewer:
104 return OcrClientTypeForMetrics::kPdfViewer;
105 case mojom::OcrClientType::kLocalSearch:
106 return OcrClientTypeForMetrics::kLocalSearch;
107 case mojom::OcrClientType::kCameraApp:
108 return OcrClientTypeForMetrics::kCameraApp;
Ramin Halavatiad969ef2024-05-21 06:59:47109 case mojom::OcrClientType::kMediaApp:
110 return OcrClientTypeForMetrics::kMediaApp;
Michelle Chen5192d522024-10-22 23:38:22111 case mojom::OcrClientType::kScreenshotTextDetection:
112 return OcrClientTypeForMetrics::kScreenshotTextDetection;
Ramin Halavatiad969ef2024-05-21 06:59:47113 }
114}
115
Ramin Halavatib3fe2fe2025-03-20 16:53:03116MainContentExtractionClientTypeForMetrics GetClientType(
117 mojom::MceClientType client_type) {
118 switch (client_type) {
119 case mojom::MceClientType::kTest:
120 CHECK_IS_TEST();
121 return MainContentExtractionClientTypeForMetrics::kTest;
122 case mojom::MceClientType::kReadingMode:
123 return MainContentExtractionClientTypeForMetrics::kReadingMode;
124 case mojom::MceClientType::kMainNode:
125 return MainContentExtractionClientTypeForMetrics::kMainNode;
126 case mojom::MceClientType::kMahi:
127 return MainContentExtractionClientTypeForMetrics::kMahi;
128 }
129}
130
Ramin Halavati87cfa8e62025-06-02 06:48:10131#if BUILDFLAG(IS_CHROMEOS)
Ramin Halavati89a36c92023-05-05 15:01:03132ui::AXTreeUpdate ConvertVisualAnnotationToTreeUpdate(
Ramin Halavatidcb09a172024-07-15 08:28:55133 std::optional<chrome_screen_ai::VisualAnnotation>& annotation_proto,
Ramin Halavati89a36c92023-05-05 15:01:03134 const gfx::Rect& image_rect) {
135 if (!annotation_proto) {
136 VLOG(0) << "Screen AI library could not process snapshot or no OCR data.";
137 return ui::AXTreeUpdate();
138 }
139
140 return VisualAnnotationToAXTreeUpdate(*annotation_proto, image_rect);
141}
Ramin Halavati87cfa8e62025-06-02 06:48:10142#endif // BUILDFLAG(IS_CHROMEOS)
Ramin Halavati89a36c92023-05-05 15:01:03143
Abigail Klein18673702024-03-05 20:59:06144ui::AXNodeID ComputeMainNode(
145 const ui::AXTree* tree,
146 const std::vector<ui::AXNodeID>& content_node_ids) {
147 ui::AXNode* front = tree->GetFromId(content_node_ids.front());
148 ui::AXNode* back = tree->GetFromId(content_node_ids.back());
149 ui::AXNode* main = front->GetLowestCommonAncestor(*back);
150 return main->id();
151}
152
Ramin Halavati65f96fb2025-02-24 16:45:18153#if !BUILDFLAG(USE_FAKE_SCREEN_AI)
154void SetCPUInstructionSetCrashKey() {
155#if defined(ARCH_CPU_X86_FAMILY)
156 base::CPU();
157 // Report cpu micro architecture in case of crash.
158 static crash_reporter::CrashKeyString<3> cpu_info("intel_micro_architecture");
159 cpu_info.Set(
160 base::StringPrintf("%i", base::CPU().GetIntelMicroArchitecture()));
161#endif
162}
163#endif
164
Ramin Halavati26679592025-03-13 07:24:57165// Return a maximum 11 character string with the signature of available and
166// total memory, both in MB and capped to 99999.
167std::string GetMemoryStatusForCrashKey() {
168 int total_memory = base::SysInfo::AmountOfPhysicalMemoryMB();
169 int available_memory = static_cast<int>(
170 base::SysInfo::AmountOfAvailablePhysicalMemory() / (1024 * 1024));
171
172 // Cap the number of digits for crash report.
173 total_memory = std::min(total_memory, 99999);
174 available_memory = std::min(available_memory, 99999);
175 return base::StringPrintf("%i,%i", available_memory, total_memory);
176}
177
Ramin Halavatiaa67ee52025-04-25 05:15:53178class HangTimer : public base::OneShotTimer {
179 public:
180 explicit HangTimer(bool is_ocr) : is_ocr_(is_ocr) {}
181
182 void StartTimer() {
183 Start(FROM_HERE, kMaxWaitForResponseTime,
184 base::BindOnce(
185 [](bool request_is_ocr) {
186 base::UmaHistogramBoolean(
Ramin Halavatib5db88472025-05-23 14:34:07187 "Accessibility.ScreenAI.Service.NotResponsive.IsOCR",
Ramin Halavatiaa67ee52025-04-25 05:15:53188 request_is_ocr);
189 base::Process::TerminateCurrentProcessImmediately(0);
190 },
191 is_ocr_));
192 }
193
194 private:
195 bool is_ocr_;
196};
197
Ramin Halavati26ae6b72022-11-10 06:45:05198} // namespace
199
Ramin Halavati39bab0d2024-01-30 06:05:01200// The library accepts simple pointers to model data retrieval functions, hence
201// callback functions with linked object are not safe to pass.
Ramin Halavati63d9c252024-07-29 06:59:17202// This global variable keeps the pointer the only instance of this class.
203ModelDataHolder* g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50204
Ramin Halavati63d9c252024-07-29 06:59:17205// Keeps the handles of model files, and replies to calls for copying their
206// content.
207class ModelDataHolder {
Ramin Halavati8ced6392023-09-25 07:27:50208 public:
Ramin Halavati63d9c252024-07-29 06:59:17209 ModelDataHolder() {
210 CHECK(!g_model_data_holder_instance);
211 g_model_data_holder_instance = this;
212 }
Ramin Halavati8ced6392023-09-25 07:27:50213
Ramin Halavati63d9c252024-07-29 06:59:17214 ModelDataHolder(const ModelDataHolder&) = delete;
215 ModelDataHolder& operator=(const ModelDataHolder&) = delete;
216
217 ~ModelDataHolder() {
218 CHECK_EQ(g_model_data_holder_instance, this);
219 g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50220 }
221
222 // Returns 0 if file is not found.
223 static uint32_t GetDataSize(const char* relative_file_path) {
Ramin Halavati63d9c252024-07-29 06:59:17224 CHECK(g_model_data_holder_instance);
225 base::File* model_file =
226 g_model_data_holder_instance->GetModelFile(relative_file_path);
227 return model_file ? model_file->GetLength() : 0;
Ramin Halavati8ced6392023-09-25 07:27:50228 }
229
Ramin Halavati63d9c252024-07-29 06:59:17230 // Copies content of the file in `relative_file_path` to `buffer`. Expects
231 // that `buffer_size` would be enough for the entire file content.
Ramin Halavati8ced6392023-09-25 07:27:50232 static void CopyData(const char* relative_file_path,
233 uint32_t buffer_size,
234 char* buffer) {
Ramin Halavati63d9c252024-07-29 06:59:17235 CHECK(g_model_data_holder_instance);
236 base::File* model_file =
237 g_model_data_holder_instance->GetModelFile(relative_file_path);
238 CHECK(model_file);
239
240 int64_t length = model_file->GetLength();
241 CHECK_GE(buffer_size, length);
Tom Sepeze9b4dd552024-08-14 22:44:29242 CHECK_EQ(UNSAFE_TODO(model_file->Read(0, buffer, length)), length);
Ramin Halavati8ced6392023-09-25 07:27:50243 }
244
Ramin Halavati63d9c252024-07-29 06:59:17245 void AddModelFiles(base::flat_map<base::FilePath, base::File> model_files) {
246 for (auto& model_file : model_files) {
247 model_files_[model_file.first.MaybeAsASCII()] =
248 std::move(model_file.second);
Ramin Halavati39bab0d2024-01-30 06:05:01249 }
250 }
Ramin Halavati8ced6392023-09-25 07:27:50251
Ramin Halavati63d9c252024-07-29 06:59:17252 // Returns the file handle for `relative_file_path` if it exists.
253 base::File* GetModelFile(const char* relative_file_path) {
254 if (!base::Contains(model_files_, relative_file_path)) {
255 return nullptr;
256 }
257 return &model_files_[relative_file_path];
258 }
259
Ramin Halavati8ced6392023-09-25 07:27:50260 private:
Ramin Halavati63d9c252024-07-29 06:59:17261 std::map<std::string, base::File> model_files_;
Ramin Halavati8ced6392023-09-25 07:27:50262};
263
Ramin Halavati26ae6b72022-11-10 06:45:05264ScreenAIService::ScreenAIService(
Ramin Halavatic1e4fa92023-05-17 17:22:06265 mojo::PendingReceiver<mojom::ScreenAIServiceFactory> receiver)
266 : factory_receiver_(this, std::move(receiver)),
267 ocr_receiver_(this),
Ramin Halavatiad969ef2024-05-21 06:59:47268 main_content_extraction_receiver_(this) {
Ramin Halavati361d0e42025-06-03 05:58:12269#if BUILDFLAG(IS_LINUX) && \
270 PA_BUILDFLAG( \
271 ENABLE_ALLOCATOR_SHIM_PARTITION_ALLOC_DISPATCH_WITH_ADVANCED_CHECKS_SUPPORT)
272 // TODO(crbug.com/418199684): Remove when the bug is fixed.
273 if (base::FeatureList::IsEnabled(
274 ::features::kScreenAIPartitionAllocAdvancedChecksEnabled)) {
275 allocator_shim::InstallCustomDispatchForPartitionAllocWithAdvancedChecks();
276 }
277#endif
278
Ramin Halavati6ff56412024-08-13 07:15:54279 screen2x_main_content_extractors_.set_disconnect_handler(
Ramin Halavati1520b702025-04-23 15:24:18280 base::BindRepeating(&ScreenAIService::MceReceiverDisconnected,
Ramin Halavati6ff56412024-08-13 07:15:54281 weak_ptr_factory_.GetWeakPtr()));
282 screen_ai_annotators_.set_disconnect_handler(
283 base::BindRepeating(&ScreenAIService::OcrReceiverDisconnected,
284 weak_ptr_factory_.GetWeakPtr()));
Ramin Halavati63d9c252024-07-29 06:59:17285 model_data_holder_ = std::make_unique<ModelDataHolder>();
Ramin Halavatiaa67ee52025-04-25 05:15:53286
287 background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
288 {base::TaskPriority::BEST_EFFORT,
289 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
Ramin Halavatiad969ef2024-05-21 06:59:47290}
Ramin Halavati26ae6b72022-11-10 06:45:05291
292ScreenAIService::~ScreenAIService() = default;
293
Ramin Halavati8ced6392023-09-25 07:27:50294void ScreenAIService::LoadLibrary(const base::FilePath& library_path) {
Abigail Klein7a63c572024-02-28 20:45:09295 // The ScopedBlockingCall in LoadLibrary guarantees that this is not run on
296 // the UI thread.
Ramin Halavatia9b50a102024-02-07 18:37:23297#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Ramin Halavati767b8fc2024-02-02 06:23:08298 library_ = std::make_unique<ScreenAILibraryWrapperFake>();
299#else
300 library_ = std::make_unique<ScreenAILibraryWrapperImpl>();
Ramin Halavati65f96fb2025-02-24 16:45:18301
302 // TODO(crbug.com/381256355): Remove when the library is SSE3 compatible.
303 SetCPUInstructionSetCrashKey();
Ramin Halavati767b8fc2024-02-02 06:23:08304#endif
Ramin Halavati8ced6392023-09-25 07:27:50305
306 bool load_sucessful = library_->Load(library_path);
307 base::UmaHistogramBoolean("Accessibility.ScreenAI.Library.Initialized",
308 load_sucessful);
309
310 if (!load_sucessful) {
311 library_.reset();
312 return;
313 }
314
315 uint32_t version_major;
316 uint32_t version_minor;
317 library_->GetLibraryVersion(version_major, version_minor);
318 VLOG(2) << "Screen AI library version: " << version_major << "."
319 << version_minor;
320
Georg Neis06e387772024-12-25 07:32:22321#if BUILDFLAG(IS_CHROMEOS)
Ramin Halavati8ced6392023-09-25 07:27:50322 library_->SetLogger();
323#endif
324
325 if (features::IsScreenAIDebugModeEnabled()) {
326 library_->EnableDebugMode();
327 }
328
Ramin Halavati63d9c252024-07-29 06:59:17329 library_->SetFileContentFunctions(&ModelDataHolder::GetDataSize,
330 &ModelDataHolder::CopyData);
Ramin Halavati8ced6392023-09-25 07:27:50331}
332
Ramin Halavatic1e4fa92023-05-17 17:22:06333void ScreenAIService::InitializeMainContentExtraction(
Ramin Halavatid139a0f2023-02-28 13:25:28334 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07335 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06336 mojo::PendingReceiver<mojom::MainContentExtractionService>
337 main_content_extractor_service_receiver,
338 InitializeMainContentExtractionCallback callback) {
339 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50340 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06341 }
342
343 if (!library_) {
344 std::move(callback).Run(false);
345 base::Process::TerminateCurrentProcessImmediately(-1);
346 }
347
Ramin Halavati63d9c252024-07-29 06:59:17348 model_data_holder_->AddModelFiles(std::move(model_files));
Ramin Halavatib044504d2022-10-24 06:36:34349
Ramin Halavati8ced6392023-09-25 07:27:50350 bool init_successful = library_->InitMainContentExtraction();
Ramin Halavati1d57c2d2023-05-24 05:18:24351 base::UmaHistogramBoolean(
352 "Accessibility.ScreenAI.MainContentExtraction.Initialized",
353 init_successful);
354 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06355 std::move(callback).Run(false);
Ramin Halavati89a36c92023-05-05 15:01:03356 return;
Ramin Halavatid139a0f2023-02-28 13:25:28357 }
Ramin Halavati89a36c92023-05-05 15:01:03358
Ramin Halavatic1e4fa92023-05-17 17:22:06359 // This interface should be created only once.
360 CHECK(!main_content_extraction_receiver_.is_bound());
361
362 main_content_extraction_receiver_.Bind(
363 std::move(main_content_extractor_service_receiver));
364
365 std::move(callback).Run(true);
Ramin Halavati1520b702025-04-23 15:24:18366 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavaticf155012025-05-05 15:50:01367 StartShutDownOnIdleTimer();
Ramin Halavatic1e4fa92023-05-17 17:22:06368}
369
370void ScreenAIService::InitializeOCR(
371 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07372 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06373 mojo::PendingReceiver<mojom::OCRService> ocr_service_receiver,
374 InitializeOCRCallback callback) {
Ramin Halavati26679592025-03-13 07:24:57375 static crash_reporter::CrashKeyString<12> memory_ocr_init(
376 "screen_ai_mem_ocr_init");
377 memory_ocr_init.Set(GetMemoryStatusForCrashKey());
Ramin Halavatic1e4fa92023-05-17 17:22:06378 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50379 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06380 }
381
382 if (!library_) {
383 std::move(callback).Run(false);
384 base::Process::TerminateCurrentProcessImmediately(-1);
385 }
386
Ramin Halavati63d9c252024-07-29 06:59:17387 model_data_holder_->AddModelFiles(std::move(model_files));
388
Ramin Halavati0e2ae5742023-11-10 05:57:54389 bool init_successful = library_->InitOCR();
Ramin Halavati35d129f2023-06-22 16:37:09390 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Initialized",
Ramin Halavati1d57c2d2023-05-24 05:18:24391 init_successful);
Ramin Halavati480584332023-08-02 06:38:40392
Ramin Halavati1d57c2d2023-05-24 05:18:24393 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06394 std::move(callback).Run(false);
395 return;
396 }
397
Ramin Halavatida3ec442025-05-06 04:14:03398 max_ocr_dimension_ = library_->GetMaxImageDimension();
399 CHECK(max_ocr_dimension_);
400
Ramin Halavatic1e4fa92023-05-17 17:22:06401 // This interface should be created only once.
402 CHECK(!ocr_receiver_.is_bound());
403
404 ocr_receiver_.Bind(std::move(ocr_service_receiver));
405
406 std::move(callback).Run(true);
Ramin Halavati2acf2362024-08-02 14:22:53407 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavaticf155012025-05-05 15:50:01408 StartShutDownOnIdleTimer();
Ramin Halavatib044504d2022-10-24 06:36:34409}
Ramin Halavati26dcce22022-02-23 13:11:14410
Ramin Halavatif26d7d42025-03-12 16:04:46411void ScreenAIService::BindShutdownHandler(
412 mojo::PendingRemote<mojom::ScreenAIServiceShutdownHandler>
413 shutdown_handler) {
414 DCHECK(!screen_ai_shutdown_handler_.is_bound());
415 screen_ai_shutdown_handler_.Bind(std::move(shutdown_handler));
416}
417
Ramin Halavati26dcce22022-02-23 13:11:14418void ScreenAIService::BindAnnotator(
419 mojo::PendingReceiver<mojom::ScreenAIAnnotator> annotator) {
420 screen_ai_annotators_.Add(this, std::move(annotator));
421}
422
Ramin Halavatieddadb62022-05-04 17:29:49423void ScreenAIService::BindMainContentExtractor(
424 mojo::PendingReceiver<mojom::Screen2xMainContentExtractor>
425 main_content_extractor) {
Ramin Halavati6ff56412024-08-13 07:15:54426 screen2x_main_content_extractors_.Add(this,
427 std::move(main_content_extractor));
Ramin Halavatieddadb62022-05-04 17:29:49428}
429
Arthur Sonzognic571efb2024-01-26 20:26:18430std::optional<chrome_screen_ai::VisualAnnotation>
Ramin Halavati7716b032024-08-19 14:56:20431ScreenAIService::PerformOcrAndRecordMetrics(const SkBitmap& image) {
Ramin Halavati26679592025-03-13 07:24:57432 static crash_reporter::CrashKeyString<12> memory_perform_ocr(
433 "screen_ai_mem_ocr_perform");
434 memory_perform_ocr.Set(GetMemoryStatusForCrashKey());
435
Ramin Halavati7716b032024-08-19 14:56:20436 CHECK(base::Contains(ocr_client_types_,
437 screen_ai_annotators_.current_receiver()));
Ramin Halavati4805c882025-03-03 18:28:48438 OcrClientTypeForMetrics client_type = GetClientType(
439 ocr_client_types_.find(screen_ai_annotators_.current_receiver())->second);
Ramin Halavatiad969ef2024-05-21 06:59:47440 base::UmaHistogramEnumeration("Accessibility.ScreenAI.OCR.ClientType",
Ramin Halavati4805c882025-03-03 18:28:48441 client_type);
Ramin Halavatiad969ef2024-05-21 06:59:47442
Ramin Halavatif4b48e02025-07-02 12:49:48443 bool light_client = base::Contains(light_ocr_clients_,
444 screen_ai_annotators_.current_receiver());
445 if (light_client != last_ocr_light_) {
446 library_->SetOCRLightMode(light_client);
447 last_ocr_light_ = light_client;
448 ocr_mode_switch_count_++;
449 }
450
Ramin Halavatiac1ee2922025-05-21 17:17:17451 base::TimeTicks start_time = base::TimeTicks::Now();
Ramin Halavatiaa67ee52025-04-25 05:15:53452 base::SequenceBound<HangTimer> hang_timer(background_task_runner_,
453 /*is_ocr=*/true);
454 hang_timer.AsyncCall(&HangTimer::StartTimer);
Ramin Halavati7252cd02023-05-10 07:21:33455 auto result = library_->PerformOcr(image);
Ramin Halavatiaa67ee52025-04-25 05:15:53456 hang_timer.AsyncCall(&base::OneShotTimer::Stop);
Ramin Halavatiac1ee2922025-05-21 17:17:17457 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
Ramin Halavati4825419e2025-04-02 05:49:46458
Ramin Halavati2650ebe2023-11-27 19:56:35459 int lines_count = result ? result->lines_size() : 0;
460 VLOG(1) << "OCR returned " << lines_count << " lines in " << elapsed_time;
Ramin Halavati7252cd02023-05-10 07:21:33461
Ramin Halavati18aba872024-08-23 14:33:05462 if (!result) {
463 base::UmaHistogramEnumeration(
Ramin Halavati4805c882025-03-03 18:28:48464 "Accessibility.ScreenAI.OCR.Failed.ClientType", client_type);
Ramin Halavati18aba872024-08-23 14:33:05465 }
Ramin Halavati49c2b5c2025-04-25 05:43:01466
Ramin Halavatida3ec442025-05-06 04:14:03467 int max_dimension = base::checked_cast<int>(max_ocr_dimension_);
468 if (image.width() > max_dimension || image.height() > max_dimension) {
Ramin Halavati4805c882025-03-03 18:28:48469 base::UmaHistogramEnumeration(
Ramin Halavati49c2b5c2025-04-25 05:43:01470 "Accessibility.ScreenAI.OCR.Downsampled.ClientType", client_type);
Ramin Halavati4805c882025-03-03 18:28:48471 }
472
Ramin Halavati81b6d922024-06-07 14:20:34473 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Successful",
474 result.has_value());
Ramin Halavati2650ebe2023-11-27 19:56:35475 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount",
476 lines_count);
Ramin Halavatida3ec442025-05-06 04:14:03477 if (image.width() < max_dimension && image.height() < max_dimension) {
Ramin Halavati49c2b5c2025-04-25 05:43:01478 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.NotDownsampled",
Ramin Halavati3ae33ca2024-01-01 12:58:39479 elapsed_time);
480 } else {
Ramin Halavati49c2b5c2025-04-25 05:43:01481 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.Downsampled",
Ramin Halavati3ae33ca2024-01-01 12:58:39482 elapsed_time);
483 }
Ramin Halavati2650ebe2023-11-27 19:56:35484
Ramin Halavati7716b032024-08-19 14:56:20485 // MediaApp provides OCR for ChromeOS PDF viewer.
Ramin Halavati4805c882025-03-03 18:28:48486 if (client_type == OcrClientTypeForMetrics::kPdfViewer ||
487 client_type == OcrClientTypeForMetrics::kMediaApp) {
Ramin Halavati2650ebe2023-11-27 19:56:35488 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount.PDF",
489 lines_count);
490 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Time.PDF",
491 elapsed_time);
Ramin Halavati696b8d5c2025-02-28 16:41:58492 base::UmaHistogramCounts10M(
493 lines_count ? "Accessibility.ScreenAI.OCR.ImageSize.PDF.WithText"
494 : "Accessibility.ScreenAI.OCR.ImageSize.PDF.NoText",
Ramin Halavati49c2b5c2025-04-25 05:43:01495 image.width() * image.height());
Ramin Halavati4c47eb82024-12-03 19:06:42496
Ramin Halavati01267d12025-01-21 18:11:30497 if (result.has_value()) {
498 std::optional<uint64_t> most_detected_language =
499 GetMostDetectedLanguageInOcrData(*result);
500 if (most_detected_language.has_value()) {
501 base::UmaHistogramSparse(
502 "Accessibility.ScreenAI.OCR.MostDetectedLanguage.PDF",
503 most_detected_language.value());
504 }
Ramin Halavati4c47eb82024-12-03 19:06:42505 }
Ramin Halavati2650ebe2023-11-27 19:56:35506 }
507
Ramin Halavatiac1ee2922025-05-21 17:17:17508 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavati7252cd02023-05-10 07:21:33509 return result;
510}
511
Ramin Halavatiad969ef2024-05-21 06:59:47512void ScreenAIService::SetClientType(mojom::OcrClientType client_type) {
513 ocr_client_types_[screen_ai_annotators_.current_receiver()] = client_type;
514}
515
Ramin Halavatid0d6231b2025-03-20 05:36:43516void ScreenAIService::SetClientType(mojom::MceClientType client_type) {
517 mce_client_types_[screen2x_main_content_extractors_.current_receiver()] =
518 client_type;
519}
520
Ramin Halavati1520b702025-04-23 15:24:18521void ScreenAIService::OcrReceiverDisconnected() {
522 auto entry = ocr_client_types_.find(screen_ai_annotators_.current_receiver());
523 if (entry != ocr_client_types_.end()) {
524 ocr_client_types_.erase(entry);
525 }
526 // Modify last used time to ensure the service does not shutdown while a
527 // client is disconnecting.
528 ocr_last_used_ = base::TimeTicks::Now();
529}
530
531void ScreenAIService::MceReceiverDisconnected() {
532 auto entry = mce_client_types_.find(
533 screen2x_main_content_extractors_.current_receiver());
534 if (entry != mce_client_types_.end()) {
535 mce_client_types_.erase(entry);
536 }
537 // Modify last used time to ensure the service does not shutdown while a
538 // client is disconnecting.
539 mce_last_used_ = base::TimeTicks::Now();
540}
541
Ramin Halavati062042f2025-05-02 05:47:57542void ScreenAIService::GetMaxImageDimension(
543 GetMaxImageDimensionCallback callback) {
Ramin Halavatida3ec442025-05-06 04:14:03544 CHECK(max_ocr_dimension_);
545 std::move(callback).Run(max_ocr_dimension_);
Ramin Halavati062042f2025-05-02 05:47:57546}
547
Ramin Halavatif4b48e02025-07-02 12:49:48548void ScreenAIService::SetOCRLightMode(bool enabled) {
549 const auto client = screen_ai_annotators_.current_receiver();
550 if (enabled) {
551 light_ocr_clients_.insert(client);
552 } else {
553 light_ocr_clients_.erase(client);
554 }
555}
556
557void ScreenAIService::IsOCRBusy(IsOCRBusyCallback callback) {
558 std::move(callback).Run(screen_ai_annotators_.size() > 1);
559}
560
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07561void ScreenAIService::PerformOcrAndReturnAnnotation(
562 const SkBitmap& image,
563 PerformOcrAndReturnAnnotationCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18564 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati7716b032024-08-19 14:56:20565 PerformOcrAndRecordMetrics(image);
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07566
Ramin Halavati89a36c92023-05-05 15:01:03567 if (annotation_proto) {
568 std::move(callback).Run(ConvertProtoToVisualAnnotation(*annotation_proto));
569 return;
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07570 }
Ramin Halavati89a36c92023-05-05 15:01:03571
572 std::move(callback).Run(mojom::VisualAnnotation::New());
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07573}
574
Ramin Halavati87cfa8e62025-06-02 06:48:10575#if BUILDFLAG(IS_CHROMEOS)
Kyungjun Lee90461522023-04-25 06:50:09576void ScreenAIService::PerformOcrAndReturnAXTreeUpdate(
Ramin Halavatib446c022023-03-29 15:36:36577 const SkBitmap& image,
Kyungjun Lee90461522023-04-25 06:50:09578 PerformOcrAndReturnAXTreeUpdateCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18579 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati7716b032024-08-19 14:56:20580 PerformOcrAndRecordMetrics(image);
Ramin Halavati89a36c92023-05-05 15:01:03581 ui::AXTreeUpdate update = ConvertVisualAnnotationToTreeUpdate(
582 annotation_proto, gfx::Rect(image.width(), image.height()));
Kyungjun Lee90461522023-04-25 06:50:09583
Ramin Halavati89a36c92023-05-05 15:01:03584 // The original caller is always replied to, and an empty AXTreeUpdate tells
585 // that the annotation function was not successful.
586 std::move(callback).Run(update);
Ramin Halavatib446c022023-03-29 15:36:36587}
Ramin Halavati87cfa8e62025-06-02 06:48:10588#endif // BUILDFLAG(IS_CHROMEOS)
Ramin Halavatib446c022023-03-29 15:36:36589
590void ScreenAIService::ExtractMainContent(const ui::AXTreeUpdate& snapshot,
Ramin Halavatib446c022023-03-29 15:36:36591 ExtractMainContentCallback callback) {
Abigail Klein18673702024-03-05 20:59:06592 ui::AXTree tree;
593 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03594 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
595 content_node_ids);
Abigail Klein18673702024-03-05 20:59:06596
597 if (success) {
598 std::move(callback).Run(*content_node_ids);
599 } else {
600 std::move(callback).Run(std::vector<int32_t>());
601 }
602}
603
604void ScreenAIService::ExtractMainNode(const ui::AXTreeUpdate& snapshot,
605 ExtractMainNodeCallback callback) {
606 ui::AXTree tree;
607 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03608 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
609 content_node_ids);
Abigail Klein18673702024-03-05 20:59:06610
611 if (success) {
612 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
613 std::move(callback).Run(main_node_id);
614 } else {
615 std::move(callback).Run(ui::kInvalidAXNodeID);
616 }
617}
618
Mark Schillaci79c82812025-03-07 21:46:37619void ScreenAIService::IdentifyMainNode(const ui::AXTreeUpdate& snapshot,
620 IdentifyMainNodeCallback callback) {
621 ui::AXTree tree;
622 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03623 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
624 content_node_ids);
Mark Schillaci79c82812025-03-07 21:46:37625
626 if (success) {
627 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
628 std::move(callback).Run(tree.GetAXTreeID(), main_node_id);
629 } else {
630 std::move(callback).Run(ui::AXTreeIDUnknown(), ui::kInvalidAXNodeID);
631 }
632}
633
Ramin Halavatib3fe2fe2025-03-20 16:53:03634bool ScreenAIService::ExtractMainContentInternalAndRecordMetrics(
Abigail Klein18673702024-03-05 20:59:06635 const ui::AXTreeUpdate& snapshot,
636 ui::AXTree& tree,
637 std::optional<std::vector<int32_t>>& content_node_ids) {
Ramin Halavatid0d6231b2025-03-20 05:36:43638 CHECK(base::Contains(mce_client_types_,
639 screen2x_main_content_extractors_.current_receiver()));
Ramin Halavati1520b702025-04-23 15:24:18640 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavatib3fe2fe2025-03-20 16:53:03641 MainContentExtractionClientTypeForMetrics client_type = GetClientType(
642 mce_client_types_[screen2x_main_content_extractors_.current_receiver()]);
643
644 static crash_reporter::CrashKeyString<2> cpu_info(
645 "main_content_extraction_client");
646 cpu_info.Set(base::StringPrintf("%i", static_cast<int>(client_type)));
647
Ramin Halavatib446c022023-03-29 15:36:36648 // Early return if input is empty.
649 if (snapshot.nodes.empty()) {
Ramin Halavatib3fe2fe2025-03-20 16:53:03650 base::UmaHistogramEnumeration(
651 "Accessibility.ScreenAI.MainContentExtraction.Error.SnapshotEmpty",
652 client_type);
Abigail Klein18673702024-03-05 20:59:06653 return false;
Ramin Halavatib33fc0c2022-05-06 09:32:22654 }
Ramin Halavatie2549a92022-08-02 07:43:19655
Abigail Klein18673702024-03-05 20:59:06656 // Deserialize the snapshot and reserialize it to a view hierarchy proto.
Ramin Halavatib3fe2fe2025-03-20 16:53:03657 if (!tree.Unserialize(snapshot)) {
658 base::UmaHistogramEnumeration(
659 "Accessibility.ScreenAI.MainContentExtraction.Error."
660 "SnapshotUnserialize",
661 client_type);
Ramin Halavati62e034d2024-08-02 08:58:22662 return false;
663 }
664
Ramin Halavatib3fe2fe2025-03-20 16:53:03665 std::optional<ViewHierarchyAndTreeSize> converted_snapshot =
666 SnapshotToViewHierarchy(tree);
667 if (!converted_snapshot) {
668 base::UmaHistogramEnumeration(
669 "Accessibility.ScreenAI.MainContentExtraction.Error.SnapshotProto",
670 client_type);
671 return false;
672 }
673
674 base::TimeTicks start_time = base::TimeTicks::Now();
Ramin Halavatiaa67ee52025-04-25 05:15:53675 base::SequenceBound<HangTimer> hang_timer(background_task_runner_,
676 /*is_ocr=*/false);
677 hang_timer.AsyncCall(&HangTimer::StartTimer);
Ramin Halavati62e034d2024-08-02 08:58:22678 content_node_ids =
679 library_->ExtractMainContent(converted_snapshot->serialized_proto);
Ramin Halavatiaa67ee52025-04-25 05:15:53680 hang_timer.AsyncCall(&HangTimer::Stop);
Ramin Halavatib3fe2fe2025-03-20 16:53:03681 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
682
683 bool successful =
684 content_node_ids.has_value() && content_node_ids->size() > 0;
Ramin Halavati81b6d922024-06-07 14:20:34685 base::UmaHistogramBoolean(
Ramin Halavatib3fe2fe2025-03-20 16:53:03686 "Accessibility.ScreenAI.MainContentExtraction.Successful2", successful);
687
688 if (!content_node_ids.has_value()) {
689 base::UmaHistogramEnumeration(
690 "Accessibility.ScreenAI.MainContentExtraction.Error.ResultNull",
691 client_type);
692 } else if (content_node_ids->empty()) {
693 base::UmaHistogramEnumeration(
694 "Accessibility.ScreenAI.MainContentExtraction.Error.ResultEmpty",
695 client_type);
696 }
697
Ramin Halavatiac1ee2922025-05-21 17:17:17698 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavatib3fe2fe2025-03-20 16:53:03699 if (successful) {
700 base::UmaHistogramTimes(
701 "Accessibility.ScreenAI.MainContentExtraction.Latency.Success",
702 elapsed_time);
Ramin Halavati5aad701d2023-05-09 17:23:29703 VLOG(2) << "Screen2x returned " << content_node_ids->size() << " node ids.";
Abigail Klein18673702024-03-05 20:59:06704 return true;
705 } else {
Ramin Halavatib3fe2fe2025-03-20 16:53:03706 base::UmaHistogramTimes(
707 "Accessibility.ScreenAI.MainContentExtraction.Latency.Failure",
708 elapsed_time);
Abigail Klein18673702024-03-05 20:59:06709 VLOG(0) << "Screen2x returned no results.";
710 return false;
Ramin Halavati5aad701d2023-05-09 17:23:29711 }
Abigail Klein18673702024-03-05 20:59:06712}
Ramin Halavati89a36c92023-05-05 15:01:03713
Abigail Klein18673702024-03-05 20:59:06714ui::AXNodeID ScreenAIService::ComputeMainNodeForTesting(
715 const ui::AXTree* tree,
716 const std::vector<ui::AXNodeID>& content_node_ids) {
717 return ComputeMainNode(tree, content_node_ids);
Abigail Kleindfdde352023-01-27 21:03:10718}
719
Ramin Halavaticf155012025-05-05 15:50:01720void ScreenAIService::StartShutDownOnIdleTimer() {
721 if (!idle_checking_timer_) {
722 idle_checking_timer_ = std::make_unique<base::RepeatingTimer>();
723 idle_checking_timer_->Start(FROM_HERE, kIdleCheckingDelay, this,
724 &ScreenAIService::ShutDownOnIdle);
725 }
726}
727
Ramin Halavatibe45dde2025-05-01 04:19:10728void ScreenAIService::ShutDownOnIdle() {
Ramin Halavati2acf2362024-08-02 14:22:53729 const base::TimeTicks kIdlenessThreshold =
730 base::TimeTicks::Now() - kIdleCheckingDelay;
Ramin Halavatibe45dde2025-05-01 04:19:10731 if (ocr_last_used_ < kIdlenessThreshold &&
732 mce_last_used_ < kIdlenessThreshold) {
Ramin Halavatif26d7d42025-03-12 16:04:46733 screen_ai_shutdown_handler_->ShuttingDownOnIdle();
Ramin Halavatif4b48e02025-07-02 12:49:48734
735 // If OCR was used, record the number of times it's mode was switched.
736 if (ocr_last_used_ != base::TimeTicks()) {
737 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.ModeSwitch",
738 ocr_mode_switch_count_);
739 }
740
Ramin Halavatidc0b2e512024-08-16 17:39:09741 base::Process::TerminateCurrentProcessImmediately(0);
Ramin Halavati2acf2362024-08-02 14:22:53742 }
Ramin Halavati3842e7e2024-07-31 04:39:21743}
744
Ramin Halavati26dcce22022-02-23 13:11:14745} // namespace screen_ai