chromium/ash/accessibility/accessibility_controller.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/accessibility/accessibility_controller.h"

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accessibility/a11y_feature_type.h"
#include "ash/accessibility/accessibility_notification_controller.h"
#include "ash/accessibility/accessibility_observer.h"
#include "ash/accessibility/autoclick/autoclick_controller.h"
#include "ash/accessibility/disable_trackpad_event_rewriter.h"
#include "ash/accessibility/filter_keys_event_rewriter.h"
#include "ash/accessibility/flash_screen_controller.h"
#include "ash/accessibility/mouse_keys/mouse_keys_controller.h"
#include "ash/accessibility/sticky_keys/sticky_keys_controller.h"
#include "ash/accessibility/switch_access/point_scan_controller.h"
#include "ash/accessibility/ui/accessibility_highlight_controller.h"
#include "ash/accessibility/ui/accessibility_panel_layout_manager.h"
#include "ash/color_enhancement/color_enhancement_controller.h"
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/events/accessibility_event_rewriter.h"
#include "ash/events/event_rewriter_controller_impl.h"
#include "ash/events/select_to_speak_event_handler.h"
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/login_status.h"
#include "ash/policy/policy_recommendation_restorer.h"
#include "ash/public/cpp/accessibility_controller_client.h"
#include "ash/public/cpp/ash_constants.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/public/cpp/system/anchored_nudge_manager.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/accessibility/accessibility_feature_disable_dialog.h"
#include "ash/system/accessibility/dictation_bubble_controller.h"
#include "ash/system/accessibility/dictation_button_tray.h"
#include "ash/system/accessibility/floating_accessibility_controller.h"
#include "ash/system/accessibility/select_to_speak/select_to_speak_menu_bubble_controller.h"
#include "ash/system/accessibility/switch_access/switch_access_menu_bubble_controller.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/power/backlights_forced_off_setter.h"
#include "ash/system/power/power_status.h"
#include "ash/system/power/scoped_backlights_forced_off.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/wm/window_util.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_number_conversions.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/ash/components/audio/sounds.h"
#include "components/live_caption/caption_util.h"
#include "components/live_caption/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/session_manager_types.h"
#include "components/user_manager/user_type.h"
#include "components/vector_icons/vector_icons.h"
#include "media/base/media_switches.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/aura/aura_window_properties.h"
#include "ui/aura/window.h"
#include "ui/base/cursor/cursor_size.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/screen.h"
#include "ui/display/tablet_state.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/event_sink.h"
#include "ui/gfx/animation/animation.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "ui/native_theme/native_theme.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/cursor_manager.h"

using session_manager::SessionState;

namespace ash {
namespace {

// How much distance to travel with each generated scroll event.
const int kScrollDelta = 15;

AccessibilityController* g_instance = nullptr;

using FeatureType = A11yFeatureType;

// These classes are used to store the static configuration for a11y features.
struct FeatureData {
  FeatureType type;
  const char* pref;
  // This field is not a raw_ptr<> because it was filtered by the rewriter
  // for: #global-scope
  RAW_PTR_EXCLUSION const gfx::VectorIcon* icon;
  const int name_resource_id;
  bool toggleable_in_quicksettings = true;
  FeatureType conflicting_feature = FeatureType::kNoConflictingFeature;
};

struct FeatureDialogData {
  FeatureType type;
  const char* pref;
  int title;
  int body;
};

// A static array describing each feature.
const FeatureData kFeatures[] = {
    {FeatureType::kAutoclick, prefs::kAccessibilityAutoclickEnabled,
     &kSystemMenuAccessibilityAutoClickIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_AUTOCLICK},
    {FeatureType::kCaretHighlight, prefs::kAccessibilityCaretHighlightEnabled,
     nullptr, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_CARET_HIGHLIGHT},
    {FeatureType::kCursorHighlight, prefs::kAccessibilityCursorHighlightEnabled,
     nullptr, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGHLIGHT_MOUSE_CURSOR},
    {FeatureType::kCursorColor, prefs::kAccessibilityCursorColorEnabled,
     nullptr, 0, /*toggleable_in_quicksettings=*/false},
    {FeatureType::kDictation, prefs::kAccessibilityDictationEnabled,
     &kDictationMenuIcon, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION},
    {FeatureType::kColorCorrection, prefs::kAccessibilityColorCorrectionEnabled,
     &kColorCorrectionIcon, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION},
    {FeatureType::kFlashNotifications,
     prefs::kAccessibilityFlashNotificationsEnabled, nullptr, 0,
     /*toggleable_in_quicksettings=*/false},
    {FeatureType::kFocusHighlight, prefs::kAccessibilityFocusHighlightEnabled,
     nullptr, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGHLIGHT_KEYBOARD_FOCUS,
     /*toggleable_in_quicksettings=*/true,
     /* conflicting_feature= */ FeatureType::kSpokenFeedback},
    {FeatureType::kFloatingMenu, prefs::kAccessibilityFloatingMenuEnabled,
     nullptr, IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU,
     /*toggleable_in_quicksettings=*/false},
    {FeatureType::kFullscreenMagnifier,
     prefs::kAccessibilityScreenMagnifierEnabled,
     &kSystemMenuAccessibilityFullscreenMagnifierIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SCREEN_MAGNIFIER},
    {FeatureType::kDockedMagnifier, prefs::kDockedMagnifierEnabled,
     &kSystemMenuAccessibilityDockedMagnifierIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DOCKED_MAGNIFIER},
    {FeatureType::kHighContrast, prefs::kAccessibilityHighContrastEnabled,
     &kSystemMenuAccessibilityContrastIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGH_CONTRAST_MODE},
    {FeatureType::kLargeCursor, prefs::kAccessibilityLargeCursorEnabled,
     nullptr, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LARGE_CURSOR},
    {FeatureType::kLiveCaption, ::prefs::kLiveCaptionEnabled,
     &vector_icons::kLiveCaptionOnIcon, IDS_ASH_STATUS_TRAY_LIVE_CAPTION},
    {FeatureType::kMonoAudio, prefs::kAccessibilityMonoAudioEnabled, nullptr,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_MONO_AUDIO},
    {FeatureType::kMouseKeys, prefs::kAccessibilityMouseKeysEnabled, nullptr, 0,
     /*toggleable_in_quicksettings=*/false},
    {FeatureType::kSpokenFeedback, prefs::kAccessibilitySpokenFeedbackEnabled,
     &kSystemMenuAccessibilityChromevoxIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SPOKEN_FEEDBACK},
    {FeatureType::kReducedAnimations,
     prefs::kAccessibilityReducedAnimationsEnabled, nullptr, 0,
     /*toggleable_in_quicksettings=*/false},
    {FeatureType::kSelectToSpeak, prefs::kAccessibilitySelectToSpeakEnabled,
     &kSystemMenuAccessibilitySelectToSpeakIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SELECT_TO_SPEAK},
    {FeatureType::kStickyKeys, prefs::kAccessibilityStickyKeysEnabled, nullptr,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_STICKY_KEYS,
     /*toggleable_in_quicksettings=*/true,
     /*conflicting_feature=*/FeatureType::kSpokenFeedback},
    {FeatureType::kSwitchAccess, prefs::kAccessibilitySwitchAccessEnabled,
     &kSwitchAccessIcon, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SWITCH_ACCESS},
    {FeatureType::kVirtualKeyboard, prefs::kAccessibilityVirtualKeyboardEnabled,
     &kSystemMenuKeyboardLegacyIcon,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD},
    {FeatureType::kFaceGaze, prefs::kAccessibilityFaceGazeEnabled, nullptr,
     IDS_ASH_STATUS_TRAY_ACCESSIBILITY_FACEGAZE,
     /*toggleable_in_quicksettings=*/true},
    {FeatureType::kDisableTrackpad, prefs::kAccessibilityDisableTrackpadEnabled,
     nullptr, 0, /*toggleable_in_quicksettings=*/false},
};

// An array describing the confirmation dialogs for the features which have
// them.
const FeatureDialogData kFeatureDialogs[] = {
    {FeatureType::kFullscreenMagnifier,
     prefs::kScreenMagnifierAcceleratorDialogHasBeenAccepted},
    {FeatureType::kDockedMagnifier,
     prefs::kDockedMagnifierAcceleratorDialogHasBeenAccepted},
    {FeatureType::kHighContrast,
     prefs::kHighContrastAcceleratorDialogHasBeenAccepted}};

constexpr char kNotificationId[] = "chrome://settings/accessibility";
constexpr char kNotifierAccessibility[] = "ash.accessibility";
constexpr char kDictationLanguageUpgradedNudgeId[] =
    "dictation_language_upgraded.nudge_id";

// TODO(warx): Signin screen has more controllable accessibility prefs. We may
// want to expand this to a complete list. If so, merge this with
// |kCopiedOnSigninAccessibilityPrefs|.
constexpr const char* const kA11yPrefsForRecommendedValueOnSignin[]{
    prefs::kAccessibilityLargeCursorEnabled,
    prefs::kAccessibilityHighContrastEnabled,
    prefs::kAccessibilityScreenMagnifierEnabled,
    prefs::kAccessibilitySpokenFeedbackEnabled,
    prefs::kAccessibilityVirtualKeyboardEnabled,
};

// List of accessibility prefs that are to be copied (if changed by the user) on
// signin screen profile to a newly created user profile or a guest session.
constexpr const char* const kCopiedOnSigninAccessibilityPrefs[]{
    prefs::kAccessibilityAutoclickDelayMs,
    prefs::kAccessibilityAutoclickEnabled,
    prefs::kAccessibilityCaretHighlightEnabled,
    prefs::kAccessibilityChromeVoxAutoRead,
    prefs::kAccessibilityChromeVoxAnnounceDownloadNotifications,
    prefs::kAccessibilityChromeVoxAnnounceRichTextAttributes,
    prefs::kAccessibilityChromeVoxAudioStrategy,
    prefs::kAccessibilityChromeVoxBrailleSideBySide,
    prefs::kAccessibilityChromeVoxBrailleTable,
    prefs::kAccessibilityChromeVoxBrailleTable6,
    prefs::kAccessibilityChromeVoxBrailleTable8,
    prefs::kAccessibilityChromeVoxBrailleTableType,
    prefs::kAccessibilityChromeVoxBrailleWordWrap,
    prefs::kAccessibilityChromeVoxCapitalStrategy,
    prefs::kAccessibilityChromeVoxCapitalStrategyBackup,
    prefs::kAccessibilityChromeVoxEnableBrailleLogging,
    prefs::kAccessibilityChromeVoxEnableEarconLogging,
    prefs::kAccessibilityChromeVoxEnableEventStreamLogging,
    prefs::kAccessibilityChromeVoxEnableSpeechLogging,
    prefs::kAccessibilityChromeVoxEventStreamFilters,
    prefs::kAccessibilityChromeVoxLanguageSwitching,
    prefs::kAccessibilityChromeVoxMenuBrailleCommands,
    prefs::kAccessibilityChromeVoxNumberReadingStyle,
    prefs::kAccessibilityChromeVoxPreferredBrailleDisplayAddress,
    prefs::kAccessibilityChromeVoxPunctuationEcho,
    prefs::kAccessibilityChromeVoxSmartStickyMode,
    prefs::kAccessibilityChromeVoxSpeakTextUnderMouse,
    prefs::kAccessibilityChromeVoxUsePitchChanges,
    prefs::kAccessibilityChromeVoxUseVerboseMode,
    prefs::kAccessibilityChromeVoxVirtualBrailleColumns,
    prefs::kAccessibilityChromeVoxVirtualBrailleRows,
    prefs::kAccessibilityChromeVoxVoiceName,
    prefs::kAccessibilityColorCorrectionEnabled,
    prefs::kAccessibilityCursorHighlightEnabled,
    prefs::kAccessibilityCursorColorEnabled,
    prefs::kAccessibilityCursorColor,
    prefs::kAccessibilityDictationEnabled,
    prefs::kAccessibilityDictationLocale,
    prefs::kAccessibilityDictationLocaleOfflineNudge,
    prefs::kAccessibilityDisableTrackpadEnabled,
    prefs::kAccessibilityDisableTrackpadMode,
    prefs::kAccessibilityFocusHighlightEnabled,
    prefs::kAccessibilityHighContrastEnabled,
    prefs::kAccessibilityLargeCursorEnabled,
    prefs::kAccessibilityFaceGazeEnabled,
    prefs::kAccessibilityMonoAudioEnabled,
    prefs::kAccessibilityReducedAnimationsEnabled,
    prefs::kAccessibilityMouseKeysEnabled,
    prefs::kAccessibilityMouseKeysAcceleration,
    prefs::kAccessibilityMouseKeysMaxSpeed,
    prefs::kAccessibilityMouseKeysUsePrimaryKeys,
    prefs::kAccessibilityMouseKeysDominantHand,
    prefs::kAccessibilityScreenMagnifierEnabled,
    prefs::kAccessibilityScreenMagnifierFocusFollowingEnabled,
    prefs::kAccessibilityMagnifierFollowsChromeVox,
    prefs::kAccessibilityMagnifierFollowsSts,
    prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
    prefs::kAccessibilityScreenMagnifierScale,
    prefs::kAccessibilitySelectToSpeakEnabled,
    prefs::kAccessibilitySpokenFeedbackEnabled,
    prefs::kAccessibilityStickyKeysEnabled,
    prefs::kAccessibilityShortcutsEnabled,
    prefs::kAccessibilitySwitchAccessEnabled,
    prefs::kAccessibilityVirtualKeyboardEnabled,
    prefs::kDockedMagnifierEnabled,
    prefs::kDockedMagnifierScale,
    prefs::kDockedMagnifierScreenHeightDivisor,
    prefs::kHighContrastAcceleratorDialogHasBeenAccepted,
    prefs::kScreenMagnifierAcceleratorDialogHasBeenAccepted,
    prefs::kDockedMagnifierAcceleratorDialogHasBeenAccepted,
    prefs::kDictationAcceleratorDialogHasBeenAccepted,
    prefs::kDictationDlcSuccessNotificationHasBeenShown,
    prefs::kDictationDlcOnlyPumpkinDownloadedNotificationHasBeenShown,
    prefs::kDictationDlcOnlySodaDownloadedNotificationHasBeenShown,
    prefs::kDictationNoDlcsDownloadedNotificationHasBeenShown,
    prefs::kDisplayRotationAcceleratorDialogHasBeenAccepted2,
    prefs::kSelectToSpeakAcceleratorDialogHasBeenAccepted,
    prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted,
    prefs::kFaceGazeDlcSuccessNotificationHasBeenShown,
    prefs::kFaceGazeDlcFailureNotificationHasBeenShown,
};

// List of switch access accessibility prefs that are to be copied (if changed
// by the user) from the current user to the signin screen profile. That way
// if a switch access user signs out, their switch continues to function.
constexpr const char* const kSwitchAccessPrefsCopiedToSignin[]{
    prefs::kAccessibilitySwitchAccessAutoScanEnabled,
    prefs::kAccessibilitySwitchAccessAutoScanKeyboardSpeedMs,
    prefs::kAccessibilitySwitchAccessAutoScanSpeedMs,
    prefs::kAccessibilitySwitchAccessPointScanSpeedDipsPerSecond,
    prefs::kAccessibilitySwitchAccessEnabled,
    prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes,
    prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes,
    prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes,
};

// Helper function that is used to verify the validity of kFeatures and
// kFeatureDialogs.
bool VerifyFeaturesData() {
  // All feature prefs must be unique.
  std::set<const char*> feature_prefs;
  for (auto feature_data : kFeatures) {
    if (base::Contains(feature_prefs, feature_data.pref)) {
      return false;
    }
    feature_prefs.insert(feature_data.pref);
  }

  for (auto dialog_data : kFeatureDialogs) {
    if (base::Contains(feature_prefs, dialog_data.pref)) {
      return false;
    }
    feature_prefs.insert(dialog_data.pref);
  }

  return true;
}

// Returns true if |pref_service| is the one used for the signin screen.
bool IsSigninPrefService(PrefService* pref_service) {
  const PrefService* signin_pref_service =
      Shell::Get()->session_controller()->GetSigninScreenPrefService();
  DCHECK(signin_pref_service);
  return pref_service == signin_pref_service;
}

// Returns true if the current session is the guest session.
bool IsCurrentSessionGuest() {
  const std::optional<user_manager::UserType> user_type =
      Shell::Get()->session_controller()->GetUserType();
  return user_type && *user_type == user_manager::UserType::kGuest;
}

bool IsUserFirstLogin() {
  return Shell::Get()->session_controller()->IsUserFirstLogin();
}

// The copying of any modified accessibility prefs on the signin prefs happens
// when the |previous_pref_service| is of the signin profile, and the
// |current_pref_service| is of a newly created profile first logged in, or if
// the current session is the guest session.
bool ShouldCopySigninPrefs(PrefService* previous_pref_service,
                           PrefService* current_pref_service) {
  DCHECK(previous_pref_service);
  if (IsUserFirstLogin() && IsSigninPrefService(previous_pref_service) &&
      !IsSigninPrefService(current_pref_service)) {
    // If the user set a pref value on the login screen and is now starting a
    // session with a new profile, copy the pref value to the profile.
    return true;
  }

  if (IsCurrentSessionGuest()) {
    // Guest sessions don't have their own prefs, so always copy.
    return true;
  }

  return false;
}

// On a user's first login into a device, any a11y features enabled/disabled
// by the user on the login screen are enabled/disabled in the user's profile.
// This function copies settings from the signin prefs into the user's prefs
// when it detects a login with a newly created profile.
void CopySigninPrefsIfNeeded(PrefService* previous_pref_service,
                             PrefService* current_pref_service) {
  DCHECK(current_pref_service);
  if (!ShouldCopySigninPrefs(previous_pref_service, current_pref_service)) {
    return;
  }

  PrefService* signin_prefs =
      Shell::Get()->session_controller()->GetSigninScreenPrefService();
  DCHECK(signin_prefs);
  for (const auto* pref_path : kCopiedOnSigninAccessibilityPrefs) {
    const PrefService::Preference* pref =
        signin_prefs->FindPreference(pref_path);

    // Ignore if the pref has not been set by the user.
    if (!pref || !pref->IsUserControlled()) {
      continue;
    }

    // Copy the pref value from the signin profile.
    const base::Value* value_on_login = pref->GetValue();
    current_pref_service->Set(pref_path, *value_on_login);
  }
}

// Returns notification icon based on the A11yNotificationType.
const gfx::VectorIcon& GetNotificationIcon(A11yNotificationType type) {
  switch (type) {
    case A11yNotificationType::kSpokenFeedbackBrailleEnabled:
      return kNotificationAccessibilityIcon;
    case A11yNotificationType::kBrailleDisplayConnected:
      return kNotificationAccessibilityBrailleIcon;
    case A11yNotificationType::kSwitchAccessEnabled:
      return kSwitchAccessIcon;
    case A11yNotificationType::kDictationAllDlcsDownloaded:
    case A11yNotificationType::kDictationNoDlcsDownloaded:
    case A11yNotificationType::kDicationOnlyPumpkinDownloaded:
    case A11yNotificationType::kDictationOnlySodaDownloaded:
      return kDictationMenuIcon;
    default:
      return kNotificationChromevoxIcon;
  }
}

void ShowAccessibilityNotification(
    const AccessibilityController::A11yNotificationWrapper& wrapper) {
  A11yNotificationType type = wrapper.type;
  const auto& replacements = wrapper.replacements;
  message_center::MessageCenter* message_center =
      message_center::MessageCenter::Get();
  message_center->RemoveNotification(kNotificationId, false /* by_user */);

  if (type == A11yNotificationType::kNone) {
    return;
  }

  std::u16string text;
  std::u16string title;
  std::u16string display_source;
  auto catalog_name = NotificationCatalogName::kNone;
  bool pinned = true;
  message_center::SystemNotificationWarningLevel warning =
      message_center::SystemNotificationWarningLevel::NORMAL;

  if (type == A11yNotificationType::kBrailleDisplayConnected) {
    title = l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_BRAILLE_DISPLAY_CONNECTED);
    catalog_name = NotificationCatalogName::kBrailleDisplayConnected;
  } else if (type == A11yNotificationType::kDictationAllDlcsDownloaded) {
    display_source =
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
    title = l10n_util::GetStringFUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_TITLE,
        replacements, nullptr);
    text = l10n_util::GetStringUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_DESC);
    catalog_name = NotificationCatalogName::kDictationAllDlcsDownloaded;
    pinned = false;
  } else if (type == A11yNotificationType::kDictationNoDlcsDownloaded) {
    display_source =
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
    title = l10n_util::GetStringFUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_TITLE,
        replacements, nullptr);
    text = l10n_util::GetStringUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_DESC);
    catalog_name = NotificationCatalogName::kDictationNoDlcsDownloaded;
    pinned = false;
    // Use CRITICAL_WARNING to force the notification color to red.
    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
  } else if (type == A11yNotificationType::kDicationOnlyPumpkinDownloaded) {
    display_source =
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);

    title = l10n_util::GetStringFUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_TITLE,
        replacements, nullptr);
    text = l10n_util::GetStringUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_DESC);

    catalog_name = NotificationCatalogName::kDicationOnlyPumpkinDownloaded;
    pinned = false;
    // Use CRITICAL_WARNING to force the notification color to red.
    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
  } else if (type == A11yNotificationType::kDictationOnlySodaDownloaded) {
    display_source =
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
    title = l10n_util::GetStringFUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_TITLE,
        replacements, nullptr);
    text = l10n_util::GetStringUTF16(
        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_DESC);
    catalog_name = NotificationCatalogName::kDictationOnlySodaDownloaded;
    pinned = false;
    // Use CRITICAL_WARNING to force the notification color to red.
    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
  } else if (type == A11yNotificationType::kFaceGazeAssetsDownloaded) {
    title = l10n_util::GetStringUTF16(
        IDS_ASH_A11Y_FACEGAZE_ASSETS_DOWNLOADED_TITLE);
    text =
        l10n_util::GetStringUTF16(IDS_ASH_A11Y_FACEGAZE_ASSETS_DOWNLOADED_DESC);
    catalog_name = NotificationCatalogName::kFaceGazeAssetsDownloaded;
    pinned = false;
  } else if (type == A11yNotificationType::kFaceGazeAssetsFailed) {
    title =
        l10n_util::GetStringUTF16(IDS_ASH_A11Y_FACEGAZE_ASSETS_FAILED_TITLE);
    text = l10n_util::GetStringUTF16(IDS_ASH_A11Y_FACEGAZE_ASSETS_FAILED_DESC);
    catalog_name = NotificationCatalogName::kFaceGazeAssetsFailed;
    pinned = false;
    // Use CRITICAL_WARNING to force the notification color to red.
    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
  } else if (type == A11yNotificationType::kSwitchAccessEnabled) {
    title = l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_SWITCH_ACCESS_ENABLED_TITLE);
    text = l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SWITCH_ACCESS_ENABLED);
    catalog_name = NotificationCatalogName::kSwitchAccessEnabled;
  } else {
    bool is_tablet = display::Screen::GetScreen()->InTabletMode();

    title = l10n_util::GetStringUTF16(
        type == A11yNotificationType::kSpokenFeedbackBrailleEnabled
            ? IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_BRAILLE_ENABLED_TITLE
            : IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED_TITLE);
    text = l10n_util::GetStringUTF16(
        is_tablet ? IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED_TABLET
                  : IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED);
    catalog_name = type == A11yNotificationType::kSpokenFeedbackBrailleEnabled
                       ? NotificationCatalogName::kSpokenFeedbackBrailleEnabled
                       : NotificationCatalogName::kSpokenFeedbackEnabled;
  }

  message_center::RichNotificationData options;
  options.should_make_spoken_feedback_for_popup_updates = false;
  std::unique_ptr<message_center::Notification> notification =
      ash::CreateSystemNotificationPtr(
          message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, title,
          text, display_source, GURL(),
          message_center::NotifierId(
              message_center::NotifierType::SYSTEM_COMPONENT,
              kNotifierAccessibility, catalog_name),
          options, nullptr, GetNotificationIcon(type), warning);
  notification->set_pinned(pinned);
  message_center->AddNotification(std::move(notification));
}

