// Copyright 2013 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/ash/accessibility/accessibility_manager.h"

#include <optional>

#include "ash/constants/ash_extension_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/test/accessibility_controller_test_api.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ash/accessibility/accessibility_test_utils.h"
#include "chrome/browser/ash/accessibility/dictation_test_utils.h"
#include "chrome/browser/ash/accessibility/magnification_manager.h"
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/ash/login/test/guest_session_mixin.h"
#include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
#include "chrome/browser/ash/login/test/oobe_base_test.h"
#include "chrome/browser/ash/preferences/preferences.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/extensions/api/braille_display_private/mock_braille_controller.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/speech/speech_recognition_constants.h"
#include "chrome/browser/speech/speech_recognition_test_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "components/account_id/account_id.h"
#include "components/live_caption/pref_names.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/session_manager/core/session_manager.h"
#include "components/supervised_user/core/common/supervised_user_constants.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/user_manager/test_helper.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "extensions/common/constants.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/base/ime/ash/component_extension_ime_manager.h"
#include "ui/base/ime/ash/extension_ime_util.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
#include "ui/message_center/message_center.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/widget/widget_utils.h"

namespace ash {

namespace {

using ::extensions::api::accessibility_private::DlcType;
using ::extensions::api::braille_display_private::KeyEvent;
using ::extensions::api::braille_display_private::MockBrailleController;
using input_method::InputMethodDescriptors;
using input_method::InputMethodManager;
using ::testing::WithParamInterface;

// Use a real domain to avoid policy loading problems.
constexpr char kTestUserName[] = "owner@gmail.com";
constexpr GaiaId::Literal kTestUserGaiaId("9876543210");
constexpr char kSodaUnsupportedLocale[] = "af-ZA";

// Dictation notification titles and descriptions. '*'s are used as placeholders
// for languages, which are substituted in at a later time.
std::u16string kDictationAllDlcsDownloadedTitle = u"* speech files downloaded";
std::u16string kDictationAllDlcsDownloadedDesc =
    u"Speech is now processed locally and Dictation works offline";
std::u16string kDictationNoDlcsDownloadedTitle =
    u"Couldn't download * speech files";
std::u16string kDictationNoDlcsDownloadedDesc =
    u"Download will be attempted later. Speech will be sent to Google for "
    u"processing for now.";
std::u16string kDicationOnlyPumpkinDownloadedTitle =
    u"* speech files partially downloaded";
std::u16string kDicationOnlyPumpkinDownloadedDesc =
    u"Download will be attempted later. Speech will be sent to Google for "
    u"processing for now.";
std::u16string kDictationOnlySodaDownloadedTitle =
    u"* speech files partially downloaded";
std::u16string kDictationOnlySodaDownloadedDesc =
    u"Speech is processed locally and dictation works offline, but some voice "
    u"commands won’t work.";

constexpr int kTestAutoclickDelayMs = 2000;

class MockAccessibilityObserver {
 public:
  MockAccessibilityObserver() {
    AccessibilityManager* accessibility_manager = AccessibilityManager::Get();
    CHECK(accessibility_manager);
    accessibility_subscription_ =
        accessibility_manager->RegisterCallback(base::BindRepeating(
            &MockAccessibilityObserver::OnAccessibilityStatusChanged,
            base::Unretained(this)));
  }

  MockAccessibilityObserver(const MockAccessibilityObserver&) = delete;
  MockAccessibilityObserver& operator=(const MockAccessibilityObserver&) =
      delete;

  virtual ~MockAccessibilityObserver() = default;

  bool observed() const { return observed_; }
  bool observed_enabled() const { return observed_enabled_; }
  std::optional<AccessibilityNotificationType> observed_type() const {
    return observed_type_;
  }

  void reset() { observed_ = false; }

 private:
  void OnAccessibilityStatusChanged(
      const AccessibilityStatusEventDetails& details) {
    if (details.notification_type !=
        AccessibilityNotificationType::kToggleScreenMagnifier) {
      observed_type_ = details.notification_type;
      observed_enabled_ = details.enabled;
      observed_ = true;
    }
  }

  bool observed_ = false;
  bool observed_enabled_ = false;
  std::optional<AccessibilityNotificationType> observed_type_;

  base::CallbackListSubscription accessibility_subscription_;
};

Profile* GetActiveUserProfile() {
  return ProfileManager::GetActiveUserProfile();
}

PrefService* GetActiveUserPrefs() {
  return GetActiveUserProfile()->GetPrefs();
}

void SetLargeCursorEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableLargeCursor(enabled);
}

bool IsLargeCursorEnabled() {
  return AccessibilityManager::Get()->IsLargeCursorEnabled();
}

void SetLiveCaptionEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableLiveCaption(enabled);
}

bool IsLiveCaptionEnabled() {
  return AccessibilityManager::Get()->IsLiveCaptionEnabled();
}

bool ShouldShowAccessibilityMenu() {
  return AccessibilityManager::Get()->ShouldShowAccessibilityMenu();
}

void SetHighContrastEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableHighContrast(enabled);
}

bool IsHighContrastEnabled() {
  return AccessibilityManager::Get()->IsHighContrastEnabled();
}

void SetSpokenFeedbackEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableSpokenFeedback(enabled);
}

bool IsSpokenFeedbackEnabled() {
  return AccessibilityManager::Get()->IsSpokenFeedbackEnabled();
}

void SetAutoclickEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableAutoclick(enabled);
}

bool IsAutoclickEnabled() {
  return AccessibilityManager::Get()->IsAutoclickEnabled();
}

void SetAutoclickDelay(int delay_ms) {
  GetActiveUserPrefs()->SetInteger(prefs::kAccessibilityAutoclickDelayMs,
                                   delay_ms);
  GetActiveUserPrefs()->CommitPendingWrite();
}

int GetAutoclickDelay() {
  return GetActiveUserPrefs()->GetInteger(
      prefs::kAccessibilityAutoclickDelayMs);
}

void SetReducedAnimationsEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableReducedAnimations(enabled);
}

bool IsReducedAnimationsEnabled() {
  return AccessibilityManager::Get()->IsReducedAnimationsEnabled();
}

bool IsAlwaysShowScrollbarsEnabled() {
  return AccessibilityManager::Get()->IsAlwaysShowScrollbarsEnabled();
}

void SetAlwaysShowScrollbarsEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableAlwaysShowScrollbars(enabled);
}

void SetMouseKeysEnabled(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityMouseKeysEnabled,
                                   enabled);
  GetActiveUserPrefs()->CommitPendingWrite();
}

bool IsMouseKeysEnabled() {
  return GetActiveUserPrefs()->GetBoolean(
      prefs::kAccessibilityMouseKeysEnabled);
}

void SetVirtualKeyboardEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableVirtualKeyboard(enabled);
}

bool IsVirtualKeyboardEnabled() {
  return AccessibilityManager::Get()->IsVirtualKeyboardEnabled();
}

void SetMonoAudioEnabled(bool enabled) {
  AccessibilityManager::Get()->EnableMonoAudio(enabled);
}

bool IsMonoAudioEnabled() {
  return AccessibilityManager::Get()->IsMonoAudioEnabled();
}

bool IsColorCorrectionEnabled() {
  return GetActiveUserPrefs()->GetBoolean(
      prefs::kAccessibilityColorCorrectionEnabled);
}

void SetColorCorrectionEnabled(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityColorCorrectionEnabled,
                                   enabled);
}

void SetSelectToSpeakEnabled(bool enabled) {
  AccessibilityManager::Get()->SetSelectToSpeakEnabled(enabled);
}

bool IsSelectToSpeakEnabled() {
  return AccessibilityManager::Get()->IsSelectToSpeakEnabled();
}

void SetSwitchAccessEnabled(bool enabled) {
  AccessibilityManager::Get()->SetSwitchAccessEnabled(enabled);
}

void SetMagnifierEnabled(bool enabled) {
  MagnificationManager::Get()->SetMagnifierEnabled(enabled);
}

void SetDictationEnabled(bool enabled) {
  AccessibilityManager::Get()->SetDictationEnabled(enabled);
}

bool IsDictationEnabled() {
  return AccessibilityManager::Get()->IsDictationEnabled();
}

std::string GetDictationLocale() {
  return GetActiveUserPrefs()->GetString(prefs::kAccessibilityDictationLocale);
}

void SetDictationLocale(const std::string& locale) {
  GetActiveUserPrefs()->SetString(prefs::kAccessibilityDictationLocale, locale);
}

void ClearDictationLocale() {
  GetActiveUserPrefs()->SetString(prefs::kAccessibilityDictationLocale,
                                  std::string());
}

void SetAlwaysShowMenuEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu,
                                   enabled);
}

void SetLargeCursorEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityLargeCursorEnabled,
                                   enabled);
}

void SetLiveCaptionEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(::prefs::kLiveCaptionEnabled, enabled);
}

void SetHighContrastEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityHighContrastEnabled,
                                   enabled);
}

void SetSpokenFeedbackEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled,
                                   enabled);
}

void SetAutoclickEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityAutoclickEnabled,
                                   enabled);
}

void SetReducedAnimationsEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(
      prefs::kAccessibilityReducedAnimationsEnabled, enabled);
}

void SetAlwaysShowScrollbarsEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(
      prefs::kAccessibilityAlwaysShowScrollbarsEnabled, enabled);
}

void SetMouseKeysEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityMouseKeysEnabled,
                                   enabled);
}

void SetAutoclickDelayPref(int delay_ms) {
  GetActiveUserPrefs()->SetInteger(prefs::kAccessibilityAutoclickDelayMs,
                                   delay_ms);
}

void SetVirtualKeyboardEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
                                   enabled);
}

void SetMonoAudioEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityMonoAudioEnabled,
                                   enabled);
}

void SetSelectToSpeakEnabledPref(bool enabled) {
  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilitySelectToSpeakEnabled,
                                   enabled);
}

