blob: 6a165a127d4ce00450cbb9c7e205933b34c010ca [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 Halavati55fdf4d2022-11-07 06:35:187#include <memory>
Ramin Halavati63d9c252024-07-29 06:59:178#include <optional>
Ramin Halavatifd712d8e2022-10-19 05:11:279#include <utility>
Ramin Halavatib044504d2022-10-24 06:36:3410#include <vector>
Ramin Halavatifd712d8e2022-10-19 05:11:2711
12#include "base/check.h"
Ramin Halavatiad969ef2024-05-21 06:59:4713#include "base/check_is_test.h"
Tom Sepeze9b4dd552024-08-14 22:44:2914#include "base/compiler_specific.h"
Ramin Halavati55fdf4d2022-11-07 06:35:1815#include "base/functional/bind.h"
16#include "base/location.h"
Ramin Halavatifafff152022-12-05 09:39:3817#include "base/logging.h"
Kyungjun Lee9d755a02022-11-08 17:57:1118#include "base/metrics/histogram_functions.h"
Ramin Halavati5ff855c2022-04-27 16:30:4819#include "base/process/process.h"
Ramin Halavati62e034d2024-08-02 08:58:2220#include "base/strings/stringprintf.h"
Sean Maher5b9af51f2022-11-21 15:32:4721#include "base/task/single_thread_task_runner.h"
Ramin Halavati55fdf4d2022-11-07 06:35:1822#include "base/task/thread_pool.h"
Ramin Halavati62e034d2024-08-02 08:58:2223#include "components/crash/core/common/crash_key.h"
Abigail Kleindfdde352023-01-27 21:03:1024#include "services/metrics/public/cpp/ukm_builders.h"
25#include "services/metrics/public/cpp/ukm_recorder.h"
Abigail Klein7a63c572024-02-28 20:45:0926#include "services/screen_ai/buildflags/buildflags.h"
27#include "services/screen_ai/proto/main_content_extractor_proto_convertor.h"
28#include "services/screen_ai/proto/visual_annotator_proto_convertor.h"
29#include "services/screen_ai/public/cpp/utilities.h"
Ramin Halavatieddadb62022-05-04 17:29:4930#include "ui/accessibility/accessibility_features.h"
Ramin Halavati4f904d0a2024-06-25 15:32:3231#include "ui/accessibility/ax_node.h"
32#include "ui/accessibility/ax_tree.h"
Ramin Halavati84231072022-08-17 08:02:2033#include "ui/accessibility/ax_tree_id.h"
Nektarios Paisiosf73d6972022-06-04 11:32:2534#include "ui/gfx/geometry/rect_f.h"
Ramin Halavati367352a2022-04-14 06:00:2335
Ramin Halavatia9b50a102024-02-07 18:37:2336#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Abigail Klein7a63c572024-02-28 20:45:0937#include "services/screen_ai/screen_ai_library_wrapper_fake.h"
Ramin Halavati767b8fc2024-02-02 06:23:0838#else
Abigail Klein7a63c572024-02-28 20:45:0939#include "services/screen_ai/screen_ai_library_wrapper_impl.h"
Ramin Halavati767b8fc2024-02-02 06:23:0840#endif
41
Ramin Halavati69bf7752022-04-04 09:58:1542namespace screen_ai {
Ramin Halavati26dcce22022-02-23 13:11:1443
Ramin Halavati6d4fc2b2022-06-16 15:33:3544namespace {
45
Ramin Halavati2acf2362024-08-02 14:22:5346// How often it would be checked that the service is idle and can be shutdown.
47constexpr base::TimeDelta kIdleCheckingDelay = base::Minutes(10);
48
Ramin Halavati6ff56412024-08-13 07:15:5449// How long after all clients are disconnected, it is checked if service is
50// idle.
51constexpr base::TimeDelta kCoolDownTime = base::Seconds(1);
52
Ramin Halavatiad969ef2024-05-21 06:59:4753// These values are persisted to logs. Entries should not be renumbered and
54// numeric values should never be reused.
55enum class OcrClientTypeForMetrics {
56 kTest = 0,
57 kPdfViewer = 1,
58 kLocalSearch = 2,
59 kCameraApp = 3,
60 kPdfSearchify = 4,
61 kMediaApp = 5,
62 kMaxValue = kMediaApp
63};
64
65OcrClientTypeForMetrics GetClientType(mojom::OcrClientType client_type) {
66 switch (client_type) {
67 case mojom::OcrClientType::kTest:
68 CHECK_IS_TEST();
69 return OcrClientTypeForMetrics::kTest;
70 case mojom::OcrClientType::kPdfViewer:
71 return OcrClientTypeForMetrics::kPdfViewer;
72 case mojom::OcrClientType::kLocalSearch:
73 return OcrClientTypeForMetrics::kLocalSearch;
74 case mojom::OcrClientType::kCameraApp:
75 return OcrClientTypeForMetrics::kCameraApp;
76 case mojom::OcrClientType::kPdfSearchify:
77 return OcrClientTypeForMetrics::kPdfSearchify;
78 case mojom::OcrClientType::kMediaApp:
79 return OcrClientTypeForMetrics::kMediaApp;
80 }
81}
82
Ramin Halavati89a36c92023-05-05 15:01:0383ui::AXTreeUpdate ConvertVisualAnnotationToTreeUpdate(
Ramin Halavatidcb09a172024-07-15 08:28:5584 std::optional<chrome_screen_ai::VisualAnnotation>& annotation_proto,
Ramin Halavati89a36c92023-05-05 15:01:0385 const gfx::Rect& image_rect) {
86 if (!annotation_proto) {
87 VLOG(0) << "Screen AI library could not process snapshot or no OCR data.";
88 return ui::AXTreeUpdate();
89 }
90
91 return VisualAnnotationToAXTreeUpdate(*annotation_proto, image_rect);
92}
93
Abigail Klein18673702024-03-05 20:59:0694ui::AXNodeID ComputeMainNode(
95 const ui::AXTree* tree,
96 const std::vector<ui::AXNodeID>& content_node_ids) {
97 ui::AXNode* front = tree->GetFromId(content_node_ids.front());
98 ui::AXNode* back = tree->GetFromId(content_node_ids.back());
99 ui::AXNode* main = front->GetLowestCommonAncestor(*back);
100 return main->id();
101}
102
Ramin Halavati26ae6b72022-11-10 06:45:05103} // namespace
104
Ramin Halavati39bab0d2024-01-30 06:05:01105// The library accepts simple pointers to model data retrieval functions, hence
106// callback functions with linked object are not safe to pass.
Ramin Halavati63d9c252024-07-29 06:59:17107// This global variable keeps the pointer the only instance of this class.
108ModelDataHolder* g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50109
Ramin Halavati63d9c252024-07-29 06:59:17110// Keeps the handles of model files, and replies to calls for copying their
111// content.
112class ModelDataHolder {
Ramin Halavati8ced6392023-09-25 07:27:50113 public:
Ramin Halavati63d9c252024-07-29 06:59:17114 ModelDataHolder() {
115 CHECK(!g_model_data_holder_instance);
116 g_model_data_holder_instance = this;
117 }
Ramin Halavati8ced6392023-09-25 07:27:50118
Ramin Halavati63d9c252024-07-29 06:59:17119 ModelDataHolder(const ModelDataHolder&) = delete;
120 ModelDataHolder& operator=(const ModelDataHolder&) = delete;
121
122 ~ModelDataHolder() {
123 CHECK_EQ(g_model_data_holder_instance, this);
124 g_model_data_holder_instance = nullptr;
Ramin Halavati8ced6392023-09-25 07:27:50125 }
126
127 // Returns 0 if file is not found.
128 static uint32_t GetDataSize(const char* relative_file_path) {
Ramin Halavati63d9c252024-07-29 06:59:17129 CHECK(g_model_data_holder_instance);
130 base::File* model_file =
131 g_model_data_holder_instance->GetModelFile(relative_file_path);
132 return model_file ? model_file->GetLength() : 0;
Ramin Halavati8ced6392023-09-25 07:27:50133 }
134
Ramin Halavati63d9c252024-07-29 06:59:17135 // Copies content of the file in `relative_file_path` to `buffer`. Expects
136 // that `buffer_size` would be enough for the entire file content.
Ramin Halavati8ced6392023-09-25 07:27:50137 static void CopyData(const char* relative_file_path,
138 uint32_t buffer_size,
139 char* buffer) {
Ramin Halavati63d9c252024-07-29 06:59:17140 CHECK(g_model_data_holder_instance);
141 base::File* model_file =
142 g_model_data_holder_instance->GetModelFile(relative_file_path);
143 CHECK(model_file);
144
145 int64_t length = model_file->GetLength();
146 CHECK_GE(buffer_size, length);
Tom Sepeze9b4dd552024-08-14 22:44:29147 CHECK_EQ(UNSAFE_TODO(model_file->Read(0, buffer, length)), length);
Ramin Halavati8ced6392023-09-25 07:27:50148 }
149
Ramin Halavati63d9c252024-07-29 06:59:17150 void AddModelFiles(base::flat_map<base::FilePath, base::File> model_files) {
151 for (auto& model_file : model_files) {
152 model_files_[model_file.first.MaybeAsASCII()] =
153 std::move(model_file.second);
Ramin Halavati39bab0d2024-01-30 06:05:01154 }
155 }
Ramin Halavati8ced6392023-09-25 07:27:50156
Ramin Halavati63d9c252024-07-29 06:59:17157 // Returns the file handle for `relative_file_path` if it exists.
158 base::File* GetModelFile(const char* relative_file_path) {
159 if (!base::Contains(model_files_, relative_file_path)) {
160 return nullptr;
161 }
162 return &model_files_[relative_file_path];
163 }
164
Ramin Halavati8ced6392023-09-25 07:27:50165 private:
Ramin Halavati63d9c252024-07-29 06:59:17166 std::map<std::string, base::File> model_files_;
Ramin Halavati8ced6392023-09-25 07:27:50167};
168
Ramin Halavati26ae6b72022-11-10 06:45:05169ScreenAIService::ScreenAIService(
Ramin Halavatic1e4fa92023-05-17 17:22:06170 mojo::PendingReceiver<mojom::ScreenAIServiceFactory> receiver)
171 : factory_receiver_(this, std::move(receiver)),
172 ocr_receiver_(this),
Ramin Halavatiad969ef2024-05-21 06:59:47173 main_content_extraction_receiver_(this) {
Ramin Halavati6ff56412024-08-13 07:15:54174 screen2x_main_content_extractors_.set_disconnect_handler(
175 base::BindRepeating(&ScreenAIService::CheckIdleStateAfterDelay,
176 weak_ptr_factory_.GetWeakPtr()));
177 screen_ai_annotators_.set_disconnect_handler(
178 base::BindRepeating(&ScreenAIService::OcrReceiverDisconnected,
179 weak_ptr_factory_.GetWeakPtr()));
Ramin Halavati63d9c252024-07-29 06:59:17180 model_data_holder_ = std::make_unique<ModelDataHolder>();
Ramin Halavati2acf2362024-08-02 14:22:53181 idle_checking_timer_ = std::make_unique<base::RepeatingTimer>();
182 idle_checking_timer_->Start(FROM_HERE, kIdleCheckingDelay, this,
183 &ScreenAIService::ShutDownIfNoClients);
Ramin Halavatiad969ef2024-05-21 06:59:47184}
Ramin Halavati26ae6b72022-11-10 06:45:05185
186ScreenAIService::~ScreenAIService() = default;
187
Ramin Halavati8ced6392023-09-25 07:27:50188void ScreenAIService::LoadLibrary(const base::FilePath& library_path) {
Abigail Klein7a63c572024-02-28 20:45:09189 // The ScopedBlockingCall in LoadLibrary guarantees that this is not run on
190 // the UI thread.
Ramin Halavatia9b50a102024-02-07 18:37:23191#if BUILDFLAG(USE_FAKE_SCREEN_AI)
Ramin Halavati767b8fc2024-02-02 06:23:08192 library_ = std::make_unique<ScreenAILibraryWrapperFake>();
193#else
194 library_ = std::make_unique<ScreenAILibraryWrapperImpl>();
195#endif
Ramin Halavati8ced6392023-09-25 07:27:50196
197 bool load_sucessful = library_->Load(library_path);
198 base::UmaHistogramBoolean("Accessibility.ScreenAI.Library.Initialized",
199 load_sucessful);
200
201 if (!load_sucessful) {
202 library_.reset();
203 return;
204 }
205
206 uint32_t version_major;
207 uint32_t version_minor;
208 library_->GetLibraryVersion(version_major, version_minor);
209 VLOG(2) << "Screen AI library version: " << version_major << "."
210 << version_minor;
211
212#if BUILDFLAG(IS_CHROMEOS_ASH)
213 library_->SetLogger();
214#endif
215
216 if (features::IsScreenAIDebugModeEnabled()) {
217 library_->EnableDebugMode();
218 }
219
Ramin Halavati63d9c252024-07-29 06:59:17220 library_->SetFileContentFunctions(&ModelDataHolder::GetDataSize,
221 &ModelDataHolder::CopyData);
Ramin Halavati8ced6392023-09-25 07:27:50222}
223
Ramin Halavatic1e4fa92023-05-17 17:22:06224void ScreenAIService::InitializeMainContentExtraction(
Ramin Halavatid139a0f2023-02-28 13:25:28225 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07226 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06227 mojo::PendingReceiver<mojom::MainContentExtractionService>
228 main_content_extractor_service_receiver,
229 InitializeMainContentExtractionCallback callback) {
230 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50231 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06232 }
233
234 if (!library_) {
235 std::move(callback).Run(false);
236 base::Process::TerminateCurrentProcessImmediately(-1);
237 }
238
Ramin Halavati63d9c252024-07-29 06:59:17239 model_data_holder_->AddModelFiles(std::move(model_files));
Ramin Halavatib044504d2022-10-24 06:36:34240
Ramin Halavati8ced6392023-09-25 07:27:50241 bool init_successful = library_->InitMainContentExtraction();
Ramin Halavati1d57c2d2023-05-24 05:18:24242 base::UmaHistogramBoolean(
243 "Accessibility.ScreenAI.MainContentExtraction.Initialized",
244 init_successful);
245 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06246 std::move(callback).Run(false);
Ramin Halavati89a36c92023-05-05 15:01:03247 return;
Ramin Halavatid139a0f2023-02-28 13:25:28248 }
Ramin Halavati89a36c92023-05-05 15:01:03249
Ramin Halavatic1e4fa92023-05-17 17:22:06250 // This interface should be created only once.
251 CHECK(!main_content_extraction_receiver_.is_bound());
252
253 main_content_extraction_receiver_.Bind(
254 std::move(main_content_extractor_service_receiver));
255
256 std::move(callback).Run(true);
Ramin Halavati2acf2362024-08-02 14:22:53257 main_content_extraction_last_used_ = base::TimeTicks::Now();
Ramin Halavatic1e4fa92023-05-17 17:22:06258}
259
260void ScreenAIService::InitializeOCR(
261 const base::FilePath& library_path,
Ramin Halavatifbc62022023-11-10 11:05:07262 base::flat_map<base::FilePath, base::File> model_files,
Ramin Halavatic1e4fa92023-05-17 17:22:06263 mojo::PendingReceiver<mojom::OCRService> ocr_service_receiver,
264 InitializeOCRCallback callback) {
265 if (!library_) {
Ramin Halavati8ced6392023-09-25 07:27:50266 LoadLibrary(library_path);
Ramin Halavatic1e4fa92023-05-17 17:22:06267 }
268
269 if (!library_) {
270 std::move(callback).Run(false);
271 base::Process::TerminateCurrentProcessImmediately(-1);
272 }
273
Ramin Halavati63d9c252024-07-29 06:59:17274 model_data_holder_->AddModelFiles(std::move(model_files));
275
Ramin Halavati0e2ae5742023-11-10 05:57:54276 bool init_successful = library_->InitOCR();
Ramin Halavati35d129f2023-06-22 16:37:09277 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Initialized",
Ramin Halavati1d57c2d2023-05-24 05:18:24278 init_successful);
Ramin Halavati480584332023-08-02 06:38:40279
Ramin Halavati1d57c2d2023-05-24 05:18:24280 if (!init_successful) {
Ramin Halavatic1e4fa92023-05-17 17:22:06281 std::move(callback).Run(false);
282 return;
283 }
284
285 // This interface should be created only once.
286 CHECK(!ocr_receiver_.is_bound());
287
288 ocr_receiver_.Bind(std::move(ocr_service_receiver));
289
290 std::move(callback).Run(true);
Ramin Halavati2acf2362024-08-02 14:22:53291 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavatib044504d2022-10-24 06:36:34292}
Ramin Halavati26dcce22022-02-23 13:11:14293
294void ScreenAIService::BindAnnotator(
295 mojo::PendingReceiver<mojom::ScreenAIAnnotator> annotator) {
296 screen_ai_annotators_.Add(this, std::move(annotator));
297}
298
Ramin Halavatieddadb62022-05-04 17:29:49299void ScreenAIService::BindMainContentExtractor(
300 mojo::PendingReceiver<mojom::Screen2xMainContentExtractor>
301 main_content_extractor) {
Ramin Halavati6ff56412024-08-13 07:15:54302 screen2x_main_content_extractors_.Add(this,
303 std::move(main_content_extractor));
Ramin Halavatieddadb62022-05-04 17:29:49304}
305
Arthur Sonzognic571efb2024-01-26 20:26:18306std::optional<chrome_screen_ai::VisualAnnotation>
Ramin Halavati2650ebe2023-11-27 19:56:35307ScreenAIService::PerformOcrAndRecordMetrics(const SkBitmap& image,
308 bool a11y_tree_request) {
Ramin Halavatiad969ef2024-05-21 06:59:47309 auto entry = ocr_client_types_.find(screen_ai_annotators_.current_receiver());
310 CHECK(entry != ocr_client_types_.end()) << "OCR client type is not set.";
311 base::UmaHistogramEnumeration("Accessibility.ScreenAI.OCR.ClientType",
312 GetClientType(entry->second));
313
Ramin Halavati2acf2362024-08-02 14:22:53314 ocr_last_used_ = base::TimeTicks::Now();
Ramin Halavati7252cd02023-05-10 07:21:33315 auto result = library_->PerformOcr(image);
Ramin Halavati2acf2362024-08-02 14:22:53316 base::TimeDelta elapsed_time = base::TimeTicks::Now() - ocr_last_used_;
Ramin Halavati2650ebe2023-11-27 19:56:35317 int lines_count = result ? result->lines_size() : 0;
Ramin Halavati3ae33ca2024-01-01 12:58:39318 unsigned image_size = image.width() * image.height();
Ramin Halavati2650ebe2023-11-27 19:56:35319 VLOG(1) << "OCR returned " << lines_count << " lines in " << elapsed_time;
Ramin Halavati7252cd02023-05-10 07:21:33320
Ramin Halavati81b6d922024-06-07 14:20:34321 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Successful",
322 result.has_value());
Ramin Halavati2650ebe2023-11-27 19:56:35323 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount",
324 lines_count);
Ramin Halavatia0548b262023-06-27 05:56:47325 base::UmaHistogramCounts10M("Accessibility.ScreenAI.OCR.ImageSize10M",
Ramin Halavati3ae33ca2024-01-01 12:58:39326 image_size);
327 if (image_size < 500 * 500) {
328 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.Small",
329 elapsed_time);
330 } else if (image_size < 1000 * 1000) {
331 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.Medium",
332 elapsed_time);
333 } else if (image_size < 2000 * 2000) {
334 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.Large",
335 elapsed_time);
336 } else {
337 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Latency.XLarge",
338 elapsed_time);
339 }
Ramin Halavati2650ebe2023-11-27 19:56:35340
341 // If needed to extend to more clients, an identifier can be passed from the
342 // client to introduce itself and these metrics can be collected based on it.
343 if (a11y_tree_request) {
344 base::UmaHistogramCounts100("Accessibility.ScreenAI.OCR.LinesCount.PDF",
345 lines_count);
346 base::UmaHistogramTimes("Accessibility.ScreenAI.OCR.Time.PDF",
347 elapsed_time);
348 base::UmaHistogramCounts10M("Accessibility.ScreenAI.OCR.ImageSize.PDF",
349 image.width() * image.height());
350 }
351
Ramin Halavati7252cd02023-05-10 07:21:33352 return result;
353}
354
Ramin Halavatiad969ef2024-05-21 06:59:47355void ScreenAIService::SetClientType(mojom::OcrClientType client_type) {
356 ocr_client_types_[screen_ai_annotators_.current_receiver()] = client_type;
357}
358
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07359void ScreenAIService::PerformOcrAndReturnAnnotation(
360 const SkBitmap& image,
361 PerformOcrAndReturnAnnotationCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18362 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati2650ebe2023-11-27 19:56:35363 PerformOcrAndRecordMetrics(image, /*a11y_tree_request=*/false);
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07364
Ramin Halavati89a36c92023-05-05 15:01:03365 if (annotation_proto) {
366 std::move(callback).Run(ConvertProtoToVisualAnnotation(*annotation_proto));
367 return;
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07368 }
Ramin Halavati89a36c92023-05-05 15:01:03369
370 std::move(callback).Run(mojom::VisualAnnotation::New());
Dmitry Grebenyukc49b95ee2023-05-02 05:52:07371}
372
Kyungjun Lee90461522023-04-25 06:50:09373void ScreenAIService::PerformOcrAndReturnAXTreeUpdate(
Ramin Halavatib446c022023-03-29 15:36:36374 const SkBitmap& image,
Kyungjun Lee90461522023-04-25 06:50:09375 PerformOcrAndReturnAXTreeUpdateCallback callback) {
Arthur Sonzognic571efb2024-01-26 20:26:18376 std::optional<chrome_screen_ai::VisualAnnotation> annotation_proto =
Ramin Halavati2650ebe2023-11-27 19:56:35377 PerformOcrAndRecordMetrics(image, /*a11y_tree_request=*/true);
Ramin Halavati89a36c92023-05-05 15:01:03378 ui::AXTreeUpdate update = ConvertVisualAnnotationToTreeUpdate(
379 annotation_proto, gfx::Rect(image.width(), image.height()));
Kyungjun Lee90461522023-04-25 06:50:09380
Ramin Halavati89a36c92023-05-05 15:01:03381 // The original caller is always replied to, and an empty AXTreeUpdate tells
382 // that the annotation function was not successful.
383 std::move(callback).Run(update);
Ramin Halavatib446c022023-03-29 15:36:36384}
385
386void ScreenAIService::ExtractMainContent(const ui::AXTreeUpdate& snapshot,
387 ukm::SourceId ukm_source_id,
388 ExtractMainContentCallback callback) {
Ramin Halavati2acf2362024-08-02 14:22:53389 main_content_extraction_last_used_ = base::TimeTicks::Now();
Abigail Klein18673702024-03-05 20:59:06390 ui::AXTree tree;
391 std::optional<std::vector<int32_t>> content_node_ids;
392 bool success = ExtractMainContentInternal(snapshot, tree, content_node_ids);
Ramin Halavati2acf2362024-08-02 14:22:53393 base::TimeDelta elapsed_time =
394 base::TimeTicks::Now() - main_content_extraction_last_used_;
Abigail Klein18673702024-03-05 20:59:06395 RecordMetrics(ukm_source_id, ukm::UkmRecorder::Get(), elapsed_time, success);
396
397 if (success) {
398 std::move(callback).Run(*content_node_ids);
399 } else {
400 std::move(callback).Run(std::vector<int32_t>());
401 }
402}
403
404void ScreenAIService::ExtractMainNode(const ui::AXTreeUpdate& snapshot,
405 ExtractMainNodeCallback callback) {
406 ui::AXTree tree;
407 std::optional<std::vector<int32_t>> content_node_ids;
408 bool success = ExtractMainContentInternal(snapshot, tree, content_node_ids);
409
410 if (success) {
411 ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
412 std::move(callback).Run(main_node_id);
413 } else {
414 std::move(callback).Run(ui::kInvalidAXNodeID);
415 }
416}
417
418bool ScreenAIService::ExtractMainContentInternal(
419 const ui::AXTreeUpdate& snapshot,
420 ui::AXTree& tree,
421 std::optional<std::vector<int32_t>>& content_node_ids) {
Ramin Halavatib446c022023-03-29 15:36:36422 // Early return if input is empty.
423 if (snapshot.nodes.empty()) {
Abigail Klein18673702024-03-05 20:59:06424 return false;
Ramin Halavatib33fc0c2022-05-06 09:32:22425 }
Ramin Halavatie2549a92022-08-02 07:43:19426
Abigail Klein18673702024-03-05 20:59:06427 // Deserialize the snapshot and reserialize it to a view hierarchy proto.
428 CHECK(tree.Unserialize(snapshot));
Ramin Halavati62e034d2024-08-02 08:58:22429 std::optional<ViewHierarchyAndTreeSize> converted_snapshot =
430 SnapshotToViewHierarchy(tree);
431 if (!converted_snapshot) {
432 VLOG(0) << "Proto not generated.";
433 return false;
434 }
435
436 // Report request specifications in case the call crashes.
437 static crash_reporter::CrashKeyString<95> crash_info(
438 "main_content_extraction_info");
439 crash_info.Set(base::StringPrintf(
440 "TD:%i, TR:%i, SNC:%10zu, SBS:%10zu, TS:%10i, TW:%6i, TH:%6i, SS:%10zu",
441 snapshot.has_tree_data, snapshot.root_id != ui::kInvalidAXNodeID,
442 snapshot.nodes.size(), snapshot.ByteSize(), tree.size(),
443 static_cast<int>(converted_snapshot->tree_dimensions.width()),
444 static_cast<int>(converted_snapshot->tree_dimensions.height()),
445 converted_snapshot->serialized_proto.size()));
446
447 content_node_ids =
448 library_->ExtractMainContent(converted_snapshot->serialized_proto);
Ramin Halavati81b6d922024-06-07 14:20:34449 base::UmaHistogramBoolean(
450 "Accessibility.ScreenAI.MainContentExtraction.Successful",
451 content_node_ids.has_value());
Abigail Klein18673702024-03-05 20:59:06452 if (content_node_ids.has_value() && content_node_ids->size() > 0) {
Ramin Halavati5aad701d2023-05-09 17:23:29453 VLOG(2) << "Screen2x returned " << content_node_ids->size() << " node ids.";
Abigail Klein18673702024-03-05 20:59:06454 return true;
455 } else {
456 VLOG(0) << "Screen2x returned no results.";
457 return false;
Ramin Halavati5aad701d2023-05-09 17:23:29458 }
Abigail Klein18673702024-03-05 20:59:06459}
Ramin Halavati89a36c92023-05-05 15:01:03460
Abigail Klein18673702024-03-05 20:59:06461ui::AXNodeID ScreenAIService::ComputeMainNodeForTesting(
462 const ui::AXTree* tree,
463 const std::vector<ui::AXNodeID>& content_node_ids) {
464 return ComputeMainNode(tree, content_node_ids);
Abigail Kleindfdde352023-01-27 21:03:10465}
466
467// static
468void ScreenAIService::RecordMetrics(ukm::SourceId ukm_source_id,
469 ukm::UkmRecorder* ukm_recorder,
470 base::TimeDelta elapsed_time,
471 bool success) {
472 if (success) {
473 base::UmaHistogramTimes(
474 "Accessibility.ScreenAI.Screen2xDistillationTime.Success",
475 elapsed_time);
476 if (ukm_source_id != ukm::kInvalidSourceId) {
477 ukm::builders::Accessibility_ScreenAI(ukm_source_id)
478 .SetScreen2xDistillationTime_Success(elapsed_time.InMilliseconds())
479 .Record(ukm_recorder);
480 }
481 } else {
482 base::UmaHistogramTimes(
483 "Accessibility.ScreenAI.Screen2xDistillationTime.Failure",
484 elapsed_time);
485 if (ukm_source_id != ukm::kInvalidSourceId) {
486 ukm::builders::Accessibility_ScreenAI(ukm_source_id)
487 .SetScreen2xDistillationTime_Failure(elapsed_time.InMilliseconds())
488 .Record(ukm_recorder);
489 }
490 }
Ramin Halavatieddadb62022-05-04 17:29:49491}
492
Ramin Halavati6ff56412024-08-13 07:15:54493void ScreenAIService::OcrReceiverDisconnected() {
Ramin Halavatiad969ef2024-05-21 06:59:47494 auto entry = ocr_client_types_.find(screen_ai_annotators_.current_receiver());
495 if (entry != ocr_client_types_.end()) {
496 ocr_client_types_.erase(entry);
497 }
Ramin Halavati6ff56412024-08-13 07:15:54498
499 CheckIdleStateAfterDelay();
500}
501
502void ScreenAIService::CheckIdleStateAfterDelay() {
503 // Check if service is idle, a little after the client disconnects.
504 base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
505 FROM_HERE,
506 base::BindOnce(&ScreenAIService::ShutDownIfNoClients,
507 weak_ptr_factory_.GetWeakPtr()),
508 kCoolDownTime);
Ramin Halavatiad969ef2024-05-21 06:59:47509}
510
Ramin Halavati3842e7e2024-07-31 04:39:21511void ScreenAIService::ShutDownIfNoClients() {
Ramin Halavati2acf2362024-08-02 14:22:53512 bool ocr_has_clients = screen_ai_annotators_.size();
513 bool main_content_extraction_has_clients =
Ramin Halavati6ff56412024-08-13 07:15:54514 screen2x_main_content_extractors_.size();
Ramin Halavati2acf2362024-08-02 14:22:53515 if (!ocr_has_clients && !main_content_extraction_has_clients) {
516 VLOG(2) << "Shutting down since no client.";
Ramin Halavati3842e7e2024-07-31 04:39:21517 base::Process::TerminateCurrentProcessImmediately(0);
518 }
Ramin Halavati2acf2362024-08-02 14:22:53519
520 // Collect data on whether each functionality of the service is idle.
521 // This will be used to plan further to shut down the service when idle, or
522 // track features which keep the service in idle state.
523 // TODO(b/353718857): Shut down when both features are idle, after ensuring
524 // all clients support reconnecting.
525 const base::TimeTicks kIdlenessThreshold =
526 base::TimeTicks::Now() - kIdleCheckingDelay;
527 if (!ocr_idle_reported_ && !ocr_last_used_.is_null() &&
528 ocr_last_used_ < kIdlenessThreshold) {
529 ocr_idle_reported_ = true;
530 base::UmaHistogramBoolean("Accessibility.ScreenAI.OCR.Idle.Connected",
531 ocr_has_clients);
532 }
533 if (!main_content_extraction_idle_reported_ &&
534 !main_content_extraction_last_used_.is_null() &&
535 main_content_extraction_last_used_ < kIdlenessThreshold) {
536 main_content_extraction_idle_reported_ = true;
537 base::UmaHistogramBoolean(
538 "Accessibility.ScreenAI.MainContentExtraction.Idle.Connected",
539 main_content_extraction_has_clients);
540 }
Ramin Halavati3842e7e2024-07-31 04:39:21541}
542
Ramin Halavati26dcce22022-02-23 13:11:14543} // namespace screen_ai