void RemoveAccessibilityNotification() {
  ShowAccessibilityNotification(
      AccessibilityController::A11yNotificationWrapper(
          A11yNotificationType::kNone, std::vector<std::u16string>()));
}

AccessibilityPanelLayoutManager* GetLayoutManager() {
  // The accessibility panel is only shown on the primary display.
  aura::Window* root = Shell::GetPrimaryRootWindow();
  aura::Window* container =
      Shell::GetContainer(root, kShellWindowId_AccessibilityPanelContainer);
  // TODO(jamescook): Avoid this cast by moving ash::AccessibilityObserver
  // ownership to this class and notifying it on accessibility panel fullscreen
  // updates.
  return static_cast<AccessibilityPanelLayoutManager*>(
      container->layout_manager());
}

std::string PrefKeyForSwitchAccessCommand(SwitchAccessCommand command) {
  switch (command) {
    case SwitchAccessCommand::kSelect:
      return prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes;
    case SwitchAccessCommand::kNext:
      return prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes;
    case SwitchAccessCommand::kPrevious:
      return prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes;
    case SwitchAccessCommand::kNone:
      NOTREACHED();
  }
}

std::string UmaNameForSwitchAccessCommand(SwitchAccessCommand command) {
  switch (command) {
    case SwitchAccessCommand::kSelect:
      return "Accessibility.CrosSwitchAccess.SelectKeyCode";
    case SwitchAccessCommand::kNext:
      return "Accessibility.CrosSwitchAccess.NextKeyCode";
    case SwitchAccessCommand::kPrevious:
      return "Accessibility.CrosSwitchAccess.PreviousKeyCode";
    case SwitchAccessCommand::kNone:
      NOTREACHED();
  }
}

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class SwitchAccessKeyCode {
  kUnknown = 0,
  kKeycode1 = 1,
  kKeycode2 = 2,
  kKeycode3 = 3,
  kKeycode4 = 4,
  kKeycode5 = 5,
  kKeycode6 = 6,
  kKeycode7 = 7,
  kBackspace = 8,
  kTab = 9,
  kKeycode10 = 10,
  kKeycode11 = 11,
  kClear = 12,
  kReturn = 13,
  kKeycode14 = 14,
  kKeycode15 = 15,
  kShift = 16,
  kControl = 17,
  kAlt = 18,
  kPause = 19,
  kCapital = 20,
  kKana = 21,
  kKeycode22 = 22,
  kJunja = 23,
  kFinal = 24,
  kHanja = 25,
  kKeycode26 = 26,
  kEscape = 27,
  kConvert = 28,
  kNonconvert = 29,
  kAccept = 30,
  kModechange = 31,
  kSpace = 32,
  kPrior = 33,
  kNext = 34,
  kEnd = 35,
  kHome = 36,
  kLeft = 37,
  kUp = 38,
  kRight = 39,
  kDown = 40,
  kSelect = 41,
  kPrint = 42,
  kExecute = 43,
  kSnapshot = 44,
  kInsert = 45,
  kKeyDelete = 46,
  kHelp = 47,
  kNum0 = 48,
  kNum1 = 49,
  kNum2 = 50,
  kNum3 = 51,
  kNum4 = 52,
  kNum5 = 53,
  kNum6 = 54,
  kNum7 = 55,
  kNum8 = 56,
  kNum9 = 57,
  kKeycode58 = 58,
  kKeycode59 = 59,
  kKeycode60 = 60,
  kKeycode61 = 61,
  kKeycode62 = 62,
  kKeycode63 = 63,
  kKeycode64 = 64,
  kA = 65,
  kB = 66,
  kC = 67,
  kD = 68,
  kE = 69,
  kF = 70,
  kG = 71,
  kH = 72,
  kI = 73,
  kJ = 74,
  kK = 75,
  kL = 76,
  kM = 77,
  kN = 78,
  kO = 79,
  kP = 80,
  kQ = 81,
  kR = 82,
  kS = 83,
  kT = 84,
  kU = 85,
  kV = 86,
  kW = 87,
  kX = 88,
  kY = 89,
  kZ = 90,
  kLwin = 91,
  kRwin = 92,
  kApps = 93,
  kKeycode94 = 94,
  kSleep = 95,
  kNumpad0 = 96,
  kNumpad1 = 97,
  kNumpad2 = 98,
  kNumpad3 = 99,
  kNumpad4 = 100,
  kNumpad5 = 101,
  kNumpad6 = 102,
  kNumpad7 = 103,
  kNumpad8 = 104,
  kNumpad9 = 105,
  kMultiply = 106,
  kAdd = 107,
  kSeparator = 108,
  kSubtract = 109,
  kDecimal = 110,
  kDivide = 111,
  kF1 = 112,
  kF2 = 113,
  kF3 = 114,
  kF4 = 115,
  kF5 = 116,
  kF6 = 117,
  kF7 = 118,
  kF8 = 119,
  kF9 = 120,
  kF10 = 121,
  kF11 = 122,
  kF12 = 123,
  kF13 = 124,
  kF14 = 125,
  kF15 = 126,
  kF16 = 127,
  kF17 = 128,
  kF18 = 129,
  kF19 = 130,
  kF20 = 131,
  kF21 = 132,
  kF22 = 133,
  kF23 = 134,
  kF24 = 135,
  kKeycode136 = 136,
  kKeycode137 = 137,
  kKeycode138 = 138,
  kKeycode139 = 139,
  kKeycode140 = 140,
  kKeycode141 = 141,
  kKeycode142 = 142,
  kKeycode143 = 143,
  kNumlock = 144,
  kScroll = 145,
  kKeycode146 = 146,
  kKeycode147 = 147,
  kKeycode148 = 148,
  kKeycode149 = 149,
  kKeycode150 = 150,
  kWlan = 151,
  kPower = 152,
  kAssistant = 153,
  kKeycode154 = 154,
  kKeycode155 = 155,
  kKeycode156 = 156,
  kKeycode157 = 157,
  kKeycode158 = 158,
  kKeycode159 = 159,
  kLshift = 160,
  kRshift = 161,
  kLcontrol = 162,
  kRcontrol = 163,
  kLmenu = 164,
  kRmenu = 165,
  kBrowserBack = 166,
  kBrowserForward = 167,
  kBrowserRefresh = 168,
  kBrowserStop = 169,
  kBrowserSearch = 170,
  kBrowserFavorites = 171,
  kBrowserHome = 172,
  kVolumeMute = 173,
  kVolumeDown = 174,
  kVolumeUp = 175,
  kMediaNextTrack = 176,
  kMediaPrevTrack = 177,
  kMediaStop = 178,
  kMediaPlayPause = 179,
  kMediaLaunchMail = 180,
  kMediaLaunchMediaSelect = 181,
  kMediaLaunchApp1 = 182,
  kMediaLaunchApp2 = 183,
  kKeycode184 = 184,
  kKeycode185 = 185,
  kOem1 = 186,
  kOemPlus = 187,
  kOemComma = 188,
  kOemMinus = 189,
  kOemPeriod = 190,
  kOem2 = 191,
  kOem3 = 192,
  kKeycode193 = 193,
  kKeycode194 = 194,
  kKeycode195 = 195,
  kKeycode196 = 196,
  kKeycode197 = 197,
  kKeycode198 = 198,
  kKeycode199 = 199,
  kKeycode200 = 200,
  kKeycode201 = 201,
  kKeycode202 = 202,
  kKeycode203 = 203,
  kKeycode204 = 204,
  kKeycode205 = 205,
  kKeycode206 = 206,
  kKeycode207 = 207,
  kKeycode208 = 208,
  kKeycode209 = 209,
  kKeycode210 = 210,
  kKeycode211 = 211,
  kKeycode212 = 212,
  kKeycode213 = 213,
  kKeycode214 = 214,
  kKeycode215 = 215,
  kBrightnessDown = 216,
  kBrightnessUp = 217,
  kKbdBrightnessDown = 218,
  kOem4 = 219,
  kOem5 = 220,
  kOem6 = 221,
  kOem7 = 222,
  kOem8 = 223,
  kKeycode224 = 224,
  kAltgr = 225,
  kOem102 = 226,
  kKeycode227 = 227,
  kKeycode228 = 228,
  kProcesskey = 229,
  kCompose = 230,
  kPacket = 231,
  kKbdBrightnessUp = 232,
  kKeycode233 = 233,
  kKeycode234 = 234,
  kKeycode235 = 235,
  kKeycode236 = 236,
  kKeycode237 = 237,
  kKeycode238 = 238,
  kKeycode239 = 239,
  kKeycode240 = 240,
  kKeycode241 = 241,
  kKeycode242 = 242,
  kDbeSbcschar = 243,
  kDbeDbcschar = 244,
  kKeycode245 = 245,
  kAttn = 246,
  kCrsel = 247,
  kExsel = 248,
  kEreof = 249,
  kPlay = 250,
  kZoom = 251,
  kNoname = 252,
  kPa1 = 253,
  kOemClear = 254,
  kKeycode255 = 255,
  kNone = 256,
  kMaxValue = kNone,
};

}  // namespace

