chromium/ash/system/accessibility/accessibility_detailed_view.cc

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

#include "ash/system/accessibility/accessibility_detailed_view.h"

#include <memory>
#include <utility>

#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/accessibility_delegate.h"
#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/rounded_container.h"
#include "ash/style/switch.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_detailed_view.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_toggle_button.h"
#include "ash/system/tray/tri_view.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/user_metrics.h"
#include "components/live_caption/caption_util.h"
#include "components/live_caption/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/soda/soda_installer.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"

namespace ash {
namespace {

speech::LanguageCode GetSodaFeatureLocale(SodaFeature feature) {
  std::string feature_locale = speech::kUsEnglishLocale;
  PrefService* pref_service =
      Shell::Get()->session_controller()->GetActivePrefService();
  if (pref_service) {
    switch (feature) {
      case SodaFeature::kDictation:
        feature_locale =
            pref_service->GetString(prefs::kAccessibilityDictationLocale);
        break;
      case SodaFeature::kLiveCaption:
        feature_locale = ::prefs::GetLiveCaptionLanguageCode(pref_service);
        break;
    }
  }
  return speech::GetLanguageCode(feature_locale);
}

bool IsSodaFeatureEnabled(SodaFeature feature) {
  AccessibilityController* controller =
      Shell::Get()->accessibility_controller();
  switch (feature) {
    case SodaFeature::kDictation:
      return ::features::IsDictationOfflineAvailable() &&
             controller->dictation().enabled();
    case SodaFeature::kLiveCaption:
      return controller->live_caption().enabled();
  }
}

bool SodaFeatureHasUpdate(SodaFeature feature,
                          speech::LanguageCode language_code) {
  // Only show updates for this feature if the language code applies to the SODA
  // binary (encoded by by LanguageCode::kNone) or the language pack matching
  // the feature locale.
  return language_code == speech::LanguageCode::kNone ||
         language_code == GetSodaFeatureLocale(feature);
}

// Updates the toggle button state and accessibility state for `item` to
// `toggled`.
void UpdateToggleState(HoverHighlightView* item, bool toggled) {
  if (!item) {
    return;
  }
  views::View* right_view = item->right_view();

  // The right view is either an enterprise icon or a `Switch`.
  if (views::IsViewClass<Switch>(right_view)) {
    Switch* button = static_cast<Switch*>(right_view);
    button->AnimateIsOn(toggled);
  }

  // The entire row is treated as one element for accessibility.
  item->SetAccessibilityState(
      toggled ? HoverHighlightView::AccessibilityState::CHECKED_CHECKBOX
              : HoverHighlightView::AccessibilityState::UNCHECKED_CHECKBOX);
}

// Updates the feature state in the UI to `enabled` on `view1` and `view2` if
// the views exist.
void UpdateFeatureState(bool enabled,
                        HoverHighlightView* view1,
                        HoverHighlightView* view2) {
  UpdateToggleState(view1, enabled);
  UpdateToggleState(view2, enabled);
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// AccessibilityDetailedView

AccessibilityDetailedView::AccessibilityDetailedView(
    DetailedViewDelegate* delegate)
    : TrayDetailedView(delegate) {
  Reset();
  AppendAccessibilityList();
  CreateTitleRow(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TITLE);
  DeprecatedLayoutImmediately();

  if (!::features::IsDictationOfflineAvailable() &&
      !captions::IsLiveCaptionFeatureSupported()) {
    return;
  }
  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
  if (soda_installer) {
    soda_installer->AddObserver(this);
  }
}

AccessibilityDetailedView::~AccessibilityDetailedView() {
  if (!::features::IsDictationOfflineAvailable() &&
      !captions::IsLiveCaptionFeatureSupported()) {
    return;
  }
  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
  // `soda_installer` is not guaranteed to be valid, since it's possible for
  // this class to out-live it. This means that this class cannot use
  // ScopedObservation and needs to manage removing the observer itself.
  if (soda_installer) {
    soda_installer->RemoveObserver(this);
  }
}

void AccessibilityDetailedView::OnAccessibilityStatusChanged() {
  AccessibilityDelegate* delegate = Shell::Get()->accessibility_delegate();
  AccessibilityController* controller =
      Shell::Get()->accessibility_controller();

  if (controller->IsSpokenFeedbackSettingVisibleInTray()) {
    bool checked = controller->spoken_feedback().enabled();
    UpdateFeatureState(checked, spoken_feedback_view_,
                       spoken_feedback_top_view_);
  }

  if (controller->IsSelectToSpeakSettingVisibleInTray()) {
    bool checked = controller->select_to_speak().enabled();
    UpdateFeatureState(checked, select_to_speak_view_,
                       select_to_speak_top_view_);
  }

  if (controller->IsDictationSettingVisibleInTray()) {
    bool checked = controller->dictation().enabled();
    UpdateFeatureState(checked, dictation_view_, dictation_top_view_);
  }

  if (controller->IsFaceGazeSettingVisibleInTray()) {
    bool checked = controller->face_gaze().enabled();
    UpdateFeatureState(checked, facegaze_view_, facegaze_top_view_);
  }

  if (controller->IsColorCorrectionSettingVisibleInTray()) {
    bool checked = controller->color_correction().enabled();
    UpdateFeatureState(checked, color_correction_view_,
                       color_correction_top_view_);
  }

  if (controller->IsHighContrastSettingVisibleInTray()) {
    bool checked = controller->high_contrast().enabled();
    UpdateFeatureState(checked, high_contrast_view_, high_contrast_top_view_);
  }

  if (controller->IsFullScreenMagnifierSettingVisibleInTray()) {
    bool checked = delegate->IsMagnifierEnabled();
    UpdateFeatureState(checked, screen_magnifier_view_,
                       screen_magnifier_top_view_);
  }

  if (controller->IsDockedMagnifierSettingVisibleInTray()) {
    bool checked = Shell::Get()->docked_magnifier_controller()->GetEnabled();
    UpdateFeatureState(checked, docked_magnifier_view_,
                       docked_magnifier_top_view_);
  }

  if (controller->IsAutoclickSettingVisibleInTray()) {
    bool checked = controller->autoclick().enabled();
    UpdateFeatureState(checked, autoclick_view_, autoclick_top_view_);
  }

  if (controller->IsVirtualKeyboardSettingVisibleInTray()) {
    bool checked = controller->virtual_keyboard().enabled();
    UpdateFeatureState(checked, virtual_keyboard_view_,
                       virtual_keyboard_top_view_);
  }

  if (controller->IsSwitchAccessSettingVisibleInTray()) {
    bool checked = controller->switch_access().enabled();
    UpdateFeatureState(checked, switch_access_view_, switch_access_top_view_);
  }

  if (controller->IsLiveCaptionSettingVisibleInTray()) {
    bool checked = controller->live_caption().enabled();
    UpdateFeatureState(checked, live_caption_view_, live_caption_top_view_);
  }

  if (controller->IsLargeCursorSettingVisibleInTray()) {
    bool checked = controller->large_cursor().enabled();
    UpdateFeatureState(checked, large_cursor_view_, large_cursor_top_view_);
  }

  if (controller->IsMonoAudioSettingVisibleInTray()) {
    bool checked = controller->mono_audio().enabled();
    UpdateFeatureState(checked, mono_audio_view_, mono_audio_top_view_);
  }

  if (controller->IsCaretHighlightSettingVisibleInTray()) {
    bool checked = controller->caret_highlight().enabled();
    UpdateFeatureState(checked, caret_highlight_view_,
                       caret_highlight_top_view_);
  }

  if (controller->IsCursorHighlightSettingVisibleInTray()) {
    bool checked = controller->cursor_highlight().enabled();
    UpdateFeatureState(checked, highlight_mouse_cursor_view_,
                       highlight_mouse_cursor_top_view_);
  }

  if (controller->IsFocusHighlightSettingVisibleInTray()) {
    bool checked = controller->focus_highlight().enabled();
    UpdateFeatureState(checked, highlight_keyboard_focus_view_,
                       highlight_keyboard_focus_top_view_);
  }

  if (controller->IsStickyKeysSettingVisibleInTray()) {
    bool checked = controller->sticky_keys().enabled();
    UpdateFeatureState(checked, sticky_keys_view_, sticky_keys_top_view_);
  }

  if (controller->IsReducedAnimationsSettingVisibleInTray()) {
    bool checked = controller->reduced_animations().enabled();
    UpdateFeatureState(checked, reduced_animations_view_,
                       reduced_animations_top_view_);
  }
}

void AccessibilityDetailedView::AppendAccessibilityList() {
  CreateScrollableList();

  auto top_container = std::make_unique<RoundedContainer>();
  AddEnabledFeatures(top_container.get());

  // If enabled features were added, add the top container and some padding.
  if (!top_container->children().empty()) {
    auto* view = scroll_content()->AddChildView(std::move(top_container));
    constexpr auto kTopContainerMargins = gfx::Insets::TLBR(0, 0, 8, 0);
    view->SetProperty(views::kMarginsKey, kTopContainerMargins);
  }

  views::View* main_container =
      scroll_content()->AddChildView(std::make_unique<RoundedContainer>());
  AddAllFeatures(main_container);
}

void AccessibilityDetailedView::AddEnabledFeatures(views::View* container) {
  AccessibilityController* controller =
      Shell::Get()->accessibility_controller();

  if (controller->IsSpokenFeedbackSettingVisibleInTray() &&
      controller->spoken_feedback().enabled()) {
    spoken_feedback_top_view_ = AddSpokenFeedbackView(container);
  }
  if (controller->IsSelectToSpeakSettingVisibleInTray() &&
      controller->select_to_speak().enabled()) {
    select_to_speak_top_view_ = AddSelectToSpeakView(container);
  }
  if (controller->IsDictationSettingVisibleInTray() &&
      controller->dictation().enabled()) {
    dictation_top_view_ = AddDictationView(container);
  }
  if (controller->IsFaceGazeSettingVisibleInTray() &&
      controller->face_gaze().enabled()) {
    facegaze_top_view_ = AddFaceGazeView(container);
  }
  if (controller->IsColorCorrectionSettingVisibleInTray() &&
      controller->color_correction().enabled()) {
    color_correction_top_view_ = AddColorCorrectionView(container);
  }
  if (controller->IsHighContrastSettingVisibleInTray() &&
      controller->high_contrast().enabled()) {
    high_contrast_top_view_ = AddHighContrastView(container);
  }
  if (controller->IsFullScreenMagnifierSettingVisibleInTray() &&
      Shell::Get()->accessibility_delegate()->IsMagnifierEnabled()) {
    screen_magnifier_top_view_ = AddScreenMagnifierView(container);
  }
  if (controller->IsDockedMagnifierSettingVisibleInTray() &&
      Shell::Get()->docked_magnifier_controller()->GetEnabled()) {
    docked_magnifier_top_view_ = AddDockedMagnifierView(container);
  }
  if (controller->IsAutoclickSettingVisibleInTray() &&
      controller->autoclick().enabled()) {
    autoclick_top_view_ = AddAutoclickView(container);
  }
  if (controller->IsVirtualKeyboardSettingVisibleInTray() &&
      controller->virtual_keyboard().enabled()) {
    virtual_keyboard_top_view_ = AddVirtualKeyboardView(container);
  }
  if (controller->IsSwitchAccessSettingVisibleInTray() &&
      controller->switch_access().enabled()) {
    switch_access_top_view_ = AddSwitchAccessView(container);
  }
  if (controller->IsLiveCaptionSettingVisibleInTray() &&
      controller->live_caption().enabled()) {
    live_caption_top_view_ = AddLiveCaptionView(container);
  }
  if (controller->IsLargeCursorSettingVisibleInTray() &&
      controller->large_cursor().enabled()) {
    large_cursor_top_view_ = AddLargeCursorView(container);
  }
  if (controller->IsMonoAudioSettingVisibleInTray() &&
      controller->mono_audio().enabled()) {
    mono_audio_top_view_ = AddMonoAudioView(container);
  }
  if (controller->IsCaretHighlightSettingVisibleInTray() &&
      controller->caret_highlight().enabled()) {
    caret_highlight_top_view_ = AddCaretHighlightView(container);
  }
  if (controller->IsCursorHighlightSettingVisibleInTray() &&
      controller->cursor_highlight().enabled()) {
    highlight_mouse_cursor_top_view_ = AddHighlightMouseCursorView(container);
  }
  if (controller->IsFocusHighlightSettingVisibleInTray() &&
      controller->focus_highlight().enabled()) {
    highlight_keyboard_focus_top_view_ =
        AddHighlightKeyboardFocusView(container);
  }
  if (controller->IsStickyKeysSettingVisibleInTray() &&
      controller->sticky_keys().enabled()) {
    sticky_keys_top_view_ = AddStickyKeysView(container);
  }
  if (controller->IsReducedAnimationsSettingVisibleInTray() &&
      controller->reduced_animations().enabled()) {
    reduced_animations_top_view_ = AddReducedAnimationsView(container);
  }
}

void AccessibilityDetailedView::AddAllFeatures(views::View* container) {
  AccessibilityController* controller =
      Shell::Get()->accessibility_controller();

  if (controller->IsSpokenFeedbackSettingVisibleInTray()) {
    spoken_feedback_view_ = AddSpokenFeedbackView(container);
  }

  if (controller->IsSelectToSpeakSettingVisibleInTray()) {
    select_to_speak_view_ = AddSelectToSpeakView(container);
  }

  if (controller->IsDictationSettingVisibleInTray()) {
    dictation_view_ = AddDictationView(container);
  }

  if (controller->IsFaceGazeSettingVisibleInTray()) {
    facegaze_view_ = AddFaceGazeView(container);
  }

  if (controller->IsColorCorrectionSettingVisibleInTray()) {
    color_correction_view_ = AddColorCorrectionView(container);
  }

  if (controller->IsHighContrastSettingVisibleInTray()) {
    high_contrast_view_ = AddHighContrastView(container);
  }

  if (controller->IsFullScreenMagnifierSettingVisibleInTray()) {
    screen_magnifier_view_ = AddScreenMagnifierView(container);
  }

  if (controller->IsDockedMagnifierSettingVisibleInTray()) {
    docked_magnifier_view_ = AddDockedMagnifierView(container);
  }

  if (controller->IsAutoclickSettingVisibleInTray()) {
    autoclick_view_ = AddAutoclickView(container);
  }

  if (controller->IsVirtualKeyboardSettingVisibleInTray()) {
    virtual_keyboard_view_ = AddVirtualKeyboardView(container);
    virtual_keyboard_view_->SetID(ash::VIEW_ID_ACCESSIBILITY_VIRTUAL_KEYBOARD);
    virtual_keyboard_view_->right_view()->SetID(
        ash::VIEW_ID_ACCESSIBILITY_VIRTUAL_KEYBOARD_ENABLED);
  }

  if (controller->IsSwitchAccessSettingVisibleInTray()) {
    switch_access_view_ = AddSwitchAccessView(container);
  }

  if (controller->IsLiveCaptionSettingVisibleInTray()) {
    live_caption_view_ = AddLiveCaptionView(container);
  }

  if (controller->IsLargeCursorSettingVisibleInTray()) {
    large_cursor_view_ = AddLargeCursorView(container);
  }

  if (controller->IsMonoAudioSettingVisibleInTray()) {
    mono_audio_view_ = AddMonoAudioView(container);
  }

  if (controller->IsCaretHighlightSettingVisibleInTray()) {
    caret_highlight_view_ = AddCaretHighlightView(container);
  }

  if (controller->IsCursorHighlightSettingVisibleInTray()) {
    highlight_mouse_cursor_view_ = AddHighlightMouseCursorView(container);
  }

  if (controller->IsFocusHighlightSettingVisibleInTray()) {
    highlight_keyboard_focus_view_ = AddHighlightKeyboardFocusView(container);
  }

  if (controller->IsStickyKeysSettingVisibleInTray()) {
    sticky_keys_view_ = AddStickyKeysView(container);
  }

  if (controller->IsReducedAnimationsSettingVisibleInTray()) {
    reduced_animations_view_ = AddReducedAnimationsView(container);
  }
}

HoverHighlightView* AccessibilityDetailedView::AddSpokenFeedbackView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->spoken_feedback().enabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuAccessibilityChromevoxIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SPOKEN_FEEDBACK),
      checked, controller->IsEnterpriseIconVisibleForSpokenFeedback());
}

