blob: 223649bf2a6c49b81c78d52b799e347a6add2473 [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 Halavatia9b50a102024-02-07 18:37:2341#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Abigail Klein7a63c572024-02-28 20:45:0942#include "services/screen_ai/screen_ai_library_wrapper_fake.h"
Ramin Halavati767b8fc2024-02-02 06:23:0843#else
Abigail Klein7a63c572024-02-28 20:45:0944#include "services/screen_ai/screen_ai_library_wrapper_impl.h"
Ramin Halavati767b8fc2024-02-02 06:23:0845#endif
46
Ramin Halavati69bf7752022-04-04 09:58:1547namespace screen_ai {
Ramin Halavati26dcce22022-02-23 13:11:1448
Ramin Halavati6d4fc2b2022-06-16 15:33:3549namespace {
50
Ramin Halavati2acf2362024-08-02 14:22:5351// How often it would be checked that the service is idle and can be shutdown.
Ramin Halavatibe45dde2025-05-01 04:19:1052// LINT.IfChange(kIdleCheckingDelay)
Ramin Halavati1520b702025-04-23 15:24:1853constexpr base::TimeDelta kIdleCheckingDelay = base::Seconds(3);
Ramin Halavatibe45dde2025-05-01 04:19:1054// LINT.ThenChange(//chrome/browser/screen_ai/optical_character_recognizer_browsertest.cc:kServiceIdleCheckingDelay)
Ramin Halavati6ff56412024-08-13 07:15:5455
Ramin Halavati4825419e2025-04-02 05:49:4656// How long to wait for a request to the library be responded, before assuming
57// that the library is not responsive.
Ramin Halavatiaa67ee52025-04-25 05:15:5358constexpr base::TimeDelta kMaxWaitForResponseTime = base::Seconds(10);
Ramin Halavati4825419e2025-04-02 05:49:4659
Ramin Halavatiad969ef2024-05-21 06:59:4760// These values are persisted to logs. Entries should not be renumbered and
61// numeric values should never be reused.
Ramin Halavati29cc1152024-10-27 17:30:2862// See `screen_ai_service.mojom` for more info.
Ramin Halavatib3fe2fe2025-03-20 16:53:0363// LINT.IfChange(OcrClientType)
Ramin Halavatiad969ef2024-05-21 06:59:4764enum class OcrClientTypeForMetrics {
65 kTest = 0,
66 kPdfViewer = 1,
67 kLocalSearch = 2,
68 kCameraApp = 3,
Ramin Halavati29cc1152024-10-27 17:30:2869 kNotUsed = 4, // Can be used for a new client.
Ramin Halavatiad969ef2024-05-21 06:59:4770 kMediaApp = 5,
Michelle Chen5192d522024-10-22 23:38:2271 kScreenshotTextDetection,
72 kMaxValue = kScreenshotTextDetection
Ramin Halavatiad969ef2024-05-21 06:59:4773};
Ramin Halavatib3fe2fe2025-03-20 16:53:0374// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:OcrClientType)
75
76// These values are persisted to logs. Entries should not be renumbered and
77// numeric values should never be reused.
78// See `screen_ai_service.mojom` for more info.
79// LINT.IfChange(MainContentExtractionClientType)
80enum class MainContentExtractionClientTypeForMetrics {
81 kTest = 0,
82 kReadingMode = 1,
83 kMainNode = 2,
84 kMahi = 3,
85 kMaxValue = kMahi
86};
87// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:MainContentExtractionClientType)
Ramin Halavatiad969ef2024-05-21 06:59:4788
89OcrClientTypeForMetrics GetClientType(mojom::OcrClientType client_type) {
90 switch (client_type) {
91 case mojom::OcrClientType::kTest:
92 CHECK_IS_TEST();
93 return OcrClientTypeForMetrics::kTest;
94 case mojom::OcrClientType::kPdfViewer:
95 return OcrClientTypeForMetrics::kPdfViewer;
96 case mojom::OcrClientType::kLocalSearch:
97 return OcrClientTypeForMetrics::kLocalSearch;
98 case mojom::OcrClientType::kCameraApp:
99 return OcrClientTypeForMetrics::kCameraApp;
Ramin Halavatiad969ef2024-05-21 06:59:47100 case mojom::OcrClientType::kMediaApp:
101 return OcrClientTypeForMetrics::kMediaApp;
Michelle Chen5192d522024-10-22 23:38:22102 case mojom::OcrClientType::kScreenshotTextDetection:
103 return OcrClientTypeForMetrics::kScreenshotTextDetection;
Ramin Halavatiad969ef2024-05-21 06:59:47104 }
105}
106
Ramin Halavatib3fe2fe2025-03-20 16:53:03107MainContentExtractionClientTypeForMetrics GetClientType(
108 mojom::MceClientType client_type) {
109 switch (client_type) {
110 case mojom::MceClientType::kTest:
111 CHECK_IS_TEST();
112 return MainContentExtractionClientTypeForMetrics::kTest;
113 case mojom::MceClientType::kReadingMode:
114 return MainContentExtractionClientTypeForMetrics::kReadingMode;
115 case mojom::MceClientType::kMainNode:
116 return MainContentExtractionClientTypeForMetrics::kMainNode;
117 case mojom::MceClientType::kMahi:
118 return MainContentExtractionClientTypeForMetrics::kMahi;
119 }
120}
121
Ramin Halavati89a36c92023-05-05 15:01:03122ui::AXTreeUpdate ConvertVisualAnnotationToTreeUpdate(
Ramin Halavatidcb09a172024-07-15 08:28:55123 std::optional<chrome_screen_ai::VisualAnnotation>& annotation_proto,
Ramin Halavati89a36c92023-05-05 15:01:03124 const gfx::Rect& image_rect) {
125 if (!annotation_proto) {
126 VLOG(0) << "Screen AI library could not process snapshot or no OCR data.";
127 return ui::AXTreeUpdate();
128 }
129
130 return VisualAnnotationToAXTreeUpdate(*annotation_proto, image_rect);
131}
132
Abigail Klein18673702024-03-05 20:59:06133ui::AXNodeID ComputeMainNode(
134 const ui::AXTree* tree,
135 const std::vector<ui::AXNodeID>& content_node_ids) {
136 ui::AXNode* front = tree->GetFromId(content_node_ids.front());
137 ui::AXNode* back = tree->GetFromId(content_node_ids.back());
138 ui::AXNode* main = front->GetLowestCommonAncestor(*back);
139 return main->id();
140}
141
Ramin Halavati65f96fb2025-02-24 16:45:18142#if !BUILDFLAG(USE_FAKE_SCREEN_AI)
143void SetCPUInstructionSetCrashKey() {
144#if defined(ARCH_CPU_X86_FAMILY)
145 base::CPU();
146 // Report cpu micro architecture in case of crash.
147 static crash_reporter::CrashKeyString<3> cpu_info("intel_micro_architecture");
148 cpu_info.Set(
149 base::StringPrintf("%i", base::CPU().GetIntelMicroArchitecture()));
150#endif
151}
152#endif
153
Ramin Halavati26679592025-03-13 07:24:57154// Return a maximum 11 character string with the signature of available and
155// total memory, both in MB and capped to 99999.
156std::string GetMemoryStatusForCrashKey() {
157 int total_memory = base::SysInfo::AmountOfPhysicalMemoryMB();
158 int available_memory = static_cast<int>(
159 base::SysInfo::AmountOfAvailablePhysicalMemory() / (1024 * 1024));
160
161 // Cap the number of digits for crash report.
162 total_memory = std::min(total_memory, 99999);
163 available_memory = std::min(available_memory, 99999);
164 return base::StringPrintf("%i,%i", available_memory, total_memory);
165}
166
Ramin Halavatiaa67ee52025-04-25 05:15:53167class HangTimer : public base::OneShotTimer {
168 public:
169 explicit HangTimer(bool is_ocr) : is_ocr_(is_ocr) {}
170
171 void StartTimer() {
172 Start(FROM_HERE, kMaxWaitForResponseTime,
173 base::BindOnce(
174 [](bool request_is_ocr) {
175 base::UmaHistogramBoolean(
Ramin Halavatib5db88472025-05-23 14:34:07176 "Accessibility.ScreenAI.Service.NotResponsive.IsOCR",
Ramin Halavatiaa67ee52025-04-25 05:15:53177 request_is_ocr);
178 base::Process::TerminateCurrentProcessImmediately(0);
179 },
180 is_ocr_));
181 }
182
183 private:
184 bool is_ocr_;
185};
186
Ramin Halavati26ae6b72022-11-10 06:45:05187} // namespace
188
Ramin Halavati39bab0d2024-01-30 06:05:01189// The library accepts simple pointers to model data retrieval functions, hence
190// callback functions with linked object are not safe to pass.
Ramin Halavati63d9c252024-07-29 06:59:17191// This global variable keeps the pointer the only instance of this class.
192ModelDataHolder* g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50193
Ramin Halavati63d9c252024-07-29 06:59:17194// Keeps the handles of model files, and replies to calls for copying their
195// content.
196class ModelDataHolder {
Ramin Halavati8ced6392023-09-25 07:27:50197 public:
Ramin Halavati63d9c252024-07-29 06:59:17198 ModelDataHolder() {
199 CHECK(!g_model_data_holder_instance);
200 g_model_data_holder_instance = this;
201 }
Ramin Halavati8ced6392023-09-25 07:27:50202
Ramin Halavati63d9c252024-07-29 06:59:17203 ModelDataHolder(const ModelDataHolder&) = delete;
204 ModelDataHolder& operator=(const ModelDataHolder&) = delete;
205
206 ~ModelDataHolder() {
207 CHECK_EQ(g_model_data_holder_instance, this);
208 g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50209 }
210
211 // Returns 0 if file is not found.
212 static uint32_t GetDataSize(const char* relative_file_path) {
Ramin Halavati63d9c252024-07-29 06:59:17213 CHECK(g_model_data_holder_instance);
214 base::File* model_file =
215 g_model_data_holder_instance->GetModelFile(relative_file_path);
216 return model_file ? model_file->GetLength() : 0;
Ramin Halavati8ced6392023-09-25 07:27:50217 }
218
Ramin Halavati63d9c252024-07-29 06:59:17219 // Copies content of the file in `relative_file_path` to `buffer`. Expects
220 // that `buffer_size` would be enough for the entire file content.
Ramin Halavati8ced6392023-09-25 07:27:50221 static void CopyData(const char* relative_file_path,
222 uint32_t buffer_size,
223 char* buffer) {
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 CHECK(model_file);
228
229 int64_t length = model_file->GetLength();
230 CHECK_GE(buffer_size, length);
Tom Sepeze9b4dd552024-08-14 22:44:29231 CHECK_EQ(UNSAFE_TODO(model_file->Read(0, buffer, length)), length);
Ramin Halavati8ced6392023-09-25 07:27:50232 }
233
Ramin Halavati63d9c252024-07-29 06:59:17234 void AddModelFiles(base::flat_map<base::FilePath, base::File> model_files) {
235 for (auto& model_file : model_files) {
236 model_files_[model_file.first.MaybeAsASCII()] =
237 std::move(model_file.second);
Ramin Halavati39bab0d2024-01-30 06:05:01238 }
239 }
Ramin Halavati8ced6392023-09-25 07:27:50240
Ramin Halavati63d9c252024-07-29 06:59:17241 // Returns the file handle for `relative_file_path` if it exists.
242 base::File* GetModelFile(const char* relative_file_path) {
243 if (!base::Contains(model_files_, relative_file_path)) {
244 return nullptr;
245 }
246 return &model_files_[relative_file_path];
247 }
248
Ramin Halavati8ced6392023-09-25 07:27:50249 private:
Ramin Halavati63d9c252024-07-29 06:59:17250 std::map<std::string, base::File> model_files_;
Ramin Halavati8ced6392023-09-25 07:27:50251};
252
Ramin Halavati26ae6b72022-11-10 06:45:05253ScreenAIService::ScreenAIService(
Ramin Halavatic1e4fa92023-05-17 17:22:06254 mojo::PendingReceiver<mojom::ScreenAIServiceFactory> receiver)
255 : factory_receiver_(this, std::move(receiver)),
256 ocr_receiver_(this),
Ramin Halavatiad969ef2024-05-21 06:59:47257 main_content_extraction_receiver_(this) {
Ramin Halavati6ff56412024-08-13 07:15:54258 screen2x_main_content_extractors_.set_disconnect_handler(
Ramin Halavati1520b702025-04-23 15:24:18259 base::BindRepeating(&ScreenAIService::MceReceiverDisconnected,
Ramin Halavati6ff56412024-08-13 07:15:54260 weak_ptr_factory_.GetWeakPtr()));
261 screen_ai_annotators_.set_disconnect_handler(
262 base::BindRepeating(&ScreenAIService::OcrReceiverDisconnected,
263 weak_ptr_factory_.GetWeakPtr()));
Ramin Halavati63d9c252024-07-29 06:59:17264 model_data_holder_ = std::make_unique<ModelDataHolder>();
Ramin Halavatiaa67ee52025-04-25 05:15:53265
266 background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
267 {base::TaskPriority::BEST_EFFORT,
268 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
Ramin Halavatiad969ef2024-05-21 06:59:47269}
Ramin Halavati26ae6b72022-11-10 06:45:05270
271ScreenAIService::~ScreenAIService() = default;
272
Ramin Halavati8ced6392023-09-25 07:27:50273void ScreenAIService::LoadLibrary(const base::FilePath& library_path) {
Abigail Klein7a63c572024-02-28 20:45:09274 // The ScopedBlockingCall in LoadLibrary guarantees that this is not run on
275 // the UI thread.
Ramin Halavatia9b50a102024-02-07 18:37:23276#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Ramin Halavati767b8fc2024-02-02 06:23:08277 library_ = std::make_unique<ScreenAILibraryWrapperFake>();
278#else
279 library_ = std::make_unique<ScreenAILibraryWrapperImpl>();
Ramin Halavati65f96fb2025-02-24 16:45:18280
281 // TODO(crbug.com/381256355): Remove when the library is SSE3 compatible.
282 SetCPUInstructionSetCrashKey();
Ramin Halavati767b8fc2024-02-02 06:23:08283#endif
Ramin Halavati8ced6392023-09-25 07:27:50284
285 bool load_sucessful = library_->Load(library_path);
286 base::UmaHistogramBoolean("Accessibility.ScreenAI.Library.Initialized",
287 load_sucessful);
288
289 if (!load_sucessful) {
290 library_.reset();
291 return;
292 }
293
294 uint32_t version_major;
295 uint32_t version_minor;
296 library_->GetLibraryVersion(version_major, version_minor);
297 VLOG(2) << "Screen AI library version: " << version_major << "."
298 << version_minor;
299
Georg Neis06e387772024-12-25 07:32:22300#if BUILDFLAG(IS_CHROMEOS)
Ramin Halavati8ced6392023-09-25 07:27:50301 library_->SetLogger();
302#endif
303
304 if (features::IsScreenAIDebugModeEnabled()) {
305 library_->EnableDebugMode();
306 }
307
Ramin Halavati63d9c252024-07-29 06:59:17308 library_->SetFileContentFunctions(&ModelDataHolder::GetDataSize,
309 &ModelDataHolder::CopyData);
Ramin Halavati8ced6392023-09-25 07:27:50310}
311
Ramin Halavatic1e4fa92023-05-17 17:22:06312void ScreenAIService::InitializeMainContentExtraction(
Ramin Halavatid139a0f2023-02-28 13:25:28313 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07314 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06315 mojo::PendingReceiver<mojom::MainContentExtractionService>
316 main_content_extractor_service_receiver,
317 InitializeMainContentExtractionCallback callback) {
318 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50319 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06320 }
321
322 if (!library_) {
323 std::move(callback).Run(false);
324 base::Process::TerminateCurrentProcessImmediately(-1);
325 }
326
Ramin Halavati63d9c252024-07-29 06:59:17327 model_data_holder_->AddModelFiles(std::move(model_files));
Ramin Halavatib044504d2022-10-24 06:36:34328
Ramin Halavati8ced6392023-09-25 07:27:50329 bool init_successful = library_->InitMainContentExtraction();
Ramin Halavati1d57c2d2023-05-24 05:18:24330 base::UmaHistogramBoolean(
331 "Accessibility.ScreenAI.MainContentExtraction.Initialized",
332 init_successful);
333 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06334 std::move(callback).Run(false);
Ramin Halavati89a36c92023-05-05 15:01:03335 return;
Ramin Halavatid139a0f2023-02-28 13:25:28336 }
Ramin Halavati89a36c92023-05-05 15:01:03337
Ramin Halavatic1e4fa92023-05-17 17:22:06338 // This interface should be created only once.
339 CHECK(!main_content_extraction_receiver_.is_bound());
340
341 main_content_extraction_receiver_.Bind(
342 std::move(main_content_extractor_service_receiver));
343
344 std::move(callback).Run(true);
Ramin Halavati1520b702025-04-23 15:24:18345 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavaticf155012025-05-05 15:50:01346 StartShutDownOnIdleTimer();
Ramin Halavatic1e4fa92023-05-17 17:22:06347}
348
349void ScreenAIService::InitializeOCR(
350 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07351 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06352 mojo::PendingReceiver<mojom::OCRService> ocr_service_receiver,
353 InitializeOCRCallback callback) {
Ramin Halavati26679592025-03-13 07:24:57354 static crash_reporter::CrashKeyString<12> memory_ocr_init(
355 "screen_ai_mem_ocr_init");
356 memory_ocr_init.Set(GetMemoryStatusForCrashKey());
Ramin Halavatic1e4fa92023-05-17 17:22:06357 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50358 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06359 }
360
361 if (!library_) {
362 std::move(callback).Run(false);
363 base::Process::TerminateCurrentProcessImmediately(-1);
364 }
365
Ramin Halavati63d9c252024-07-29 06:59:17366 model_data_holder_->AddModelFiles(std::move(model_files));
367
Ramin Halavati0e2ae5742023-11-10 05:57:54368 bool init_successful = library_->InitOCR();
Ramin Halavati35d129f2023-06-22 16:37:09369 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Initialized",
Ramin Halavati1d57c2d2023-05-24 05:18:24370 init_successful);
Ramin Halavati480584332023-08-02 06:38:40371
Ramin Halavati1d57c2d2023-05-24 05:18:24372 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06373 std::move(callback).Run(false);
374 return;
375 }
376
Ramin Halavatida3ec442025-05-06 04:14:03377 max_ocr_dimension_ = library_->GetMaxImageDimension();
378 CHECK(max_ocr_dimension_);
379
Ramin Halavatic1e4fa92023-05-17 17:22:06380 // This interface should be created only once.
381 CHECK(!ocr_receiver_.is_bound());
382
383 ocr_receiver_.Bind(std::move(ocr_service_receiver));
384
385 std::move(callback).Run(true);
Ramin Halavati2acf2362024-08-02 14:22:53386 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavaticf155012025-05-05 15:50:01387 StartShutDownOnIdleTimer();
Ramin Halavatib044504d2022-10-24 06:36:34388}
Ramin Halavati26dcce22022-02-23 13:11:14389
Ramin Halavatif26d7d42025-03-12 16:04:46390void ScreenAIService::BindShutdownHandler(
391 mojo::PendingRemote<mojom::ScreenAIServiceShutdownHandler>
392 shutdown_handler) {
393 DCHECK(!screen_ai_shutdown_handler_.is_bound());
394 screen_ai_shutdown_handler_.Bind(std::move(shutdown_handler));
395}
396
Ramin Halavati26dcce22022-02-23 13:11:14397void ScreenAIService::BindAnnotator(
398 mojo::PendingReceiver<mojom::ScreenAIAnnotator> annotator) {
399 screen_ai_annotators_.Add(this, std::move(annotator));
400}
401
Ramin Halavatieddadb62022-05-04 17:29:49402void ScreenAIService::BindMainContentExtractor(
403 mojo::PendingReceiver<mojom::Screen2xMainContentExtractor>
404 main_content_extractor) {
Ramin Halavati6ff56412024-08-13 07:15:54405 screen2x_main_content_extractors_.Add(this,
406 std::move(main_content_extractor));
Ramin Halavatieddadb62022-05-04 17:29:49407}
408
Arthur Sonzognic571efb2024-01-26 20:26:18409std::optional<chrome_screen_ai::VisualAnnotation>
Ramin Halavati7716b032024-08-19 14:56:20410ScreenAIService::PerformOcrAndRecordMetrics(const SkBitmap& image) {
Ramin Halavati26679592025-03-13 07:24:57411 static crash_reporter::CrashKeyString<12> memory_perform_ocr(
412 "screen_ai_mem_ocr_perform");
413 memory_perform_ocr.Set(GetMemoryStatusForCrashKey());
414
Ramin Halavati7716b032024-08-19 14:56:20415 CHECK(base::Contains(ocr_client_types_,
416 screen_ai_annotators_.current_receiver()));
Ramin Halavati4805c882025-03-03 18:28:48417 OcrClientTypeForMetrics client_type = GetClientType(
418 ocr_client_types_.find(screen_ai_annotators_.current_receiver())->second);
Ramin Halavatiad969ef2024-05-21 06:59:47419 base::UmaHistogramEnumeration("Accessibility.ScreenAI.OCR.ClientType",
Ramin Halavati4805c882025-03-03 18:28:48420 client_type);
Ramin Halavatiad969ef2024-05-21 06:59:47421
Ramin Halavatiac1ee2922025-05-21 17:17:17422 base::TimeTicks start_time = base::TimeTicks::Now();
Ramin Halavatiaa67ee52025-04-25 05:15:53423 base::SequenceBound<HangTimer> hang_timer(background_task_runner_,
424 /*is_ocr=*/true);
425 hang_timer.AsyncCall(&HangTimer::StartTimer);
Ramin Halavati7252cd02023-05-10 07:21:33426 auto result = library_->PerformOcr(image);
Ramin Halavatiaa67ee52025-04-25 05:15:53427 hang_timer.AsyncCall(&base::OneShotTimer::Stop);
Ramin Halavatiac1ee2922025-05-21 17:17:17428 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
Ramin Halavati4825419e2025-04-02 05:49:46429
Ramin Halavati2650ebe2023-11-27 19:56:35430 int lines_count = result ? result->lines_size() : 0;
431 VLOG(1) << "OCR returned " << lines_count << " lines in " << elapsed_time;
Ramin Halavati7252cd02023-05-10 07:21:33432
Ramin Halavati18aba872024-08-23 14:33:05433 if (!result) {
434 base::UmaHistogramEnumeration(
Ramin Halavati4805c882025-03-03 18:28:48435 "Accessibility.ScreenAI.OCR.Failed.ClientType", client_type);
Ramin Halavati18aba872024-08-23 14:33:05436 }
Ramin Halavati49c2b5c2025-04-25 05:43:01437
Ramin Halavatida3ec442025-05-06 04:14:03438 int max_dimension = base::checked_cast<int>(max_ocr_dimension_);
439 if (image.width() > max_dimension || image.height() > max_dimension) {
Ramin Halavati4805c882025-03-03 18:28:48440 base::UmaHistogramEnumeration(
Ramin Halavati49c2b5c2025-04-25 05:43:01441 "Accessibility.ScreenAI.OCR.Downsampled.ClientType", client_type);
Ramin Halavati4805c882025-03-03 18:28:48442 }
443
Ramin Halavati81b6d922024-06-07 14:20:34444 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Successful",
445 result.has_value());
Ramin Halavati2650ebe2023-11-27 19:56:35446 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount",
447 lines_count);
Ramin Halavatida3ec442025-05-06 04:14:03448 if (image.width() < max_dimension && image.height() < max_dimension) {
Ramin Halavati49c2b5c2025-04-25 05:43:01449 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.NotDownsampled",
Ramin Halavati3ae33ca2024-01-01 12:58:39450 elapsed_time);
451 } else {
Ramin Halavati49c2b5c2025-04-25 05:43:01452 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.Downsampled",
Ramin Halavati3ae33ca2024-01-01 12:58:39453 elapsed_time);
454 }
Ramin Halavati2650ebe2023-11-27 19:56:35455
Ramin Halavati7716b032024-08-19 14:56:20456 // MediaApp provides OCR for ChromeOS PDF viewer.
Ramin Halavati4805c882025-03-03 18:28:48457 if (client_type == OcrClientTypeForMetrics::kPdfViewer ||
458 client_type == OcrClientTypeForMetrics::kMediaApp) {
Ramin Halavati2650ebe2023-11-27 19:56:35459 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount.PDF",
460 lines_count);
461 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Time.PDF",
462 elapsed_time);
Ramin Halavati696b8d5c2025-02-28 16:41:58463 base::UmaHistogramCounts10M(
464 lines_count ? "Accessibility.ScreenAI.OCR.ImageSize.PDF.WithText"
465 : "Accessibility.ScreenAI.OCR.ImageSize.PDF.NoText",
Ramin Halavati49c2b5c2025-04-25 05:43:01466 image.width() * image.height());
Ramin Halavati4c47eb82024-12-03 19:06:42467
Ramin Halavati01267d12025-01-21 18:11:30468 if (result.has_value()) {
469 std::optional<uint64_t> most_detected_language =
470 GetMostDetectedLanguageInOcrData(*result);
471 if (most_detected_language.has_value()) {
472 base::UmaHistogramSparse(
473 "Accessibility.ScreenAI.OCR.MostDetectedLanguage.PDF",
474 most_detected_language.value());
475 }
Ramin Halavati4c47eb82024-12-03 19:06:42476 }
Ramin Halavati2650ebe2023-11-27 19:56:35477 }
478
Ramin Halavatiac1ee2922025-05-21 17:17:17479 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavati7252cd02023-05-10 07:21:33480 return result;
481}
482
Ramin Halavatiad969ef2024-05-21 06:59:47483void ScreenAIService::SetClientType(mojom::OcrClientType client_type) {
484 ocr_client_types_[screen_ai_annotators_.current_receiver()] = client_type;
485}
486
Ramin Halavatid0d6231b2025-03-20 05:36:43487void ScreenAIService::SetClientType(mojom::MceClientType client_type) {
488 mce_client_types_[screen2x_main_content_extractors_.current_receiver()] =
489 client_type;
490}
491
Ramin Halavati1520b702025-04-23 15:24:18492void ScreenAIService::OcrReceiverDisconnected() {
493 auto entry = ocr_client_types_.find(screen_ai_annotators_.current_receiver());
494 if (entry != ocr_client_types_.end()) {
495 ocr_client_types_.erase(entry);
496 }
497 // Modify last used time to ensure the service does not shutdown while a
498 // client is disconnecting.
499 ocr_last_used_ = base::TimeTicks::Now();
500}
501
502void ScreenAIService::MceReceiverDisconnected() {
503 auto entry = mce_client_types_.find(
504 screen2x_main_content_extractors_.current_receiver());
505 if (entry != mce_client_types_.end()) {
506 mce_client_types_.erase(entry);
507 }
508 // Modify last used time to ensure the service does not shutdown while a
509 // client is disconnecting.
510 mce_last_used_ = base::TimeTicks::Now();
511}
512
Ramin Halavati062042f2025-05-02 05:47:57513void ScreenAIService::GetMaxImageDimension(
514 GetMaxImageDimensionCallback callback) {
Ramin Halavatida3ec442025-05-06 04:14:03515 CHECK(max_ocr_dimension_);
516 std::move(callback).Run(max_ocr_dimension_);
Ramin Halavati062042f2025-05-02 05:47:57517}
518
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07519void ScreenAIService::PerformOcrAndReturnAnnotation(
520 const SkBitmap& image,
521 PerformOcrAndReturnAnnotationCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18522 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati7716b032024-08-19 14:56:20523 PerformOcrAndRecordMetrics(image);
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07524
Ramin Halavati89a36c92023-05-05 15:01:03525 if (annotation_proto) {
526 std::move(callback).Run(ConvertProtoToVisualAnnotation(*annotation_proto));
527 return;
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07528 }
Ramin Halavati89a36c92023-05-05 15:01:03529
530 std::move(callback).Run(mojom::VisualAnnotation::New());
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07531}
532
Kyungjun Lee90461522023-04-25 06:50:09533void ScreenAIService::PerformOcrAndReturnAXTreeUpdate(
Ramin Halavatib446c022023-03-29 15:36:36534 const SkBitmap& image,
Kyungjun Lee90461522023-04-25 06:50:09535 PerformOcrAndReturnAXTreeUpdateCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18536 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati7716b032024-08-19 14:56:20537 PerformOcrAndRecordMetrics(image);
Ramin Halavati89a36c92023-05-05 15:01:03538 ui::AXTreeUpdate update = ConvertVisualAnnotationToTreeUpdate(
539 annotation_proto, gfx::Rect(image.width(), image.height()));
Kyungjun Lee90461522023-04-25 06:50:09540
Ramin Halavati89a36c92023-05-05 15:01:03541 // The original caller is always replied to, and an empty AXTreeUpdate tells
542 // that the annotation function was not successful.
543 std::move(callback).Run(update);
Ramin Halavatib446c022023-03-29 15:36:36544}
545
546void ScreenAIService::ExtractMainContent(const ui::AXTreeUpdate& snapshot,
Ramin Halavatib446c022023-03-29 15:36:36547 ExtractMainContentCallback callback) {
Abigail Klein18673702024-03-05 20:59:06548 ui::AXTree tree;
549 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03550 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
551 content_node_ids);
Abigail Klein18673702024-03-05 20:59:06552
553 if (success) {
554 std::move(callback).Run(*content_node_ids);
555 } else {
556 std::move(callback).Run(std::vector<int32_t>());
557 }
558}
559
560void ScreenAIService::ExtractMainNode(const ui::AXTreeUpdate& snapshot,
561 ExtractMainNodeCallback callback) {
562 ui::AXTree tree;
563 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03564 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
565 content_node_ids);
Abigail Klein18673702024-03-05 20:59:06566
567 if (success) {
568 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
569 std::move(callback).Run(main_node_id);
570 } else {
571 std::move(callback).Run(ui::kInvalidAXNodeID);
572 }
573}
574
Mark Schillaci79c82812025-03-07 21:46:37575void ScreenAIService::IdentifyMainNode(const ui::AXTreeUpdate& snapshot,
576 IdentifyMainNodeCallback callback) {
577 ui::AXTree tree;
578 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03579 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
580 content_node_ids);
Mark Schillaci79c82812025-03-07 21:46:37581
582 if (success) {
583 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
584 std::move(callback).Run(tree.GetAXTreeID(), main_node_id);
585 } else {
586 std::move(callback).Run(ui::AXTreeIDUnknown(), ui::kInvalidAXNodeID);
587 }
588}
589
Ramin Halavatib3fe2fe2025-03-20 16:53:03590bool ScreenAIService::ExtractMainContentInternalAndRecordMetrics(
Abigail Klein18673702024-03-05 20:59:06591 const ui::AXTreeUpdate& snapshot,
592 ui::AXTree& tree,
593 std::optional<std::vector<int32_t>>& content_node_ids) {
Ramin Halavatid0d6231b2025-03-20 05:36:43594 CHECK(base::Contains(mce_client_types_,
595 screen2x_main_content_extractors_.current_receiver()));
Ramin Halavati1520b702025-04-23 15:24:18596 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavatib3fe2fe2025-03-20 16:53:03597 MainContentExtractionClientTypeForMetrics client_type = GetClientType(
598 mce_client_types_[screen2x_main_content_extractors_.current_receiver()]);
599
600 static crash_reporter::CrashKeyString<2> cpu_info(
601 "main_content_extraction_client");
602 cpu_info.Set(base::StringPrintf("%i", static_cast<int>(client_type)));
603
Ramin Halavatib446c022023-03-29 15:36:36604 // Early return if input is empty.
605 if (snapshot.nodes.empty()) {
Ramin Halavatib3fe2fe2025-03-20 16:53:03606 base::UmaHistogramEnumeration(
607 "Accessibility.ScreenAI.MainContentExtraction.Error.SnapshotEmpty",
608 client_type);
Abigail Klein18673702024-03-05 20:59:06609 return false;
Ramin Halavatib33fc0c2022-05-06 09:32:22610 }
Ramin Halavatie2549a92022-08-02 07:43:19611
Abigail Klein18673702024-03-05 20:59:06612 // Deserialize the snapshot and reserialize it to a view hierarchy proto.
Ramin Halavatib3fe2fe2025-03-20 16:53:03613 if (!tree.Unserialize(snapshot)) {
614 base::UmaHistogramEnumeration(
615 "Accessibility.ScreenAI.MainContentExtraction.Error."
616 "SnapshotUnserialize",
617 client_type);
Ramin Halavati62e034d2024-08-02 08:58:22618 return false;
619 }
620
Ramin Halavatib3fe2fe2025-03-20 16:53:03621 std::optional<ViewHierarchyAndTreeSize> converted_snapshot =
622 SnapshotToViewHierarchy(tree);
623 if (!converted_snapshot) {
624 base::UmaHistogramEnumeration(
625 "Accessibility.ScreenAI.MainContentExtraction.Error.SnapshotProto",
626 client_type);
627 return false;
628 }
629
630 base::TimeTicks start_time = base::TimeTicks::Now();
Ramin Halavatiaa67ee52025-04-25 05:15:53631 base::SequenceBound<HangTimer> hang_timer(background_task_runner_,
632 /*is_ocr=*/false);
633 hang_timer.AsyncCall(&HangTimer::StartTimer);
Ramin Halavati62e034d2024-08-02 08:58:22634 content_node_ids =
635 library_->ExtractMainContent(converted_snapshot->serialized_proto);
Ramin Halavatiaa67ee52025-04-25 05:15:53636 hang_timer.AsyncCall(&HangTimer::Stop);
Ramin Halavatib3fe2fe2025-03-20 16:53:03637 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
638
639 bool successful =
640 content_node_ids.has_value() && content_node_ids->size() > 0;
Ramin Halavati81b6d922024-06-07 14:20:34641 base::UmaHistogramBoolean(
Ramin Halavatib3fe2fe2025-03-20 16:53:03642 "Accessibility.ScreenAI.MainContentExtraction.Successful2", successful);
643
644 if (!content_node_ids.has_value()) {
645 base::UmaHistogramEnumeration(
646 "Accessibility.ScreenAI.MainContentExtraction.Error.ResultNull",
647 client_type);
648 } else if (content_node_ids->empty()) {
649 base::UmaHistogramEnumeration(
650 "Accessibility.ScreenAI.MainContentExtraction.Error.ResultEmpty",
651 client_type);
652 }
653
Ramin Halavatiac1ee2922025-05-21 17:17:17654 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavatib3fe2fe2025-03-20 16:53:03655 if (successful) {
656 base::UmaHistogramTimes(
657 "Accessibility.ScreenAI.MainContentExtraction.Latency.Success",
658 elapsed_time);
Ramin Halavati5aad701d2023-05-09 17:23:29659 VLOG(2) << "Screen2x returned " << content_node_ids->size() << " node ids.";
Abigail Klein18673702024-03-05 20:59:06660 return true;
661 } else {
Ramin Halavatib3fe2fe2025-03-20 16:53:03662 base::UmaHistogramTimes(
663 "Accessibility.ScreenAI.MainContentExtraction.Latency.Failure",
664 elapsed_time);
Abigail Klein18673702024-03-05 20:59:06665 VLOG(0) << "Screen2x returned no results.";
666 return false;
Ramin Halavati5aad701d2023-05-09 17:23:29667 }
Abigail Klein18673702024-03-05 20:59:06668}
Ramin Halavati89a36c92023-05-05 15:01:03669
Abigail Klein18673702024-03-05 20:59:06670ui::AXNodeID ScreenAIService::ComputeMainNodeForTesting(
671 const ui::AXTree* tree,
672 const std::vector<ui::AXNodeID>& content_node_ids) {
673 return ComputeMainNode(tree, content_node_ids);
Abigail Kleindfdde352023-01-27 21:03:10674}
675
Ramin Halavaticf155012025-05-05 15:50:01676void ScreenAIService::StartShutDownOnIdleTimer() {
677 if (!idle_checking_timer_) {
678 idle_checking_timer_ = std::make_unique<base::RepeatingTimer>();
679 idle_checking_timer_->Start(FROM_HERE, kIdleCheckingDelay, this,
680 &ScreenAIService::ShutDownOnIdle);
681 }
682}
683
Ramin Halavatibe45dde2025-05-01 04:19:10684void ScreenAIService::ShutDownOnIdle() {
Ramin Halavati2acf2362024-08-02 14:22:53685 const base::TimeTicks kIdlenessThreshold =
686 base::TimeTicks::Now() - kIdleCheckingDelay;
Ramin Halavatibe45dde2025-05-01 04:19:10687 if (ocr_last_used_ < kIdlenessThreshold &&
688 mce_last_used_ < kIdlenessThreshold) {
Ramin Halavatif26d7d42025-03-12 16:04:46689 screen_ai_shutdown_handler_->ShuttingDownOnIdle();
Ramin Halavatidc0b2e512024-08-16 17:39:09690 base::Process::TerminateCurrentProcessImmediately(0);
Ramin Halavati2acf2362024-08-02 14:22:53691 }
Ramin Halavati3842e7e2024-07-31 04:39:21692}
693
Ramin Halavati26dcce22022-02-23 13:11:14694} // namespace screen_ai