AccessibilityController::Feature::Feature(
    FeatureType type,
    const std::string& pref_name,
    const gfx::VectorIcon* icon,
    const int name_resource_id,
    const bool toggleable_in_quicksettings,
    AccessibilityController* controller)
    : type_(type),
      pref_name_(pref_name),
      icon_(icon),
      name_resource_id_(name_resource_id),
      toggleable_in_quicksettings_(toggleable_in_quicksettings),
      owner_(controller) {
  // If a feature is toggleable in quicksettings it must have a
  // `name_resource_id` so it's name can be looked up.
  if (toggleable_in_quicksettings_) {
    CHECK(name_resource_id);
  }
}

AccessibilityController::Feature::~Feature() = default;

void AccessibilityController::Feature::SetEnabled(bool enabled) {
  PrefService* prefs = owner_->active_user_prefs_;
  if (!prefs) {
    return;
  }
  prefs->SetBoolean(pref_name_, enabled);
  prefs->CommitPendingWrite();
}

bool AccessibilityController::Feature::IsVisibleInTray() const {
  return (conflicting_feature_ == FeatureType::kNoConflictingFeature ||
          !owner_->GetFeature(conflicting_feature_).enabled()) &&
         owner_->IsAccessibilityFeatureVisibleInTrayMenu(pref_name_);
}

bool AccessibilityController::Feature::IsEnterpriseIconVisible() const {
  return owner_->IsEnterpriseIconVisibleInTrayMenu(pref_name_);
}

const gfx::VectorIcon& AccessibilityController::Feature::icon() const {
  DCHECK(icon_);
  if (icon_) {
    return *icon_;
  }
  return kPaletteTrayIconDefaultIcon;
}

void AccessibilityController::Feature::UpdateFromPref() {
  PrefService* prefs = owner_->active_user_prefs_;
  DCHECK(prefs);

  bool enabled = prefs->GetBoolean(pref_name_);

  if (conflicting_feature_ != FeatureType::kNoConflictingFeature &&
      owner_->GetFeature(conflicting_feature_).enabled()) {
    enabled = false;
  }

  if (enabled) {
    // If it was turned on and we are in a active logged in session,
    // prepare to record duration metrics.
    session_manager::SessionState session_state =
        Shell::Get()->session_controller()->GetSessionState();
    if (session_state == session_manager::SessionState::ACTIVE) {
      enabled_time_ = base::Time::Now();
    }
  } else {
    // Disabled. Log the duration since it was enabled, if needed.
    LogDurationMetric();
  }

  if (enabled == enabled_) {
    return;
  }

  enabled_ = enabled;
  owner_->UpdateFeatureFromPref(type_);
}

// don't pass prefservice here because it might be old.
// instead save the session state type from when enabled_time_ was set.
// maybe don't bother logging user type. just have this be for logged in??
// is session state more interesting?
// duration if session state is ACTIVE
void AccessibilityController::Feature::LogDurationMetric() {
  if (enabled_time_ == base::Time()) {
    return;
  }

  std::string feature_duration_metric = "Accessibility.";
  switch (type_) {
    case FeatureType::kAutoclick:
      feature_duration_metric += "CrosAutoclick";
      break;
    case FeatureType::kCaretHighlight:
      feature_duration_metric += "CrosCaretHighlight";
      break;
    case FeatureType::kColorCorrection:
      feature_duration_metric += "CrosColorCorrection";
      break;
    case FeatureType::kCursorColor:
      feature_duration_metric += "CrosCursorColor";
      break;
    case FeatureType::kCursorHighlight:
      feature_duration_metric += "CrosCursorHighlight";
      break;
    case FeatureType::kDictation:
      feature_duration_metric += "CrosDictation";
      break;
    case FeatureType::kDisableTrackpad:
      feature_duration_metric += "CrosDisableTrackpad";
      break;
    case FeatureType::kDockedMagnifier:
      feature_duration_metric += "CrosDockedMagnifier";
      break;
    case FeatureType::kFaceGaze:
      feature_duration_metric += "CrosFaceGaze";
      break;
    case FeatureType::kFlashNotifications:
      feature_duration_metric += "CrosFlashNotifications";
      break;
    case FeatureType::kFocusHighlight:
      feature_duration_metric += "CrosFocusHighlight";
      break;
    case FeatureType::kFullscreenMagnifier:
      feature_duration_metric += "CrosScreenMagnifier";
      break;
    case FeatureType::kHighContrast:
      feature_duration_metric += "CrosHighContrast";
      break;
    case FeatureType::kLargeCursor:
      feature_duration_metric += "CrosLargeCursor";
      break;
    case FeatureType::kLiveCaption:
      feature_duration_metric += "CrosLiveCaption";
      break;
    case FeatureType::kMonoAudio:
      feature_duration_metric += "CrosMonoAudio";
      break;
    case FeatureType::kMouseKeys:
      feature_duration_metric += "CrosMouseKeys";
      break;
    case FeatureType::kReducedAnimations:
      feature_duration_metric += "CrosReducedAnimations";
      break;
    case FeatureType::kSelectToSpeak:
      feature_duration_metric += "CrosSelectToSpeak";
      break;
    case FeatureType::kSpokenFeedback:
      feature_duration_metric += "CrosSpokenFeedback";
      break;
    case FeatureType::kStickyKeys:
      feature_duration_metric += "CrosStickyKeys";
      break;
    case FeatureType::kSwitchAccess:
      feature_duration_metric += "CrosSwitchAccess";
      break;
    case FeatureType::kVirtualKeyboard:
      feature_duration_metric += "CrosVirtualKeyboard";
      break;
    default:
      return;
  }

  feature_duration_metric += ".SessionDuration";

  base::TimeDelta duration = base::Time::Now() - enabled_time_;
  base::UmaHistogramCustomCounts(feature_duration_metric, duration.InSeconds(),
                                 1, base::Days(1) / base::Seconds(1), 100);

  // Reset enabled time as this duration is now logged and accounted for.
  enabled_time_ = base::Time();
}

void AccessibilityController::Feature::SetConflictingFeature(
    FeatureType feature) {
  DCHECK_EQ(conflicting_feature_, FeatureType::kNoConflictingFeature);
  conflicting_feature_ = feature;
}

void AccessibilityController::Feature::ObserveConflictingFeature() {
  std::string conflicting_pref_name = "";
  switch (conflicting_feature_) {
    case A11yFeatureType::kSpokenFeedback:
      conflicting_pref_name = prefs::kAccessibilitySpokenFeedbackEnabled;
      break;
    default:
      // No other features are used as conflicting features at the moment,
      // but this could be populated if needed in the future.
      NOTREACHED() << "No pref name for conflicting feature "
                   << static_cast<int>(conflicting_feature_);
  }
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(owner_->active_user_prefs_);
  pref_change_registrar_->Add(
      conflicting_pref_name,
      base::BindRepeating(&AccessibilityController::Feature::UpdateFromPref,
                          base::Unretained(this)));
}

AccessibilityController::FeatureWithDialog::FeatureWithDialog(
    FeatureType type,
    const std::string& pref_name,
    const gfx::VectorIcon* icon,
    const int name_resource_id,
    const bool toggleable_in_quicksettings,
    const std::string& dialog_pref,
    AccessibilityController* controller)
    : AccessibilityController::Feature(type,
                                       pref_name,
                                       icon,
                                       name_resource_id,
                                       toggleable_in_quicksettings,
                                       controller),
      dialog_pref_(dialog_pref) {}
AccessibilityController::FeatureWithDialog::~FeatureWithDialog() = default;

void AccessibilityController::FeatureWithDialog::SetDialogAccepted() {
  PrefService* prefs = owner_->active_user_prefs_;
  if (!prefs) {
    return;
  }
  prefs->SetBoolean(dialog_pref_, true);
  prefs->CommitPendingWrite();
}

bool AccessibilityController::FeatureWithDialog::WasDialogAccepted() const {
  PrefService* prefs = owner_->active_user_prefs_;
  DCHECK(prefs);
  return prefs->GetBoolean(dialog_pref_);
}

// static
AccessibilityController* AccessibilityController::Get() {
  return g_instance;
}

AccessibilityController::AccessibilityController()
    : autoclick_delay_(AutoclickController::GetDefaultAutoclickDelay()) {
  DCHECK_EQ(nullptr, g_instance);
  g_instance = this;

  Shell::Get()->session_controller()->AddObserver(this);
  display::Screen::GetScreen()->AddObserver(this);
  CreateAccessibilityFeatures();

  accessibility_notification_controller_ =
      std::make_unique<AccessibilityNotificationController>();

  flash_screen_controller_ = std::make_unique<FlashScreenController>();
}