HoverHighlightView* AccessibilityDetailedView::AddSelectToSpeakView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->select_to_speak().enabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuAccessibilitySelectToSpeakIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SELECT_TO_SPEAK),
      checked, controller->IsEnterpriseIconVisibleForSelectToSpeak());
}

HoverHighlightView* AccessibilityDetailedView::AddDictationView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->dictation().enabled();
  return AddScrollListFeatureItem(
      container, kDictationMenuIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION),
      checked, controller->IsEnterpriseIconVisibleForDictation());
}

HoverHighlightView* AccessibilityDetailedView::AddFaceGazeView(
    views::View* container) {
  if (!base::FeatureList::IsEnabled(::features::kAccessibilityFaceGaze)) {
    return nullptr;
  }
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->face_gaze().enabled();
  return AddScrollListFeatureItem(
      container, kFacegazeIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_FACEGAZE),
      checked, controller->IsEnterpriseIconVisibleForFaceGaze());
}

HoverHighlightView* AccessibilityDetailedView::AddColorCorrectionView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->color_correction().enabled();
  return AddScrollListFeatureItem(
      container, kColorCorrectionIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION),
      checked, controller->IsEnterpriseIconVisibleForColorCorrection());
}

HoverHighlightView* AccessibilityDetailedView::AddHighContrastView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->high_contrast().enabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuAccessibilityContrastIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGH_CONTRAST_MODE),
      checked, controller->IsEnterpriseIconVisibleForHighContrast());
}

