910e62b5创建于 1月15日历史提交
// Copyright 2024 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_main_node_annotator_controller.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_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/scoped_accessibility_mode.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"

namespace {

// Invoke screen reader alert to notify the user of the state.
void AnnounceToScreenReader(const int message_id) {
  const BrowserWindowInterface* const bwi =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  if (!bwi) {
    VLOG(2) << "Browser is not ready to announce";
    return;
  }
  BrowserView* const browser_view = BrowserView::GetBrowserViewForBrowser(bwi);
  if (!browser_view) {
    VLOG(2) << "Browser is not ready to announce";
    return;
  }

  browser_view->GetViewAccessibility().AnnounceText(
      l10n_util::GetStringUTF16(message_id));
}

}  // namespace

namespace screen_ai {

AXMainNodeAnnotatorController::AXMainNodeAnnotatorController(Profile* profile)
    : profile_(profile) {
  // Initialize an observer for changes of AX Main Node Annotation pref.
  DCHECK(profile_);
  VLOG(2) << "Init AXMainNodeAnnotatorController";
  pref_change_registrar_.Init(profile_->GetPrefs());
  pref_change_registrar_.Add(
      prefs::kAccessibilityMainNodeAnnotationsEnabled,
      base::BindRepeating(
          &AXMainNodeAnnotatorController::OnAXMainNodeAnnotationsEnabledChanged,
          weak_ptr_factory_.GetWeakPtr()));

  // Register for changes to screenreader/spoken feedback.
  ax_mode_observation_.Observe(&ui::AXPlatform::GetInstance());

  activated_ = ui::AXPlatform::GetInstance().IsScreenReaderActive();
  OnActivationChanged();
}

AXMainNodeAnnotatorController::~AXMainNodeAnnotatorController() = default;

bool AXMainNodeAnnotatorController::IsEnabled() const {
  return scoped_accessibility_mode_ != nullptr;
}

void AXMainNodeAnnotatorController::OnAXMainNodeAnnotationsEnabledChanged() {
  const auto& pref_value = profile_->GetPrefs()->GetValue(
      prefs::kAccessibilityMainNodeAnnotationsEnabled);
  VLOG(2) << "AXMainNode annotations enabled changed: " << pref_value.GetBool();

  OnActivationChanged();
}

void AXMainNodeAnnotatorController::OnActivationChanged() {
  const bool is_activated =
      activated_ && profile_->GetPrefs()->GetBoolean(
                        prefs::kAccessibilityMainNodeAnnotationsEnabled);

  if (is_activated == IsEnabled()) {
    return;  // No change in activation.
  }

  if (is_activated) {
    if (!service_ready_) {
      // Avoid repeated requests.
      if (waiting_for_service_initialization_) {
        return;
      }
      waiting_for_service_initialization_ = true;

      if (ScreenAIInstallState::GetInstance()->get_state() !=
              ScreenAIInstallState::State::kDownloaded &&
          !component_ready_observer_.IsObserving()) {
        // Start observing ScreenAIInstallState to report it to user.
        component_ready_observer_.Observe(ScreenAIInstallState::GetInstance());
      }

      screen_ai::ScreenAIServiceRouterFactory::GetForBrowserContext(profile_)
          ->GetServiceStateAsync(
              ScreenAIServiceRouter::Service::kMainContentExtraction,
              base::BindOnce(
                  &AXMainNodeAnnotatorController::
                      MainNodeExtractionServiceInitializationCallback,
                  weak_ptr_factory_.GetWeakPtr()));
      return;
    }

    // This will send the `kAnnotateMainNode` flag to all WebContents.
    scoped_accessibility_mode_ =
        content::BrowserAccessibilityState::GetInstance()
            ->CreateScopedModeForBrowserContext(profile_,
                                                ui::AXMode::kAnnotateMainNode);
  } else {
    scoped_accessibility_mode_.reset();
  }
}

void AXMainNodeAnnotatorController::
    MainNodeExtractionServiceInitializationCallback(bool successful) {
  waiting_for_service_initialization_ = false;
  service_ready_ = successful;
  if (successful) {
    OnActivationChanged();
  } else {
    // Call `StateChanged` to announce the state to user.
    StateChanged(ScreenAIInstallState::State::kDownloadFailed);
  }

  // No more need for observing Screen AI state changes.
  component_ready_observer_.Reset();
}

void AXMainNodeAnnotatorController::StateChanged(
    ScreenAIInstallState::State state) {
  switch (state) {
    case ScreenAIInstallState::State::kNotDownloaded:
      break;

    case ScreenAIInstallState::State::kDownloading:
      AnnounceToScreenReader(IDS_SETTINGS_MAIN_NODE_ANNOTATIONS_DOWNLOADING);
      break;

    case ScreenAIInstallState::State::kDownloadFailed:
      AnnounceToScreenReader(IDS_SETTINGS_MAIN_NODE_ANNOTATIONS_DOWNLOAD_ERROR);
      // Update the main node annotations pref to be false to toggle off the
      // button.
      profile_->GetPrefs()->SetBoolean(
          prefs::kAccessibilityMainNodeAnnotationsEnabled, false);
      break;

    case ScreenAIInstallState::State::kDownloaded:
      AnnounceToScreenReader(
          IDS_SETTINGS_MAIN_NODE_ANNOTATIONS_DOWNLOAD_COMPLETE);
      break;
  }
}

void AXMainNodeAnnotatorController::Activate() {
  activated_ = true;
  OnActivationChanged();
}

void AXMainNodeAnnotatorController::OnAssistiveTechChanged(
    ui::AssistiveTech assistive_tech) {
  activated_ = ui::IsScreenReader(assistive_tech);
  OnActivationChanged();
}

}  // namespace screen_ai