blob: fe5322bcb934478340e890dbb43f23db8123742e [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 Halavati5ff855c2022-04-27 16:30:4821#include "base/process/process.h"
Ramin Halavati65f96fb2025-02-24 16:45:1822#include "base/strings/stringprintf.h"
Ramin Halavati26679592025-03-13 07:24:5723#include "base/system/sys_info.h"
Sean Maher5b9af51f2022-11-21 15:32:4724#include "base/task/single_thread_task_runner.h"
Ramin Halavati55fdf4d2022-11-07 06:35:1825#include "base/task/thread_pool.h"
Ramin Halavatiaa67ee52025-04-25 05:15:5326#include "base/threading/sequence_bound.h"
Ramin Halavati65f96fb2025-02-24 16:45:1827#include "components/crash/core/common/crash_key.h"
Abigail Klein7a63c572024-02-28 20:45:0928#include "services/screen_ai/buildflags/buildflags.h"
Ramin Halavati4c47eb82024-12-03 19:06:4229#include "services/screen_ai/proto/chrome_screen_ai.pb.h"
Abigail Klein7a63c572024-02-28 20:45:0930#include "services/screen_ai/proto/main_content_extractor_proto_convertor.h"
31#include "services/screen_ai/proto/visual_annotator_proto_convertor.h"
Ramin Halavati4c47eb82024-12-03 19:06:4232#include "services/screen_ai/public/cpp/metrics.h"
Abigail Klein7a63c572024-02-28 20:45:0933#include "services/screen_ai/public/cpp/utilities.h"
Ramin Halavatieddadb62022-05-04 17:29:4934#include "ui/accessibility/accessibility_features.h"
Ramin Halavati4f904d0a2024-06-25 15:32:3235#include "ui/accessibility/ax_node.h"
36#include "ui/accessibility/ax_tree.h"
Ramin Halavati84231072022-08-17 08:02:2037#include "ui/accessibility/ax_tree_id.h"
Nektarios Paisiosf73d6972022-06-04 11:32:2538#include "ui/gfx/geometry/rect_f.h"
Ramin Halavati367352a2022-04-14 06:00:2339
Ramin Halavatia9b50a102024-02-07 18:37:2340#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Abigail Klein7a63c572024-02-28 20:45:0941#include "services/screen_ai/screen_ai_library_wrapper_fake.h"
Ramin Halavati767b8fc2024-02-02 06:23:0842#else
Abigail Klein7a63c572024-02-28 20:45:0943#include "services/screen_ai/screen_ai_library_wrapper_impl.h"
Ramin Halavati767b8fc2024-02-02 06:23:0844#endif
45
Ramin Halavati69bf7752022-04-04 09:58:1546namespace screen_ai {
Ramin Halavati26dcce22022-02-23 13:11:1447
Ramin Halavati6d4fc2b2022-06-16 15:33:3548namespace {
49
Ramin Halavati49c2b5c2025-04-25 05:43:0150// Maximum image dimension that OCR service processes. Images with width or
51// height larger than this threshold are downsampled before processing.
52// TODO(crbug.com/413318481): Get this threshold from the library.
53constexpr int kMaxOcrDimension = 2048;
Ramin Halavati4805c882025-03-03 18:28:4854
Ramin Halavati2acf2362024-08-02 14:22:5355// How often it would be checked that the service is idle and can be shutdown.
Ramin Halavatibe45dde2025-05-01 04:19:1056// LINT.IfChange(kIdleCheckingDelay)
Ramin Halavati1520b702025-04-23 15:24:1857constexpr base::TimeDelta kIdleCheckingDelay = base::Seconds(3);
Ramin Halavatibe45dde2025-05-01 04:19:1058// LINT.ThenChange(//chrome/browser/screen_ai/optical_character_recognizer_browsertest.cc:kServiceIdleCheckingDelay)
Ramin Halavati6ff56412024-08-13 07:15:5459
Ramin Halavati4825419e2025-04-02 05:49:4660// How long to wait for a request to the library be responded, before assuming
61// that the library is not responsive.
Ramin Halavatiaa67ee52025-04-25 05:15:5362constexpr base::TimeDelta kMaxWaitForResponseTime = base::Seconds(10);
Ramin Halavati4825419e2025-04-02 05:49:4663
Ramin Halavatiad969ef2024-05-21 06:59:4764// These values are persisted to logs. Entries should not be renumbered and
65// numeric values should never be reused.
Ramin Halavati29cc1152024-10-27 17:30:2866// See `screen_ai_service.mojom` for more info.
Ramin Halavatib3fe2fe2025-03-20 16:53:0367// LINT.IfChange(OcrClientType)
Ramin Halavatiad969ef2024-05-21 06:59:4768enum class OcrClientTypeForMetrics {
69 kTest = 0,
70 kPdfViewer = 1,
71 kLocalSearch = 2,
72 kCameraApp = 3,
Ramin Halavati29cc1152024-10-27 17:30:2873 kNotUsed = 4, // Can be used for a new client.
Ramin Halavatiad969ef2024-05-21 06:59:4774 kMediaApp = 5,
Michelle Chen5192d522024-10-22 23:38:2275 kScreenshotTextDetection,
76 kMaxValue = kScreenshotTextDetection
Ramin Halavatiad969ef2024-05-21 06:59:4777};
Ramin Halavatib3fe2fe2025-03-20 16:53:0378// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:OcrClientType)
79
80// These values are persisted to logs. Entries should not be renumbered and
81// numeric values should never be reused.
82// See `screen_ai_service.mojom` for more info.
83// LINT.IfChange(MainContentExtractionClientType)
84enum class MainContentExtractionClientTypeForMetrics {
85 kTest = 0,
86 kReadingMode = 1,
87 kMainNode = 2,
88 kMahi = 3,
89 kMaxValue = kMahi
90};
91// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:MainContentExtractionClientType)
Ramin Halavatiad969ef2024-05-21 06:59:4792
93OcrClientTypeForMetrics GetClientType(mojom::OcrClientType client_type) {
94 switch (client_type) {
95 case mojom::OcrClientType::kTest:
96 CHECK_IS_TEST();
97 return OcrClientTypeForMetrics::kTest;
98 case mojom::OcrClientType::kPdfViewer:
99 return OcrClientTypeForMetrics::kPdfViewer;
100 case mojom::OcrClientType::kLocalSearch:
101 return OcrClientTypeForMetrics::kLocalSearch;
102 case mojom::OcrClientType::kCameraApp:
103 return OcrClientTypeForMetrics::kCameraApp;
Ramin Halavatiad969ef2024-05-21 06:59:47104 case mojom::OcrClientType::kMediaApp:
105 return OcrClientTypeForMetrics::kMediaApp;
Michelle Chen5192d522024-10-22 23:38:22106 case mojom::OcrClientType::kScreenshotTextDetection:
107 return OcrClientTypeForMetrics::kScreenshotTextDetection;
Ramin Halavatiad969ef2024-05-21 06:59:47108 }
109}
110
Ramin Halavatib3fe2fe2025-03-20 16:53:03111MainContentExtractionClientTypeForMetrics GetClientType(
112 mojom::MceClientType client_type) {
113 switch (client_type) {
114 case mojom::MceClientType::kTest:
115 CHECK_IS_TEST();
116 return MainContentExtractionClientTypeForMetrics::kTest;
117 case mojom::MceClientType::kReadingMode:
118 return MainContentExtractionClientTypeForMetrics::kReadingMode;
119 case mojom::MceClientType::kMainNode:
120 return MainContentExtractionClientTypeForMetrics::kMainNode;
121 case mojom::MceClientType::kMahi:
122 return MainContentExtractionClientTypeForMetrics::kMahi;
123 }
124}
125
Ramin Halavati89a36c92023-05-05 15:01:03126ui::AXTreeUpdate ConvertVisualAnnotationToTreeUpdate(
Ramin Halavatidcb09a172024-07-15 08:28:55127 std::optional<chrome_screen_ai::VisualAnnotation>& annotation_proto,
Ramin Halavati89a36c92023-05-05 15:01:03128 const gfx::Rect& image_rect) {
129 if (!annotation_proto) {
130 VLOG(0) << "Screen AI library could not process snapshot or no OCR data.";
131 return ui::AXTreeUpdate();
132 }
133
134 return VisualAnnotationToAXTreeUpdate(*annotation_proto, image_rect);
135}
136
Abigail Klein18673702024-03-05 20:59:06137ui::AXNodeID ComputeMainNode(
138 const ui::AXTree* tree,
139 const std::vector<ui::AXNodeID>& content_node_ids) {
140 ui::AXNode* front = tree->GetFromId(content_node_ids.front());
141 ui::AXNode* back = tree->GetFromId(content_node_ids.back());
142 ui::AXNode* main = front->GetLowestCommonAncestor(*back);
143 return main->id();
144}
145
Ramin Halavati65f96fb2025-02-24 16:45:18146#if !BUILDFLAG(USE_FAKE_SCREEN_AI)
147void SetCPUInstructionSetCrashKey() {
148#if defined(ARCH_CPU_X86_FAMILY)
149 base::CPU();
150 // Report cpu micro architecture in case of crash.
151 static crash_reporter::CrashKeyString<3> cpu_info("intel_micro_architecture");
152 cpu_info.Set(
153 base::StringPrintf("%i", base::CPU().GetIntelMicroArchitecture()));
154#endif
155}
156#endif
157
Ramin Halavati26679592025-03-13 07:24:57158// Return a maximum 11 character string with the signature of available and
159// total memory, both in MB and capped to 99999.
160std::string GetMemoryStatusForCrashKey() {
161 int total_memory = base::SysInfo::AmountOfPhysicalMemoryMB();
162 int available_memory = static_cast<int>(
163 base::SysInfo::AmountOfAvailablePhysicalMemory() / (1024 * 1024));
164
165 // Cap the number of digits for crash report.
166 total_memory = std::min(total_memory, 99999);
167 available_memory = std::min(available_memory, 99999);
168 return base::StringPrintf("%i,%i", available_memory, total_memory);
169}
170
Ramin Halavatiaa67ee52025-04-25 05:15:53171class HangTimer : public base::OneShotTimer {
172 public:
173 explicit HangTimer(bool is_ocr) : is_ocr_(is_ocr) {}
174
175 void StartTimer() {
176 Start(FROM_HERE, kMaxWaitForResponseTime,
177 base::BindOnce(
178 [](bool request_is_ocr) {
179 base::UmaHistogramBoolean(
180 "Accessibility.ScreenAI.Service.NotReponsive.IsOCR",
181 request_is_ocr);
182 base::Process::TerminateCurrentProcessImmediately(0);
183 },
184 is_ocr_));
185 }
186
187 private:
188 bool is_ocr_;
189};
190
Ramin Halavati26ae6b72022-11-10 06:45:05191} // namespace
192
Ramin Halavati39bab0d2024-01-30 06:05:01193// The library accepts simple pointers to model data retrieval functions, hence
194// callback functions with linked object are not safe to pass.
Ramin Halavati63d9c252024-07-29 06:59:17195// This global variable keeps the pointer the only instance of this class.
196ModelDataHolder* g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50197
Ramin Halavati63d9c252024-07-29 06:59:17198// Keeps the handles of model files, and replies to calls for copying their
199// content.
200class ModelDataHolder {
Ramin Halavati8ced6392023-09-25 07:27:50201 public:
Ramin Halavati63d9c252024-07-29 06:59:17202 ModelDataHolder() {
203 CHECK(!g_model_data_holder_instance);
204 g_model_data_holder_instance = this;
205 }
Ramin Halavati8ced6392023-09-25 07:27:50206
Ramin Halavati63d9c252024-07-29 06:59:17207 ModelDataHolder(const ModelDataHolder&) = delete;
208 ModelDataHolder& operator=(const ModelDataHolder&) = delete;
209
210 ~ModelDataHolder() {
211 CHECK_EQ(g_model_data_holder_instance, this);
212 g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50213 }
214
215 // Returns 0 if file is not found.
216 static uint32_t GetDataSize(const char* relative_file_path) {
Ramin Halavati63d9c252024-07-29 06:59:17217 CHECK(g_model_data_holder_instance);
218 base::File* model_file =
219 g_model_data_holder_instance->GetModelFile(relative_file_path);
220 return model_file ? model_file->GetLength() : 0;
Ramin Halavati8ced6392023-09-25 07:27:50221 }
222
Ramin Halavati63d9c252024-07-29 06:59:17223 // Copies content of the file in `relative_file_path` to `buffer`. Expects
224 // that `buffer_size` would be enough for the entire file content.
Ramin Halavati8ced6392023-09-25 07:27:50225 static void CopyData(const char* relative_file_path,
226 uint32_t buffer_size,
227 char* buffer) {
Ramin Halavati63d9c252024-07-29 06:59:17228 CHECK(g_model_data_holder_instance);
229 base::File* model_file =
230 g_model_data_holder_instance->GetModelFile(relative_file_path);
231 CHECK(model_file);
232
233 int64_t length = model_file->GetLength();
234 CHECK_GE(buffer_size, length);
Tom Sepeze9b4dd552024-08-14 22:44:29235 CHECK_EQ(UNSAFE_TODO(model_file->Read(0, buffer, length)), length);
Ramin Halavati8ced6392023-09-25 07:27:50236 }
237
Ramin Halavati63d9c252024-07-29 06:59:17238 void AddModelFiles(base::flat_map<base::FilePath, base::File> model_files) {
239 for (auto& model_file : model_files) {
240 model_files_[model_file.first.MaybeAsASCII()] =
241 std::move(model_file.second);
Ramin Halavati39bab0d2024-01-30 06:05:01242 }
243 }
Ramin Halavati8ced6392023-09-25 07:27:50244
Ramin Halavati63d9c252024-07-29 06:59:17245 // Returns the file handle for `relative_file_path` if it exists.
246 base::File* GetModelFile(const char* relative_file_path) {
247 if (!base::Contains(model_files_, relative_file_path)) {
248 return nullptr;
249 }
250 return &model_files_[relative_file_path];
251 }
252
Ramin Halavati8ced6392023-09-25 07:27:50253 private:
Ramin Halavati63d9c252024-07-29 06:59:17254 std::map<std::string, base::File> model_files_;
Ramin Halavati8ced6392023-09-25 07:27:50255};
256
Ramin Halavati26ae6b72022-11-10 06:45:05257ScreenAIService::ScreenAIService(
Ramin Halavatic1e4fa92023-05-17 17:22:06258 mojo::PendingReceiver<mojom::ScreenAIServiceFactory> receiver)
259 : factory_receiver_(this, std::move(receiver)),
260 ocr_receiver_(this),
Ramin Halavatiad969ef2024-05-21 06:59:47261 main_content_extraction_receiver_(this) {
Ramin Halavati6ff56412024-08-13 07:15:54262 screen2x_main_content_extractors_.set_disconnect_handler(
Ramin Halavati1520b702025-04-23 15:24:18263 base::BindRepeating(&ScreenAIService::MceReceiverDisconnected,
Ramin Halavati6ff56412024-08-13 07:15:54264 weak_ptr_factory_.GetWeakPtr()));
265 screen_ai_annotators_.set_disconnect_handler(
266 base::BindRepeating(&ScreenAIService::OcrReceiverDisconnected,
267 weak_ptr_factory_.GetWeakPtr()));
Ramin Halavati63d9c252024-07-29 06:59:17268 model_data_holder_ = std::make_unique<ModelDataHolder>();
Ramin Halavati2acf2362024-08-02 14:22:53269 idle_checking_timer_ = std::make_unique<base::RepeatingTimer>();
270 idle_checking_timer_->Start(FROM_HERE, kIdleCheckingDelay, this,
Ramin Halavatibe45dde2025-05-01 04:19:10271 &ScreenAIService::ShutDownOnIdle);
Ramin Halavatiaa67ee52025-04-25 05:15:53272
273 background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
274 {base::TaskPriority::BEST_EFFORT,
275 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
Ramin Halavatiad969ef2024-05-21 06:59:47276}
Ramin Halavati26ae6b72022-11-10 06:45:05277
278ScreenAIService::~ScreenAIService() = default;
279
Ramin Halavati8ced6392023-09-25 07:27:50280void ScreenAIService::LoadLibrary(const base::FilePath& library_path) {
Abigail Klein7a63c572024-02-28 20:45:09281 // The ScopedBlockingCall in LoadLibrary guarantees that this is not run on
282 // the UI thread.
Ramin Halavatia9b50a102024-02-07 18:37:23283#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Ramin Halavati767b8fc2024-02-02 06:23:08284 library_ = std::make_unique<ScreenAILibraryWrapperFake>();
285#else
286 library_ = std::make_unique<ScreenAILibraryWrapperImpl>();
Ramin Halavati65f96fb2025-02-24 16:45:18287
288 // TODO(crbug.com/381256355): Remove when the library is SSE3 compatible.
289 SetCPUInstructionSetCrashKey();
Ramin Halavati767b8fc2024-02-02 06:23:08290#endif
Ramin Halavati8ced6392023-09-25 07:27:50291
292 bool load_sucessful = library_->Load(library_path);
293 base::UmaHistogramBoolean("Accessibility.ScreenAI.Library.Initialized",
294 load_sucessful);
295
296 if (!load_sucessful) {
297 library_.reset();
298 return;
299 }
300
301 uint32_t version_major;
302 uint32_t version_minor;
303 library_->GetLibraryVersion(version_major, version_minor);
304 VLOG(2) << "Screen AI library version: " << version_major << "."
305 << version_minor;
306
Georg Neis06e387772024-12-25 07:32:22307#if BUILDFLAG(IS_CHROMEOS)
Ramin Halavati8ced6392023-09-25 07:27:50308 library_->SetLogger();
309#endif
310
311 if (features::IsScreenAIDebugModeEnabled()) {
312 library_->EnableDebugMode();
313 }
314
Ramin Halavati63d9c252024-07-29 06:59:17315 library_->SetFileContentFunctions(&ModelDataHolder::GetDataSize,
316 &ModelDataHolder::CopyData);
Ramin Halavati8ced6392023-09-25 07:27:50317}
318
Ramin Halavatic1e4fa92023-05-17 17:22:06319void ScreenAIService::InitializeMainContentExtraction(
Ramin Halavatid139a0f2023-02-28 13:25:28320 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07321 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06322 mojo::PendingReceiver<mojom::MainContentExtractionService>
323 main_content_extractor_service_receiver,
324 InitializeMainContentExtractionCallback callback) {
325 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50326 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06327 }
328
329 if (!library_) {
330 std::move(callback).Run(false);
331 base::Process::TerminateCurrentProcessImmediately(-1);
332 }
333
Ramin Halavati63d9c252024-07-29 06:59:17334 model_data_holder_->AddModelFiles(std::move(model_files));
Ramin Halavatib044504d2022-10-24 06:36:34335
Ramin Halavati8ced6392023-09-25 07:27:50336 bool init_successful = library_->InitMainContentExtraction();
Ramin Halavati1d57c2d2023-05-24 05:18:24337 base::UmaHistogramBoolean(
338 "Accessibility.ScreenAI.MainContentExtraction.Initialized",
339 init_successful);
340 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06341 std::move(callback).Run(false);
Ramin Halavati89a36c92023-05-05 15:01:03342 return;
Ramin Halavatid139a0f2023-02-28 13:25:28343 }
Ramin Halavati89a36c92023-05-05 15:01:03344
Ramin Halavatic1e4fa92023-05-17 17:22:06345 // This interface should be created only once.
346 CHECK(!main_content_extraction_receiver_.is_bound());
347
348 main_content_extraction_receiver_.Bind(
349 std::move(main_content_extractor_service_receiver));
350
351 std::move(callback).Run(true);
Ramin Halavati1520b702025-04-23 15:24:18352 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavatic1e4fa92023-05-17 17:22:06353}
354
355void ScreenAIService::InitializeOCR(
356 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07357 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06358 mojo::PendingReceiver<mojom::OCRService> ocr_service_receiver,
359 InitializeOCRCallback callback) {
Ramin Halavati26679592025-03-13 07:24:57360 static crash_reporter::CrashKeyString<12> memory_ocr_init(
361 "screen_ai_mem_ocr_init");
362 memory_ocr_init.Set(GetMemoryStatusForCrashKey());
Ramin Halavatic1e4fa92023-05-17 17:22:06363 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50364 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06365 }
366
367 if (!library_) {
368 std::move(callback).Run(false);
369 base::Process::TerminateCurrentProcessImmediately(-1);
370 }
371
Ramin Halavati63d9c252024-07-29 06:59:17372 model_data_holder_->AddModelFiles(std::move(model_files));
373
Ramin Halavati0e2ae5742023-11-10 05:57:54374 bool init_successful = library_->InitOCR();
Ramin Halavati35d129f2023-06-22 16:37:09375 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Initialized",
Ramin Halavati1d57c2d2023-05-24 05:18:24376 init_successful);
Ramin Halavati480584332023-08-02 06:38:40377
Ramin Halavati1d57c2d2023-05-24 05:18:24378 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06379 std::move(callback).Run(false);
380 return;
381 }
382
383 // This interface should be created only once.
384 CHECK(!ocr_receiver_.is_bound());
385
386 ocr_receiver_.Bind(std::move(ocr_service_receiver));
387
388 std::move(callback).Run(true);
Ramin Halavati2acf2362024-08-02 14:22:53389 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavatib044504d2022-10-24 06:36:34390}
Ramin Halavati26dcce22022-02-23 13:11:14391
Ramin Halavatif26d7d42025-03-12 16:04:46392void ScreenAIService::BindShutdownHandler(
393 mojo::PendingRemote<mojom::ScreenAIServiceShutdownHandler>
394 shutdown_handler) {
395 DCHECK(!screen_ai_shutdown_handler_.is_bound());
396 screen_ai_shutdown_handler_.Bind(std::move(shutdown_handler));
397}
398
Ramin Halavati26dcce22022-02-23 13:11:14399void ScreenAIService::BindAnnotator(
400 mojo::PendingReceiver<mojom::ScreenAIAnnotator> annotator) {
401 screen_ai_annotators_.Add(this, std::move(annotator));
402}
403
Ramin Halavatieddadb62022-05-04 17:29:49404void ScreenAIService::BindMainContentExtractor(
405 mojo::PendingReceiver<mojom::Screen2xMainContentExtractor>
406 main_content_extractor) {
Ramin Halavati6ff56412024-08-13 07:15:54407 screen2x_main_content_extractors_.Add(this,
408 std::move(main_content_extractor));
Ramin Halavatieddadb62022-05-04 17:29:49409}
410
Arthur Sonzognic571efb2024-01-26 20:26:18411std::optional<chrome_screen_ai::VisualAnnotation>
Ramin Halavati7716b032024-08-19 14:56:20412ScreenAIService::PerformOcrAndRecordMetrics(const SkBitmap& image) {
Ramin Halavati26679592025-03-13 07:24:57413 static crash_reporter::CrashKeyString<12> memory_perform_ocr(
414 "screen_ai_mem_ocr_perform");
415 memory_perform_ocr.Set(GetMemoryStatusForCrashKey());
416
Ramin Halavati7716b032024-08-19 14:56:20417 CHECK(base::Contains(ocr_client_types_,
418 screen_ai_annotators_.current_receiver()));
Ramin Halavati4805c882025-03-03 18:28:48419 OcrClientTypeForMetrics client_type = GetClientType(
420 ocr_client_types_.find(screen_ai_annotators_.current_receiver())->second);
Ramin Halavatiad969ef2024-05-21 06:59:47421 base::UmaHistogramEnumeration("Accessibility.ScreenAI.OCR.ClientType",
Ramin Halavati4805c882025-03-03 18:28:48422 client_type);
Ramin Halavatiad969ef2024-05-21 06:59:47423
Ramin Halavati2acf2362024-08-02 14:22:53424 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavatiaa67ee52025-04-25 05:15:53425 base::SequenceBound<HangTimer> hang_timer(background_task_runner_,
426 /*is_ocr=*/true);
427 hang_timer.AsyncCall(&HangTimer::StartTimer);
Ramin Halavati7252cd02023-05-10 07:21:33428 auto result = library_->PerformOcr(image);
Ramin Halavatiaa67ee52025-04-25 05:15:53429 hang_timer.AsyncCall(&base::OneShotTimer::Stop);
Ramin Halavati2acf2362024-08-02 14:22:53430 base::TimeDelta elapsed_time = base::TimeTicks::Now() - ocr_last_used_;
Ramin Halavati4825419e2025-04-02 05:49:46431
Ramin Halavati2650ebe2023-11-27 19:56:35432 int lines_count = result ? result->lines_size() : 0;
433 VLOG(1) << "OCR returned " << lines_count << " lines in " << elapsed_time;
Ramin Halavati7252cd02023-05-10 07:21:33434
Ramin Halavati18aba872024-08-23 14:33:05435 if (!result) {
436 base::UmaHistogramEnumeration(
Ramin Halavati4805c882025-03-03 18:28:48437 "Accessibility.ScreenAI.OCR.Failed.ClientType", client_type);
Ramin Halavati18aba872024-08-23 14:33:05438 }
Ramin Halavati49c2b5c2025-04-25 05:43:01439
440 if (image.width() > kMaxOcrDimension || image.height() > kMaxOcrDimension) {
Ramin Halavati4805c882025-03-03 18:28:48441 base::UmaHistogramEnumeration(
Ramin Halavati49c2b5c2025-04-25 05:43:01442 "Accessibility.ScreenAI.OCR.Downsampled.ClientType", client_type);
Ramin Halavati4805c882025-03-03 18:28:48443 }
444
Ramin Halavati81b6d922024-06-07 14:20:34445 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Successful",
446 result.has_value());
Ramin Halavati2650ebe2023-11-27 19:56:35447 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount",
448 lines_count);
Ramin Halavatia0548b262023-06-27 05:56:47449 base::UmaHistogramCounts10M("Accessibility.ScreenAI.OCR.ImageSize10M",
Ramin Halavati49c2b5c2025-04-25 05:43:01450 image.width() * image.height());
451 if (image.width() < kMaxOcrDimension && image.height() < kMaxOcrDimension) {
452 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.NotDownsampled",
Ramin Halavati3ae33ca2024-01-01 12:58:39453 elapsed_time);
454 } else {
Ramin Halavati49c2b5c2025-04-25 05:43:01455 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.Downsampled",
Ramin Halavati3ae33ca2024-01-01 12:58:39456 elapsed_time);
457 }
Ramin Halavati2650ebe2023-11-27 19:56:35458
Ramin Halavati7716b032024-08-19 14:56:20459 // MediaApp provides OCR for ChromeOS PDF viewer.
Ramin Halavati4805c882025-03-03 18:28:48460 if (client_type == OcrClientTypeForMetrics::kPdfViewer ||
461 client_type == OcrClientTypeForMetrics::kMediaApp) {
Ramin Halavati2650ebe2023-11-27 19:56:35462 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount.PDF",
463 lines_count);
464 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Time.PDF",
465 elapsed_time);
Ramin Halavati696b8d5c2025-02-28 16:41:58466 base::UmaHistogramCounts10M(
467 lines_count ? "Accessibility.ScreenAI.OCR.ImageSize.PDF.WithText"
468 : "Accessibility.ScreenAI.OCR.ImageSize.PDF.NoText",
Ramin Halavati49c2b5c2025-04-25 05:43:01469 image.width() * image.height());
Ramin Halavati4c47eb82024-12-03 19:06:42470
Ramin Halavati01267d12025-01-21 18:11:30471 if (result.has_value()) {
472 std::optional<uint64_t> most_detected_language =
473 GetMostDetectedLanguageInOcrData(*result);
474 if (most_detected_language.has_value()) {
475 base::UmaHistogramSparse(
476 "Accessibility.ScreenAI.OCR.MostDetectedLanguage.PDF",
477 most_detected_language.value());
478 }
Ramin Halavati4c47eb82024-12-03 19:06:42479 }
Ramin Halavati2650ebe2023-11-27 19:56:35480 }
481
Ramin Halavati7252cd02023-05-10 07:21:33482 return result;
483}
484
Ramin Halavatiad969ef2024-05-21 06:59:47485void ScreenAIService::SetClientType(mojom::OcrClientType client_type) {
486 ocr_client_types_[screen_ai_annotators_.current_receiver()] = client_type;
487}
488
Ramin Halavatid0d6231b2025-03-20 05:36:43489void ScreenAIService::SetClientType(mojom::MceClientType client_type) {
490 mce_client_types_[screen2x_main_content_extractors_.current_receiver()] =
491 client_type;
492}
493
Ramin Halavati1520b702025-04-23 15:24:18494void ScreenAIService::OcrReceiverDisconnected() {
495 auto entry = ocr_client_types_.find(screen_ai_annotators_.current_receiver());
496 if (entry != ocr_client_types_.end()) {
497 ocr_client_types_.erase(entry);
498 }
499 // Modify last used time to ensure the service does not shutdown while a
500 // client is disconnecting.
501 ocr_last_used_ = base::TimeTicks::Now();
502}
503
504void ScreenAIService::MceReceiverDisconnected() {
505 auto entry = mce_client_types_.find(
506 screen2x_main_content_extractors_.current_receiver());
507 if (entry != mce_client_types_.end()) {
508 mce_client_types_.erase(entry);
509 }
510 // Modify last used time to ensure the service does not shutdown while a
511 // client is disconnecting.
512 mce_last_used_ = base::TimeTicks::Now();
513}
514
Ramin Halavati062042f2025-05-02 05:47:57515void ScreenAIService::GetMaxImageDimension(
516 GetMaxImageDimensionCallback callback) {
517 std::move(callback).Run(kMaxOcrDimension);
518}
519
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07520void ScreenAIService::PerformOcrAndReturnAnnotation(
521 const SkBitmap& image,
522 PerformOcrAndReturnAnnotationCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18523 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati7716b032024-08-19 14:56:20524 PerformOcrAndRecordMetrics(image);
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07525
Ramin Halavati89a36c92023-05-05 15:01:03526 if (annotation_proto) {
527 std::move(callback).Run(ConvertProtoToVisualAnnotation(*annotation_proto));
528 return;
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07529 }
Ramin Halavati89a36c92023-05-05 15:01:03530
531 std::move(callback).Run(mojom::VisualAnnotation::New());
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07532}
533
Kyungjun Lee90461522023-04-25 06:50:09534void ScreenAIService::PerformOcrAndReturnAXTreeUpdate(
Ramin Halavatib446c022023-03-29 15:36:36535 const SkBitmap& image,
Kyungjun Lee90461522023-04-25 06:50:09536 PerformOcrAndReturnAXTreeUpdateCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18537 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati7716b032024-08-19 14:56:20538 PerformOcrAndRecordMetrics(image);
Ramin Halavati89a36c92023-05-05 15:01:03539 ui::AXTreeUpdate update = ConvertVisualAnnotationToTreeUpdate(
540 annotation_proto, gfx::Rect(image.width(), image.height()));
Kyungjun Lee90461522023-04-25 06:50:09541
Ramin Halavati89a36c92023-05-05 15:01:03542 // The original caller is always replied to, and an empty AXTreeUpdate tells
543 // that the annotation function was not successful.
544 std::move(callback).Run(update);
Ramin Halavatib446c022023-03-29 15:36:36545}
546
547void ScreenAIService::ExtractMainContent(const ui::AXTreeUpdate& snapshot,
Ramin Halavatib446c022023-03-29 15:36:36548 ExtractMainContentCallback callback) {
Abigail Klein18673702024-03-05 20:59:06549 ui::AXTree tree;
550 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03551 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
552 content_node_ids);
Abigail Klein18673702024-03-05 20:59:06553
554 if (success) {
555 std::move(callback).Run(*content_node_ids);
556 } else {
557 std::move(callback).Run(std::vector<int32_t>());
558 }
559}
560
561void ScreenAIService::ExtractMainNode(const ui::AXTreeUpdate& snapshot,
562 ExtractMainNodeCallback callback) {
563 ui::AXTree tree;
564 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03565 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
566 content_node_ids);
Abigail Klein18673702024-03-05 20:59:06567
568 if (success) {
569 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
570 std::move(callback).Run(main_node_id);
571 } else {
572 std::move(callback).Run(ui::kInvalidAXNodeID);
573 }
574}
575
Mark Schillaci79c82812025-03-07 21:46:37576void ScreenAIService::IdentifyMainNode(const ui::AXTreeUpdate& snapshot,
577 IdentifyMainNodeCallback callback) {
578 ui::AXTree tree;
579 std::optional<std::vector<int32_t>> content_node_ids;
Ramin Halavatib3fe2fe2025-03-20 16:53:03580 bool success = ExtractMainContentInternalAndRecordMetrics(snapshot, tree,
581 content_node_ids);
Mark Schillaci79c82812025-03-07 21:46:37582
583 if (success) {
584 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
585 std::move(callback).Run(tree.GetAXTreeID(), main_node_id);
586 } else {
587 std::move(callback).Run(ui::AXTreeIDUnknown(), ui::kInvalidAXNodeID);
588 }
589}
590
Ramin Halavatib3fe2fe2025-03-20 16:53:03591bool ScreenAIService::ExtractMainContentInternalAndRecordMetrics(
Abigail Klein18673702024-03-05 20:59:06592 const ui::AXTreeUpdate& snapshot,
593 ui::AXTree& tree,
594 std::optional<std::vector<int32_t>>& content_node_ids) {
Ramin Halavatid0d6231b2025-03-20 05:36:43595 CHECK(base::Contains(mce_client_types_,
596 screen2x_main_content_extractors_.current_receiver()));
Ramin Halavati1520b702025-04-23 15:24:18597 mce_last_used_ = base::TimeTicks::Now();
Ramin Halavatib3fe2fe2025-03-20 16:53:03598 MainContentExtractionClientTypeForMetrics client_type = GetClientType(
599 mce_client_types_[screen2x_main_content_extractors_.current_receiver()]);
600
601 static crash_reporter::CrashKeyString<2> cpu_info(
602 "main_content_extraction_client");
603 cpu_info.Set(base::StringPrintf("%i", static_cast<int>(client_type)));
604
Ramin Halavatib446c022023-03-29 15:36:36605 // Early return if input is empty.
606 if (snapshot.nodes.empty()) {
Ramin Halavatib3fe2fe2025-03-20 16:53:03607 base::UmaHistogramEnumeration(
608 "Accessibility.ScreenAI.MainContentExtraction.Error.SnapshotEmpty",
609 client_type);
Abigail Klein18673702024-03-05 20:59:06610 return false;
Ramin Halavatib33fc0c2022-05-06 09:32:22611 }
Ramin Halavatie2549a92022-08-02 07:43:19612
Abigail Klein18673702024-03-05 20:59:06613 // Deserialize the snapshot and reserialize it to a view hierarchy proto.
Ramin Halavatib3fe2fe2025-03-20 16:53:03614 if (!tree.Unserialize(snapshot)) {
615 base::UmaHistogramEnumeration(
616 "Accessibility.ScreenAI.MainContentExtraction.Error."
617 "SnapshotUnserialize",
618 client_type);
Ramin Halavati62e034d2024-08-02 08:58:22619 return false;
620 }
621
Ramin Halavatib3fe2fe2025-03-20 16:53:03622 std::optional<ViewHierarchyAndTreeSize> converted_snapshot =
623 SnapshotToViewHierarchy(tree);
624 if (!converted_snapshot) {
625 base::UmaHistogramEnumeration(
626 "Accessibility.ScreenAI.MainContentExtraction.Error.SnapshotProto",
627 client_type);
628 return false;
629 }
630
631 base::TimeTicks start_time = base::TimeTicks::Now();
Ramin Halavatiaa67ee52025-04-25 05:15:53632 base::SequenceBound<HangTimer> hang_timer(background_task_runner_,
633 /*is_ocr=*/false);
634 hang_timer.AsyncCall(&HangTimer::StartTimer);
Ramin Halavati62e034d2024-08-02 08:58:22635 content_node_ids =
636 library_->ExtractMainContent(converted_snapshot->serialized_proto);
Ramin Halavatiaa67ee52025-04-25 05:15:53637 hang_timer.AsyncCall(&HangTimer::Stop);
Ramin Halavatib3fe2fe2025-03-20 16:53:03638 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
639
640 bool successful =
641 content_node_ids.has_value() && content_node_ids->size() > 0;
Ramin Halavati81b6d922024-06-07 14:20:34642 base::UmaHistogramBoolean(
Ramin Halavatib3fe2fe2025-03-20 16:53:03643 "Accessibility.ScreenAI.MainContentExtraction.Successful2", successful);
644
645 if (!content_node_ids.has_value()) {
646 base::UmaHistogramEnumeration(
647 "Accessibility.ScreenAI.MainContentExtraction.Error.ResultNull",
648 client_type);
649 } else if (content_node_ids->empty()) {
650 base::UmaHistogramEnumeration(
651 "Accessibility.ScreenAI.MainContentExtraction.Error.ResultEmpty",
652 client_type);
653 }
654
655 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 Halavatibe45dde2025-05-01 04:19:10676void ScreenAIService::ShutDownOnIdle() {
Ramin Halavati2acf2362024-08-02 14:22:53677 const base::TimeTicks kIdlenessThreshold =
678 base::TimeTicks::Now() - kIdleCheckingDelay;
Ramin Halavatibe45dde2025-05-01 04:19:10679 if (ocr_last_used_ < kIdlenessThreshold &&
680 mce_last_used_ < kIdlenessThreshold) {
Ramin Halavatif26d7d42025-03-12 16:04:46681 screen_ai_shutdown_handler_->ShuttingDownOnIdle();
Ramin Halavatidc0b2e512024-08-16 17:39:09682 base::Process::TerminateCurrentProcessImmediately(0);
Ramin Halavati2acf2362024-08-02 14:22:53683 }
Ramin Halavati3842e7e2024-07-31 04:39:21684}
685
Ramin Halavati26dcce22022-02-23 13:11:14686} // namespace screen_ai