HoverHighlightView* AccessibilityDetailedView::AddScreenMagnifierView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = Shell::Get()->accessibility_delegate()->IsMagnifierEnabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuAccessibilityFullscreenMagnifierIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SCREEN_MAGNIFIER),
      checked, controller->IsEnterpriseIconVisibleForFullScreenMagnifier());
}

HoverHighlightView* AccessibilityDetailedView::AddDockedMagnifierView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = Shell::Get()->docked_magnifier_controller()->GetEnabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuAccessibilityDockedMagnifierIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DOCKED_MAGNIFIER),
      checked, controller->IsEnterpriseIconVisibleForDockedMagnifier());
}

HoverHighlightView* AccessibilityDetailedView::AddAutoclickView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->autoclick().enabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuAccessibilityAutoClickIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_AUTOCLICK),
      checked, controller->IsEnterpriseIconVisibleForAutoclick());
}

HoverHighlightView* AccessibilityDetailedView::AddVirtualKeyboardView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->virtual_keyboard().enabled();
  return AddScrollListFeatureItem(
      container, kSystemMenuKeyboardLegacyIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD),
      checked, controller->IsEnterpriseIconVisibleForVirtualKeyboard());
}

HoverHighlightView* AccessibilityDetailedView::AddSwitchAccessView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->switch_access().enabled();
  return AddScrollListFeatureItem(
      container, kSwitchAccessIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SWITCH_ACCESS),
      checked, controller->IsEnterpriseIconVisibleForSwitchAccess());
}