bool IsBrailleImeEnabled() {
  InputMethodManager* imm = InputMethodManager::Get();
  InputMethodDescriptors descriptors =
      imm->GetActiveIMEState()->GetEnabledInputMethods();
  for (const auto& descriptor : descriptors) {
    if (descriptor.id() == extension_ime_util::kBrailleImeEngineId)
      return true;
  }
  return false;
}

bool IsBrailleImeCurrent() {
  InputMethodManager* imm = InputMethodManager::Get();
  return imm->GetActiveIMEState()->GetCurrentInputMethod().id() ==
         extension_ime_util::kBrailleImeEngineId;
}

bool IsSodaDownloading() {
  return speech::SodaInstaller::GetInstance()->IsSodaDownloading(
      speech::LanguageCode::kEnUs);
}

void UninstallSodaForTesting() {
  speech::SodaInstaller::GetInstance()->UninstallSodaForTesting();
}

void ClearDictationOfflineNudgePref(const std::string& locale) {
  ScopedDictPrefUpdate update(GetActiveUserPrefs(),
                              prefs::kAccessibilityDictationLocaleOfflineNudge);
  update->RemoveByDottedPath(locale);
}

std::optional<bool> GetDictationOfflineNudgePref(const std::string& locale) {
  const base::DictValue& offline_nudges = GetActiveUserPrefs()->GetDict(
      prefs::kAccessibilityDictationLocaleOfflineNudge);
  return offline_nudges.FindBool(locale);
}

void AssertDictationNotificationShown(const std::u16string& display_language,
                                      const std::u16string& title,
                                      const std::u16string& description,
                                      bool is_critical) {
  // Replace the '*' placeholder in `title` with `display_name`.
  std::u16string new_title = title;
  ASSERT_TRUE(
      base::ReplaceChars(new_title, u"*", display_language, &new_title));

  message_center::NotificationList::Notifications notifications =
      message_center::MessageCenter::Get()->GetVisibleNotifications();
  ASSERT_EQ(1u, notifications.size());
  ASSERT_EQ(new_title, (*notifications.begin())->title());
  ASSERT_EQ(description, (*notifications.begin())->message());
  ASSERT_EQ(u"Dictation", (*notifications.begin())->display_source());

  message_center::SystemNotificationWarningLevel warning =
      is_critical
          ? message_center::SystemNotificationWarningLevel::CRITICAL_WARNING
          : message_center::SystemNotificationWarningLevel::NORMAL;
  ASSERT_EQ(warning,
            (*notifications.begin())->system_notification_warning_level());
}

void AssertDictationAllDlcsNotifcation(const std::u16string& display_language) {
  AssertDictationNotificationShown(display_language,
                                   kDictationAllDlcsDownloadedTitle,
                                   kDictationAllDlcsDownloadedDesc,
                                   /*is_critical=*/false);
}

void AssertDictationNoDlcsNotifcation(const std::u16string& display_language) {
  AssertDictationNotificationShown(display_language,
                                   kDictationNoDlcsDownloadedTitle,
                                   kDictationNoDlcsDownloadedDesc,
                                   /*is_critical=*/true);
}

void AssertDictationOnlySodaNotifcation(
    const std::u16string& display_language) {
  AssertDictationNotificationShown(display_language,
                                   kDictationOnlySodaDownloadedTitle,
                                   kDictationOnlySodaDownloadedDesc,
                                   /*is_critical=*/true);
}

void AssertDictationOnlyPumpkinNotifcation(
    const std::u16string& display_language) {
  AssertDictationNotificationShown(display_language,
                                   kDicationOnlyPumpkinDownloadedTitle,
                                   kDicationOnlyPumpkinDownloadedDesc,
                                   /*is_critical=*/true);
}

void AssertMessageCenterEmpty() {
  message_center::NotificationList::Notifications notifications =
      message_center::MessageCenter::Get()->GetVisibleNotifications();
  ASSERT_EQ(0u, notifications.size());
}

void ClearMessageCenter() {
  message_center::MessageCenter::Get()->RemoveAllNotifications(
      /*by_user=*/false, message_center::MessageCenter::RemoveType::ALL);
}

}  // namespace

// For user session accessibility manager tests.
class AccessibilityManagerTest : public MixinBasedInProcessBrowserTest {
 protected:
  AccessibilityManagerTest()
      : disable_animations_(
            gfx::ScopedAnimationDurationScaleMode::ZERO_DURATION) {}

  AccessibilityManagerTest(const AccessibilityManagerTest&) = delete;
  AccessibilityManagerTest& operator=(const AccessibilityManagerTest&) = delete;

  ~AccessibilityManagerTest() override = default;

  void SetUpOnMainThread() override {
    default_autoclick_delay_ = GetAutoclickDelay();
    // For general AccessibilityManagerTests, pretend that the Dictation
    // confirmation dialog has been accepted so that it doesn't interfere with
    // tests. There is a dedicated test suite to exercise the logic for the
    // dialog.
    GetActiveUserPrefs()->SetBoolean(
        prefs::kDictationAcceleratorDialogHasBeenAccepted, true);
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    scoped_feature_list_.InitWithFeatures(
        {features::kOnDeviceSpeechRecognition,
         ::features::kAccessibilityReducedAnimations,
         ::features::kAccessibilityMouseKeys},
        {});
    MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
  }

  void EnableDictationTriggeredByUser(bool soda_uninstalled_first) {
    SetDictationEnabled(true);
    if (soda_uninstalled_first) {
      // Enabling Dictation may trigger a SODA download depending on
      // SODA download state and locale. Forces uninstall.
      // Cancel any SODA downloads to pretend this logic didn't trigger.
      UninstallSodaForTesting();
    }
    TriggerDictationChangedBySystem();
  }

  void TriggerDictationChangedBySystem() {
    AccessibilityManager::Get()->OnDictationChanged(
        /*triggered_by_user=*/false);
  }

  void WaitForEnhancedNetworkTtsLoad() {
    base::RunLoop runner;
    AccessibilityManager::Get()->enhanced_network_tts_waiter_for_test_ =
        runner.QuitClosure();
    runner.Run();
  }

  int default_autoclick_delay() const { return default_autoclick_delay_; }

  int default_autoclick_delay_ = 0;

  const AccountId test_account_id_ =
      AccountId::FromUserEmailGaiaId(kTestUserName, kTestUserGaiaId);

  bool ShouldShowNetworkDictationDialog(const std::string& locale) {
    return AccessibilityManager::Get()->ShouldShowNetworkDictationDialog(
        locale);
  }

  void PostSwitchChromeVoxProfile() {
    AccessibilityManager::Get()->PostSwitchChromeVoxProfile();
  }

  bool IsChromeVoxPanelActive() {
    return AccessibilityManager::Get()->chromevox_panel_ != nullptr;
  }

  ChromeVoxPanel* GetChromeVoxPanel() {
    return AccessibilityManager::Get()->chromevox_panel_;
  }

  base::HistogramTester histogram_tester_;

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;

 private:
  gfx::ScopedAnimationDurationScaleMode disable_animations_;
};

