blob: 65dd3c5c4e7982684a6a4c79b8dc869489f082a4 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/accessibility/ax_screen_ai_annotator.h"
#include <utility>
#include "base/check_op.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/screen_ai/screen_ai_service_router.h"
#include "chrome/browser/screen_ai/screen_ai_service_router_factory.h"
#include "chrome/browser/ui/browser.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/gfx/image/image.h"
#include "ui/snapshot/snapshot.h"
#if defined(USE_AURA)
#include "extensions/browser/api/automation_internal/automation_event_router.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_event.h"
#include "ui/aura/env.h"
#include "ui/gfx/geometry/point.h"
#endif // defined(USE_AURA)
namespace screen_ai {
AXScreenAIAnnotator::AXScreenAIAnnotator(
content::BrowserContext* browser_context)
: browser_context_(browser_context), screen_ai_service_client_(this) {
// TODO(crbug.com/40911117): Add a separate initializer for Layout Extraction.
ScreenAIServiceRouterFactory::GetForBrowserContext(browser_context)
->GetServiceStateAsync(
ScreenAIServiceRouter::Service::kOCR,
base::BindOnce(
&AXScreenAIAnnotator::ScreenAIServiceInitializationCallback,
weak_ptr_factory_.GetWeakPtr()));
}
AXScreenAIAnnotator::~AXScreenAIAnnotator() = default;
void AXScreenAIAnnotator::ScreenAIServiceInitializationCallback(
bool successful) {
if (!successful) {
return;
}
CHECK(!screen_ai_service_client_.is_bound());
mojo::PendingReceiver<mojom::ScreenAIAnnotator> screen_ai_receiver =
screen_ai_annotator_.BindNewPipeAndPassReceiver();
ScreenAIServiceRouter* service_router =
ScreenAIServiceRouterFactory::GetForBrowserContext(browser_context_);
// Client interface should be ready to receive annotation results before a
// request is sent to the service, therefore it should be created first.
service_router->BindScreenAIAnnotatorClient(
screen_ai_service_client_.BindNewPipeAndPassRemote());
service_router->BindScreenAIAnnotator(std::move(screen_ai_receiver));
}
void AXScreenAIAnnotator::AnnotateScreenshot(
content::WebContents* web_contents) {
// Request screenshot from content area of the main frame.
if (!web_contents)
return;
gfx::NativeView native_view = web_contents->GetContentNativeView();
if (!native_view)
return;
if (!web_contents->GetPrimaryMainFrame())
return;
base::TimeTicks start_time = base::TimeTicks::Now();
ui::GrabViewSnapshot(
native_view, gfx::Rect(web_contents->GetSize()),
base::BindOnce(&AXScreenAIAnnotator::OnScreenshotReceived,
weak_ptr_factory_.GetWeakPtr(),
web_contents->GetPrimaryMainFrame()->GetAXTreeID(),
start_time));
}
void AXScreenAIAnnotator::OnScreenshotReceived(
const ui::AXTreeID& ax_tree_id,
const base::TimeTicks& start_time,
gfx::Image snapshot) {
base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
if (snapshot.IsEmpty()) {
VLOG(1) << "AxScreenAIAnnotator could not grab snapshot.";
base::UmaHistogramTimes(
"Accessibility.ScreenAI.AnnotateScreenshotTime.Failure", elapsed_time);
return;
}
base::UmaHistogramTimes(
"Accessibility.ScreenAI.AnnotateScreenshotTime.Success", elapsed_time);
// If screenshot is ready before service is initialized, the service call is
// delayed for 3 seconds.
// TODO(crbug.com/40911117): This solution is only for prototyping and should
// be updated so that the requests are queued before initialization
// completes.
if (!screen_ai_annotator_.is_bound() ||
!screen_ai_service_client_.is_bound()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AXScreenAIAnnotator::ExtractSemanticLayout,
weak_ptr_factory_.GetWeakPtr(), ax_tree_id,
snapshot.AsBitmap()),
base::Seconds(3));
} else {
ExtractSemanticLayout(ax_tree_id, snapshot.AsBitmap());
}
}
void AXScreenAIAnnotator::ExtractSemanticLayout(const ui::AXTreeID& ax_tree_id,
const SkBitmap bitmap) {
if (!screen_ai_annotator_.is_bound() ||
!screen_ai_service_client_.is_bound()) {
VLOG(0) << "Service is not ready yet.";
return;
}
screen_ai_annotator_->ExtractSemanticLayout(
bitmap, ax_tree_id,
base::BindOnce(&AXScreenAIAnnotator::OnSemanticLayoutExtractionPerformed,
weak_ptr_factory_.GetWeakPtr(), ax_tree_id));
}
void AXScreenAIAnnotator::OnSemanticLayoutExtractionPerformed(
const ui::AXTreeID& parent_tree_id,
const ui::AXTreeID& screen_ai_tree_id) {
VLOG(2) << base::StrCat({"AXScreenAIAnnotator received tree ids: parent: ",
parent_tree_id.ToString().c_str(), ", ScreenAI: ",
screen_ai_tree_id.ToString().c_str()});
// TODO(crbug.com/40911117): Use!
NOTIMPLEMENTED();
}
void AXScreenAIAnnotator::HandleAXTreeUpdate(const ui::AXTreeUpdate& update) {
VLOG(2) << "HandleAXTreeUpdate:\n" << update.ToString();
DCHECK(update.has_tree_data);
DCHECK_NE(update.tree_data.tree_id, ui::AXTreeIDUnknown());
tree_ids_.push_back(update.tree_data.tree_id);
DCHECK_NE(ui::kInvalidAXNodeID, update.root_id);
#if defined(USE_AURA)
auto* event_router = extensions::AutomationEventRouter::GetInstance();
DCHECK(event_router);
const gfx::Point& mouse_location =
aura::Env::GetInstance()->last_mouse_location();
event_router->DispatchAccessibilityEvents(update.tree_data.tree_id, {update},
mouse_location, {});
#endif // defined(USE_AURA)
}
} // namespace screen_ai