AccessibilityController::~AccessibilityController() {
  floating_menu_controller_.reset();
  accessibility_notification_controller_.reset();

  DCHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

void AccessibilityController::CreateAccessibilityFeatures() {
  // First, build all features with dialog.
  std::map<FeatureType, std::string> dialogs;
  for (auto dialog_data : kFeatureDialogs) {
    dialogs[dialog_data.type] = dialog_data.pref;
  }
  for (auto feature_data : kFeatures) {
    size_t feature_index = static_cast<size_t>(feature_data.type);
    DCHECK(!features_[feature_index]);
    auto it = dialogs.find(feature_data.type);
    if (it == dialogs.end()) {
      features_[feature_index] = std::make_unique<Feature>(
          feature_data.type, feature_data.pref, feature_data.icon,
          feature_data.name_resource_id,
          feature_data.toggleable_in_quicksettings, this);
    } else {
      features_[feature_index] = std::make_unique<FeatureWithDialog>(
          feature_data.type, feature_data.pref, feature_data.icon,
          feature_data.name_resource_id,
          feature_data.toggleable_in_quicksettings, it->second, this);
    }
    if (feature_data.conflicting_feature !=
        FeatureType::kNoConflictingFeature) {
      features_[feature_index]->SetConflictingFeature(
          feature_data.conflicting_feature);
    }
  }
}

// static
void AccessibilityController::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  //
  // Non-syncable prefs.
  //
  // These prefs control whether an accessibility feature is enabled. They are
  // not synced due to the impact they have on device interaction.
  registry->RegisterBooleanPref(prefs::kAccessibilityAutoclickEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityCursorColorEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityCaretHighlightEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityCursorHighlightEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityDictationEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityFloatingMenuEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityFocusHighlightEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityHighContrastEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityLargeCursorEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityMonoAudioEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityMouseKeysEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityScreenMagnifierEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilitySpokenFeedbackEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilitySelectToSpeakEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityStickyKeysEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityShortcutsEnabled, true);
  registry->RegisterBooleanPref(prefs::kAccessibilitySwitchAccessEnabled,
                                false);
  registry->RegisterBooleanPref(prefs::kAccessibilityVirtualKeyboardEnabled,
                                false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityFaceGazeEnabled, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityDisableTrackpadEnabled,
                                false);
  registry->RegisterIntegerPref(prefs::kAccessibilityDisableTrackpadMode,
                                static_cast<int>(DisableTrackpadMode::kNever));

  // Not syncable because it might change depending on application locale,
  // user settings, and because different languages can cause speech recognition
  // files to download.
  registry->RegisterStringPref(prefs::kAccessibilityDictationLocale,
                               std::string());
  registry->RegisterDictionaryPref(
      prefs::kAccessibilityDictationLocaleOfflineNudge);

  // A pref in this list is associated with accepting for the first time,
  // enabling of some pref above. Non-syncable like all of the above prefs.
  registry->RegisterBooleanPref(
      prefs::kHighContrastAcceleratorDialogHasBeenAccepted, false);
  registry->RegisterBooleanPref(
      prefs::kScreenMagnifierAcceleratorDialogHasBeenAccepted, false);
  registry->RegisterBooleanPref(
      prefs::kDockedMagnifierAcceleratorDialogHasBeenAccepted, false);
  registry->RegisterBooleanPref(
      prefs::kDictationAcceleratorDialogHasBeenAccepted, false);
  registry->RegisterBooleanPref(
      prefs::kSelectToSpeakAcceleratorDialogHasBeenAccepted, false);
  registry->RegisterBooleanPref(
      prefs::kDictationDlcSuccessNotificationHasBeenShown, false);
  registry->RegisterBooleanPref(
      prefs::kDictationDlcOnlyPumpkinDownloadedNotificationHasBeenShown, false);
  registry->RegisterBooleanPref(
      prefs::kDictationDlcOnlySodaDownloadedNotificationHasBeenShown, false);
  registry->RegisterBooleanPref(
      prefs::kDictationNoDlcsDownloadedNotificationHasBeenShown, false);
  registry->RegisterBooleanPref(
      prefs::kDisplayRotationAcceleratorDialogHasBeenAccepted2, false);
  registry->RegisterBooleanPref(prefs::kShouldAlwaysShowAccessibilityMenu,
                                false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted, false);
  registry->RegisterBooleanPref(
      prefs::kFaceGazeDlcSuccessNotificationHasBeenShown, false);
  registry->RegisterBooleanPref(
      prefs::kFaceGazeDlcFailureNotificationHasBeenShown, false);

  registry->RegisterBooleanPref(prefs::kAccessibilityColorCorrectionEnabled,
                                false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityColorCorrectionHasBeenSetup, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityFlashNotificationsEnabled,
                                false);

  registry->RegisterBooleanPref(prefs::kAccessibilityReducedAnimationsEnabled,
                                false);

  // TODO(b/266816160): Make ChromeVox prefs are syncable, to so that ChromeOS
  // backs up users' ChromeVox settings and reflects across their devices.
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxAutoRead, false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxAnnounceDownloadNotifications, true);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxAnnounceRichTextAttributes, true);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxAudioStrategy,
                               kDefaultAccessibilityChromeVoxAudioStrategy);
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxBrailleSideBySide,
                                true);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxBrailleTable,
                               kDefaultAccessibilityChromeVoxBrailleTable);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxBrailleTable6,
                               kDefaultAccessibilityChromeVoxBrailleTable6);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxBrailleTable8,
                               kDefaultAccessibilityChromeVoxBrailleTable8);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxBrailleTableType,
                               kDefaultAccessibilityChromeVoxBrailleTableType);
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxBrailleWordWrap,
                                true);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxCapitalStrategy,
                               kDefaultAccessibilityChromeVoxCapitalStrategy);
  registry->RegisterStringPref(
      prefs::kAccessibilityChromeVoxCapitalStrategyBackup,
      kDefaultAccessibilityChromeVoxCapitalStrategyBackup);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxEnableBrailleLogging, false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxEnableEarconLogging, false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxEnableEventStreamLogging, false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxEnableSpeechLogging, false);
  registry->RegisterDictionaryPref(
      prefs::kAccessibilityChromeVoxEventStreamFilters, base::Value::Dict());
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxLanguageSwitching,
                                false);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxMenuBrailleCommands, false);
  registry->RegisterStringPref(
      prefs::kAccessibilityChromeVoxNumberReadingStyle,
      kDefaultAccessibilityChromeVoxNumberReadingStyle);
  registry->RegisterStringPref(
      prefs::kAccessibilityChromeVoxPreferredBrailleDisplayAddress,
      kDefaultAccessibilityChromeVoxPreferredBrailleDisplayAddress);
  registry->RegisterIntegerPref(prefs::kAccessibilityChromeVoxPunctuationEcho,
                                kDefaultAccessibilityChromeVoxPunctuationEcho);
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxSmartStickyMode,
                                true);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityChromeVoxSpeakTextUnderMouse, false);
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxUsePitchChanges,
                                true);
  registry->RegisterBooleanPref(prefs::kAccessibilityChromeVoxUseVerboseMode,
                                true);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityChromeVoxVirtualBrailleColumns,
      kDefaultAccessibilityChromeVoxVirtualBrailleColumns);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityChromeVoxVirtualBrailleRows,
      kDefaultAccessibilityChromeVoxVirtualBrailleRows);
  registry->RegisterStringPref(prefs::kAccessibilityChromeVoxVoiceName,
                               kDefaultAccessibilityChromeVoxVoiceName);

  // TODO(b/259372916): Enable sync for Mouse Keys settings before launch.
  registry->RegisterDoublePref(prefs::kAccessibilityMouseKeysAcceleration,
                               MouseKeysController::kDefaultAcceleration);
  registry->RegisterDoublePref(prefs::kAccessibilityMouseKeysMaxSpeed,
                               MouseKeysController::kDefaultMaxSpeed);
  registry->RegisterBooleanPref(prefs::kAccessibilityMouseKeysUsePrimaryKeys,
                                true);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityMouseKeysDominantHand,
      static_cast<int>(MouseKeysDominantHand::kRightHandDominant));

  //
  // Syncable prefs.
  //
  // These prefs pertain to specific features. They are synced to preserve
  // behaviors tied to user accounts once that user enables a feature.
  registry->RegisterIntegerPref(
      prefs::kAccessibilityAutoclickDelayMs, kDefaultAutoclickDelayMs,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityAutoclickEventType,
      static_cast<int>(kDefaultAutoclickEventType),
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityAutoclickRevertToLeftClick, true,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityAutoclickStabilizePosition, false,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityAutoclickMovementThreshold,
      kDefaultAutoclickMovementThreshold,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityAutoclickMenuPosition,
      static_cast<int>(kDefaultAutoclickMenuPosition),
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);

  registry->RegisterIntegerPref(
      prefs::kAccessibilityCursorColor, 0,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);

  registry->RegisterIntegerPref(
      prefs::kAccessibilityFloatingMenuPosition,
      static_cast<int>(kDefaultFloatingMenuPosition),
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);

  registry->RegisterIntegerPref(prefs::kAccessibilityLargeCursorDipSize,
                                kDefaultLargeCursorSize);

  registry->RegisterIntegerPref(
      prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
      static_cast<int>(MagnifierMouseFollowingMode::kEdge),
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityScreenMagnifierFocusFollowingEnabled, true,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterDoublePref(prefs::kAccessibilityScreenMagnifierScale,
                               std::numeric_limits<double>::min());
  registry->RegisterDictionaryPref(
      prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterDictionaryPref(
      prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterDictionaryPref(
      prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySwitchAccessAutoScanEnabled, false,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilitySwitchAccessAutoScanSpeedMs,
      kDefaultSwitchAccessAutoScanSpeed.InMilliseconds(),
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilitySwitchAccessAutoScanKeyboardSpeedMs,
      kDefaultSwitchAccessAutoScanSpeed.InMilliseconds(),
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilitySwitchAccessPointScanSpeedDipsPerSecond,
      kDefaultSwitchAccessPointScanSpeedDipsPerSecond,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilityEnhancedNetworkVoicesInSelectToSpeakAllowed,
      kDefaultAccessibilityEnhancedNetworkVoicesInSelectToSpeakAllowed,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySelectToSpeakBackgroundShading,
      kDefaultAccessibilitySelectToSpeakBackgroundShading,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySelectToSpeakEnhancedNetworkVoices,
      kDefaultAccessibilitySelectToSpeakEnhancedNetworkVoices,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySelectToSpeakEnhancedVoicesDialogShown,
      kDefaultAccessibilitySelectToSpeakEnhancedVoicesDialogShown,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySelectToSpeakNavigationControls,
      kDefaultAccessibilitySelectToSpeakNavigationControls,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySelectToSpeakVoiceSwitching,
      kDefaultAccessibilitySelectToSpeakVoiceSwitching,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(
      prefs::kAccessibilitySelectToSpeakWordHighlight,
      kDefaultAccessibilitySelectToSpeakWordHighlight,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterStringPref(
      prefs::kAccessibilitySelectToSpeakEnhancedVoiceName,
      kDefaultAccessibilitySelectToSpeakEnhancedVoiceName,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterStringPref(
      prefs::kAccessibilitySelectToSpeakHighlightColor,
      kDefaultAccessibilitySelectToSpeakHighlightColor,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterStringPref(
      prefs::kAccessibilitySelectToSpeakVoiceName,
      kDefaultAccessibilitySelectToSpeakVoiceName,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityColorVisionCorrectionAmount, 100,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterIntegerPref(
      prefs::kAccessibilityColorVisionCorrectionType,
      ColorVisionCorrectionType::kDeuteranomaly,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);

  if (::features::IsAccessibilityFaceGazeEnabled()) {
    registry->RegisterIntegerPref(
        prefs::kAccessibilityFaceGazeCursorSpeedUp, kDefaultFaceGazeCursorSpeed,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterIntegerPref(
        prefs::kAccessibilityFaceGazeCursorSpeedDown,
        kDefaultFaceGazeCursorSpeed,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterIntegerPref(
        prefs::kAccessibilityFaceGazeCursorSpeedLeft,
        kDefaultFaceGazeCursorSpeed,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterIntegerPref(
        prefs::kAccessibilityFaceGazeCursorSpeedRight,
        kDefaultFaceGazeCursorSpeed,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterIntegerPref(
        prefs::kAccessibilityFaceGazeCursorSmoothing,
        kDefaultFaceGazeCursorSmoothing,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterBooleanPref(
        prefs::kAccessibilityFaceGazeCursorUseAcceleration,
        kDefaultFaceGazeCursorUseAcceleration,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterDictionaryPref(
        prefs::kAccessibilityFaceGazeGesturesToKeyCombos,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterDictionaryPref(
        prefs::kAccessibilityFaceGazeGesturesToMacros,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterDictionaryPref(
        prefs::kAccessibilityFaceGazeGesturesToConfidence,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterBooleanPref(
        prefs::kAccessibilityFaceGazeCursorControlEnabled, true,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterBooleanPref(
        prefs::kAccessibilityFaceGazeActionsEnabled, true,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
    registry->RegisterBooleanPref(
        prefs::kAccessibilityFaceGazeAdjustSpeedSeparately, false,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  }

  if (::features::IsAccessibilityMagnifierFollowsChromeVoxEnabled()) {
    registry->RegisterBooleanPref(
        prefs::kAccessibilityMagnifierFollowsChromeVox, true,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  }

  if (::features::IsAccessibilityMagnifierFollowsStsEnabled()) {
    registry->RegisterBooleanPref(
        prefs::kAccessibilityMagnifierFollowsSts, true,
        user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  }

  if (::features::IsAccessibilityCaretBlinkIntervalSettingEnabled()) {
    registry->RegisterIntegerPref(prefs::kAccessibilityCaretBlinkInterval,
                                  kDefaultCaretBlinkIntervalMs);
  }

  if (::features::IsAccessibilityFlashScreenFeatureEnabled()) {
    registry->RegisterIntegerPref(prefs::kAccessibilityFlashNotificationsColor,
                                  kDefaultFlashNotificationsColor);
  }
}

void AccessibilityController::Shutdown() {
  // Log metrics at shutdown.
  for (auto& feature : features_) {
    feature->LogDurationMetric();
  }

  display::Screen::GetScreen()->RemoveObserver(this);
  Shell::Get()->session_controller()->RemoveObserver(this);

  // Clean up any child windows and widgets that might be animating out.
  dictation_bubble_controller_.reset();

  for (auto& observer : observers_) {
    observer.OnAccessibilityControllerShutdown();
  }
}

bool AccessibilityController::HasDisplayRotationAcceleratorDialogBeenAccepted()
    const {
  return active_user_prefs_ &&
         active_user_prefs_->GetBoolean(
             prefs::kDisplayRotationAcceleratorDialogHasBeenAccepted2);
}

void AccessibilityController::
    SetDisplayRotationAcceleratorDialogBeenAccepted() {
  if (!active_user_prefs_) {
    return;
  }
  active_user_prefs_->SetBoolean(
      prefs::kDisplayRotationAcceleratorDialogHasBeenAccepted2, true);
  active_user_prefs_->CommitPendingWrite();
}

void AccessibilityController::AddObserver(AccessibilityObserver* observer) {
  observers_.AddObserver(observer);
}

void AccessibilityController::RemoveObserver(AccessibilityObserver* observer) {
  observers_.RemoveObserver(observer);
}

AccessibilityController::Feature& AccessibilityController::GetFeature(
    FeatureType type) const {
  size_t feature_index = static_cast<size_t>(type);
  DCHECK(features_[feature_index].get());
  return *features_[feature_index].get();
}

std::vector<AccessibilityController::Feature*>
AccessibilityController::GetEnabledFeaturesInQuickSettings() const {
  std::vector<Feature*> enabled_features;

  for (auto& feature : features_) {
    if (feature->enabled() && feature->toggleable_in_quicksettings()) {
      enabled_features.push_back(feature.get());
    }
  }
  return enabled_features;
}

base::WeakPtr<AccessibilityController> AccessibilityController::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

AccessibilityController::Feature& AccessibilityController::autoclick() const {
  return GetFeature(FeatureType::kAutoclick);
}

AccessibilityController::Feature& AccessibilityController::caret_highlight()
    const {
  return GetFeature(FeatureType::kCaretHighlight);
}

AccessibilityController::Feature& AccessibilityController::cursor_highlight()
    const {
  return GetFeature(FeatureType::kCursorHighlight);
}

AccessibilityController::Feature& AccessibilityController::cursor_color()
    const {
  return GetFeature(FeatureType::kCursorColor);
}

AccessibilityController::Feature& AccessibilityController::dictation() const {
  return GetFeature(FeatureType::kDictation);
}

AccessibilityController::Feature& AccessibilityController::disable_trackpad()
    const {
  return GetFeature(FeatureType::kDisableTrackpad);
}

AccessibilityController::Feature& AccessibilityController::color_correction()
    const {
  return GetFeature(FeatureType::kColorCorrection);
}

AccessibilityController::Feature& AccessibilityController::face_gaze() const {
  return GetFeature(FeatureType::kFaceGaze);
}

AccessibilityController::Feature& AccessibilityController::flash_notifications()
    const {
  return GetFeature(FeatureType::kFlashNotifications);
}

AccessibilityController::Feature& AccessibilityController::focus_highlight()
    const {
  return GetFeature(FeatureType::kFocusHighlight);
}

AccessibilityController::Feature& AccessibilityController::floating_menu()
    const {
  return GetFeature(FeatureType::kFloatingMenu);
}

AccessibilityController::FeatureWithDialog&
AccessibilityController::fullscreen_magnifier() const {
  return static_cast<FeatureWithDialog&>(
      GetFeature(FeatureType::kFullscreenMagnifier));
}

AccessibilityController::FeatureWithDialog&
AccessibilityController::docked_magnifier() const {
  return static_cast<FeatureWithDialog&>(
      GetFeature(FeatureType::kDockedMagnifier));
}

AccessibilityController::FeatureWithDialog&
AccessibilityController::high_contrast() const {
  return static_cast<FeatureWithDialog&>(
      GetFeature(FeatureType::kHighContrast));
}

AccessibilityController::Feature& AccessibilityController::large_cursor()
    const {
  return GetFeature(FeatureType::kLargeCursor);
}

AccessibilityController::Feature& AccessibilityController::live_caption()
    const {
  return GetFeature(FeatureType::kLiveCaption);
}

AccessibilityController::Feature& AccessibilityController::mono_audio() const {
  return GetFeature(FeatureType::kMonoAudio);
}

AccessibilityController::Feature& AccessibilityController::mouse_keys() const {
  return GetFeature(FeatureType::kMouseKeys);
}

AccessibilityController::Feature& AccessibilityController::reduced_animations()
    const {
  return GetFeature(FeatureType::kReducedAnimations);
}

AccessibilityController::Feature& AccessibilityController::spoken_feedback()
    const {
  return GetFeature(FeatureType::kSpokenFeedback);
}

AccessibilityController::Feature& AccessibilityController::select_to_speak()
    const {
  return GetFeature(FeatureType::kSelectToSpeak);
}

AccessibilityController::Feature& AccessibilityController::sticky_keys() const {
  return GetFeature(FeatureType::kStickyKeys);
}

AccessibilityController::Feature& AccessibilityController::switch_access()
    const {
  return GetFeature(FeatureType::kSwitchAccess);
}

AccessibilityController::Feature& AccessibilityController::virtual_keyboard()
    const {
  return GetFeature(FeatureType::kVirtualKeyboard);
}

bool AccessibilityController::IsAutoclickSettingVisibleInTray() {
  return autoclick().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForAutoclick() {
  return autoclick().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsCaretHighlightSettingVisibleInTray() {
  return caret_highlight().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForCaretHighlight() {
  return caret_highlight().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsCursorHighlightSettingVisibleInTray() {
  return cursor_highlight().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForCursorHighlight() {
  return cursor_highlight().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsDictationSettingVisibleInTray() {
  return dictation().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForDictation() {
  return dictation().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsFaceGazeSettingVisibleInTray() {
  return face_gaze().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForFaceGaze() {
  return face_gaze().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsFocusHighlightSettingVisibleInTray() {
  return focus_highlight().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForFocusHighlight() {
  return focus_highlight().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsFullScreenMagnifierSettingVisibleInTray() {
  return fullscreen_magnifier().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForFullScreenMagnifier() {
  return fullscreen_magnifier().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsDockedMagnifierSettingVisibleInTray() {
  return docked_magnifier().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForDockedMagnifier() {
  return docked_magnifier().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsHighContrastSettingVisibleInTray() {
  return high_contrast().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForHighContrast() {
  return high_contrast().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsColorCorrectionSettingVisibleInTray() {
  if (!color_correction().enabled() &&
      Shell::Get()->session_controller()->login_status() ==
          ash::LoginStatus::NOT_LOGGED_IN) {
    // Don't allow users to enable this on not logged in profiles because it
    // requires set-up in settings the first time it is run.
    return false;
  }
  return color_correction().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForColorCorrection() {
  return color_correction().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsLargeCursorSettingVisibleInTray() {
  return large_cursor().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForLargeCursor() {
  return large_cursor().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsLiveCaptionSettingVisibleInTray() {
  return captions::IsLiveCaptionFeatureSupported() &&
         live_caption().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForLiveCaption() {
  return captions::IsLiveCaptionFeatureSupported() &&
         live_caption().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsMonoAudioSettingVisibleInTray() {
  return mono_audio().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForMonoAudio() {
  return mono_audio().IsEnterpriseIconVisible();
}

void AccessibilityController::SetSpokenFeedbackEnabled(
    bool enabled,
    AccessibilityNotificationVisibility notify) {
  spoken_feedback().SetEnabled(enabled);

  // Value could be left unchanged because of higher-priority pref source, eg.
  // policy. See crbug.com/953245.
  const bool actual_enabled = active_user_prefs_->GetBoolean(
      prefs::kAccessibilitySpokenFeedbackEnabled);

  A11yNotificationType type = A11yNotificationType::kNone;
  if (enabled && actual_enabled && notify == A11Y_NOTIFICATION_SHOW) {
    type = A11yNotificationType::kSpokenFeedbackEnabled;
  }
  ShowAccessibilityNotification(
      A11yNotificationWrapper(type, std::vector<std::u16string>()));
}

bool AccessibilityController::IsSpokenFeedbackSettingVisibleInTray() {
  return spoken_feedback().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForSpokenFeedback() {
  return spoken_feedback().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsSelectToSpeakSettingVisibleInTray() {
  return select_to_speak().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForSelectToSpeak() {
  return select_to_speak().IsEnterpriseIconVisible();
}

void AccessibilityController::RequestSelectToSpeakStateChange() {
  client_->RequestSelectToSpeakStateChange();
}

void AccessibilityController::RecordSelectToSpeakSpeechDuration(
    SelectToSpeakState old_state,
    SelectToSpeakState new_state) {
  if (new_state != SelectToSpeakState::kSelectToSpeakStateSpeaking &&
      select_to_speak_speech_start_time_ == base::Time()) {
    select_to_speak_speech_start_time_ = base::Time::Now();
  }
  if (old_state != SelectToSpeakState::kSelectToSpeakStateSpeaking &&
      new_state != old_state &&
      select_to_speak_speech_start_time_ != base::Time()) {
    base::TimeDelta duration =
        base::Time::Now() - select_to_speak_speech_start_time_;
    base::UmaHistogramCustomCounts(
        "Accessibility.CrosSelectToSpeak.SpeechDuration", duration.InSeconds(),
        /*min=*/1, /*max=*/base::Minutes(20) / base::Seconds(1),
        /*buckets=*/100);
    select_to_speak_speech_start_time_ = base::Time();
  }
}

void AccessibilityController::SetSelectToSpeakState(SelectToSpeakState state) {
  RecordSelectToSpeakSpeechDuration(select_to_speak_state_, state);
  select_to_speak_state_ = state;

  // Forward the state change event to select_to_speak_event_handler_.
  // The extension may have requested that the handler enter SELECTING state.
  // Prepare to start capturing events from stylus, mouse or touch.
  if (select_to_speak_event_handler_) {
    select_to_speak_event_handler_->SetSelectToSpeakStateSelecting(
        state == SelectToSpeakState::kSelectToSpeakStateSelecting);
  }
  NotifyAccessibilityStatusChanged();
}

void AccessibilityController::SetSelectToSpeakEventHandlerDelegate(
    SelectToSpeakEventHandlerDelegate* delegate) {
  select_to_speak_event_handler_delegate_ = delegate;
  MaybeCreateSelectToSpeakEventHandler();
}

SelectToSpeakState AccessibilityController::GetSelectToSpeakState() const {
  return select_to_speak_state_;
}

void AccessibilityController::ShowSelectToSpeakPanel(const gfx::Rect& anchor,
                                                     bool is_paused,
                                                     double speech_rate) {
  if (!select_to_speak_bubble_controller_) {
    select_to_speak_bubble_controller_ =
        std::make_unique<SelectToSpeakMenuBubbleController>();
  }
  select_to_speak_bubble_controller_->Show(anchor, is_paused, speech_rate);
}

void AccessibilityController::HideSelectToSpeakPanel() {
  if (!select_to_speak_bubble_controller_) {
    return;
  }
  select_to_speak_bubble_controller_->Hide();
}

void AccessibilityController::OnSelectToSpeakPanelAction(
    SelectToSpeakPanelAction action,
    double value) {
  if (!client_) {
    return;
  }
  client_->OnSelectToSpeakPanelAction(action, value);
}

bool AccessibilityController::IsSwitchAccessRunning() const {
  return switch_access().enabled() || switch_access_disable_dialog_showing_;
}

bool AccessibilityController::IsSwitchAccessSettingVisibleInTray() {
  // Switch Access cannot be enabled on the sign-in page because there is no way
  // to configure switches while the device is locked.
  if (!switch_access().enabled() &&
      Shell::Get()->session_controller()->login_status() ==
          ash::LoginStatus::NOT_LOGGED_IN) {
    return false;
  }
  return switch_access().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForSwitchAccess() {
  return switch_access().IsEnterpriseIconVisible();
}

void AccessibilityController::SetAccessibilityEventRewriter(
    AccessibilityEventRewriter* accessibility_event_rewriter) {
  accessibility_event_rewriter_ = accessibility_event_rewriter;
}

void AccessibilityController::SetDisableTrackpadEventRewriter(
    DisableTrackpadEventRewriter* rewriter) {
  disable_trackpad_event_rewriter_ = rewriter;
}

void AccessibilityController::SetFilterKeysEventRewriter(
    FilterKeysEventRewriter* rewriter) {
  filter_keys_event_rewriter_ = rewriter;
}

void AccessibilityController::HideSwitchAccessBackButton() {
  if (IsSwitchAccessRunning()) {
    switch_access_bubble_controller_->HideBackButton();
  }
}

void AccessibilityController::HideSwitchAccessMenu() {
  if (IsSwitchAccessRunning()) {
    switch_access_bubble_controller_->HideMenuBubble();
  }
}

void AccessibilityController::ShowSwitchAccessBackButton(
    const gfx::Rect& anchor) {
  switch_access_bubble_controller_->ShowBackButton(anchor);
}

void AccessibilityController::ShowSwitchAccessMenu(
    const gfx::Rect& anchor,
    std::vector<std::string> actions_to_show) {
  switch_access_bubble_controller_->ShowMenu(anchor, actions_to_show);
}

bool AccessibilityController::IsPointScanEnabled() {
  return point_scan_controller_.get() &&
         point_scan_controller_->IsPointScanEnabled();
}

void AccessibilityController::StartPointScan() {
  point_scan_controller_->Start();
}

void AccessibilityController::SetA11yOverrideWindow(
    aura::Window* a11y_override_window) {
  if (client_) {
    client_->SetA11yOverrideWindow(a11y_override_window);
  }
}

void AccessibilityController::StopPointScan() {
  if (point_scan_controller_) {
    point_scan_controller_->HideAll();
  }
}

void AccessibilityController::SetPointScanSpeedDipsPerSecond(
    int point_scan_speed_dips_per_second) {
  if (point_scan_controller_) {
    point_scan_controller_->SetSpeedDipsPerSecond(
        point_scan_speed_dips_per_second);
  }
}

void AccessibilityController::DisablePolicyRecommendationRestorerForTesting() {
  Shell::Get()->policy_recommendation_restorer()->DisableForTesting();
}

bool AccessibilityController::IsStickyKeysSettingVisibleInTray() {
  return sticky_keys().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForStickyKeys() {
  return sticky_keys().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsReducedAnimationsSettingVisibleInTray() {
  if (!::features::IsAccessibilityReducedAnimationsInKioskEnabled()) {
    return false;
  }

  // Only visible in kiosk mode.
  if (!Shell::Get()->session_controller()->IsRunningInAppMode()) {
    return false;
  }
  return reduced_animations().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForReducedAnimations() {
  return reduced_animations().IsEnterpriseIconVisible();
}

bool AccessibilityController::IsVirtualKeyboardSettingVisibleInTray() {
  return virtual_keyboard().IsVisibleInTray();
}

bool AccessibilityController::IsEnterpriseIconVisibleForVirtualKeyboard() {
  return virtual_keyboard().IsEnterpriseIconVisible();
}

void AccessibilityController::ShowFloatingMenuIfEnabled() {
  if (floating_menu().enabled() && !floating_menu_controller_) {
    floating_menu_controller_ =
        std::make_unique<FloatingAccessibilityController>(this);
    floating_menu_controller_->Show(GetFloatingMenuPosition());
  } else {
    always_show_floating_menu_when_enabled_ = true;
  }
}

FloatingAccessibilityController*
AccessibilityController::GetFloatingMenuController() {
  return floating_menu_controller_.get();
}

PointScanController* AccessibilityController::GetPointScanController() {
  return point_scan_controller_.get();
}

void AccessibilityController::SetTabletModeShelfNavigationButtonsEnabled(
    bool enabled) {
  if (!active_user_prefs_) {
    return;
  }

  active_user_prefs_->SetBoolean(
      prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, enabled);
  active_user_prefs_->CommitPendingWrite();
}

void AccessibilityController::TriggerAccessibilityAlert(
    AccessibilityAlert alert) {
  if (client_) {
    client_->TriggerAccessibilityAlert(alert);
  }
}

void AccessibilityController::TriggerAccessibilityAlertWithMessage(
    const std::string& message) {
  if (client_) {
    client_->TriggerAccessibilityAlertWithMessage(message);
  }
}

void AccessibilityController::PlayEarcon(Sound sound_key) {
  if (client_) {
    client_->PlayEarcon(sound_key);
  }
}

base::TimeDelta AccessibilityController::PlayShutdownSound() {
  return client_ ? client_->PlayShutdownSound() : base::TimeDelta();
}

void AccessibilityController::HandleAccessibilityGesture(
    ax::mojom::Gesture gesture,
    gfx::PointF location) {
  if (client_) {
    client_->HandleAccessibilityGesture(gesture, location);
  }
}

void AccessibilityController::ToggleDictation() {
  // Do nothing if dictation is not enabled.
  if (!dictation().enabled()) {
    return;
  }

  if (client_) {
    const bool is_active = client_->ToggleDictation();
    SetDictationActive(is_active);
    if (is_active) {
      Shell::Get()->OnDictationStarted();
    } else {
      Shell::Get()->OnDictationEnded();
    }
  }
}

void AccessibilityController::SetDictationActive(bool is_active) {
  dictation_active_ = is_active;
}

void AccessibilityController::ToggleDictationFromSource(
    DictationToggleSource source) {
  base::RecordAction(base::UserMetricsAction("Accel_Toggle_Dictation"));
  UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosDictation.ToggleDictationMethod",
                            source);

  dictation().SetEnabled(true);
  ToggleDictation();
}

void AccessibilityController::EnableSelectToSpeakWithDialog() {
  if (!::features::IsAccessibilitySelectToSpeakShortcutEnabled() ||
      select_to_speak().enabled()) {
    return;
  }

  if (active_user_prefs_
          ->FindPreference(prefs::kAccessibilitySelectToSpeakEnabled)
          ->IsManaged() &&
      !active_user_prefs_->GetBoolean(
          prefs::kAccessibilitySelectToSpeakEnabled)) {
    // Don't show the dialog if Select to speak has been disabled by a policy.
    return;
  }

  if (active_user_prefs_->GetBoolean(
          prefs::kSelectToSpeakAcceleratorDialogHasBeenAccepted)) {
    // Enable Select to Speak if the confirmation dialog has been previously
    // accepted.
    OnSelectToSpeakKeyboardDialogAccepted();
  } else {
    // Show the confirmation dialog if it hasn't been accepted yet.
    ShowSelectToSpeakKeyboardDialog();
  }
}

void AccessibilityController::EnableOrToggleDictationFromSource(
    DictationToggleSource source) {
  if (dictation().enabled()) {
    ToggleDictationFromSource(source);
  } else if (source == DictationToggleSource::kKeyboard) {
    // Only allow direct-enabling of Dictation from the keyboard. Show the
    // confirmation dialog if it hasn't been accepted yet.
    if (active_user_prefs_->GetBoolean(
            prefs::kDictationAcceleratorDialogHasBeenAccepted)) {
      OnDictationKeyboardDialogAccepted();
    } else {
      ShowDictationKeyboardDialog();
    }
  }
}

void AccessibilityController::ShowDictationKeyboardDialog() {
  if (!client_) {
    return;
  }

  dictation_keyboard_dialog_showing_for_testing_ = true;

  std::string dictation_locale;
  if (active_user_prefs_->GetString(prefs::kAccessibilityDictationLocale)
          .empty()) {
    dictation_locale = client_->GetDictationDefaultLocale(/*new_user=*/true);
  } else {
    dictation_locale =
        active_user_prefs_->GetString(prefs::kAccessibilityDictationLocale);
  }

  std::u16string display_locale = l10n_util::GetDisplayNameForLocale(
      /*locale=*/dictation_locale, /*display_locale=*/dictation_locale,
      /*is_for_ui=*/true);
  std::vector<std::u16string> replacements{display_locale};
  std::u16string title =
      l10n_util::GetStringUTF16(IDS_ASH_DICTATION_KEYBOARD_DIALOG_TITLE);
  std::u16string description =
      ::features::IsDictationOfflineAvailable()
          ? l10n_util::GetStringFUTF16(
                IDS_ASH_DICTATION_KEYBOARD_DIALOG_DESCRIPTION_SODA_AVAILABLE,
                replacements, nullptr)
          : l10n_util::GetStringFUTF16(
                IDS_ASH_DICTATION_KEYBOARD_DIALOG_DESCRIPTION_SODA_NOT_AVAILABLE,
                replacements, nullptr);
  ShowConfirmationDialog(
      title, description, l10n_util::GetStringUTF16(IDS_ASH_CONTINUE_BUTTON),
      l10n_util::GetStringUTF16(IDS_APP_CANCEL),
      base::BindOnce(
          &AccessibilityController::OnDictationKeyboardDialogAccepted,
          GetWeakPtr()),
      base::BindOnce(
          &AccessibilityController::OnDictationKeyboardDialogDismissed,
          GetWeakPtr()),
      base::BindOnce(
          &AccessibilityController::OnDictationKeyboardDialogDismissed,
          GetWeakPtr()));
}

void AccessibilityController::OnDictationKeyboardDialogAccepted() {
  dictation_keyboard_dialog_showing_for_testing_ = false;
  active_user_prefs_->SetBoolean(
      prefs::kDictationAcceleratorDialogHasBeenAccepted, true);
  confirmation_dialog_.reset();
  base::RecordAction(base::UserMetricsAction("Accel_Enable_Dictation"));
  dictation().SetEnabled(true);
}

void AccessibilityController::OnDictationKeyboardDialogDismissed() {
  dictation_keyboard_dialog_showing_for_testing_ = false;
}

void AccessibilityController::ShowSelectToSpeakKeyboardDialog() {
  if (!client_ || !::features::IsAccessibilitySelectToSpeakShortcutEnabled()) {
    return;
  }

  std::u16string title =
      l10n_util::GetStringUTF16(IDS_ASH_SELECT_TO_SPEAK_KEYBOARD_DIALOG_TITLE);

  std::u16string modifier_key;
  if (Shell::Get()->keyboard_capability()->HasLauncherButtonOnAnyKeyboard()) {
    modifier_key = l10n_util::GetStringUTF16(IDS_KSV_MODIFIER_LAUNCHER);
  } else {
    modifier_key = l10n_util::GetStringUTF16(IDS_KSV_MODIFIER_SEARCH);
  }
  std::u16string description = l10n_util::GetStringFUTF16(
      IDS_ASH_SELECT_TO_SPEAK_KEYBOARD_DIALOG_DESCRIPTION, modifier_key);
  ShowConfirmationDialog(
      title, description, l10n_util::GetStringUTF16(IDS_ASH_CONTINUE_BUTTON),
      l10n_util::GetStringUTF16(IDS_APP_CANCEL),
      base::BindOnce(
          &AccessibilityController::OnSelectToSpeakKeyboardDialogAccepted,
          GetWeakPtr()),
      base::BindOnce(
          &AccessibilityController::OnSelectToSpeakKeyboardDialogDismissed,
          GetWeakPtr()),
      base::BindOnce(
          &AccessibilityController::OnSelectToSpeakKeyboardDialogDismissed,
          GetWeakPtr()));
}

void AccessibilityController::OnSelectToSpeakKeyboardDialogAccepted() {
  active_user_prefs_->SetBoolean(
      prefs::kSelectToSpeakAcceleratorDialogHasBeenAccepted, true);
  confirmation_dialog_.reset();
  select_to_speak().SetEnabled(true);
}

void AccessibilityController::OnSelectToSpeakKeyboardDialogDismissed() {
  confirmation_dialog_.reset();
}

void AccessibilityController::ShowDictationLanguageUpgradedNudge(
    const std::string& dictation_locale,
    const std::string& application_locale) {
  const std::u16string language_name = l10n_util::GetDisplayNameForLocale(
      dictation_locale, application_locale, /*is_for_ui=*/true);
  const std::u16string body_text = l10n_util::GetStringFUTF16(
      IDS_ASH_DICTATION_LANGUAGE_SUPPORTED_OFFLINE_NUDGE, language_name);

  AnchoredNudgeData nudge_data(kDictationLanguageUpgradedNudgeId,
                               NudgeCatalogName::kDictation, body_text);
  AnchoredNudgeManager::Get()->Show(nudge_data);
}

void AccessibilityController::SilenceSpokenFeedback() {
  if (client_) {
    client_->SilenceSpokenFeedback();
  }
}

bool AccessibilityController::ShouldToggleSpokenFeedbackViaTouch() const {
  return client_ && client_->ShouldToggleSpokenFeedbackViaTouch();
}

void AccessibilityController::PlaySpokenFeedbackToggleCountdown(
    int tick_count) {
  if (client_) {
    client_->PlaySpokenFeedbackToggleCountdown(tick_count);
  }
}

bool AccessibilityController::IsEnterpriseIconVisibleInTrayMenu(
    const std::string& path) {
  return active_user_prefs_ &&
         active_user_prefs_->FindPreference(path)->IsManaged();
}

void AccessibilityController::SetClient(AccessibilityControllerClient* client) {
  client_ = client;
}

void AccessibilityController::SetDarkenScreen(bool darken) {
  if (darken && !scoped_backlights_forced_off_) {
    scoped_backlights_forced_off_ =
        Shell::Get()->backlights_forced_off_setter()->ForceBacklightsOff();
  } else if (!darken && scoped_backlights_forced_off_) {
    scoped_backlights_forced_off_.reset();
  }
}

void AccessibilityController::BrailleDisplayStateChanged(bool connected) {
  A11yNotificationType type = A11yNotificationType::kNone;
  if (connected && spoken_feedback().enabled()) {
    type = A11yNotificationType::kBrailleDisplayConnected;
  } else if (connected && !spoken_feedback().enabled()) {
    type = A11yNotificationType::kSpokenFeedbackBrailleEnabled;
  }

  if (connected) {
    SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
  }
  NotifyAccessibilityStatusChanged();

  ShowAccessibilityNotification(
      A11yNotificationWrapper(type, std::vector<std::u16string>()));
}

void AccessibilityController::SetFocusHighlightRect(
    const gfx::Rect& bounds_in_screen) {
  if (!accessibility_highlight_controller_) {
    return;
  }
  accessibility_highlight_controller_->SetFocusHighlightRect(bounds_in_screen);
}

void AccessibilityController::SetCaretBounds(
    const gfx::Rect& bounds_in_screen) {
  if (!accessibility_highlight_controller_) {
    return;
  }
  accessibility_highlight_controller_->SetCaretBounds(bounds_in_screen);
}

void AccessibilityController::SetAccessibilityPanelAlwaysVisible(
    bool always_visible) {
  GetLayoutManager()->SetAlwaysVisible(always_visible);
}

void AccessibilityController::SetAccessibilityPanelBounds(
    const gfx::Rect& bounds,
    AccessibilityPanelState state) {
  GetLayoutManager()->SetPanelBounds(bounds, state);
}

void AccessibilityController::OnSigninScreenPrefServiceInitialized(
    PrefService* prefs) {
  // Make |kA11yPrefsForRecommendedValueOnSignin| observing recommended values
  // on signin screen. See PolicyRecommendationRestorer.
  PolicyRecommendationRestorer* policy_recommendation_restorer =
      Shell::Get()->policy_recommendation_restorer();
  for (auto* const pref_name : kA11yPrefsForRecommendedValueOnSignin) {
    policy_recommendation_restorer->ObservePref(pref_name);
  }

  // Observe user settings. This must happen after PolicyRecommendationRestorer.
  ObservePrefs(prefs);
}

void AccessibilityController::OnActiveUserPrefServiceChanged(
    PrefService* prefs) {
  // This is guaranteed to be received after
  // OnSigninScreenPrefServiceInitialized() so only copy the signin prefs if
  // needed here.
  CopySigninPrefsIfNeeded(active_user_prefs_, prefs);
  ObservePrefs(prefs);
}

void AccessibilityController::OnSessionStateChanged(
    session_manager::SessionState state) {
  if (state != SessionState::ACTIVE) {
    // Log metrics for how long the features were enabled if needed.
    for (auto& feature : features_) {
      feature->LogDurationMetric();
    }
  }
  // Everything behind the lock screen is in
  // kShellWindowId_NonLockScreenContainersContainer. If the session state is
  // changed to block the user session due to the lock screen or similar,
  // everything in that window should be made invisible for accessibility.
  // This keeps a11y features from being able to access parts of the tree
  // that are visibly hidden behind the lock screen.
  aura::Window* container =
      Shell::GetContainer(Shell::GetPrimaryRootWindow(),
                          kShellWindowId_NonLockScreenContainersContainer);
  container->SetProperty(
      ui::kAXConsiderInvisibleAndIgnoreChildren,
      Shell::Get()->session_controller()->IsUserSessionBlocked());
}

AccessibilityEventRewriter*
AccessibilityController::GetAccessibilityEventRewriterForTest() {
  return accessibility_event_rewriter_;
}

DisableTrackpadEventRewriter*
AccessibilityController::GetDisableTrackpadEventRewriterForTest() {
  return disable_trackpad_event_rewriter_;
}

FilterKeysEventRewriter*
AccessibilityController::GetFilterKeysEventRewriterForTest() {
  return filter_keys_event_rewriter_;
}

void AccessibilityController::DisableAutoClickConfirmationDialogForTest() {
  no_auto_click_confirmation_dialog_for_testing_ = true;
}

void AccessibilityController::
    DisableSwitchAccessDisableConfirmationDialogTesting() {
  no_switch_access_disable_confirmation_dialog_for_testing_ = true;
}

void AccessibilityController::DisableSwitchAccessEnableNotificationTesting() {
  skip_switch_access_notification_ = true;
}

void AccessibilityController::OnDisplayTabletStateChanged(
    display::TabletState state) {
  if (spoken_feedback().enabled()) {
    // Show accessibility notification when tablet mode transition is completed.
    if (state == display::TabletState::kInTabletMode ||
        state == display::TabletState::kInClamshellMode) {
      ShowAccessibilityNotification(
          A11yNotificationWrapper(A11yNotificationType::kSpokenFeedbackEnabled,
                                  std::vector<std::u16string>()));
    }
  }
}

void AccessibilityController::ObservePrefs(PrefService* prefs) {
  DCHECK(prefs);

  active_user_prefs_ = prefs;

  // Watch for pref updates from webui settings and policy.
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(prefs);

  // It is safe to use base::Unreatined since we own pref_change_registrar.
  for (const std::unique_ptr<Feature>& feature : features_) {
    DCHECK(feature);
    pref_change_registrar_->Add(
        feature->pref_name(),
        base::BindRepeating(&AccessibilityController::Feature::UpdateFromPref,
                            base::Unretained(feature.get())));
    if (feature->conflicting_feature() != FeatureType::kNoConflictingFeature) {
      feature->ObserveConflictingFeature();
    }
    // Features will be initialized from current prefs later.
  }

  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickDelayMs,
      base::BindRepeating(
          &AccessibilityController::UpdateAutoclickDelayFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickEventType,
      base::BindRepeating(
          &AccessibilityController::UpdateAutoclickEventTypeFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickRevertToLeftClick,
      base::BindRepeating(
          &AccessibilityController::UpdateAutoclickRevertToLeftClickFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickStabilizePosition,
      base::BindRepeating(
          &AccessibilityController::UpdateAutoclickStabilizePositionFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickMovementThreshold,
      base::BindRepeating(
          &AccessibilityController::UpdateAutoclickMovementThresholdFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickMenuPosition,
      base::BindRepeating(
          &AccessibilityController::UpdateAutoclickMenuPositionFromPref,
          base::Unretained(this)));
  if (::features::IsAccessibilityMouseKeysEnabled()) {
    pref_change_registrar_->Add(
        prefs::kAccessibilityMouseKeysAcceleration,
        base::BindRepeating(
            &AccessibilityController::UpdateMouseKeysAccelerationFromPref,
            base::Unretained(this)));
    pref_change_registrar_->Add(
        prefs::kAccessibilityMouseKeysMaxSpeed,
        base::BindRepeating(
            &AccessibilityController::UpdateMouseKeysMaxSpeedFromPref,
            base::Unretained(this)));
    pref_change_registrar_->Add(
        prefs::kAccessibilityMouseKeysUsePrimaryKeys,
        base::BindRepeating(
            &AccessibilityController::UpdateMouseKeysUsePrimaryKeysFromPref,
            base::Unretained(this)));
    pref_change_registrar_->Add(
        prefs::kAccessibilityMouseKeysDominantHand,
        base::BindRepeating(
            &AccessibilityController::UpdateMouseKeysDominantHandFromPref,
            base::Unretained(this)));
  }
  pref_change_registrar_->Add(
      prefs::kAccessibilityFloatingMenuPosition,
      base::BindRepeating(
          &AccessibilityController::UpdateFloatingMenuPositionFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityLargeCursorDipSize,
      base::BindRepeating(&AccessibilityController::UpdateLargeCursorFromPref,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityShortcutsEnabled,
      base::BindRepeating(
          &AccessibilityController::UpdateShortcutsEnabledFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes,
      base::BindRepeating(
          &AccessibilityController::UpdateSwitchAccessKeyCodesFromPref,
          base::Unretained(this), SwitchAccessCommand::kSelect));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes,
      base::BindRepeating(
          &AccessibilityController::UpdateSwitchAccessKeyCodesFromPref,
          base::Unretained(this), SwitchAccessCommand::kNext));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes,
      base::BindRepeating(
          &AccessibilityController::UpdateSwitchAccessKeyCodesFromPref,
          base::Unretained(this), SwitchAccessCommand::kPrevious));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessAutoScanEnabled,
      base::BindRepeating(
          &AccessibilityController::UpdateSwitchAccessAutoScanEnabledFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessAutoScanSpeedMs,
      base::BindRepeating(
          &AccessibilityController::UpdateSwitchAccessAutoScanSpeedFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessAutoScanKeyboardSpeedMs,
      base::BindRepeating(&AccessibilityController::
                              UpdateSwitchAccessAutoScanKeyboardSpeedFromPref,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilitySwitchAccessPointScanSpeedDipsPerSecond,
      base::BindRepeating(
          &AccessibilityController::UpdateSwitchAccessPointScanSpeedFromPref,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled,
      base::BindRepeating(&AccessibilityController::
                              UpdateTabletModeShelfNavigationButtonsFromPref,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityCursorColor,
      base::BindRepeating(&AccessibilityController::UpdateCursorColorFromPrefs,
                          base::Unretained(this), /*notify*/ true));
  pref_change_registrar_->Add(
      prefs::kAccessibilityColorVisionCorrectionAmount,
      base::BindRepeating(
          &AccessibilityController::UpdateColorCorrectionFromPrefs,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kAccessibilityColorVisionCorrectionType,
      base::BindRepeating(
          &AccessibilityController::UpdateColorCorrectionFromPrefs,
          base::Unretained(this)));
  if (::features::IsAccessibilityCaretBlinkIntervalSettingEnabled()) {
    pref_change_registrar_->Add(
        prefs::kAccessibilityCaretBlinkInterval,
        base::BindRepeating(
            &AccessibilityController::UpdateCaretBlinkIntervalFromPrefs,
            base::Unretained(this)));
  }
  if (::features::IsAccessibilityFlashScreenFeatureEnabled()) {
    pref_change_registrar_->Add(
        prefs::kAccessibilityFlashNotificationsColor,
        base::BindRepeating(
            &AccessibilityController::UpdateFlashNotificationsFromPrefs,
            base::Unretained(this)));
  }
  if (::features::IsAccessibilityDisableTrackpadEnabled()) {
    pref_change_registrar_->Add(
        prefs::kAccessibilityDisableTrackpadMode,
        base::BindRepeating(
            &AccessibilityController::UpdateDisableTrackpadFromPrefs,
            base::Unretained(this)));
  }

  for (const std::unique_ptr<Feature>& feature : features_) {
    // Log previous duration and clear duration metric if necessary
    // when the profile has changed.
    feature->LogDurationMetric();

    // Load current state.
    feature->UpdateFromPref();
  }

  // Load current state of other prefs.
  UpdateAutoclickDelayFromPref();
  UpdateAutoclickEventTypeFromPref();
  UpdateAutoclickRevertToLeftClickFromPref();
  UpdateAutoclickStabilizePositionFromPref();
  UpdateAutoclickMovementThresholdFromPref();
  UpdateAutoclickMenuPositionFromPref();
  if (::features::IsAccessibilityMouseKeysEnabled()) {
    UpdateMouseKeysAccelerationFromPref();
    UpdateMouseKeysMaxSpeedFromPref();
    UpdateMouseKeysUsePrimaryKeysFromPref();
    UpdateMouseKeysDominantHandFromPref();
  }
  UpdateFloatingMenuPositionFromPref();
  UpdateLargeCursorFromPref();
  UpdateCursorColorFromPrefs(/*notify=*/true);
  UpdateShortcutsEnabledFromPref();
  UpdateTabletModeShelfNavigationButtonsFromPref();
  UpdateColorCorrectionFromPrefs();
  UpdateCaretBlinkIntervalFromPrefs();

  if (::features::IsAccessibilityFaceGazeEnabled()) {
    UpdateFaceGazeFromPrefs();
  }
  if (::features::IsAccessibilityFlashScreenFeatureEnabled()) {
    UpdateFlashNotificationsFromPrefs();
  }
  if (::features::IsAccessibilityDisableTrackpadEnabled()) {
    UpdateDisableTrackpadFromPrefs();
  }
}

void AccessibilityController::UpdateAutoclickDelayFromPref() {
  DCHECK(active_user_prefs_);
  base::TimeDelta autoclick_delay = base::Milliseconds(int64_t{
      active_user_prefs_->GetInteger(prefs::kAccessibilityAutoclickDelayMs)});

  if (autoclick_delay_ == autoclick_delay) {
    return;
  }
  autoclick_delay_ = autoclick_delay;

  Shell::Get()->autoclick_controller()->SetAutoclickDelay(autoclick_delay_);
}

void AccessibilityController::UpdateAutoclickEventTypeFromPref() {
  Shell::Get()->autoclick_controller()->SetAutoclickEventType(
      GetAutoclickEventType());
}

void AccessibilityController::SetAutoclickEventType(
    AutoclickEventType event_type) {
  if (!active_user_prefs_) {
    return;
  }
  active_user_prefs_->SetInteger(prefs::kAccessibilityAutoclickEventType,
                                 static_cast<int>(event_type));
  active_user_prefs_->CommitPendingWrite();
  Shell::Get()->autoclick_controller()->SetAutoclickEventType(event_type);
}

AutoclickEventType AccessibilityController::GetAutoclickEventType() {
  DCHECK(active_user_prefs_);
  return static_cast<AutoclickEventType>(
      active_user_prefs_->GetInteger(prefs::kAccessibilityAutoclickEventType));
}

void AccessibilityController::UpdateAutoclickRevertToLeftClickFromPref() {
  DCHECK(active_user_prefs_);
  bool revert_to_left_click = active_user_prefs_->GetBoolean(
      prefs::kAccessibilityAutoclickRevertToLeftClick);

  Shell::Get()->autoclick_controller()->set_revert_to_left_click(
      revert_to_left_click);
}

void AccessibilityController::UpdateAutoclickStabilizePositionFromPref() {
  DCHECK(active_user_prefs_);
  bool stabilize_position = active_user_prefs_->GetBoolean(
      prefs::kAccessibilityAutoclickStabilizePosition);

  Shell::Get()->autoclick_controller()->set_stabilize_click_position(
      stabilize_position);
}

void AccessibilityController::UpdateAutoclickMovementThresholdFromPref() {
  DCHECK(active_user_prefs_);
  int movement_threshold = active_user_prefs_->GetInteger(
      prefs::kAccessibilityAutoclickMovementThreshold);

  Shell::Get()->autoclick_controller()->SetMovementThreshold(
      movement_threshold);
}

void AccessibilityController::UpdateAutoclickMenuPositionFromPref() {
  Shell::Get()->autoclick_controller()->SetMenuPosition(
      GetAutoclickMenuPosition());
}

void AccessibilityController::UpdateMouseKeysAccelerationFromPref() {
  DCHECK(active_user_prefs_);
  double acceleration =
      active_user_prefs_->GetDouble(prefs::kAccessibilityMouseKeysAcceleration);
  Shell::Get()->mouse_keys_controller()->set_acceleration(acceleration);
}

void AccessibilityController::UpdateMouseKeysMaxSpeedFromPref() {
  DCHECK(active_user_prefs_);
  double max_speed =
      active_user_prefs_->GetDouble(prefs::kAccessibilityMouseKeysMaxSpeed);
  Shell::Get()->mouse_keys_controller()->SetMaxSpeed(max_speed);
}

void AccessibilityController::UpdateMouseKeysUsePrimaryKeysFromPref() {
  DCHECK(active_user_prefs_);
  bool value = active_user_prefs_->GetBoolean(
      prefs::kAccessibilityMouseKeysUsePrimaryKeys);
  Shell::Get()->mouse_keys_controller()->set_use_primary_keys(value);
}

void AccessibilityController::UpdateMouseKeysDominantHandFromPref() {
  DCHECK(active_user_prefs_);
  MouseKeysDominantHand dominant_hand =
      static_cast<MouseKeysDominantHand>(active_user_prefs_->GetInteger(
          prefs::kAccessibilityMouseKeysDominantHand));
  Shell::Get()->mouse_keys_controller()->set_left_handed(
      dominant_hand == MouseKeysDominantHand::kLeftHandDominant);
}

void AccessibilityController::SetAutoclickMenuPosition(
    FloatingMenuPosition position) {
  if (!active_user_prefs_) {
    return;
  }
  active_user_prefs_->SetInteger(prefs::kAccessibilityAutoclickMenuPosition,
                                 static_cast<int>(position));
  active_user_prefs_->CommitPendingWrite();
  Shell::Get()->autoclick_controller()->SetMenuPosition(position);
}

FloatingMenuPosition AccessibilityController::GetAutoclickMenuPosition() {
  DCHECK(active_user_prefs_);
  return static_cast<FloatingMenuPosition>(active_user_prefs_->GetInteger(
      prefs::kAccessibilityAutoclickMenuPosition));
}

void AccessibilityController::RequestAutoclickScrollableBoundsForPoint(
    const gfx::Point& point_in_screen) {
  if (client_) {
    client_->RequestAutoclickScrollableBoundsForPoint(point_in_screen);
  }
}

void AccessibilityController::MagnifierBoundsChanged(
    const gfx::Rect& bounds_in_screen) {
  if (client_) {
    client_->MagnifierBoundsChanged(bounds_in_screen);
  }
}

void AccessibilityController::UpdateFloatingPanelBoundsIfNeeded() {
  Shell* shell = Shell::Get();
  if (shell->accessibility_controller()->autoclick().enabled()) {
    shell->autoclick_controller()->UpdateAutoclickMenuBoundsIfNeeded();
  }
  if (shell->accessibility_controller()->sticky_keys().enabled()) {
    shell->sticky_keys_controller()->UpdateStickyKeysOverlayBoundsIfNeeded();
  }
}

void AccessibilityController::UpdateAutoclickMenuBoundsIfNeeded() {
  Shell::Get()->autoclick_controller()->UpdateAutoclickMenuBoundsIfNeeded();
}

void AccessibilityController::HandleAutoclickScrollableBoundsFound(
    const gfx::Rect& bounds_in_screen) {
  Shell::Get()->autoclick_controller()->HandleAutoclickScrollableBoundsFound(
      bounds_in_screen);
}

void AccessibilityController::SetFloatingMenuPosition(
    FloatingMenuPosition position) {
  if (!active_user_prefs_) {
    return;
  }
  active_user_prefs_->SetInteger(prefs::kAccessibilityFloatingMenuPosition,
                                 static_cast<int>(position));
  active_user_prefs_->CommitPendingWrite();
}

void AccessibilityController::UpdateFloatingMenuPositionFromPref() {
  if (floating_menu_controller_) {
    floating_menu_controller_->SetMenuPosition(GetFloatingMenuPosition());
  }
}

FloatingMenuPosition AccessibilityController::GetFloatingMenuPosition() {
  DCHECK(active_user_prefs_);
  return static_cast<FloatingMenuPosition>(active_user_prefs_->GetInteger(
      prefs::kAccessibilityFloatingMenuPosition));
}

void AccessibilityController::UpdateLargeCursorFromPref() {
  DCHECK(active_user_prefs_);
  const bool enabled = large_cursor().enabled();
  const int size = enabled ? active_user_prefs_->GetInteger(
                                 prefs::kAccessibilityLargeCursorDipSize)
                           : kDefaultLargeCursorSize;

  if (large_cursor_size_in_dip_ == size) {
    return;
  }

  large_cursor_size_in_dip_ = size;

  NotifyAccessibilityStatusChanged();

  Shell* shell = Shell::Get();
  shell->cursor_manager()->SetCursorSize(enabled ? ui::CursorSize::kLarge
                                                 : ui::CursorSize::kNormal);
  shell->SetLargeCursorSizeInDip(large_cursor_size_in_dip_);
  shell->UpdateCursorCompositingEnabled();
}

void AccessibilityController::UpdateCursorColorFromPrefs(bool notify) {
  DCHECK(active_user_prefs_);

  const bool enabled =
      active_user_prefs_->GetBoolean(prefs::kAccessibilityCursorColorEnabled);
  Shell* shell = Shell::Get();
  shell->SetCursorColor(
      enabled ? active_user_prefs_->GetInteger(prefs::kAccessibilityCursorColor)
              : kDefaultCursorColor);
  if (notify) {
    NotifyAccessibilityStatusChanged();
  }
  shell->UpdateCursorCompositingEnabled();
}

void AccessibilityController::UpdateFaceGazeFromPrefs() {
  if (!::features::IsAccessibilityFaceGazeEnabled()) {
    return;
  }
  // TODO(b/309121742): Start getting camera data.
}

void AccessibilityController::UpdateFlashNotificationsFromPrefs() {
  if (!::features::IsAccessibilityFlashScreenFeatureEnabled()) {
    return;
  }
  flash_screen_controller_->set_enabled(active_user_prefs_->GetBoolean(
      prefs::kAccessibilityFlashNotificationsEnabled));
  flash_screen_controller_->set_color(active_user_prefs_->GetInteger(
      prefs::kAccessibilityFlashNotificationsColor));
}

void AccessibilityController::UpdateDisableTrackpadFromPrefs() {
  if (!disable_trackpad_event_rewriter_ ||
      !::features::IsAccessibilityDisableTrackpadEnabled()) {
    return;
  }
  // TODO(b:354176487): Send trackpad mode to event rewriter
  disable_trackpad_event_rewriter_->SetEnabled(
      active_user_prefs_->GetInteger(
          prefs::kAccessibilityDisableTrackpadMode) !=
      static_cast<int>(DisableTrackpadMode::kNever));
}

void AccessibilityController::UpdateColorCorrectionFromPrefs() {
  DCHECK(active_user_prefs_);

  auto* color_enhancement_controller =
      Shell::Get()->color_enhancement_controller();

  if (!active_user_prefs_->GetBoolean(
          prefs::kAccessibilityColorCorrectionEnabled)) {
    color_enhancement_controller->SetColorCorrectionEnabledAndUpdateDisplays(
        false);
    return;
  }

  const float cvd_correction_amount =
      active_user_prefs_->GetInteger(
          prefs::kAccessibilityColorVisionCorrectionAmount) /
      100.0f;
  ColorVisionCorrectionType type =
      static_cast<ColorVisionCorrectionType>(active_user_prefs_->GetInteger(
          prefs::kAccessibilityColorVisionCorrectionType));
  color_enhancement_controller->SetColorVisionCorrectionFilter(
      type, cvd_correction_amount);

  // Ensure displays get updated.
  color_enhancement_controller->SetColorCorrectionEnabledAndUpdateDisplays(
      true);
}

void AccessibilityController::UpdateCaretBlinkIntervalFromPrefs() const {
  if (!::features::IsAccessibilityCaretBlinkIntervalSettingEnabled()) {
    return;
  }
  base::TimeDelta caret_blink_interval = base::Milliseconds(
      active_user_prefs_->GetInteger(prefs::kAccessibilityCaretBlinkInterval));
  bool notify_dark = false;
  bool notify_web = false;
  bool notify_native = false;
  auto* native_theme_dark = ui::NativeTheme::GetInstanceForDarkUI();
  if (native_theme_dark->GetCaretBlinkInterval() != caret_blink_interval) {
    notify_dark = true;
    native_theme_dark->set_caret_blink_interval(caret_blink_interval);
  }
  auto* native_theme_web = ui::NativeTheme::GetInstanceForWeb();
  if (native_theme_web->GetCaretBlinkInterval() != caret_blink_interval) {
    notify_web = true;
    native_theme_web->set_caret_blink_interval(caret_blink_interval);
  }
  auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
  if (native_theme->GetCaretBlinkInterval() != caret_blink_interval) {
    notify_native = true;
    native_theme->set_caret_blink_interval(caret_blink_interval);
  }
  // Avoid unnecessary notifications.
  if (notify_dark) {
    native_theme_dark->NotifyOnNativeThemeUpdated();
  }
  if (notify_web) {
    native_theme_web->NotifyOnNativeThemeUpdated();
  }
  if (notify_native) {
    native_theme->NotifyOnNativeThemeUpdated();
  }
}

std::optional<ui::KeyboardCode>
AccessibilityController::GetCaretBrowsingActionKey() {
  const std::vector<ui::KeyboardDevice>& keyboards =
      ui::DeviceDataManager::GetInstance()->GetKeyboardDevices();
  std::optional<ui::TopRowActionKey> key;
  if (keyboards.size() > 0) {
    if (ash::Shell::Get()
            ->event_rewriter_controller()
            ->event_rewriter_ash_delegate()
            ->TopRowKeysAreFunctionKeys(keyboards[0].id)) {
      return ui::VKEY_F7;
    }
    key = ash::Shell::Get()
              ->keyboard_capability()
              ->GetCorrespondingActionKeyForFKey(keyboards[0], ui::VKEY_F7);
  }
  if (key) {
    return ui::KeyboardCapability::ConvertToKeyboardCode(*key);
  }
  return std::nullopt;
}

void AccessibilityController::UpdateAccessibilityHighlightingFromPrefs() {
  if (!caret_highlight().enabled() && !cursor_highlight().enabled() &&
      !focus_highlight().enabled()) {
    accessibility_highlight_controller_.reset();
    return;
  }

  if (!accessibility_highlight_controller_) {
    accessibility_highlight_controller_ =
        std::make_unique<AccessibilityHighlightController>();
  }

  accessibility_highlight_controller_->HighlightCaret(
      caret_highlight().enabled());
  accessibility_highlight_controller_->HighlightCursor(
      cursor_highlight().enabled());
  accessibility_highlight_controller_->HighlightFocus(
      focus_highlight().enabled());
}

void AccessibilityController::MaybeCreateSelectToSpeakEventHandler() {
  // Construct the handler as needed when Select-to-Speak is enabled and the
  // delegate is set. Otherwise, destroy the handler when Select-to-Speak is
  // disabled or the delegate has been destroyed.
  if (!select_to_speak().enabled() ||
      !select_to_speak_event_handler_delegate_) {
    select_to_speak_event_handler_.reset();
    return;
  }

  if (select_to_speak_event_handler_) {
    return;
  }

  select_to_speak_event_handler_ = std::make_unique<SelectToSpeakEventHandler>(
      select_to_speak_event_handler_delegate_);
}

void AccessibilityController::UpdateSwitchAccessKeyCodesFromPref(
    SwitchAccessCommand command) {
  if (!active_user_prefs_) {
    return;
  }

  SyncSwitchAccessPrefsToSignInProfile();

  if (!accessibility_event_rewriter_) {
    return;
  }

  std::string pref_key = PrefKeyForSwitchAccessCommand(command);
  const base::Value::Dict& key_codes_pref =
      active_user_prefs_->GetDict(pref_key);
  std::map<int, std::set<std::string>> key_codes;
  for (const auto v : key_codes_pref) {
    int key_code;
    if (!base::StringToInt(v.first, &key_code)) {
      NOTREACHED();
    }

    key_codes[key_code] = std::set<std::string>();

    for (const base::Value& device_type : v.second.GetList()) {
      key_codes[key_code].insert(device_type.GetString());
    }

    DCHECK(!key_codes[key_code].empty());
  }

  std::string uma_name = UmaNameForSwitchAccessCommand(command);
  if (key_codes.size() == 0) {
    base::UmaHistogramEnumeration(uma_name, SwitchAccessKeyCode::kNone);
  }
  for (const auto& key_code : key_codes) {
    base::UmaHistogramEnumeration(
        uma_name, static_cast<SwitchAccessKeyCode>(key_code.first));
  }

  accessibility_event_rewriter_->SetKeyCodesForSwitchAccessCommand(key_codes,
                                                                   command);
}

void AccessibilityController::UpdateSwitchAccessAutoScanEnabledFromPref() {
  DCHECK(active_user_prefs_);
  const bool enabled = active_user_prefs_->GetBoolean(
      prefs::kAccessibilitySwitchAccessAutoScanEnabled);

  base::UmaHistogramBoolean("Accessibility.CrosSwitchAccess.AutoScan", enabled);
  SyncSwitchAccessPrefsToSignInProfile();
}

void AccessibilityController::UpdateSwitchAccessAutoScanSpeedFromPref() {
  DCHECK(active_user_prefs_);
  const int speed_ms = active_user_prefs_->GetInteger(
      prefs::kAccessibilitySwitchAccessAutoScanSpeedMs);

  base::UmaHistogramCustomCounts(
      "Accessibility.CrosSwitchAccess.AutoScan.SpeedMs", speed_ms, 1 /* min */,
      10000 /* max */, 100 /* buckets */);
  SyncSwitchAccessPrefsToSignInProfile();
}

void AccessibilityController::
    UpdateSwitchAccessAutoScanKeyboardSpeedFromPref() {
  DCHECK(active_user_prefs_);
  const int speed_ms = active_user_prefs_->GetInteger(
      prefs::kAccessibilitySwitchAccessAutoScanKeyboardSpeedMs);

  base::UmaHistogramCustomCounts(
      "Accessibility.CrosSwitchAccess.AutoScan.KeyboardSpeedMs", speed_ms,
      1 /* min */, 10000 /* max */, 100 /* buckets */);
  SyncSwitchAccessPrefsToSignInProfile();
}

void AccessibilityController::UpdateSwitchAccessPointScanSpeedFromPref() {
  // TODO(accessibility): Log histogram for point scan speed
  DCHECK(active_user_prefs_);
  const int point_scan_speed_dips_per_second = active_user_prefs_->GetInteger(
      prefs::kAccessibilitySwitchAccessPointScanSpeedDipsPerSecond);

  SetPointScanSpeedDipsPerSecond(point_scan_speed_dips_per_second);
  SyncSwitchAccessPrefsToSignInProfile();
}

void AccessibilityController::SwitchAccessDisableDialogClosed(
    bool disable_dialog_accepted) {
  switch_access_disable_dialog_showing_ = false;
  // Always deactivate switch access. Turning switch access off ensures it is
  // re-activated correctly.
  // The pref was already disabled, but we left switch access on so the user
  // could interact with the dialog.
  DeactivateSwitchAccess();
  if (disable_dialog_accepted) {
    RemoveAccessibilityNotification();
    NotifyAccessibilityStatusChanged();
    SyncSwitchAccessPrefsToSignInProfile();
  } else {
    // Reset the preference (which was already set to false). Doing so turns
    // switch access back on.
    skip_switch_access_notification_ = true;
    switch_access().SetEnabled(true);
  }
}

void AccessibilityController::UpdateKeyCodesAfterSwitchAccessEnabled() {
  UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand::kSelect);
  UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand::kNext);
  UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand::kPrevious);
}

void AccessibilityController::ActivateSwitchAccess() {
  switch_access_bubble_controller_ =
      std::make_unique<SwitchAccessMenuBubbleController>();
  point_scan_controller_ = std::make_unique<PointScanController>();
  UpdateKeyCodesAfterSwitchAccessEnabled();
  UpdateSwitchAccessPointScanSpeedFromPref();
  if (skip_switch_access_notification_) {
    skip_switch_access_notification_ = false;
    return;
  }

  ShowAccessibilityNotification(
      A11yNotificationWrapper(A11yNotificationType::kSwitchAccessEnabled,
                              std::vector<std::u16string>()));
}

void AccessibilityController::DeactivateSwitchAccess() {
  if (client_) {
    client_->OnSwitchAccessDisabled();
  }
  point_scan_controller_.reset();
  switch_access_bubble_controller_.reset();
}

void AccessibilityController::SyncSwitchAccessPrefsToSignInProfile() {
  if (!active_user_prefs_ || IsSigninPrefService(active_user_prefs_)) {
    return;
  }

  PrefService* signin_prefs =
      Shell::Get()->session_controller()->GetSigninScreenPrefService();
  DCHECK(signin_prefs);

  for (const auto* pref_path : kSwitchAccessPrefsCopiedToSignin) {
    const PrefService::Preference* pref =
        active_user_prefs_->FindPreference(pref_path);

    // Ignore if the pref has not been set by the user.
    if (!pref || !pref->IsUserControlled()) {
      continue;
    }

    // Copy the pref value to the signin profile.
    const base::Value* value = pref->GetValue();
    signin_prefs->Set(pref_path, *value);
  }
}

void AccessibilityController::UpdateShortcutsEnabledFromPref() {
  DCHECK(active_user_prefs_);
  const bool enabled =
      active_user_prefs_->GetBoolean(prefs::kAccessibilityShortcutsEnabled);

  if (shortcuts_enabled_ == enabled) {
    return;
  }

  shortcuts_enabled_ = enabled;

  NotifyAccessibilityStatusChanged();
}

void AccessibilityController::UpdateTabletModeShelfNavigationButtonsFromPref() {
  DCHECK(active_user_prefs_);
  const bool enabled = active_user_prefs_->GetBoolean(
      prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled);

  if (tablet_mode_shelf_navigation_buttons_enabled_ == enabled) {
    return;
  }

  tablet_mode_shelf_navigation_buttons_enabled_ = enabled;

  NotifyAccessibilityStatusChanged();
}

std::u16string AccessibilityController::GetBatteryDescription() const {
  // Pass battery status as string to callback function.
  return PowerStatus::Get()->GetAccessibleNameString(
      /*full_description=*/true);
}

void AccessibilityController::SetVirtualKeyboardVisible(bool is_visible) {
  if (is_visible) {
    Shell::Get()->keyboard_controller()->ShowKeyboard();
  } else {
    Shell::Get()->keyboard_controller()->HideKeyboard(HideReason::kUser);
  }

  if (set_virtual_keyboard_visible_callback_) {
    set_virtual_keyboard_visible_callback_.Run();
  }
}

void AccessibilityController::ToggleMouseKeys() {
  if (::features::IsAccessibilityMouseKeysEnabled() && mouse_keys().enabled()) {
    Shell::Get()->mouse_keys_controller()->Toggle();
  }
}

void AccessibilityController::PerformAccessibilityAction() {
  // TODO(b/335456364): Add UMA.
  aura::Window* target_root = Shell::GetRootWindowForNewWindows();
  StatusAreaWidget* status_area_widget =
      RootWindowController::ForWindow(target_root)->GetStatusAreaWidget();
  if (!status_area_widget) {
    // TODO(b/335456364): Support Kiosk mode.
  }

  UnifiedSystemTray* tray = status_area_widget->unified_system_tray();
  if (tray->IsBubbleShown()) {
    if (tray->bubble()
            ->unified_system_tray_controller()
            ->showing_accessibility_detailed_view()) {
      tray->CloseBubble();
      return;
    }
  } else {
    tray->ShowBubble();
  }
  tray->bubble()
      ->unified_system_tray_controller()
      ->ShowAccessibilityDetailedView();
}

void AccessibilityController::PerformAcceleratorAction(
    AcceleratorAction accelerator_action) {
  AcceleratorController::Get()->PerformActionIfEnabled(accelerator_action,
                                                       /* accelerator = */ {});
}

void AccessibilityController::NotifyAccessibilityStatusChanged() {
  for (auto& observer : observers_) {
    observer.OnAccessibilityStatusChanged();
  }
}

bool AccessibilityController::IsAccessibilityFeatureVisibleInTrayMenu(
    const std::string& path) {
  if (!active_user_prefs_) {
    return true;
  }
  if (active_user_prefs_->FindPreference(path)->IsManaged() &&
      !active_user_prefs_->GetBoolean(path)) {
    return false;
  }
  return true;
}

void AccessibilityController::SuspendSwitchAccessKeyHandling(bool suspend) {
  accessibility_event_rewriter_->set_suspend_switch_access_key_handling(
      suspend);
}

void AccessibilityController::EnableChromeVoxVolumeSlideGesture() {
  enable_chromevox_volume_slide_gesture_ = true;
}

void AccessibilityController::ShowConfirmationDialog(
    const std::u16string& title,
    const std::u16string& description,
    const std::u16string& confirm_name,
    const std::u16string& cancel_name,
    base::OnceClosure on_accept_callback,
    base::OnceClosure on_cancel_callback,
    base::OnceClosure on_close_callback) {
  if (confirmation_dialog_) {
    // If a dialog is already being shown we do not show a new one.
    // Instead, run the on_close_callback on the new dialog to indicate
    // it was closed without the user taking any action.
    // This is consistent with AcceleratorController.
    std::move(on_close_callback).Run();
    return;
  }
  auto* dialog = new AccessibilityConfirmationDialog(
      title, description, confirm_name, cancel_name,
      std::move(on_accept_callback), std::move(on_cancel_callback),
      std::move(on_close_callback));
  // Save the dialog so it doesn't go out of scope before it is
  // used and closed.
  confirmation_dialog_ = dialog->GetWeakPtr();
  if (show_confirmation_dialog_callback_for_testing_) {
    show_confirmation_dialog_callback_for_testing_.Run();
  }
}

gfx::Rect AccessibilityController::GetConfirmationDialogBoundsInScreen() {
  if (!confirmation_dialog_.get()) {
    return gfx::Rect();
  }
  return confirmation_dialog_.get()->GetWidget()->GetWindowBoundsInScreen();
}

void AccessibilityController::PreviewFlashNotification() const {
  flash_screen_controller_->OnNotificationAdded("preview");
}

void AccessibilityController::
    UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
        int download_progress) {
  dictation_soda_download_progress_ = download_progress;
  Shell::Get()
      ->GetPrimaryRootWindowController()
      ->GetStatusAreaWidget()
      ->dictation_button_tray()
      ->UpdateOnSpeechRecognitionDownloadChanged(download_progress);
}

void AccessibilityController::ShowNotificationForDictation(
    DictationNotificationType type,
    const std::u16string& display_language) {
  A11yNotificationType notification_type;
  switch (type) {
    case DictationNotificationType::kAllDlcsDownloaded:
      notification_type = A11yNotificationType::kDictationAllDlcsDownloaded;
      break;
    case DictationNotificationType::kNoDlcsDownloaded:
      notification_type = A11yNotificationType::kDictationNoDlcsDownloaded;
      break;
    case DictationNotificationType::kOnlySodaDownloaded:
      notification_type = A11yNotificationType::kDictationOnlySodaDownloaded;
      break;
    case DictationNotificationType::kOnlyPumpkinDownloaded:
      notification_type = A11yNotificationType::kDicationOnlyPumpkinDownloaded;
      break;
  }

  ShowAccessibilityNotification(A11yNotificationWrapper(
      notification_type, std::vector<std::u16string>{display_language}));
}

void AccessibilityController::ShowNotificationForFaceGaze(
    FaceGazeNotificationType type) {
  A11yNotificationType notification_type;
  std::string notification_shown_pref;
  switch (type) {
    case FaceGazeNotificationType::kDlcSucceeded:
      notification_type = A11yNotificationType::kFaceGazeAssetsDownloaded;
      notification_shown_pref =
          prefs::kFaceGazeDlcSuccessNotificationHasBeenShown;
      break;
    case FaceGazeNotificationType::kDlcFailed:
      notification_type = A11yNotificationType::kFaceGazeAssetsFailed;
      notification_shown_pref =
          prefs::kFaceGazeDlcFailureNotificationHasBeenShown;
      break;
  }

  if (active_user_prefs_->GetBoolean(notification_shown_pref)) {
    // Do not show notifications more than once.
    return;
  }

  active_user_prefs_->SetBoolean(notification_shown_pref, true);
  ShowAccessibilityNotification(A11yNotificationWrapper(
      notification_type, std::vector<std::u16string>()));
}

AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper() =
    default;
AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper(
    A11yNotificationType type_in,
    std::vector<std::u16string> replacements_in)
    : type(type_in), replacements(replacements_in) {}
AccessibilityController::A11yNotificationWrapper::~A11yNotificationWrapper() =
    default;
AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper(
    const A11yNotificationWrapper&) = default;

void AccessibilityController::UpdateFeatureFromPref(FeatureType feature) {
  size_t feature_index = static_cast<size_t>(feature);
  bool enabled = features_[feature_index]->enabled();
  bool is_managed = active_user_prefs_->IsManagedPreference(
      features_[feature_index]->pref_name());

  switch (feature) {
    case FeatureType::kAutoclick:
      Shell::Get()->autoclick_controller()->SetEnabled(
          enabled, /*show_confirmation_dialog=*/
          !no_auto_click_confirmation_dialog_for_testing_ && !is_managed);
      break;
    case FeatureType::kCaretHighlight:
      UpdateAccessibilityHighlightingFromPrefs();
      break;
    case FeatureType::kCursorHighlight:
      UpdateAccessibilityHighlightingFromPrefs();
      break;
    case FeatureType::kDictation:
      if (enabled) {
        if (!dictation_bubble_controller_) {
          dictation_bubble_controller_ =
              std::make_unique<DictationBubbleController>();
        }
      } else {
        dictation_bubble_controller_.reset();
      }
      break;
    case FeatureType::kDisableTrackpad:
      if (!::features::IsAccessibilityDisableTrackpadEnabled() ||
          !disable_trackpad_event_rewriter_) {
        return;
      }

      disable_trackpad_event_rewriter_->SetEnabled(enabled);
      break;
    case FeatureType::kFloatingMenu:
      if (enabled && always_show_floating_menu_when_enabled_) {
        ShowFloatingMenuIfEnabled();
      } else {
        floating_menu_controller_.reset();
      }
      break;
    case FeatureType::kFocusHighlight:
      UpdateAccessibilityHighlightingFromPrefs();
      break;
    case FeatureType::kFullscreenMagnifier:
      break;
    case FeatureType::kDockedMagnifier:
      break;
    case FeatureType::kHighContrast:
      Shell::Get()->color_enhancement_controller()->SetHighContrastEnabled(
          enabled);
      break;
    case FeatureType::kLargeCursor:
      Shell::Get()->cursor_manager()->SetCursorSize(
          large_cursor().enabled() ? ui::CursorSize::kLarge
                                   : ui::CursorSize::kNormal);
      Shell::Get()->SetLargeCursorSizeInDip(large_cursor_size_in_dip_);
      Shell::Get()->UpdateCursorCompositingEnabled();
      break;
    case FeatureType::kLiveCaption:
      live_caption().SetEnabled(enabled);
      break;
    case FeatureType::kMonoAudio:
      CrasAudioHandler::Get()->SetOutputMonoEnabled(enabled);
      break;
    case FeatureType::kMouseKeys:
      if (::features::IsAccessibilityMouseKeysEnabled()) {
        // TODO(b/259372916): Consider creating/deleting MouseKeysController
        // here.
        Shell::Get()->mouse_keys_controller()->set_enabled(enabled);
      }
      break;
    case FeatureType::kSpokenFeedback:
      message_center::MessageCenter::Get()->SetSpokenFeedbackEnabled(enabled);
      // TODO(warx): ChromeVox loading/unloading requires browser process
      // started, thus it is still handled on Chrome side.

      // ChromeVox focus highlighting overrides the other focus highlighting.
      focus_highlight().UpdateFromPref();
      break;
    case FeatureType::kReducedAnimations:
      gfx::Animation::SetPrefersReducedMotionForA11y(
          reduced_animations().enabled());
      break;
    case FeatureType::kSelectToSpeak:
      select_to_speak_state_ = SelectToSpeakState::kSelectToSpeakStateInactive;
      if (enabled) {
        MaybeCreateSelectToSpeakEventHandler();
      } else {
        select_to_speak_event_handler_.reset();
        HideSelectToSpeakPanel();
        select_to_speak_bubble_controller_.reset();
      }
      break;
    case FeatureType::kStickyKeys:
      Shell::Get()->sticky_keys_controller()->Enable(enabled);
      break;
    case FeatureType::kSwitchAccess:
      if (!enabled) {
        if (no_switch_access_disable_confirmation_dialog_for_testing_) {
          SwitchAccessDisableDialogClosed(true);
        } else {
          // Show a dialog before disabling Switch Access.
          new AccessibilityFeatureDisableDialog(
              IDS_ASH_SWITCH_ACCESS_DISABLE_CONFIRMATION_TEXT,
              base::BindOnce(
                  &AccessibilityController::SwitchAccessDisableDialogClosed,
                  weak_ptr_factory_.GetWeakPtr(), true),
              base::BindOnce(
                  &AccessibilityController::SwitchAccessDisableDialogClosed,
                  weak_ptr_factory_.GetWeakPtr(), false));
          switch_access_disable_dialog_showing_ = true;
        }
        // Return early. We will call NotifyAccessibilityStatusChanged() if the
        // user accepts the dialog.
        return;
      } else {
        ActivateSwitchAccess();
      }
      SyncSwitchAccessPrefsToSignInProfile();
      break;
    case FeatureType::kVirtualKeyboard:
      keyboard::SetAccessibilityKeyboardEnabled(enabled);
      break;
    case FeatureType::kCursorColor:
      // The notification will already come via UpdateFeatureFromPref
      // so we don't need to run it twice.
      UpdateCursorColorFromPrefs(/*notify=*/false);
      break;
    case FeatureType::kColorCorrection:
      if (enabled && !active_user_prefs_->GetBoolean(
                         prefs::kAccessibilityColorCorrectionHasBeenSetup)) {
        Shell::Get()
            ->system_tray_model()
            ->client()
            ->ShowColorCorrectionSettings();
        active_user_prefs_->SetBoolean(
            prefs::kAccessibilityColorCorrectionHasBeenSetup, true);
      }
      UpdateColorCorrectionFromPrefs();
      break;
    case FeatureType::kFaceGaze:
      UpdateFaceGazeFromPrefs();
      break;
    case FeatureType::kFlashNotifications:
      UpdateFlashNotificationsFromPrefs();
      break;
    case FeatureType::kFeatureCount:
    case FeatureType::kNoConflictingFeature:
      NOTREACHED();
  }
  NotifyAccessibilityStatusChanged();
}

void AccessibilityController::UpdateDictationBubble(
    bool visible,
    DictationBubbleIconType icon,
    const std::optional<std::u16string>& text,
    const std::optional<std::vector<DictationBubbleHintType>>& hints) {
  DCHECK(dictation().enabled());
  DCHECK(dictation_bubble_controller_);

  dictation_bubble_controller_->UpdateBubble(visible, icon, text, hints);
}

DictationBubbleController*
AccessibilityController::GetDictationBubbleControllerForTest() {
  if (!dictation_bubble_controller_) {
    dictation_bubble_controller_ =
        std::make_unique<DictationBubbleController>();
  }

  return dictation_bubble_controller_.get();
}

void AccessibilityController::ShowToast(AccessibilityToastType type) {
  accessibility_notification_controller_->ShowToast(type);
}

void AccessibilityController::AddShowToastCallbackForTesting(
    base::RepeatingCallback<void(AccessibilityToastType)> callback) {
  accessibility_notification_controller_->AddShowToastCallbackForTesting(
      std::move(callback));
}

void AccessibilityController::AddShowConfirmationDialogCallbackForTesting(
    base::RepeatingCallback<void()> callback) {
  show_confirmation_dialog_callback_for_testing_ = std::move(callback);
}

bool AccessibilityController::VerifyFeaturesDataForTesting() {
  return VerifyFeaturesData();
}

void AccessibilityController::SetVirtualKeyboardVisibleCallbackForTesting(
    base::RepeatingCallback<void()> callback) {
  set_virtual_keyboard_visible_callback_ = std::move(callback);
}

void AccessibilityController::ScrollAtPoint(
    const gfx::Point& target,
    AccessibilityScrollDirection direction) {
  float scroll_x = 0.0f;
  float scroll_y = 0.0f;
  switch (direction) {
    case AccessibilityScrollDirection::kUp:
      scroll_y = kScrollDelta;
      break;
    case AccessibilityScrollDirection::kDown:
      scroll_y = -kScrollDelta;
      break;
    case AccessibilityScrollDirection::kLeft:
      scroll_x = kScrollDelta;
      break;
    case AccessibilityScrollDirection::kRight:
      scroll_x = -kScrollDelta;
  }

  // Generate a scroll event at the target location.
  aura::Window* root_window = window_util::GetRootWindowAt(target);
  gfx::Point location_in_pixels(target);
  ::wm::ConvertPointFromScreen(root_window, &location_in_pixels);
  aura::WindowTreeHost* host = root_window->GetHost();
  host->ConvertDIPToPixels(&location_in_pixels);
  ui::ScrollEvent scroll(
      ui::EventType::kScroll, gfx::PointF(location_in_pixels),
      gfx::PointF(location_in_pixels), ui::EventTimeForNow(),
      ui::EF_IS_SYNTHESIZED, scroll_x, scroll_y, 0 /* x_offset_ordinal */,
      0 /* y_offset_ordinal */, 2 /* finger_count */);
  ui::MouseWheelEvent wheel(scroll);
  std::ignore = host->GetEventSink()->OnEventFromSource(&wheel);
}

}  // namespace ash