// Test that a new user's application locale is mapped to a supported Dictation
// language and stored in the Dictation locale pref on Dictation enabled. See
// map of default country codes and all supported country codes in
// chrome/browser/ash/accessibility/dictation.cc.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
                       SetsDictationLocalePrefOnUserToggle) {
  struct {
    std::string application_locale;
    std::string expected_pref;
  } kTestCases[] = {
      {"en", "en-US"},     // No country code maps to default supported one.
      {"fr", "fr-FR"},     // Non-English version of above.
      {"ar-JO", "ar-JO"},  // Supported, non-default country code is not
                           // re-mapped.
      {"en-CA", "en-CA"},  // English language version of above.
      {"es-XX", "es-ES"},  // Unsupported country code maps to default for
                           // language.
      {"xx-xx", "en-US"},  // Unsupported code defaults to en-US.
  };
  for (const auto& testcase : kTestCases) {
    g_browser_process->SetApplicationLocale(testcase.application_locale);
    EXPECT_FALSE(IsDictationEnabled());
    EXPECT_TRUE(GetDictationLocale().empty());
    SetDictationEnabled(true);
    EXPECT_TRUE(IsDictationEnabled());
    EXPECT_EQ(testcase.expected_pref, GetDictationLocale());

    // Reset state for next test case.
    SetDictationEnabled(false);
    ClearDictationLocale();
  }
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest, TypePref) {
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(IsLiveCaptionEnabled());
  EXPECT_FALSE(IsSpokenFeedbackEnabled());
  EXPECT_FALSE(IsHighContrastEnabled());
  EXPECT_FALSE(IsAutoclickEnabled());
  EXPECT_FALSE(IsReducedAnimationsEnabled());
  EXPECT_FALSE(IsAlwaysShowScrollbarsEnabled());
  EXPECT_FALSE(IsMouseKeysEnabled());
  EXPECT_EQ(default_autoclick_delay_, GetAutoclickDelay());
  EXPECT_FALSE(IsVirtualKeyboardEnabled());
  EXPECT_FALSE(IsMonoAudioEnabled());
  EXPECT_FALSE(IsSelectToSpeakEnabled());
  EXPECT_FALSE(IsDictationEnabled());

  SetLargeCursorEnabledPref(true);
  EXPECT_TRUE(IsLargeCursorEnabled());

  SetLiveCaptionEnabledPref(true);
  EXPECT_TRUE(IsLiveCaptionEnabled());

  SetSpokenFeedbackEnabledPref(true);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());

  SetHighContrastEnabledPref(true);
  EXPECT_TRUE(IsHighContrastEnabled());

  SetAutoclickEnabledPref(true);
  EXPECT_TRUE(IsAutoclickEnabled());

  SetReducedAnimationsEnabledPref(true);
  EXPECT_TRUE(IsReducedAnimationsEnabled());

  SetAlwaysShowScrollbarsEnabledPref(true);
  EXPECT_TRUE(IsAlwaysShowScrollbarsEnabled());

  SetMouseKeysEnabledPref(true);
  EXPECT_TRUE(IsMouseKeysEnabled());

  SetAutoclickDelayPref(kTestAutoclickDelayMs);
  EXPECT_EQ(kTestAutoclickDelayMs, GetAutoclickDelay());

  SetVirtualKeyboardEnabledPref(true);
  EXPECT_TRUE(IsVirtualKeyboardEnabled());

  SetMonoAudioEnabledPref(true);
  EXPECT_TRUE(IsMonoAudioEnabled());

  SetSelectToSpeakEnabledPref(true);
  EXPECT_TRUE(IsSelectToSpeakEnabled());

  SetDictationEnabled(true);
  EXPECT_TRUE(IsDictationEnabled());

  SetLargeCursorEnabledPref(false);
  EXPECT_FALSE(IsLargeCursorEnabled());

  SetLiveCaptionEnabledPref(false);
  EXPECT_FALSE(IsLiveCaptionEnabled());

  SetSpokenFeedbackEnabledPref(false);
  EXPECT_FALSE(IsSpokenFeedbackEnabled());

  SetHighContrastEnabledPref(false);
  EXPECT_FALSE(IsHighContrastEnabled());

  SetAutoclickEnabledPref(false);
  EXPECT_FALSE(IsAutoclickEnabled());

  SetReducedAnimationsEnabledPref(false);
  EXPECT_FALSE(IsReducedAnimationsEnabled());

  SetAlwaysShowScrollbarsEnabledPref(false);
  EXPECT_FALSE(IsAlwaysShowScrollbarsEnabled());

  SetMouseKeysEnabledPref(false);
  EXPECT_FALSE(IsMouseKeysEnabled());

  SetVirtualKeyboardEnabledPref(false);
  EXPECT_FALSE(IsVirtualKeyboardEnabled());

  SetMonoAudioEnabledPref(false);
  EXPECT_FALSE(IsMonoAudioEnabled());

  SetSelectToSpeakEnabledPref(false);
  EXPECT_FALSE(IsSelectToSpeakEnabled());

  SetDictationEnabled(false);
  EXPECT_FALSE(IsDictationEnabled());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
                       ChangingTypeInvokesNotification) {
  MockAccessibilityObserver observer;

  SetSpokenFeedbackEnabled(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSpokenFeedback);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());

  observer.reset();
  SetSpokenFeedbackEnabled(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSpokenFeedback);
  EXPECT_FALSE(IsSpokenFeedbackEnabled());

  observer.reset();
  SetHighContrastEnabled(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleHighContrastMode);
  EXPECT_TRUE(IsHighContrastEnabled());

  observer.reset();
  SetHighContrastEnabled(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleHighContrastMode);
  EXPECT_FALSE(IsHighContrastEnabled());

  observer.reset();
  SetVirtualKeyboardEnabled(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleVirtualKeyboard);
  EXPECT_TRUE(IsVirtualKeyboardEnabled());

  observer.reset();
  SetVirtualKeyboardEnabled(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleVirtualKeyboard);
  EXPECT_FALSE(IsVirtualKeyboardEnabled());

  observer.reset();
  SetMonoAudioEnabled(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleMonoAudio);
  EXPECT_TRUE(IsMonoAudioEnabled());

  observer.reset();
  SetMonoAudioEnabled(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleMonoAudio);
  EXPECT_FALSE(IsMonoAudioEnabled());

  observer.reset();
  SetSelectToSpeakEnabled(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSelectToSpeak);
  EXPECT_TRUE(IsSelectToSpeakEnabled());

  observer.reset();
  SetSelectToSpeakEnabled(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSelectToSpeak);
  EXPECT_FALSE(IsSelectToSpeakEnabled());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
                       ChangingTypePrefInvokesNotification) {
  MockAccessibilityObserver observer;

  SetSpokenFeedbackEnabledPref(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSpokenFeedback);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());

  observer.reset();
  SetSpokenFeedbackEnabledPref(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSpokenFeedback);
  EXPECT_FALSE(IsSpokenFeedbackEnabled());

  observer.reset();
  SetHighContrastEnabledPref(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleHighContrastMode);
  EXPECT_TRUE(IsHighContrastEnabled());

  observer.reset();
  SetHighContrastEnabledPref(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleHighContrastMode);
  EXPECT_FALSE(IsHighContrastEnabled());

  observer.reset();
  SetVirtualKeyboardEnabledPref(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleVirtualKeyboard);
  EXPECT_TRUE(IsVirtualKeyboardEnabled());

  observer.reset();
  SetVirtualKeyboardEnabledPref(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleVirtualKeyboard);
  EXPECT_FALSE(IsVirtualKeyboardEnabled());

  observer.reset();
  SetMonoAudioEnabledPref(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleMonoAudio);
  EXPECT_TRUE(IsMonoAudioEnabled());

  observer.reset();
  SetMonoAudioEnabledPref(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleMonoAudio);
  EXPECT_FALSE(IsMonoAudioEnabled());

  observer.reset();
  SetSelectToSpeakEnabledPref(true);
  EXPECT_TRUE(observer.observed());
  EXPECT_TRUE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSelectToSpeak);
  EXPECT_TRUE(IsSelectToSpeakEnabled());

  observer.reset();
  SetSelectToSpeakEnabledPref(false);
  EXPECT_TRUE(observer.observed());
  EXPECT_FALSE(observer.observed_enabled());
  EXPECT_EQ(observer.observed_type(),
            AccessibilityNotificationType::kToggleSelectToSpeak);
  EXPECT_FALSE(IsSelectToSpeakEnabled());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest, AccessibilityMenuVisibility) {
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(IsLiveCaptionEnabled());
  EXPECT_FALSE(IsSpokenFeedbackEnabled());
  EXPECT_FALSE(IsHighContrastEnabled());
  EXPECT_FALSE(IsAutoclickEnabled());
  EXPECT_FALSE(IsVirtualKeyboardEnabled());
  EXPECT_FALSE(IsMonoAudioEnabled());
  EXPECT_FALSE(IsColorCorrectionEnabled());
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  EXPECT_FALSE(ShouldShowAccessibilityMenu());
  SetAlwaysShowMenuEnabledPref(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetAlwaysShowMenuEnabledPref(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetLargeCursorEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetLargeCursorEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetLiveCaptionEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetLiveCaptionEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetSpokenFeedbackEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetSpokenFeedbackEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetHighContrastEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetHighContrastEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetAutoclickEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetAutoclickEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetVirtualKeyboardEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetVirtualKeyboardEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetMonoAudioEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetMonoAudioEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetSelectToSpeakEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetSelectToSpeakEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());

  SetColorCorrectionEnabled(true);
  EXPECT_TRUE(ShouldShowAccessibilityMenu());
  SetColorCorrectionEnabled(false);
  EXPECT_FALSE(ShouldShowAccessibilityMenu());
}

// TODO (crbug.com/535989327): Re-enable this test after migrating to new speech
// backend.
IN_PROC_BROWSER_TEST_F(
    AccessibilityManagerTest,
    DISABLED_EnhancedNetworkVoicesExtensionLoadedWhenNeeded) {
  auto* component_loader =
      extensions::ComponentLoader::Get(browser()->GetProfile());

  // Not loaded yet.
  EXPECT_FALSE(
      component_loader->Exists(extension_misc::kEnhancedNetworkTtsExtensionId));

  SetSelectToSpeakEnabled(true);
  // Loaded the first time Select to Speak is enabled because the user hasn't
  // seen the dialog yet, and voices are needed immediately after the dialog is
  // accepted.
  WaitForEnhancedNetworkTtsLoad();
  EXPECT_TRUE(
      component_loader->Exists(extension_misc::kEnhancedNetworkTtsExtensionId));

  // Unloaded with Select to Speak turned off.
  SetSelectToSpeakEnabled(false);
  EXPECT_FALSE(
      component_loader->Exists(extension_misc::kEnhancedNetworkTtsExtensionId));

  // Pretend the dialog was shown but the user didn't accept it by changing the
  // pref that the dialog was shown but not the pref to enable the voices.
  SetSelectToSpeakEnabled(true);
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kAccessibilitySelectToSpeakEnhancedVoicesDialogShown, true);
  EXPECT_FALSE(
      component_loader->Exists(extension_misc::kEnhancedNetworkTtsExtensionId));

  // Pretend the user turned on the network voices setting.
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kAccessibilitySelectToSpeakEnhancedNetworkVoices, true);
  WaitForEnhancedNetworkTtsLoad();
  EXPECT_TRUE(
      component_loader->Exists(extension_misc::kEnhancedNetworkTtsExtensionId));

  // Now the admin disallows network voices by policy.
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kAccessibilityEnhancedNetworkVoicesInSelectToSpeakAllowed, false);
  EXPECT_FALSE(
      component_loader->Exists(extension_misc::kEnhancedNetworkTtsExtensionId));
}