HoverHighlightView* AccessibilityDetailedView::AddLiveCaptionView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->live_caption().enabled();
  return AddScrollListFeatureItem(
      container, vector_icons::kLiveCaptionOnIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LIVE_CAPTION), checked,
      controller->IsEnterpriseIconVisibleForLiveCaption());
}

HoverHighlightView* AccessibilityDetailedView::AddLargeCursorView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->large_cursor().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yLargeMouseCursorIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LARGE_CURSOR),
      checked, controller->IsEnterpriseIconVisibleForLargeCursor());
}

HoverHighlightView* AccessibilityDetailedView::AddMonoAudioView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->mono_audio().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yMonoAudioIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_MONO_AUDIO),
      checked, controller->IsEnterpriseIconVisibleForMonoAudio());
}

HoverHighlightView* AccessibilityDetailedView::AddCaretHighlightView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->caret_highlight().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yHighlightTextCaretIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_CARET_HIGHLIGHT),
      checked, controller->IsEnterpriseIconVisibleForCaretHighlight());
}

HoverHighlightView* AccessibilityDetailedView::AddHighlightMouseCursorView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->cursor_highlight().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yHighlightMouseCursorIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGHLIGHT_MOUSE_CURSOR),
      checked, controller->IsEnterpriseIconVisibleForCursorHighlight());
}