// Ensures that the ChromeVox panel stays in sync with ChromeVox's enabled
// state. The panel should only be created if ChromeVox is enabled and if there
// is no existing panel.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest, ChromeVoxPanel) {
  ASSERT_FALSE(IsSpokenFeedbackEnabled());
  ASSERT_FALSE(IsChromeVoxPanelActive());
  // Switch profiles. The panel shouldn't be created if ChromeVox is off.
  PostSwitchChromeVoxProfile();
  ASSERT_FALSE(IsChromeVoxPanelActive());

  extensions::ExtensionHostTestHelper host_helper(
      AccessibilityManager::Get()->profile(),
      extension_misc::kChromeVoxExtensionId);
  SetSpokenFeedbackEnabled(true);
  host_helper.WaitForHostCompletedFirstLoad();

  ASSERT_TRUE(IsSpokenFeedbackEnabled());
  ASSERT_TRUE(IsChromeVoxPanelActive());
  // Switch profiles. The panel should not be recreated.
  PostSwitchChromeVoxProfile();
  ASSERT_TRUE(IsChromeVoxPanelActive());
  SetSpokenFeedbackEnabled(false);
  ASSERT_FALSE(IsSpokenFeedbackEnabled());
  ASSERT_FALSE(IsChromeVoxPanelActive());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
                       ChromeVoxPanelMultipleDisplays) {
  // Start with two displays, the non-primary one is active for new windows.
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("600x550,800x750");
  auto root_windows = ash::Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  ASSERT_EQ(ash::Shell::GetPrimaryRootWindow(), root_windows[0]);
  ash::Shell::SetRootWindowForNewWindows(root_windows[1]);
  EXPECT_EQ(800, root_windows[1]->GetBoundsInRootWindow().width());

  // Launch ChromeVox.
  extensions::ExtensionHostTestHelper host_helper(
      AccessibilityManager::Get()->profile(),
      extension_misc::kChromeVoxExtensionId);
  SetSpokenFeedbackEnabled(true);
  host_helper.WaitForHostCompletedFirstLoad();

  ASSERT_TRUE(IsSpokenFeedbackEnabled());
  ChromeVoxPanel* panel = GetChromeVoxPanel();
  ASSERT_NE(nullptr, panel);

  // Check the panel is visible on the primary root window and is sized
  // correctly for it.
  EXPECT_EQ(views::GetRootWindow(panel->GetWidget()), root_windows[0]);
  EXPECT_GT(panel->GetWidget()->GetWindowBoundsInScreen().height(), 10);
  EXPECT_EQ(600, panel->GetWidget()->GetWindowBoundsInScreen().width());
  EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains(
      panel->GetWidget()->GetWindowBoundsInScreen()));
}

// POC: ChromeVoxPanelWebContentsObserver::DidFinishNavigation() dispatches
// EnterFullscreen()/Focus() purely on GetLastCommittedURL().GetRef() with no
// origin check, and AccessibilityPanel (the WebContentsDelegate) does not
// contain renderer-initiated cross-process navigations. Once arbitrary web
// content commits in the panel WebContents, '#fullscreen' grants it a
// fullscreen, activatable, keyboard-focused surface in
// kShellWindowId_AccessibilityPanelContainer — z-ordered above the ChromeOS
// lock screen and system modals.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
                       ChromeVoxPanelFragmentNoOriginCheck) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1) Enable ChromeVox so the panel is created.
  extensions::ExtensionHostTestHelper host_helper(
      AccessibilityManager::Get()->profile(),
      extension_misc::kChromeVoxExtensionId);
  SetSpokenFeedbackEnabled(true);
  host_helper.WaitForHostCompletedFirstLoad();
  ChromeVoxPanel* panel = GetChromeVoxPanel();
  ASSERT_NE(nullptr, panel);

  // Grab the panel WebContents (a bare views::WebView; not an ExtensionHost).
  auto* web_view = static_cast<views::WebView*>(panel->GetContentsView());
  content::WebContents* panel_wc = web_view->GetWebContents();
  ASSERT_NE(nullptr, panel_wc);
  ASSERT_TRUE(content::WaitForLoadStop(panel_wc));

  aura::Window* root = ash::Shell::GetPrimaryRootWindow();
  // Precondition: panel starts as a thin FULL_WIDTH strip, not activatable.
  EXPECT_LT(panel->GetWidget()->GetWindowBoundsInScreen().height(),
            root->bounds().height());
  EXPECT_FALSE(panel->GetWidget()->widget_delegate()->CanActivate());

  // 2) Simulate a compromised panel renderer: renderer-initiated top-level
  //    navigation to web content. This should be BLOCKED.
  const GURL attacker_url = embedded_test_server()->GetURL("/title1.html");
  const GURL original_url = panel_wc->GetLastCommittedURL();
  {
    content::TestNavigationObserver nav(panel_wc);
    content::ExecuteScriptAsync(
        panel_wc, "window.location = '" + attacker_url.spec() + "';");
    nav.Wait();
  }
  // The navigation should NOT have committed.
  EXPECT_EQ(original_url, panel_wc->GetLastCommittedURL());
  EXPECT_NE(attacker_url, panel_wc->GetLastCommittedURL());

  // 3) Test defense-in-depth: browser-initiated navigation to cross-origin URL.
  //    This is allowed, but the fragment check should still block the
  //    fullscreen/focus.
  {
    content::TestNavigationObserver nav(panel_wc);
    panel_wc->GetController().LoadURL(attacker_url, content::Referrer(),
                                      ui::PAGE_TRANSITION_TYPED, std::string());
    nav.Wait();
  }
  EXPECT_EQ(attacker_url, panel_wc->GetLastCommittedURL());

  // 4) Attacker page sets location.hash = 'fullscreen'.
  {
    content::TestNavigationObserver nav(panel_wc);
    content::ExecuteScriptAsync(panel_wc, "location.hash = 'fullscreen';");
    nav.Wait();
  }

  // The panel should NOT have entered fullscreen or become active because
  // the origin is not ChromeVox.
  EXPECT_LT(panel->GetWidget()->GetWindowBoundsInScreen().height(),
            root->bounds().height());
  EXPECT_FALSE(panel->GetWidget()->widget_delegate()->CanActivate());
  EXPECT_FALSE(panel->GetWidget()->IsActive());

  // 5) '#focus' should also be ignored.
  {
    content::TestNavigationObserver nav(panel_wc);
    content::ExecuteScriptAsync(panel_wc, "location.hash = 'focus';");
    nav.Wait();
  }
  EXPECT_FALSE(panel->GetWidget()->IsActive());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
                       FaceGazeSettingsPageOpensWhenFeatureIsEnabled) {
  GetActiveUserPrefs()->SetBoolean(
      prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted, true);
  base::RunLoop waiter;
  AccessibilityManager::Get()->SetOpenSettingsSubpageObserverForTest(
      base::BindLambdaForTesting([&waiter]() { waiter.Quit(); }));
  // Enable FaceGaze and wait for settings page to open.
  AccessibilityManager::Get()->EnableFaceGaze(true);
  waiter.Run();
}

class AccessibilityManagerDlcTest : public AccessibilityManagerTest {
 public:
  AccessibilityManagerDlcTest()
      : disable_animations_(
            gfx::ScopedAnimationDurationScaleMode::ZERO_DURATION) {}
  ~AccessibilityManagerDlcTest() override = default;
  AccessibilityManagerDlcTest(const AccessibilityManagerDlcTest&) = delete;
  AccessibilityManagerDlcTest& operator=(const AccessibilityManagerDlcTest&) =
      delete;

 protected:
  void SetUpOnMainThread() override {
    AccessibilityManagerTest::SetUpOnMainThread();
    UninstallSodaForTesting();
    EnsureSodaObservation();
    ClearMessageCenter();
    AssertMessageCenterEmpty();
  }

  void TearDownOnMainThread() override {
    UninstallSodaForTesting();
    AccessibilityManagerTest::TearDownOnMainThread();
  }

  void EnsureSodaObservation() {
    // Ensures that AccessibilityManager is observing SodaInstaller.
    if (!AccessibilityManager::Get()->soda_observation_.IsObservingSource(
            soda_installer()))
      AccessibilityManager::Get()->soda_observation_.Observe(soda_installer());
  }

  void InstallPumpkinAndWait() {
    base::RunLoop loop;
    AccessibilityManager::Get()->InstallPumpkinForDictation(base::DoNothing());
    loop.RunUntilIdle();
  }

  void OnPumpkinError() {
    AccessibilityManager* manager = AccessibilityManager::Get();
    // We require `install_pumpkin_callback_` to be set before `OnPumpkinError`
    // can be called.
    manager->install_pumpkin_callback_ = base::DoNothing();
    manager->OnPumpkinError("Error");
  }

  void OnPumpkinInstalled(bool success, const std::string& root_path) {
    AccessibilityManager::Get()->OnPumpkinInstalled(success, root_path);
  }

  void OnPumpkinDataCreated(
      std::optional<extensions::api::accessibility_private::PumpkinData> data) {
    AccessibilityManager::Get()->OnPumpkinDataCreated(std::move(data));
  }

  void InstallFaceGazeAssetsAndWait() {
    base::RunLoop loop;
    AccessibilityManager::Get()->InstallFaceGazeAssets(base::DoNothing());
    loop.RunUntilIdle();
  }

  void OnFaceGazeAssetsFailed() {
    AccessibilityManager* manager = AccessibilityManager::Get();
    // We require `install_facegaze_assets_callback_` to be set before
    // `OnFaceGazeAssetsFailed` can be called.
    manager->install_facegaze_assets_callback_ = base::DoNothing();
    manager->OnFaceGazeAssetsFailed("Error");
  }

  speech::SodaInstaller* soda_installer() {
    return speech::SodaInstaller::GetInstance();
  }

  speech::LanguageCode en_us() { return speech::LanguageCode::kEnUs; }
  speech::LanguageCode fr_fr() { return speech::LanguageCode::kFrFr; }

  const std::u16string en_us_display_name() {
    return u"English (United States)";
  }

 private:
  gfx::ScopedAnimationDurationScaleMode disable_animations_;
};

// Tests that SODA download is initiated when Dictation is enabled.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaDownloadWhenDictationEnabled) {
  ClearDictationOfflineNudgePref("en-US");
  EXPECT_FALSE(IsSodaDownloading());
  EXPECT_FALSE(ShouldShowNetworkDictationDialog("en-US"));
  SetDictationEnabled(true);
  EXPECT_TRUE(IsSodaDownloading());
  // The nudge should not be requested to be shown because this was a
  // user-initiated change.
  EXPECT_FALSE(GetDictationOfflineNudgePref("en-US"));
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  EXPECT_FALSE(IsSodaDownloading());
  // The nudge was never shown.
  EXPECT_FALSE(GetDictationOfflineNudgePref("en-US"));
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaDownloadNotTriggeredByUserShowsNudge) {
  SetDictationEnabled(true);
  // Reset to initial state: no SODA, no locale. Then trigger the dictation
  // changed callback as if by the system when the pref is set at start-up.
  ClearDictationOfflineNudgePref("en-US");
  UninstallSodaForTesting();
  ClearDictationLocale();
  TriggerDictationChangedBySystem();

  EXPECT_TRUE(IsSodaDownloading());
  // The nudge should be shown when SODA download finishes.
  EXPECT_FALSE(GetDictationOfflineNudgePref("en-US").value());
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
  EXPECT_FALSE(IsSodaDownloading());
  EXPECT_TRUE(GetDictationOfflineNudgePref("en-US").value());
  // No notifications were shown.
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaErrorNotTriggeredByUserTriesToShowNudge) {
  SetDictationEnabled(true);
  // Reset to initial state: no SODA, no locale. Then trigger the dictation
  // changed callback as if by the system when the pref is set at start-up.
  ClearDictationOfflineNudgePref("en-US");
  UninstallSodaForTesting();
  ClearDictationLocale();
  TriggerDictationChangedBySystem();

  EXPECT_TRUE(IsSodaDownloading());
  // The nudge should be shown when SODA download finishes.
  EXPECT_FALSE(GetDictationOfflineNudgePref("en-US").value());
  speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting();
  EXPECT_FALSE(IsSodaDownloading());
  // The nudge was never shown because of the error.
  EXPECT_FALSE(GetDictationOfflineNudgePref("en-US").value());
  // No download failed notifications were shown.
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       OneNudgeForSodaMultipleDownload) {
  ClearDictationOfflineNudgePref("en-US");
  EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/true);
  EXPECT_TRUE(IsSodaDownloading());
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  EXPECT_FALSE(IsSodaDownloading());
  EXPECT_TRUE(GetDictationOfflineNudgePref("en-US").value());
  UninstallSodaForTesting();
  SetDictationEnabled(false);

  // The second time the same language downloads, the nudge is not shown again.
  EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/false);
  EXPECT_TRUE(IsSodaDownloading());
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  EXPECT_FALSE(IsSodaDownloading());
  // Unchanged.
  EXPECT_TRUE(GetDictationOfflineNudgePref("en-US").value());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaInstalledBeforeDictationEnabled) {
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  ClearDictationOfflineNudgePref("en-US");
  EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/false);

  // Already downloaded.
  EXPECT_FALSE(IsSodaDownloading());

  // Nudge is shown immediately.
  EXPECT_TRUE(GetDictationOfflineNudgePref("en-US").value());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaDownloadTriggeredByLocaleChange) {
  EXPECT_FALSE(IsSodaDownloading());

  // af-ZA is not supported by SODA, so download shouldn't trigger.
  SetDictationLocale(kSodaUnsupportedLocale);
  SetDictationEnabled(true);
  EXPECT_FALSE(IsSodaDownloading());
  // The nudge should not be requested to be shown because this is not an
  // offline language.
  EXPECT_FALSE(GetDictationOfflineNudgePref(kSodaUnsupportedLocale));

  // Change the locale to one supported by SODA without changing Dictation
  // enabled. This mocks selecting a new locale from settings.
  SetDictationLocale("en-US");
  EXPECT_TRUE(IsSodaDownloading());
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  EXPECT_FALSE(IsSodaDownloading());
  // The nudge was never shown because this was a user-initiated change.
  EXPECT_FALSE(GetDictationOfflineNudgePref("en-US"));
  // The notification was shown.
  AssertDictationOnlySodaNotifcation(en_us_display_name());
}

// Ensures that we show the SODA succeeded notification for Dictation if the
// SODA binary downloads, followed by the language pack matching the dictation
// language.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaSucceededNotificationCase1) {
  // For this test, pretend that the Dictation locale is fr-FR.
  g_browser_process->SetApplicationLocale("fr-FR");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting(fr_fr());
  AssertDictationOnlySodaNotifcation(u"français (France)");
}

// Similar to above. Ensures that we show the SODA succeeded notification for
// Dictation if the language pack downloads, followed by the SODA binary.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaSucceededNotificationCase2) {
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertDictationOnlySodaNotifcation(en_us_display_name());
}

// Ensures that we show the SODA failed notification for Dictation if the SODA
// binary fails.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaFailedNotificationBinaryError) {
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting();
  AssertDictationNoDlcsNotifcation(en_us_display_name());
}

// Similar to above. Ensures that we show the SODA failed notification for
// Dictation if the language pack fails.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaFailedNotificationLanguageError) {
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
}

// Ensures that the SODA failed notification for Dictation is given if
// the language pack downloads, but the SODA binary fails.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaLanguageInstalledBinaryFails) {
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaErrorForTesting();
  AssertDictationNoDlcsNotifcation(en_us_display_name());
}

// Similar to above. Ensures that we show the SODA failed notification if the
// SODA binary downloads, but the language pack fails.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaBinaryInstalledLanguageFails) {
  // For this test, pretend that the Dictation locale is fr-FR.
  g_browser_process->SetApplicationLocale("fr-FR");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaErrorForTesting(fr_fr());
  AssertDictationNoDlcsNotifcation(u"français (France)");
}

// Ensures that SODA failed notification is shown just once if both the SODA
// binary fails and the language pack fails.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaFailedNotificationNotShownTwice) {
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  ClearMessageCenter();

  // No second message is shown on additional failures.
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaErrorForTesting();
  AssertMessageCenterEmpty();
}

// Ensures that SODA failed notification is only shown once.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaFailedNotificationShownOnlyOnce) {
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  SetDictationEnabled(false);

  // Reset SODA state so that it tries the download again.
  UninstallSodaForTesting();
  ClearMessageCenter();

  // A fresh attempt at Dictation means another chance to show an error message.
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertMessageCenterEmpty();
}

// Tests that the SODA download notification for Dictation is NOT given if
// Dictation wasn't triggered by the user.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaNotTriggeredByUser) {
  EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/false);
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
}

// Tests that the SODA download notification for Dictation is NOT given if
// the installed language doesn't match the Dictation locale.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaWrongLanguage) {
  // For this test, pretend that the Dictation locale is fr-FR.
  g_browser_process->SetApplicationLocale("fr-FR");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting();
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaNotificationShownOnDictationLocaleChange) {
  // af-ZA is not supported by SODA.
  SetDictationLocale(kSodaUnsupportedLocale);
  EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/false);
  AssertMessageCenterEmpty();

  // Change the locale to one supported by SODA without changing Dictation
  // enabled. This mocks selecting a new locale from settings.
  SetDictationLocale("en-US");
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());

  // The notification should have been shown.
  AssertDictationOnlySodaNotifcation(en_us_display_name());
}

// Tests the behavior of AccessibilityManager and when it calls the
// `UpdateDictationButtonOnSpeechRecognitionDownloadChanged` method.
// The method is used to update the Dictation button tray when SODA download
// state changes.
IN_PROC_BROWSER_TEST_F(
    AccessibilityManagerDlcTest,
    UpdateDictationButtonOnSpeechRecognitionDownloadChanged) {
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  auto test_api = AccessibilityControllerTestApi::Create();
  EXPECT_EQ(0, test_api->GetDictationSodaDownloadProgress());

  // The API will not be called if the language pack differs from the Dictation
  // locale.
  soda_installer()->NotifySodaProgressForTesting(30, fr_fr());
  EXPECT_EQ(0, test_api->GetDictationSodaDownloadProgress());
  // The API will be called if the language pack matches the Dictation locale.
  soda_installer()->NotifySodaProgressForTesting(50, en_us());
  EXPECT_EQ(50, test_api->GetDictationSodaDownloadProgress());
  // If SODA download fails, the API will be called with a value of 0.
  soda_installer()->NotifySodaErrorForTesting();
  EXPECT_EQ(0, test_api->GetDictationSodaDownloadProgress());
  // Reset to a non-zero value.
  soda_installer()->NotifySodaProgressForTesting(70, en_us());
  EXPECT_EQ(70, test_api->GetDictationSodaDownloadProgress());
  // If SODA download succeeds, the API will be called with a value of 100.
  soda_installer()->NotifySodaInstalledForTesting();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  EXPECT_EQ(100, test_api->GetDictationSodaDownloadProgress());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaSuccessPumpkinSuccess) {
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertDictationOnlySodaNotifcation(en_us_display_name());

  InstallPumpkinAndWait();
  AssertDictationAllDlcsNotifcation(en_us_display_name());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaSuccessPumpkinFail) {
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertDictationOnlySodaNotifcation(en_us_display_name());

  OnPumpkinError();
  AssertDictationOnlySodaNotifcation(en_us_display_name());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaFailPumpkinSuccess) {
  // Calling `InstallPumpkinAndWait()` will run the message loop and cause SODA
  // to be installed. Avoid this scenario for the purposes of this test.
  soda_installer()->NeverDownloadSodaForTesting();
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  InstallPumpkinAndWait();
  AssertDictationOnlyPumpkinNotifcation(en_us_display_name());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaFailPumpkinFail) {
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  OnPumpkinError();
  AssertDictationNoDlcsNotifcation(en_us_display_name());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SuccessNotificationOnlyShownOnce) {
  // Show the success message for the first time.
  ASSERT_FALSE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcSuccessNotificationHasBeenShown));
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertDictationOnlySodaNotifcation(en_us_display_name());
  InstallPumpkinAndWait();
  AssertDictationAllDlcsNotifcation(en_us_display_name());
  ASSERT_TRUE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcSuccessNotificationHasBeenShown));

  ClearMessageCenter();

  // Recreate the conditions for the notification and ensure it's not shown
  // again.
  InstallPumpkinAndWait();
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       PumpkinNotificationOnlyShownOnce) {
  ASSERT_FALSE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcOnlyPumpkinDownloadedNotificationHasBeenShown));
  // Calling `InstallPumpkinAndWait()` will run the message loop and cause SODA
  // to be installed. Avoid this scenario for the purposes of this test.
  soda_installer()->NeverDownloadSodaForTesting();
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  InstallPumpkinAndWait();
  AssertDictationOnlyPumpkinNotifcation(en_us_display_name());
  ASSERT_TRUE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcOnlyPumpkinDownloadedNotificationHasBeenShown));

  ClearMessageCenter();

  // Recreate the conditions for the notification and ensure it's not shown
  // again.
  InstallPumpkinAndWait();
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       SodaNotificationOnlyShownOnce) {
  ASSERT_FALSE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcOnlySodaDownloadedNotificationHasBeenShown));
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertDictationOnlySodaNotifcation(en_us_display_name());
  OnPumpkinError();
  AssertDictationOnlySodaNotifcation(en_us_display_name());
  ASSERT_TRUE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcOnlySodaDownloadedNotificationHasBeenShown));

  ClearMessageCenter();

  // Recreate the conditions for the notification and ensure it's not shown
  // again.
  OnPumpkinError();
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertMessageCenterEmpty();
  soda_installer()->NotifySodaInstalledForTesting();
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       NoDlcsNotificationOnlyShownOnce) {
  ASSERT_FALSE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationNoDlcsDownloadedNotificationHasBeenShown));
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  OnPumpkinError();
  AssertDictationNoDlcsNotifcation(en_us_display_name());
  ASSERT_TRUE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationNoDlcsDownloadedNotificationHasBeenShown));

  ClearMessageCenter();

  // Recreate the conditions for the notification and ensure it's not shown
  // again.
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertMessageCenterEmpty();
  OnPumpkinError();
  AssertMessageCenterEmpty();
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       PumpkinNotificationOnlyShownOnceFrench) {
  ASSERT_FALSE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcOnlyPumpkinDownloadedNotificationHasBeenShown));
  g_browser_process->SetApplicationLocale("fr-FR");
  SetDictationLocale("fr-FR");
  SetDictationEnabled(true);
  AssertMessageCenterEmpty();
  InstallPumpkinAndWait();
  AssertDictationOnlyPumpkinNotifcation(u"français (France)");
  ASSERT_TRUE(GetActiveUserPrefs()->GetBoolean(
      prefs::kDictationDlcOnlyPumpkinDownloadedNotificationHasBeenShown));

  ClearMessageCenter();

  // Recreate the conditions for the notification and ensure it's not shown
  // again.
  InstallPumpkinAndWait();
  AssertMessageCenterEmpty();
}