HoverHighlightView* AccessibilityDetailedView::AddHighlightKeyboardFocusView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->focus_highlight().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yHighlightKeyboardFocusIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGHLIGHT_KEYBOARD_FOCUS),
      checked, controller->IsEnterpriseIconVisibleForFocusHighlight());
}

HoverHighlightView* AccessibilityDetailedView::AddStickyKeysView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->sticky_keys().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yStickyKeysIcon,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_STICKY_KEYS),
      checked, controller->IsEnterpriseIconVisibleForStickyKeys());
}

HoverHighlightView* AccessibilityDetailedView::AddReducedAnimationsView(
    views::View* container) {
  auto* controller = Shell::Get()->accessibility_controller();
  bool checked = controller->reduced_animations().enabled();
  return AddScrollListFeatureItem(
      container, kQuickSettingsA11yReducedAnimationsIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_REDUCED_ANIMATIONS),
      checked, controller->IsEnterpriseIconVisibleForReducedAnimations());
}

HoverHighlightView* AccessibilityDetailedView::AddScrollListFeatureItem(
    views::View* container,
    const gfx::VectorIcon& icon,
    const std::u16string& text,
    bool checked,
    bool enterprise_managed) {
  return AddScrollListToggleItem(container, icon, text, checked,
                                 enterprise_managed);
}

HoverHighlightView* AccessibilityDetailedView::AddScrollListToggleItem(
    views::View* container,
    const gfx::VectorIcon& icon,
    const std::u16string& text,
    bool checked,
    bool enterprise_managed) {
  HoverHighlightView* item = AddScrollListItem(container, icon, text);
  item->SetAccessibilityState(
      checked ? HoverHighlightView::AccessibilityState::CHECKED_CHECKBOX
              : HoverHighlightView::AccessibilityState::UNCHECKED_CHECKBOX);
  if (enterprise_managed) {
    // Show the enterprise "building" icon on the right.
    item->GetViewAccessibility().SetName(l10n_util::GetStringFUTF16(
        IDS_ASH_ACCESSIBILITY_FEATURE_MANAGED, text));
    ui::ImageModel enterprise_managed_icon = ui::ImageModel::FromVectorIcon(
        kSystemMenuBusinessIcon, kColorAshIconColorPrimary, kMenuIconSize);
    item->AddRightIcon(enterprise_managed_icon,
                       enterprise_managed_icon.Size().width());
  } else {
    // Create a non-clickable non-focusable toggle button on the right.
    std::unique_ptr<views::ToggleButton> toggle;

    toggle = std::make_unique<Switch>();
    toggle->SetIsOn(checked);
    toggle->SetCanProcessEventsWithinSubtree(false);
    toggle->SetFocusBehavior(views::View::FocusBehavior::NEVER);
    // Ignore the toggle for accessibility.
    auto& view_accessibility = toggle->GetViewAccessibility();
    view_accessibility.SetIsLeaf(true);
    view_accessibility.SetIsIgnored(true);
    item->AddRightView(toggle.release());
  }
  return item;
}