// Ensures that AccessibilityManager can handle when OnPumpkinInstalled is
// called multiple times, which can happen in production.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       OnPumpkinInstalledMultiple) {
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  InstallPumpkinAndWait();
  OnPumpkinInstalled(true, "fake/pumpkin/root/path");
}

// Ensures that AccessibilityManager can handle when OnPumpkinDataCreated is
// called multiple times, which can happen in production.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                       OnPumpkinDataCreatedMultiple) {
  SetDictationLocale("en-US");
  SetDictationEnabled(true);
  InstallPumpkinAndWait();
  OnPumpkinDataCreated(std::nullopt);
}

// Ensures that the correct notification is shown when the facegaze-assets DLC
// is successfully downloaded.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, FaceGazeAssetsSucceeded) {
  AccessibilityManager::Get()->EnableFaceGaze(true);
  // Turning on FaceGaze will add a pinned notification to the message center,
  // so clear it for the purposes of this test.
  ClearMessageCenter();
  InstallFaceGazeAssetsAndWait();

  message_center::NotificationList::Notifications notifications =
      message_center::MessageCenter::Get()->GetVisibleNotifications();
  ASSERT_EQ(1u, notifications.size());
  ASSERT_EQ(u"Face control files downloaded",
            (*notifications.begin())->title());
  ASSERT_EQ(u"Face control will be available to other users on the device",
            (*notifications.begin())->message());
}

// Ensures that the correct notification is shown when the facegaze-assets DLC
// fails to download.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, FaceGazeAssetsFailed) {
  AccessibilityManager::Get()->EnableFaceGaze(true);
  // Turning on FaceGaze will add a pinned notification to the message center,
  // so clear it for the purposes of this test.
  ClearMessageCenter();
  OnFaceGazeAssetsFailed();

  message_center::NotificationList::Notifications notifications =
      message_center::MessageCenter::Get()->GetVisibleNotifications();
  ASSERT_EQ(1u, notifications.size());
  ASSERT_EQ(u"Couldn't download face control files",
            (*notifications.begin())->title());
  ASSERT_EQ(u"Try again later", (*notifications.begin())->message());
}

enum DictationDialogTestVariant {
  kOfflineEnabledAndAvailable,
  kOfflineEnabledAndUnavailable,
  kOfflineDisabled
};

class AccessibilityManagerDictationDialogTest
    : public AccessibilityManagerTest,
      public WithParamInterface<DictationDialogTestVariant> {
 protected:
  AccessibilityManagerDictationDialogTest()
      : disable_animations_(
            gfx::ScopedAnimationDurationScaleMode::ZERO_DURATION) {}
  ~AccessibilityManagerDictationDialogTest() override = default;
  AccessibilityManagerDictationDialogTest(
      const AccessibilityManagerDictationDialogTest&) = delete;
  AccessibilityManagerDictationDialogTest& operator=(
      const AccessibilityManagerDictationDialogTest&) = delete;

  void SetUpOnMainThread() override {
    // Ensure the dialog has not been accepted yet.
    GetActiveUserPrefs()->SetBoolean(
        prefs::kDictationAcceleratorDialogHasBeenAccepted, false);
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Set the device language to one that is not supported by SODA on Chrome
    // OS. This will force Dictation to show the confirmation dialog when
    // enabled.
    locale_ = kSodaUnsupportedLocale;
    command_line->AppendSwitchASCII(::switches::kLang, locale_);

    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;
    if (GetParam() == DictationDialogTestVariant::kOfflineEnabledAndAvailable) {
      enabled_features.push_back(features::kOnDeviceSpeechRecognition);
    } else if (GetParam() ==
               DictationDialogTestVariant::kOfflineEnabledAndUnavailable) {
      // SODA isn't available on this device.
      disabled_features.push_back(features::kOnDeviceSpeechRecognition);
    } else {
      disabled_features.push_back(features::kOnDeviceSpeechRecognition);
    }
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
    MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
  }

  std::string locale() { return locale_; }

  void AcceptDialog() {
    AccessibilityManager::Get()->OnNetworkDictationDialogAccepted();
  }

  void DismissDialog() {
    AccessibilityManager::Get()->OnNetworkDictationDialogDismissed();
  }

  bool IsDictationNetworkDialogShowing() {
    return AccessibilityManager::Get()->network_dictation_dialog_is_showing_;
  }

 private:
  std::string locale_;
  gfx::ScopedAnimationDurationScaleMode disable_animations_;
};

INSTANTIATE_TEST_SUITE_P(
    TestWithDictationOfflineEnabledAndAvailable,
    AccessibilityManagerDictationDialogTest,
    ::testing::Values(DictationDialogTestVariant::kOfflineEnabledAndAvailable,
                      DictationDialogTestVariant::kOfflineEnabledAndUnavailable,
                      DictationDialogTestVariant::kOfflineDisabled));

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationDialogTest,
                       ShouldShowNetworkDictationDialog) {
  if (GetParam() == DictationDialogTestVariant::kOfflineEnabledAndAvailable) {
    // The dialog should be shown for languages not supported by SODA.
    // Currently, the only supported language is English.
    EXPECT_FALSE(ShouldShowNetworkDictationDialog("en-US"));
  } else {
    EXPECT_TRUE(ShouldShowNetworkDictationDialog("en-US"));
  }
  EXPECT_TRUE(ShouldShowNetworkDictationDialog(""));
  EXPECT_TRUE(ShouldShowNetworkDictationDialog(kSodaUnsupportedLocale));

  PrefService* prefs = GetActiveUserPrefs();
  prefs->SetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted, true);
  // If we have already shown the dialog, then we never need to do so again.
  EXPECT_FALSE(ShouldShowNetworkDictationDialog("fr-FR"));
}

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationDialogTest, DismissDialog) {
  PrefService* prefs = GetActiveUserPrefs();
  EXPECT_FALSE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));
  EXPECT_TRUE(ShouldShowNetworkDictationDialog(locale()));

  SetDictationEnabled(true);
  EXPECT_TRUE(IsDictationEnabled());
  EXPECT_TRUE(IsDictationNetworkDialogShowing());

  // Dismissing the dialog should turn Dictation off.
  DismissDialog();
  EXPECT_FALSE(IsDictationEnabled());
  EXPECT_FALSE(IsDictationNetworkDialogShowing());
  EXPECT_FALSE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));
  EXPECT_TRUE(ShouldShowNetworkDictationDialog(locale()));
}

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationDialogTest, AcceptDialog) {
  PrefService* prefs = GetActiveUserPrefs();
  EXPECT_FALSE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));
  EXPECT_TRUE(ShouldShowNetworkDictationDialog(locale()))
      << " locale " << locale();

  SetDictationEnabled(true);
  EXPECT_TRUE(IsDictationEnabled());
  EXPECT_TRUE(IsDictationNetworkDialogShowing());
  EXPECT_FALSE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));

  // Accepting the dialog should keep Dictation on and change the pref that
  // tracks whether or not the dialog has been accepted.
  AcceptDialog();
  EXPECT_TRUE(IsDictationEnabled());
  EXPECT_FALSE(IsDictationNetworkDialogShowing());
  EXPECT_TRUE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));
  EXPECT_FALSE(ShouldShowNetworkDictationDialog(locale()));
}

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationDialogTest,
                       DialogNotShownAfterAccepted) {
  PrefService* prefs = GetActiveUserPrefs();
  // Pretend that the dialog was already accepted.
  AcceptDialog();
  EXPECT_TRUE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));
  EXPECT_FALSE(ShouldShowNetworkDictationDialog(locale()));

  // Once the dialog has been accepted, we should not show it again.
  SetDictationEnabled(true);
  EXPECT_TRUE(IsDictationEnabled());
  EXPECT_FALSE(IsDictationNetworkDialogShowing());
  EXPECT_TRUE(
      prefs->GetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted));
  EXPECT_FALSE(ShouldShowNetworkDictationDialog(locale()));
}

// For signin screen to user session accessibility manager tests.
class AccessibilityManagerLoginTest : public OobeBaseTest {
 protected:
  AccessibilityManagerLoginTest()
      : disable_animations_(
            gfx::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
    scoped_feature_list_.InitWithFeatures(
        {::features::kAccessibilityReducedAnimations,
         ::features::kAccessibilityMouseKeys},
        {});
  }

  AccessibilityManagerLoginTest(const AccessibilityManagerLoginTest&) = delete;
  AccessibilityManagerLoginTest& operator=(
      const AccessibilityManagerLoginTest&) = delete;

  ~AccessibilityManagerLoginTest() override = default;

  void SetUpOnMainThread() override {
    // BrailleController has to be set before SetUpOnMainThread call as
    // observers subscribe to the controller during SetUpOnMainThread.
    AccessibilityManager::SetBrailleControllerForTest(&braille_controller_);
    default_autoclick_delay_ = GetAutoclickDelay();
    OobeBaseTest::SetUpOnMainThread();
  }

  void TearDownOnMainThread() override {
    OobeBaseTest::TearDownOnMainThread();
    AccessibilityManager::SetBrailleControllerForTest(nullptr);
  }

  void CreateSession(const AccountId& account_id) {
    ASSERT_TRUE(user_manager::TestHelper(user_manager::UserManager::Get())
                    .AddRegularUser(account_id));

    auto* session_manager = session_manager::SessionManager::Get();
    session_manager->CreateSession(
        account_id,
        // TODO(crbug.com/278643115): Use fake username hash.
        account_id.GetUserEmail(),
        /*new_user=*/false,
        /*has_active_session=*/false);
  }

  void StartUserSession(const AccountId& account_id) {
    profiles::testing::CreateProfileSync(
        g_browser_process->profile_manager(),
        BrowserContextHelper::Get()->GetBrowserContextPathByUserIdHash(
            user_manager::UserManager::Get()
                ->FindUser(account_id)
                ->username_hash()));

    auto* session_manager = session_manager::SessionManager::Get();
    session_manager->NotifyUserProfileLoaded(account_id);
    session_manager->SessionStarted();
  }

  void SetBrailleDisplayAvailability(bool available) {
    braille_controller_.SetAvailable(available);
    braille_controller_.GetObserver()->OnBrailleDisplayStateChanged(
        *braille_controller_.GetDisplayState());
  }

  int default_autoclick_delay_ = 0;

  MockBrailleController braille_controller_;

  const AccountId test_account_id_ =
      AccountId::FromUserEmailGaiaId(kTestUserName, kTestUserGaiaId);

 private:
  gfx::ScopedAnimationDurationScaleMode disable_animations_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(AccessibilityManagerLoginTest, BrailleOnLoginScreen) {
  WaitForSigninScreen();
  EXPECT_FALSE(IsSpokenFeedbackEnabled());

  // Signal the accessibility manager that a braille display was connected.
  SetBrailleDisplayAvailability(true);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());
}

IN_PROC_BROWSER_TEST_F(AccessibilityManagerLoginTest, Login) {
  WaitForSigninScreen();
  EXPECT_FALSE(IsAutoclickEnabled());
  EXPECT_FALSE(IsHighContrastEnabled());
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(IsMonoAudioEnabled());
  EXPECT_FALSE(IsMouseKeysEnabled());
  EXPECT_FALSE(IsAlwaysShowScrollbarsEnabled());
  EXPECT_FALSE(IsReducedAnimationsEnabled());
  EXPECT_FALSE(IsSpokenFeedbackEnabled());
  EXPECT_FALSE(IsVirtualKeyboardEnabled());
  EXPECT_EQ(default_autoclick_delay_, GetAutoclickDelay());

  CreateSession(test_account_id_);

  // Confirms that the features are still disabled just after login.
  EXPECT_FALSE(IsAutoclickEnabled());
  EXPECT_FALSE(IsHighContrastEnabled());
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(IsMonoAudioEnabled());
  EXPECT_FALSE(IsMouseKeysEnabled());
  EXPECT_FALSE(IsAlwaysShowScrollbarsEnabled());
  EXPECT_FALSE(IsReducedAnimationsEnabled());
  EXPECT_FALSE(IsSpokenFeedbackEnabled());
  EXPECT_FALSE(IsVirtualKeyboardEnabled());
  EXPECT_EQ(default_autoclick_delay_, GetAutoclickDelay());

  StartUserSession(test_account_id_);

  // Confirms that the features are still disabled after session starts.
  EXPECT_FALSE(IsAutoclickEnabled());
  EXPECT_FALSE(IsHighContrastEnabled());
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(IsMonoAudioEnabled());
  EXPECT_FALSE(IsMouseKeysEnabled());
  EXPECT_FALSE(IsAlwaysShowScrollbarsEnabled());
  EXPECT_FALSE(IsReducedAnimationsEnabled());
  EXPECT_FALSE(IsSpokenFeedbackEnabled());
  EXPECT_FALSE(IsVirtualKeyboardEnabled());
  EXPECT_EQ(default_autoclick_delay_, GetAutoclickDelay());

  SetAutoclickDelay(kTestAutoclickDelayMs);
  EXPECT_EQ(kTestAutoclickDelayMs, GetAutoclickDelay());

  SetAutoclickEnabled(true);
  EXPECT_TRUE(IsAutoclickEnabled());

  SetHighContrastEnabled(true);
  EXPECT_TRUE(IsHighContrastEnabled());

  SetLargeCursorEnabled(true);
  EXPECT_TRUE(IsLargeCursorEnabled());

  SetMonoAudioEnabled(true);
  EXPECT_TRUE(IsMonoAudioEnabled());

  SetMouseKeysEnabled(true);
  EXPECT_TRUE(IsMouseKeysEnabled());

  SetAlwaysShowScrollbarsEnabled(true);
  EXPECT_TRUE(IsAlwaysShowScrollbarsEnabled());

  SetReducedAnimationsEnabled(true);
  EXPECT_TRUE(IsReducedAnimationsEnabled());

  SetSpokenFeedbackEnabled(true);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());

  SetVirtualKeyboardEnabled(true);
  EXPECT_TRUE(IsVirtualKeyboardEnabled());
}

// Tests that ash and browser process has the same states after sign-in.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerLoginTest, AshState) {
  WaitForSigninScreen();
  CreateSession(test_account_id_);
  StartUserSession(test_account_id_);

  auto ash_a11y_controller_test_api = AccessibilityControllerTestApi::Create();

  // Ash and browser has the same state.
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(ash_a11y_controller_test_api->IsLargeCursorEnabled());

  // Changes from the browser side is reflected in both browser and ash.
  SetLargeCursorEnabled(true);
  EXPECT_TRUE(IsLargeCursorEnabled());
  EXPECT_TRUE(ash_a11y_controller_test_api->IsLargeCursorEnabled());

  // Changes from ash is also reflect in both browser and ash.
  ash_a11y_controller_test_api->SetLargeCursorEnabled(false);
  EXPECT_FALSE(IsLargeCursorEnabled());
  EXPECT_FALSE(ash_a11y_controller_test_api->IsLargeCursorEnabled());
}

class AccessibilityManagerUserTypeTest
    : public AccessibilityManagerTest,
      public WithParamInterface<user_manager::UserType> {
 protected:
  AccessibilityManagerUserTypeTest() {
    if (GetParam() == user_manager::UserType::kGuest) {
      guest_session_ = std::make_unique<GuestSessionMixin>(&mixin_host_);
    } else if (GetParam() == user_manager::UserType::kChild) {
      logged_in_user_mixin_ = std::make_unique<LoggedInUserMixin>(
          &mixin_host_, /*test_base=*/this, embedded_test_server(),
          LoggedInUserMixin::LogInType::kChild);
    }
  }

  AccessibilityManagerUserTypeTest(const AccessibilityManagerUserTypeTest&) =
      delete;
  AccessibilityManagerUserTypeTest& operator=(
      const AccessibilityManagerUserTypeTest&) = delete;

  ~AccessibilityManagerUserTypeTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    AccessibilityManager::SetBrailleControllerForTest(&braille_controller_);
    AccessibilityManagerTest::SetUpCommandLine(command_line);
  }

  void TearDownOnMainThread() override {
    AccessibilityManager::SetBrailleControllerForTest(nullptr);
    AccessibilityManagerTest::TearDownOnMainThread();
  }

  void SetBrailleDisplayAvailability(bool available) {
    braille_controller_.SetAvailable(available);
    braille_controller_.GetObserver()->OnBrailleDisplayStateChanged(
        *braille_controller_.GetDisplayState());
  }

  std::unique_ptr<GuestSessionMixin> guest_session_;

  std::unique_ptr<LoggedInUserMixin> logged_in_user_mixin_;

  MockBrailleController braille_controller_;
};

INSTANTIATE_TEST_SUITE_P(UserTypeInstantiation,
                         AccessibilityManagerUserTypeTest,
                         ::testing::Values(user_manager::UserType::kRegular,
                                           user_manager::UserType::kGuest,
                                           user_manager::UserType::kChild));