void AccessibilityDetailedView::HandleViewClicked(views::View* view) {
  AccessibilityDelegate* delegate = Shell::Get()->accessibility_delegate();
  AccessibilityController* controller =
      Shell::Get()->accessibility_controller();
  using base::RecordAction;
  using base::UserMetricsAction;
  // Since `view` is never null, there's no need to check for the existence of
  // individual views in the if statements below.
  DCHECK(view);
  if ((view == spoken_feedback_top_view_ || view == spoken_feedback_view_) &&
      !controller->IsEnterpriseIconVisibleForSpokenFeedback()) {
    bool new_state = !controller->spoken_feedback().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_SpokenFeedbackEnabled")
                     : UserMetricsAction("StatusArea_SpokenFeedbackDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.SpokenFeedback",
                              new_state);
    controller->SetSpokenFeedbackEnabled(new_state, A11Y_NOTIFICATION_NONE);
  } else if ((view == select_to_speak_top_view_ ||
              view == select_to_speak_view_) &&
             !controller->IsEnterpriseIconVisibleForSelectToSpeak()) {
    bool new_state = !controller->select_to_speak().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_SelectToSpeakEnabled")
                     : UserMetricsAction("StatusArea_SelectToSpeakDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.SelectToSpeak",
                              new_state);
    controller->select_to_speak().SetEnabled(new_state);
  } else if ((view == dictation_top_view_ || view == dictation_view_) &&
             !controller->IsEnterpriseIconVisibleForDictation()) {
    bool new_state = !controller->dictation().enabled();
    RecordAction(new_state ? UserMetricsAction("StatusArea_DictationEnabled")
                           : UserMetricsAction("StatusArea_DictationDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.Dictation",
                              new_state);
    controller->dictation().SetEnabled(new_state);
  } else if ((view == facegaze_view_ || view == facegaze_top_view_) &&
             !controller->IsEnterpriseIconVisibleForFaceGaze()) {
    bool new_state = !controller->face_gaze().enabled();
    RecordAction(new_state ? UserMetricsAction("StatusArea_FaceGazeEnabled")
                           : UserMetricsAction("StatusArea_FaceGazeDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.FaceGaze",
                              new_state);
    controller->face_gaze().SetEnabled(new_state);
  } else if ((view == color_correction_view_ ||
              view == color_correction_top_view_) &&
             !controller->IsEnterpriseIconVisibleForColorCorrection()) {
    bool new_state = !controller->color_correction().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_ColorCorrectionEnabled")
                     : UserMetricsAction("StatusArea_ColorCorrectionDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.ColorCorrection",
                              new_state);
    controller->color_correction().SetEnabled(new_state);
  } else if ((view == high_contrast_top_view_ || view == high_contrast_view_) &&
             !controller->IsEnterpriseIconVisibleForHighContrast()) {
    bool new_state = !controller->high_contrast().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_HighContrastEnabled")
                     : UserMetricsAction("StatusArea_HighContrastDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.HighContrast",
                              new_state);
    controller->high_contrast().SetEnabled(new_state);
  } else if ((view == screen_magnifier_top_view_ ||
              view == screen_magnifier_view_) &&
             !controller->IsEnterpriseIconVisibleForFullScreenMagnifier()) {
    bool new_state = !delegate->IsMagnifierEnabled();
    RecordAction(new_state ? UserMetricsAction("StatusArea_MagnifierEnabled")
                           : UserMetricsAction("StatusArea_MagnifierDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.ScreenMagnifier",
                              new_state);
    delegate->SetMagnifierEnabled(new_state);
  } else if ((view == docked_magnifier_top_view_ ||
              view == docked_magnifier_view_) &&
             !controller->IsEnterpriseIconVisibleForDockedMagnifier()) {
    auto* docked_magnifier_controller =
        Shell::Get()->docked_magnifier_controller();
    const bool new_state = !docked_magnifier_controller->GetEnabled();

    // Close the system tray bubble as the available screen space has changed
    // E.g. there may not be enough screen space to display the current bubble
    // after enabling the docked magnifier or more space is made available after
    // disabling the docked magnifier.
    CloseBubble();

    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_DockedMagnifierEnabled")
                     : UserMetricsAction("StatusArea_DockedMagnifierDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.DockedMagnifier",
                              new_state);
    docked_magnifier_controller->SetEnabled(new_state);
  } else if ((view == large_cursor_top_view_ || view == large_cursor_view_) &&
             !controller->IsEnterpriseIconVisibleForLargeCursor()) {
    bool new_state = !controller->large_cursor().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_LargeCursorEnabled")
                     : UserMetricsAction("StatusArea_LargeCursorDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.LargeCursor",
                              new_state);
    controller->large_cursor().SetEnabled(new_state);
  } else if ((view == autoclick_top_view_ || view == autoclick_view_) &&
             !controller->IsEnterpriseIconVisibleForAutoclick()) {
    bool new_state = !controller->autoclick().enabled();
    RecordAction(new_state ? UserMetricsAction("StatusArea_AutoClickEnabled")
                           : UserMetricsAction("StatusArea_AutoClickDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.Autoclick",
                              new_state);
    controller->autoclick().SetEnabled(new_state);
  } else if ((view == virtual_keyboard_top_view_ ||
              view == virtual_keyboard_view_) &&
             !controller->IsEnterpriseIconVisibleForVirtualKeyboard()) {
    bool new_state = !controller->virtual_keyboard().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_VirtualKeyboardEnabled")
                     : UserMetricsAction("StatusArea_VirtualKeyboardDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.VirtualKeyboard",
                              new_state);
    controller->virtual_keyboard().SetEnabled(new_state);
  } else if ((view == switch_access_top_view_ || view == switch_access_view_) &&
             !controller->IsEnterpriseIconVisibleForSwitchAccess()) {
    bool new_state = !controller->switch_access().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_SwitchAccessEnabled")
                     : UserMetricsAction("StatusArea_SwitchAccessDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.SwitchAccess",
                              new_state);
    controller->switch_access().SetEnabled(new_state);
  } else if (view == live_caption_top_view_ || view == live_caption_view_) {
    bool new_state = !controller->live_caption().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_LiveCaptionEnabled")
                     : UserMetricsAction("StatusArea_LiveCaptionDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.LiveCaption",
                              new_state);
    controller->live_caption().SetEnabled(new_state);
  } else if ((view == caret_highlight_top_view_ ||
              view == caret_highlight_view_) &&
             !controller->IsEnterpriseIconVisibleForCaretHighlight()) {
    bool new_state = !controller->caret_highlight().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_CaretHighlightEnabled")
                     : UserMetricsAction("StatusArea_CaretHighlightDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.CaretHighlight",
                              new_state);
    controller->caret_highlight().SetEnabled(new_state);
  } else if ((view == mono_audio_top_view_ || view == mono_audio_view_) &&
             !controller->IsEnterpriseIconVisibleForMonoAudio()) {
    bool new_state = !controller->mono_audio().enabled();
    RecordAction(new_state ? UserMetricsAction("StatusArea_MonoAudioEnabled")
                           : UserMetricsAction("StatusArea_MonoAudioDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.MonoAudio",
                              new_state);
    controller->mono_audio().SetEnabled(new_state);
  } else if ((view == highlight_mouse_cursor_top_view_ ||
              view == highlight_mouse_cursor_view_) &&
             !controller->IsEnterpriseIconVisibleForCursorHighlight()) {
    bool new_state = !controller->cursor_highlight().enabled();
    RecordAction(
        new_state
            ? UserMetricsAction("StatusArea_HighlightMouseCursorEnabled")
            : UserMetricsAction("StatusArea_HighlightMouseCursorDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.CursorHighlight",
                              new_state);
    controller->cursor_highlight().SetEnabled(new_state);
  } else if ((view == highlight_keyboard_focus_top_view_ ||
              view == highlight_keyboard_focus_view_) &&
             !controller->IsEnterpriseIconVisibleForFocusHighlight()) {
    bool new_state = !controller->focus_highlight().enabled();
    RecordAction(
        new_state
            ? UserMetricsAction("StatusArea_HighlightKeyboardFocusEnabled")
            : UserMetricsAction("StatusArea_HighlightKeyboardFocusDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.FocusHighlight",
                              new_state);
    controller->focus_highlight().SetEnabled(new_state);
  } else if ((view == sticky_keys_top_view_ || view == sticky_keys_view_) &&
             !controller->IsEnterpriseIconVisibleForStickyKeys()) {
    bool new_state = !controller->sticky_keys().enabled();
    RecordAction(new_state
                     ? UserMetricsAction("StatusArea_StickyKeysEnabled")
                     : UserMetricsAction("StatusArea_StickyKeysDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.StickyKeys",
                              new_state);
    controller->sticky_keys().SetEnabled(new_state);
  } else if ((view == reduced_animations_top_view_ ||
              view == reduced_animations_view_) &&
             !controller->IsEnterpriseIconVisibleForReducedAnimations()) {
    bool new_state = !controller->reduced_animations().enabled();
    RecordAction(
        new_state ? UserMetricsAction("StatusArea_ReducedAnimationsEnabled")
                  : UserMetricsAction("StatusArea_ReducedAnimationsDisabled"));
    base::UmaHistogramBoolean("Accessibility.CrosStatusArea.ReducedAnimations",
                              new_state);
    controller->reduced_animations().SetEnabled(new_state);
  }
}

void AccessibilityDetailedView::CreateExtraTitleRowButtons() {
  DCHECK(!help_view_);
  DCHECK(!settings_view_);

  tri_view()->SetContainerVisible(TriView::Container::END, true);

  help_view_ = CreateHelpButton(base::BindRepeating(
      &AccessibilityDetailedView::ShowHelp, base::Unretained(this)));
  settings_view_ = CreateSettingsButton(
      base::BindRepeating(&AccessibilityDetailedView::ShowSettings,
                          base::Unretained(this)),
      IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SETTINGS);
  tri_view()->AddView(TriView::Container::END, help_view_);
  tri_view()->AddView(TriView::Container::END, settings_view_);
}

void AccessibilityDetailedView::ShowSettings() {
  if (TrayPopupUtils::CanOpenWebUISettings()) {
    CloseBubble();  // Deletes |this|.
    Shell::Get()->system_tray_model()->client()->ShowAccessibilitySettings();
  }
}

void AccessibilityDetailedView::ShowHelp() {
  if (TrayPopupUtils::CanOpenWebUISettings()) {
    CloseBubble();  // Deletes |this|.
    Shell::Get()->system_tray_model()->client()->ShowAccessibilityHelp();
  }
}

// SodaInstaller::Observer:
void AccessibilityDetailedView::OnSodaInstalled(
    speech::LanguageCode language_code) {
  std::u16string message = l10n_util::GetStringUTF16(
      IDS_ASH_ACCESSIBILITY_SETTING_SUBTITLE_SODA_DOWNLOAD_COMPLETE);
  MaybeShowSodaMessage(SodaFeature::kDictation, language_code, message);
  MaybeShowSodaMessage(SodaFeature::kLiveCaption, language_code, message);
}

void AccessibilityDetailedView::OnSodaInstallError(
    speech::LanguageCode language_code,
    speech::SodaInstaller::ErrorCode error_code) {
  std::u16string error_message;
  switch (error_code) {
    case speech::SodaInstaller::ErrorCode::kUnspecifiedError: {
      error_message = l10n_util::GetStringUTF16(
          IDS_ASH_ACCESSIBILITY_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR);
      break;
    }
    case speech::SodaInstaller::ErrorCode::kNeedsReboot: {
      error_message = l10n_util::GetStringUTF16(
          IDS_ASH_ACCESSIBILITY_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR_REBOOT_REQUIRED);
      break;
    }
  }

  MaybeShowSodaMessage(SodaFeature::kDictation, language_code, error_message);
  MaybeShowSodaMessage(SodaFeature::kLiveCaption, language_code, error_message);
}

void AccessibilityDetailedView::OnSodaProgress(
    speech::LanguageCode language_code,
    int progress) {
  std::u16string message = l10n_util::GetStringFUTF16Int(
      IDS_ASH_ACCESSIBILITY_SETTING_SUBTITLE_SODA_DOWNLOAD_PROGRESS, progress);
  MaybeShowSodaMessage(SodaFeature::kDictation, language_code, message);
  MaybeShowSodaMessage(SodaFeature::kLiveCaption, language_code, message);
}

void AccessibilityDetailedView::MaybeShowSodaMessage(
    SodaFeature feature,
    speech::LanguageCode language_code,
    std::u16string message) {
  if (IsSodaFeatureEnabled(feature) && IsSodaFeatureInTray(feature) &&
      SodaFeatureHasUpdate(feature, language_code)) {
    SetSodaFeatureSubtext(feature, message);
  }
}

bool AccessibilityDetailedView::IsSodaFeatureInTray(SodaFeature feature) {
  AccessibilityController* controller =
      Shell::Get()->accessibility_controller();
  switch (feature) {
    case SodaFeature::kDictation:
      return dictation_view_ && controller->IsDictationSettingVisibleInTray();
    case SodaFeature::kLiveCaption:
      return live_caption_view_ &&
             controller->IsLiveCaptionSettingVisibleInTray();
  }
}

void AccessibilityDetailedView::SetSodaFeatureSubtext(SodaFeature feature,
                                                      std::u16string message) {
  switch (feature) {
    case SodaFeature::kDictation:
      DCHECK(dictation_view_);
      dictation_view_->SetSubText(message);
      break;
    case SodaFeature::kLiveCaption:
      DCHECK(live_caption_view_);
      live_caption_view_->SetSubText(message);
      break;
  }
}

BEGIN_METADATA(AccessibilityDetailedView)
END_METADATA

}  // namespace ash