IN_PROC_BROWSER_TEST_P(AccessibilityManagerUserTypeTest, BrailleWhenLoggedIn) {
  if (GetParam() == user_manager::UserType::kChild) {
    logged_in_user_mixin_->LogInUser();
    histogram_tester_.ExpectBucketCount("Accessibility.CrosSpokenFeedback",
                                        /*sample=*/false, 2);
  } else {
    histogram_tester_.ExpectBucketCount("Accessibility.CrosSpokenFeedback",
                                        /*sample=*/false, 1);
  }

  histogram_tester_.ExpectBucketCount("Accessibility.CrosSpokenFeedback",
                                      /*sample=*/true, 0);
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/true, 0);
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/false, 0);

  // This object watches for IME preference changes and reflects those in
  // the IME framework state.
  Preferences prefs(
      g_browser_process->local_state(),
      g_browser_process->GetFeatures()->application_locale_storage(),
      g_browser_process->platform_part()->GetTimezoneResolverManager());
  prefs.InitUserPrefsForTesting(
      static_cast<sync_preferences::PrefServiceSyncable*>(
          GetActiveUserProfile()->GetPrefs()),
      user_manager::UserManager::Get()->GetActiveUser(),
      UserSessionManager::GetInstance()->GetDefaultIMEState(
          GetActiveUserProfile()));

  // Make sure we start in the expected state.
  EXPECT_FALSE(IsBrailleImeEnabled());
  EXPECT_FALSE(IsSpokenFeedbackEnabled());

  // Signal the accessibility manager that a braille display was connected.
  SetBrailleDisplayAvailability(true);

  // Now, both spoken feedback and the Braille IME should be enabled.
  EXPECT_TRUE(IsSpokenFeedbackEnabled());
  EXPECT_TRUE(IsBrailleImeEnabled());

  // A metric should have been logged for braille display connected but not
  // disconnect or duration.
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/true, 1);
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/false, 0);
  histogram_tester_.ExpectTotalCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionDuration",
      0);

  // Send a braille dots key event and make sure that the braille IME is
  // activated.
  KeyEvent event;
  event.command = extensions::api::braille_display_private::KeyCommand::kDots;
  event.braille_dots = 0;
  braille_controller_.GetObserver()->OnBrailleKeyEvent(event);
  EXPECT_TRUE(IsBrailleImeCurrent());

  // Unplug the display.  Spoken feedback remains on, but the Braille IME
  // should get disabled and deactivated.
  SetBrailleDisplayAvailability(false);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());
  EXPECT_FALSE(IsBrailleImeEnabled());
  EXPECT_FALSE(IsBrailleImeCurrent());

  // Check metrics.
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/true, 1);
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/false, 1);
  histogram_tester_.ExpectTotalCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionDuration",
      1);

  // Plugging in a display while spoken feedback is enabled should enable
  // the Braille IME.
  SetBrailleDisplayAvailability(true);
  EXPECT_TRUE(IsSpokenFeedbackEnabled());
  EXPECT_TRUE(IsBrailleImeEnabled());

  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/true, 2);
  histogram_tester_.ExpectBucketCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionChanged",
      /*sample=*/false, 1);
  histogram_tester_.ExpectTotalCount(
      "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected."
      "ConnectionDuration",
      1);
}



class AccessibilityManagerWithManifestV3Test : public AccessibilityManagerTest {
 public:
  AccessibilityManagerWithManifestV3Test() = default;
  AccessibilityManagerWithManifestV3Test(
      const AccessibilityManagerWithManifestV3Test&) = delete;
  AccessibilityManagerWithManifestV3Test& operator=(
      const AccessibilityManagerWithManifestV3Test&) = delete;
  ~AccessibilityManagerWithManifestV3Test() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        ::switches::kEnableExperimentalAccessibilityManifestV3);
  }
};

IN_PROC_BROWSER_TEST_F(AccessibilityManagerWithManifestV3Test, DoesNotCrash) {
  SetSpokenFeedbackEnabled(true);
  SetSelectToSpeakEnabled(true);
  SetSwitchAccessEnabled(true);
  SetAutoclickEnabled(true);
  SetDictationEnabled(true);
  SetMagnifierEnabled(true);

  SetSpokenFeedbackEnabled(false);
  SetSelectToSpeakEnabled(false);
  SetSwitchAccessEnabled(false);
  SetAutoclickEnabled(false);
  SetDictationEnabled(false);
  SetMagnifierEnabled(false);
}

enum class DictationKeyboardShortcutType { kKey, kKeyboardCombo };

class AccessibilityManagerDictationKeyboardImprovementsTest
    : public AccessibilityManagerTest,
      public ::testing::WithParamInterface<DictationKeyboardShortcutType> {
 public:
  AccessibilityManagerDictationKeyboardImprovementsTest() = default;
  ~AccessibilityManagerDictationKeyboardImprovementsTest() override = default;
  AccessibilityManagerDictationKeyboardImprovementsTest(
      const AccessibilityManagerDictationKeyboardImprovementsTest&) = delete;
  AccessibilityManagerDictationKeyboardImprovementsTest& operator=(
      const AccessibilityManagerDictationKeyboardImprovementsTest&) = delete;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Set the device language to one that is not supported by SODA on ChromeOS.
    // This will force Dictation to show the confirmation dialog when enabled.
    command_line->AppendSwitchASCII(::switches::kLang, kSodaUnsupportedLocale);
    AccessibilityManagerTest::SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    test_api_ = AccessibilityControllerTestApi::Create();
    AccessibilityManagerTest::SetUpOnMainThread();
  }

  // Invokes Dictation via the keyboard. The keys that are pressed depend on the
  // parameter that is passed at test construction.
  void PressKeys() {
    switch (GetParam()) {
      case DictationKeyboardShortcutType::kKey:
        ASSERT_NO_FATAL_FAILURE(
            ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
                /*window=*/nullptr, /*key=*/ui::KeyboardCode::VKEY_DICTATE,
                /*control=*/false, /*shift=*/false, /*alt=*/false,
                /*command=*/false)));
        return;
      case DictationKeyboardShortcutType::kKeyboardCombo:
        ASSERT_NO_FATAL_FAILURE(
            ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
                /*window=*/nullptr, /*key=*/ui::KeyboardCode::VKEY_D,
                /*control=*/false, /*shift=*/false, /*alt=*/false,
                /*command=*/true)));
        return;
    }
  }

  AccessibilityControllerTestApi* test_api() { return test_api_.get(); }

 private:
  std::unique_ptr<AccessibilityControllerTestApi> test_api_;
};

INSTANTIATE_TEST_SUITE_P(
    DictationKey,
    AccessibilityManagerDictationKeyboardImprovementsTest,
    ::testing::Values(DictationKeyboardShortcutType::kKey));

INSTANTIATE_TEST_SUITE_P(
    DictationKeyboardCombo,
    AccessibilityManagerDictationKeyboardImprovementsTest,
    ::testing::Values(DictationKeyboardShortcutType::kKeyboardCombo));

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationKeyboardImprovementsTest,
                       DictationDisabledShowDialogDismiss) {
  AccessibilityManager* manager = AccessibilityManager::Get();
  PrefService* prefs = GetActiveUserPrefs();
  prefs->SetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted, false);
  manager->SetDictationEnabled(false);

  PressKeys();
  // If the dialog hasn't been accepted yet, then pressing the Dictation key
  // should show a dialog.
  ASSERT_FALSE(manager->IsDictationEnabled());
  ASSERT_TRUE(test_api()->IsDictationKeboardDialogShowing());
  test_api()->DismissDictationKeyboardDialog();
  ASSERT_FALSE(manager->IsDictationEnabled());
  ASSERT_FALSE(test_api()->IsDictationKeboardDialogShowing());
}

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationKeyboardImprovementsTest,
                       DictationDisabledShowDialogAccept) {
  AccessibilityManager* manager = AccessibilityManager::Get();
  PrefService* prefs = GetActiveUserPrefs();
  prefs->SetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted, false);
  manager->SetDictationEnabled(false);

  PressKeys();
  ASSERT_FALSE(manager->IsDictationEnabled());
  ASSERT_TRUE(test_api()->IsDictationKeboardDialogShowing());
  // Accepting the dialog should enable the Dictation feature.
  test_api()->AcceptDictationKeyboardDialog();
  ASSERT_TRUE(manager->IsDictationEnabled());
  ASSERT_FALSE(test_api()->IsDictationKeboardDialogShowing());
}

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationKeyboardImprovementsTest,
                       DictationDisabledNoShowDialog) {
  AccessibilityManager* manager = AccessibilityManager::Get();
  PrefService* prefs = GetActiveUserPrefs();
  prefs->SetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted, true);
  manager->SetDictationEnabled(false);

  PressKeys();
  // If the dialog has already been accepted yet, then pressing the Dictation
  // key should enable Dictation.
  ASSERT_TRUE(manager->IsDictationEnabled());
  ASSERT_FALSE(test_api()->IsDictationKeboardDialogShowing());
}

IN_PROC_BROWSER_TEST_P(AccessibilityManagerDictationKeyboardImprovementsTest,
                       DictationEnabled) {
  // Setup and enable Dictation.
  DictationTestUtils utils =
      DictationTestUtils(speech::SpeechRecognitionType::kNetwork,
                         DictationTestUtils::EditableType::kInput);
  utils.EnableDictation(
      /*profile=*/AccessibilityManager::Get()->profile(),
      /*navigate_to_url=*/base::BindLambdaForTesting([this](const GURL& url) {
        ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
      }));

  // If Dictation is already enabled, then pressing the Dictation key should
  // toggle Dictation on/off normally.
  PressKeys();
  ASSERT_TRUE(AccessibilityManager::Get()->IsDictationEnabled());
  utils.WaitForRecognitionStarted();
  PressKeys();
  ASSERT_TRUE(AccessibilityManager::Get()->IsDictationEnabled());
  utils.WaitForRecognitionStopped();
}

}  // namespace ash
